6464 </div >
6565</template >
6666
67- <script lang="ts">
67+ <script setup lang="ts">
6868import { PropType } from ' vue' ;
6969import { Link as InertiaLink } from ' @inertiajs/inertia-vue3' ;
7070import { CdxButton as LanguageSelectorButton , CdxIcon } from " @wikimedia/codex" ;
@@ -73,113 +73,98 @@ import AuthWidget from '../Components/AuthWidget.vue';
7373import LanguageSelector from ' ../Components/LanguageSelector.vue' ;
7474import WikidataToolFooter from ' ../Components/WikidataToolFooter.vue' ;
7575import { DirectiveBinding , ComponentPublicInstance } from ' vue' ;
76- import { defineComponent } from ' vue' ;
76+ import { ref , computed , nextTick , onMounted , onBeforeUnmount } from ' vue' ;
77+ import type { Ref } from ' vue' ;
7778import User from ' ../types/User' ;
7879import languagedata from ' @wikimedia/language-data' ;
7980
8081let handleOutsideClick: (event : MouseEvent | TouchEvent ) => void ;
8182
82- export default defineComponent ({
83- components: {
84- AuthWidget ,
85- LanguageSelectorButton ,
86- CdxIcon ,
87- InertiaLink ,
88- LanguageSelector ,
89- WikidataToolFooter
90- },
91- setup() {
92- return {
93- cdxIconLanguage
94- };
95- },
96- data() {
97- return {
98- showLanguageSelector: false ,
99- resizeObserver: null as unknown as ResizeObserver ,
100- };
101- },
102- directives: {
103- detectClickOutside: {
104- mounted(element : HTMLElement , binding : DirectiveBinding ): void {
105- handleOutsideClick = (event : MouseEvent | TouchEvent ): void => {
106- const callback = binding .value ;
107- if (! element .contains (event .target as Node )) {
108- callback ();
109- }
110- };
83+ const showLanguageSelector = ref (false );
84+ const resizeObserver: Ref <ResizeObserver > = ref (null );
85+ const languageSelector: Ref <ComponentPublicInstance > = ref (null );
86+ const header: Ref <HTMLElement > = ref (null );
87+ const userSection: Ref <HTMLElement > = ref (null );
88+ const contentWrap: Ref <Element > = ref (null );
11189
112- document .addEventListener (' click' , handleOutsideClick );
113- document .addEventListener (' touchstart' , handleOutsideClick );
114- },
115- unmounted(): void {
116- document .removeEventListener (' click' , handleOutsideClick );
117- document .removeEventListener (' touchstart' , handleOutsideClick );
118- },
119- },
120- },
121- mounted() {
122- this .resizeObserver = new ResizeObserver (this .onWindowResize );
123- this .resizeObserver .observe (this .$refs .contentWrap as Element );
124- },
125- computed: {
126- currentLanguageAutonym(): string {
127- return languagedata .getAutonym (document .documentElement .lang );
128- },
129- },
130- props: {
131- user: Object as PropType <User >,
132- },
133- methods: {
134- onChangeLanguage(newLanguage : string ): void {
135- /**
136- * Manipulate the url to maintain it as the single source of truth
137- * and avoid having either to load all language files upfront or
138- * request language file reactively.
139- */
140- const url = new URL (document .URL );
141- url .searchParams .set (' uselang' , newLanguage );
142- document .location .assign (url .toString ());
143- },
144- onCloseLanguageSelector(): void {
145- this .showLanguageSelector = false ;
146- },
147- onToggleLanguageSelector(): void {
148- this .showLanguageSelector = ! this .showLanguageSelector ;
149- if (this .showLanguageSelector === true ) {
150- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
151- const languageSelectorRefs = this .$refs .languageSelector as any ;
152- this .$nextTick (() => {
153- languageSelectorRefs .focus ();
154- this .changeLanguageSelectorMenuDirection ();
155- });
156- }
157- },
158- onClickOutsideLanguageSelector(): void {
159- this .showLanguageSelector = false ;
160- },
161- changeLanguageSelectorMenuDirection(): void {
162- const headerTop = (this .$refs .header as HTMLElement ).getBoundingClientRect ().top ;
163- const userSectionTop = (this .$refs .userSection as HTMLElement ).getBoundingClientRect ().top ;
164- if ( userSectionTop > headerTop ){
165- ((this .$refs .languageSelector as ComponentPublicInstance )
166- .$el as HTMLElement ).style .insetInlineEnd = ' unset' ;
167- ((this .$refs .languageSelector as ComponentPublicInstance )
168- .$el as HTMLElement ).style .insetInlineStart = ' 0' ;
169- } else {
170- ((this .$refs .languageSelector as ComponentPublicInstance )
171- .$el as HTMLElement ).style .insetInlineEnd = ' 0' ;
172- ((this .$refs .languageSelector as ComponentPublicInstance )
173- .$el as HTMLElement ).style .insetInlineStart = ' unset' ;
90+ const props = defineProps <{user: User }>();
91+
92+ const currentLanguageAutonym = computed <string >(() => {
93+ return languagedata .getAutonym (document .documentElement .lang );
94+ });
95+
96+ const vDetectClickOutside = {
97+ mounted : (element : HTMLElement , binding : DirectiveBinding ): void => {
98+ handleOutsideClick = (event : MouseEvent | TouchEvent ): void => {
99+ const callback = binding .value ;
100+ if (! element .contains (event .target as Node )) {
101+ callback ();
174102 }
175- },
176- onWindowResize() : void {
177- this . changeLanguageSelectorMenuDirection ( );
178- },
103+ };
104+
105+ document . addEventListener ( ' click ' , handleOutsideClick );
106+ document . addEventListener ( ' touchstart ' , handleOutsideClick );
179107 },
180- beforeUnmount () {
181- this .resizeObserver .unobserve (this .$refs .contentWrap as Element )
108+ unmounted(): void {
109+ document .removeEventListener (' click' , handleOutsideClick );
110+ document .removeEventListener (' touchstart' , handleOutsideClick );
111+ }
112+ };
113+
114+ function onChangeLanguage(newLanguage : string ): void {
115+ /**
116+ * Manipulate the url to maintain it as the single source of truth
117+ * and avoid having either to load all language files upfront or
118+ * request language file reactively.
119+ */
120+ const url = new URL (document .URL );
121+ url .searchParams .set (' uselang' , newLanguage );
122+ document .location .assign (url .toString ());
123+ };
124+
125+ function onCloseLanguageSelector(): void {
126+ showLanguageSelector .value = false ;
127+ };
128+
129+ async function onToggleLanguageSelector(): Promise <void > {
130+ showLanguageSelector .value = ! showLanguageSelector .value ;
131+ if (showLanguageSelector .value === true ) {
132+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
133+ const languageSelectorRefs = languageSelector .value as any ;
134+ await nextTick (() => {
135+ languageSelectorRefs .focus ();
136+ changeLanguageSelectorMenuDirection ();
137+ });
182138 }
139+ };
140+
141+ function onClickOutsideLanguageSelector(): void {
142+ showLanguageSelector .value = false ;
143+ };
144+
145+ function changeLanguageSelectorMenuDirection(): void {
146+ const headerTop = header .value .getBoundingClientRect ().top ;
147+ const userSectionTop = (userSection ).value .getBoundingClientRect ().top ;
148+ if ( userSectionTop > headerTop ){
149+ (languageSelector .value .$el as HTMLElement ).style .insetInlineEnd = ' unset' ;
150+ languageSelector .value .$el .style .insetInlineStart = ' 0' ;
151+ } else {
152+ languageSelector .value .$el .style .insetInlineEnd = ' 0' ;
153+ languageSelector .value .$el .style .insetInlineStart = ' unset' ;
154+ }
155+ };
156+
157+ function onWindowResize(): void {
158+ changeLanguageSelectorMenuDirection ();
159+ };
160+
161+ onMounted (() => {
162+ resizeObserver .value = new ResizeObserver (onWindowResize );
163+ resizeObserver .value .observe (contentWrap .value );
164+ });
165+
166+ onBeforeUnmount (() => {
167+ resizeObserver .value .unobserve (contentWrap .value )
183168});
184169 </script >
185170
0 commit comments