Page MenuHomePhabricator
Paste P17678

Abandoned code for auto-detecting root elements in lang/dir composables
ActivePublic

Authored by Catrope on Nov 4 2021, 12:33 AM.
diff --git a/packages/vue-components/src/Sandbox.vue b/packages/vue-components/src/Sandbox.vue
index 10a5418..b3bf1ec 100644
--- a/packages/vue-components/src/Sandbox.vue
+++ b/packages/vue-components/src/Sandbox.vue
@@ -5,6 +5,7 @@
</header>
<main>
+ <p class="foo"><cdx-icon :icon="cdxIconArrowNext" /> Next</p>
<h2>Codex Button</h2>
<table>
<thead>
@@ -93,8 +94,9 @@
</template>
<script lang="ts" setup>
-import { CdxButton } from './lib';
+import { CdxButton, CdxIcon } from './lib';
import { ButtonActions, ButtonTypes } from './constants';
+import { cdxIconArrowNext } from 'icons';
function onClick( e: Event ) {
// eslint-disable-next-line no-console
@@ -107,4 +109,8 @@ td {
/* stylelint-disable-next-line plugin/no-unsupported-browser-features, unit-disallowed-list */
padding: 1.5rem;
}
+
+.foo .cdx-icon {
+ direction: rtl;
+}
</style>
diff --git a/packages/vue-components/src/components/icon/Icon.vue b/packages/vue-components/src/components/icon/Icon.vue
index 2986a3e..d8a664e 100644
--- a/packages/vue-components/src/components/icon/Icon.vue
+++ b/packages/vue-components/src/components/icon/Icon.vue
@@ -1,6 +1,5 @@
<template>
<span
- ref="rootElement"
class="cdx-icon"
:class="rootClasses"
@click="onClick"
@@ -76,10 +75,8 @@ export default defineComponent( {
},
emits: [ 'click' ],
setup( props, { emit } ) {
- const rootElement = ref<HTMLSpanElement>();
-
- const computedDir = useComputedDirection( rootElement, toRef( props, 'dir' ) );
- const computedLang = useComputedLanguage( rootElement, toRef( props, 'lang' ) );
+ const computedDir = useComputedDirection( { override: toRef( props, 'dir' ) } );
+ const computedLang = useComputedLanguage( { override: toRef( props, 'lang' ) } );
const rootClasses = computed( () => ( {
'cdx-icon--flipped': computedDir.value === 'rtl' && computedLang.value !== null &&
@@ -97,7 +94,6 @@ export default defineComponent( {
};
return {
- rootElement,
rootClasses,
iconSvg,
iconPath,
diff --git a/packages/vue-components/src/composables/useComputedDirection.ts b/packages/vue-components/src/composables/useComputedDirection.ts
index cdf47b0..17b6df2 100644
--- a/packages/vue-components/src/composables/useComputedDirection.ts
+++ b/packages/vue-components/src/composables/useComputedDirection.ts
@@ -1,5 +1,6 @@
-import { ref, computed, onMounted, Ref, ComputedRef } from 'vue';
+import { ref, computed, getCurrentInstance, onMounted, Ref, ComputedRef } from 'vue';
import { HTMLDirection } from '../types';
+import { getRootElementOrParent } from '../utils';
/**
* Composable for detecting the directionality of the context surrounding a component.
@@ -9,20 +10,30 @@ import { HTMLDirection } from '../types';
* If the component has a "dir" prop that allows callers to manually set the direction,
* it can pass that to the override parameter. If the override value is not null, it will
* be used in instead of the detected directionality.
+ *
+ * If no override value is set, the value returned by this composable will initially be null, and
+ * will then update to the detected value later, when the component mounts. This is because
+ * detecting the language is not possible until the component has been mounted. Code using this
+ * composable should anticipate this, and check whether the value is null.
*
- * @param root Template ref for the root element of the component
* @param override Override value; will be returned instead of the detected value if not null
+ * @param el Template ref for the HTML element to compute the direction of. This defaults to the
+ * root element of the component (if the component has one root element) or to the parent node
+ * of the component (if the component has multiple root elements). This default behavior usually
+ * works well; you only need to pass this parameter if the default behavior doesn't work.
* @return The override value if it's not null, or the detected direction otherwise. Null if the
- * direction couldn't be detected and no override was set.
+ * direction couldn't be detected and no override was set, or if the component hasn't been
+ * mounted yet.
*/
-export default function useComputedDirection(
- root: Ref<HTMLElement | undefined>,
- override?: Ref<HTMLDirection | null>
-) : ComputedRef<HTMLDirection | null> {
+export default function useComputedDirection( { override, el } : {
+ override?: Ref<HTMLDirection | null>,
+ el?: Ref<HTMLElement | undefined>
+} ) : ComputedRef<HTMLDirection | null> {
const computedDir = ref<HTMLDirection | null>( null );
onMounted( () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const dir = window.getComputedStyle( root.value! ).direction;
+ const element = el ? el.value! : getRootElementOrParent( getCurrentInstance()! );
+ const dir = window.getComputedStyle( element ).direction;
computedDir.value = dir === 'ltr' || dir === 'rtl' ? dir : null;
} );
return computed( () => {
diff --git a/packages/vue-components/src/composables/useComputedLanguage.ts b/packages/vue-components/src/composables/useComputedLanguage.ts
index c9c3e2b..92529f3 100644
--- a/packages/vue-components/src/composables/useComputedLanguage.ts
+++ b/packages/vue-components/src/composables/useComputedLanguage.ts
@@ -1,4 +1,5 @@
-import { ref, computed, onMounted, Ref, ComputedRef } from 'vue';
+import { ref, computed, getCurrentInstance, onMounted, Ref, ComputedRef } from 'vue';
+import { getRootElementOrParent } from '../utils';
/**
* Composable for detecting the language of the context surrounding a component.
@@ -8,22 +9,31 @@ import { ref, computed, onMounted, Ref, ComputedRef } from 'vue';
* If the component has a "lang" prop that allows callers to manually set the direction,
* it can pass that to the override parameter. If the override value is not null, it will
* be used in instead of the detected directionality.
+ *
+ * If no override value is set, the value returned by this composable will initially be null, and
+ * will then update to the detected value later, when the component mounts. This is because
+ * detecting the language is not possible until the component has been mounted. Code using this
+ * composable should anticipate this, and check whether the value is null.
*
- * @param root Template ref for the root element of the component
* @param override Override value; will be returned instead of the detected value if not null
+ * @param el Template ref for the HTML element to compute the direction of. This defaults to the
+ * root element of the component (if the component has one root element) or to the parent node
+ * of the component (if the component has multiple root elements). This default behavior usually
+ * works well; you only need to pass this parameter if the default behavior doesn't work.
* @return The override value if it's not null, or the detected language code otherwise. Null if the
- * language couldn't be detected and no override was set.
+ * language couldn't be detected and no override was set, or if the component hasn't been mounted
+ * yet.
*/
-export default function useComputedLanguage(
- root: Ref<HTMLElement | undefined>,
- override?: Ref<string | null>
-) : ComputedRef<string | null> {
- const computedLang = ref<string | null>( '' );
+export default function useComputedLanguage( { override, el } : {
+ override?: Ref<string | null>,
+ el?: Ref<HTMLElement | undefined>
+} ) : ComputedRef<string | null> {
+ const computedLang = ref<string | null>( null );
onMounted( () => {
// There's no CSS property for language, so we can't use getComputedStyle().
// Instead, traverse up the tree until we find an ancestor with the 'lang' attribute set
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- let ancestor : HTMLElement | null = root.value!;
+ let ancestor : HTMLElement | null = el ? el.value! : getRootElementOrParent( getCurrentInstance()! );
while ( ancestor && ancestor.lang === '' ) {
ancestor = ancestor.parentElement;
}
diff --git a/packages/vue-components/src/utils.ts b/packages/vue-components/src/utils.ts
index 6285cfe..ea9a154 100644
--- a/packages/vue-components/src/utils.ts
+++ b/packages/vue-components/src/utils.ts
@@ -1,3 +1,4 @@
+import { ComponentInternalInstance } from 'vue';
import { StringTypeValidator } from './types';
/**
@@ -41,3 +42,21 @@ export function makeStringTypeValidator<T extends string>(
return ( s: unknown ) : s is T => typeof s === 'string' &&
( allowedValues as readonly string[] ).indexOf( s ) !== -1;
}
+
+/**
+ * Get the root element of the current component or, if the component has multiple roots, the parent
+ * element of these root elements.
+ *
+ * @param instance Result of getComponentInstance()
+ * @return Root element (if one root) or parent element (if multiple roots)
+ */
+export function getRootElementOrParent( instance: ComponentInternalInstance ) : HTMLElement {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const rootNode = instance.proxy!.$el as Node;
+ const rootElementOrParent = rootNode.nodeType === Node.ELEMENT_NODE ?
+ rootNode as HTMLElement : rootNode.parentElement;
+ if ( !rootElementOrParent ) {
+ throw new Error( 'Could not access root element' );
+ }
+ return rootElementOrParent;
+}