PageSourceSearch

https://www.valuemax.com.sg/wp-content/plugins/translatepress-mult…trp-frontend-language-switcher.js?ver=3.0.5

js valuemax.com.sg collected 2026-10-06 22:41:24 UTC 14,773 bytes, 408 lines download raw bytes

1class BaseSwitcher {
2    constructor(rootEl) {
3        this.root = rootEl;
4        this.list = rootEl?.querySelector('.trp-switcher-dropdown-list') || null;
5        this.isOpen = false;
6        this._pendingFocusOnOpen = false;
7        if (!this.root || !this.list) return;
8
9        if (!this.list.id) {
10            this.list.id = `trp-list-${Math.random().toString(36).slice(2, 9)}`;
11        }
12
13        // Single transitionend handler to drop .is-transitioning and finalize hidden/inert
14        this._onTe = (e) => {
15            if (e.target !== this.list || e.propertyName !== 'max-height') return;
16
17            this.root.classList.remove('is-transitioning');
18
19            if (!this.isOpen) {
20                this.list.hidden = true;
21                this.list.setAttribute('inert', '');
22            } else if (this._pendingFocusOnOpen) {
23                this._pendingFocusOnOpen = false;
24                const first = this.list.querySelector(
25                    '[role="option"], a, button, [tabindex]:not([tabindex="-1"])'
26                );
27                first?.focus?.({ preventScroll: true });
28            }
29        };
30        this.list.addEventListener('transitionend', this._onTe);
31
32        this.collapse();
33        this.setAutoWidth();
34        this.bindKeyboard();
35    }
36
37    collapse() {
38        this.list.hidden = true;
39        this.list.setAttribute('inert', '');
40        this.setExpanded(false);
41        this.root.classList.remove('is-transitioning');
42    }
43
44    /**
45     * If width is set to auto, fix it to the calculated width + 20px
46     * We do this in order to avoid width shift on hover
47     * */
48    setAutoWidth() {
49        const bonusWidth = 10;
50
51        const cs = getComputedStyle(this.root);
52        const declaredWidth = cs.getPropertyValue('--switcher-width').trim();
53
54        if (declaredWidth === 'auto' && this.root.querySelector('.trp-language-item-name')) { // In case trp-language-item-name is not present, we are in flags only mode - so no auto width sizing is needed
55            const initialWidth = this.root.getBoundingClientRect().width;
56
57            this.root.style.setProperty('--switcher-width', (initialWidth + bonusWidth) + 'px');
58        }
59    }
60
61    setExpanded(open) {
62        const trigger = this.root.querySelector('.trp-language-item__current[role="button"]');
63        const val = String( !!open );
64        trigger?.setAttribute('aria-expanded', val);
65        this.root.classList.toggle('is-open', !!open);
66    }
67
68    setOpen(open, { source = null } = {}) {
69        if (!this.root || !this.list || open === this.isOpen) return;
70
71        // Honor reduced motion: skip the transition entirely (still class-driven)
72        const prefersReduced = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches;
73
74        this.isOpen = open;
75
76        if (open) {
77            // Prepare: must be visible for CSS transition to run
78            this.list.hidden = false;
79            this.list.removeAttribute('inert');
80
81            if (prefersReduced) {
82                this.root.classList.remove('is-transitioning');
83                this.setExpanded(true);
84            } else {
85                this.root.classList.add('is-transitioning');
86                // Next frame so the browser registers the pre-open (max-height:0) state
87                requestAnimationFrame(() => this.setExpanded(true));
88            }
89
90            // keyboard open should move focus after transition completes
91            this._pendingFocusOnOpen = (source?.type === 'keydown');
92
93        } else {
94            if (prefersReduced){
95                this.root.classList.add( 'is-transitioning' );
96            }
97
98            this.setExpanded(false);
99        }
100    }
101
102    bindKeyboard() {
103        const trigger = this.root.querySelector('.trp-language-item__current[role="button"]');
104        if ( !trigger ) return;
105
106        trigger.addEventListener('keydown', (e) => {
107            const inList = !!e.target.closest?.('.trp-switcher-dropdown-list');
108
109            if ( e.key === 'Enter' || e.key === ' ' ) {
110                e.preventDefault();
111                this.setOpen(!this.isOpen, { source: e });
112                return;
113            }
114
115            if ( e.key === 'ArrowDown' && !this.isOpen ) {
116                e.preventDefault();
117                this.setOpen(true, { source: e });
118            }
119
120            if ( e.key === 'Escape' ) {
121                this.setOpen(false, { source: e });
122                trigger.focus?.();
123            }
124        });
125    }
126}
127
128class ShortcodeSwitcher extends BaseSwitcher {
129    constructor(wrapper) {
130        const overlay =
131                  wrapper.querySelector('.trp-language-switcher.trp-shortcode-overlay')
132                  || [...wrapper.querySelectorAll('.trp-language-switcher')]
133                      .find(el => el.classList.contains('trp-shortcode-overlay'));
134
135        // Overlay must be interactable; ensure no accidental hidden/inert from server
136        overlay.hidden = false;
137        overlay.removeAttribute('hidden');
138        overlay.removeAttribute('inert');
139        if ('inert' in overlay) overlay.inert = false;
140
141        super(overlay);
142
143        if (!this.root || !this.list) return;
144
145        const control = this.root.querySelector('.trp-language-item__current[role="button"]');
146        if (control && this.list && !control.hasAttribute('aria-controls')) {
147            control.setAttribute('aria-controls', this.list.id);
148        }
149
150        const isClickMode =
151                  this.root.classList.contains('trp-open-on-click') ||
152                  wrapper.dataset.openMode === 'click' ||
153                  wrapper.classList.contains('trp-open-on-click');
154
155        if (isClickMode) {
156            // Click anywhere on overlay EXCEPT inside the list
157            this.root.addEventListener('click', (e) => {
158                const inList = e.target.closest('.trp-switcher-dropdown-list');
159                if (!inList) {
160                    e.preventDefault();
161                    e.stopPropagation();
162                    this.setOpen(!this.isOpen, { source: e });
163                }
164            }, true);
165
166            // Outside click closes
167            this.onDocClick = (evt) => {
168                if (!wrapper.contains(evt.target)) this.setOpen(false, { source: evt });
169            };
170            document.addEventListener('click', this.onDocClick, true);
171
172            // Focus leaving wrapper closes
173            wrapper.addEventListener('focusout', () => {
174                setTimeout(() => {
175                    if (!wrapper.contains(document.activeElement)) {
176                        this.setOpen(false, { source: 'keyboard' });
177                    }
178                }, 0);
179            });
180        } else {
181            // Hover mode on overlay
182            this.root.addEventListener('mouseenter', (e) => this.setOpen(true,  { source: e }));
183            this.root.addEventListener('mouseleave', (e) => this.setOpen(false, { source: e }));
184        }
185    }
186}
187
188class FloaterSwitcher extends BaseSwitcher {
189    constructor(el) {
190        super(el);
191
192        el.addEventListener('mouseenter', (e) => this.setOpen(true,  { source: e }));
193        el.addEventListener('mouseleave', (e) => this.setOpen(false, { source: e }));
194
195        this.onDocClick = (evt) => { if (!el.contains(evt.target)) this.setOpen(false, { source: evt }); };
196        document.addEventListener('click', this.onDocClick, true);
197    }
198}
199
200document.addEventListener('DOMContentLoaded', () => {
201    // Front-end or classic admin
202    initLanguageSwitchers(document);
203
204    // If no wrapper and we are in Gutenberg, watch for async SSR insert
205    if (
206        inGutenberg() &&
207        !getEditorDoc().querySelector(WRAPPER)
208    ) {
209        observeWrapperUntilFound();
210    }
211
212    if ( !inGutenberg() )
213        observeShortcodeSwitcher();
214});
215
216/** For shortcode switcher
217 *  Mark the shortcodes that were initialized
218 * */
219const TRP_BOUND = new WeakSet();
220const mark = (el) => TRP_BOUND.add(el);
221const isMarked = (el) => TRP_BOUND.has(el);
222
223const WRAPPER = '.trp-shortcode-switcher__wrapper';
224const OVERLAY = '.trp-language-switcher:not(.trp-opposite-button)';
225
226// Helpers
227function inGutenberg() {
228    return document.body?.classList?.contains('block-editor-page')
229        || !!(window.wp?.data?.select?.('core/block-editor'));
230}
231
232// If editor uses an iframe canvas, work inside it
233function getEditorDoc() {
234    const ifr = document.querySelector('iframe[name="editor-canvas"], .editor-canvas__iframe');
235
236    return (ifr && ifr.contentDocument) ? ifr.contentDocument : document;
237}
238
239function initLanguageSwitchers(root = document) {
240    const floater = root.querySelector(
241        '.trp-language-switcher.trp-ls-dropdown:not(.trp-shortcode-switcher):not(.trp-opposite-language)'
242    );
243
244    if (floater)
245        new FloaterSwitcher(floater);
246
247    root.querySelectorAll(WRAPPER)
248        .forEach(wrapper => {
249            const overlay = wrapper.querySelector('.trp-language-switcher:not(.trp-opposite-button)');
250
251            if (overlay && !isMarked(overlay)) {
252                mark(overlay);
253                new ShortcodeSwitcher(wrapper);
254            }
255        });
256}
257
258/**
259 * Observes the document for dynamically inserted shortcode switchers and initializes them automatically when detected.
260 */
261function observeShortcodeSwitcher() {
262    const initWrapper = ( wrapper ) => {
263        if ( !wrapper )
264            return;
265
266        const overlay = wrapper.querySelector( OVERLAY );
267
268        if ( !overlay || isMarked( overlay ) )
269            return;
270
271        mark( overlay );
272
273        new ShortcodeSwitcher( wrapper );
274    }
275
276    const mo = new MutationObserver( ( mutations ) => {
277        for ( const m of mutations ) {
278            for ( const n of m.addedNodes ) {
279                if ( n.nodeType !== 1 )
280                    continue;
281
282                if ( n.matches?.( WRAPPER ) )
283                    initWrapper( n );
284
285                n.querySelectorAll?.( WRAPPER ).forEach( initWrapper );
286            }
287        }
288    });
289
290    mo.observe( document, { childList: true, subtree: true } );
291}
292
293/**
294 * Observe Gutenberg for the shortcode wrapper being inserted asynchronously.
295 *
296 * Supports both Blocks API v2 (no editor iframe; wrapper appears in the outer document)
297 * and Blocks API v3 (editor content rendered inside an iframe canvas).
298 *
299 * Strategy:
300 *  1) Check the current editor document for `.trp-shortcode-switcher__wrapper` and init immediately.
301 *  2) If an editor canvas iframe exists, watch its document (and reattach on iframe load) for the wrapper.
302 *  3) If no iframe yet, watch the outer document for either the iframe (v3) or the wrapper itself (v2).
303 *
304 * Initialization is performed once per context to avoid duplicate bindings.
305 */
306function observeWrapperUntilFound() {
307    // If wrapper already exists in current editor doc, init
308    const edDoc = getEditorDoc();
309    const existing = edDoc.querySelector(WRAPPER);
310
311    if ( existing ) {
312        initLanguageSwitchers( edDoc );
313        return;
314    }
315
316    // Helper to locate the editor canvas iframe in the OUTER document
317    const findCanvasIframe = () => document.querySelector('iframe[name="editor-canvas"], .editor-canvas__iframe');
318
319    // If iframe is already present in the outer doc, start watching inside it
320    const iframeNow = findCanvasIframe();
321    if ( iframeNow ) {
322        watchIframe( iframeNow );
323        return;
324    }
325
326    // Otherwise, observe the OUTER document until the iframe appears
327    const outerMO = new MutationObserver( ( mutations ) => {
328        for ( const m of mutations ) {
329            for ( const n of m.addedNodes ) {
330                if ( n.nodeType !== 1 ) continue;
331
332                const iframe =
333                          n.matches?.('iframe[name="editor-canvas"], .editor-canvas__iframe')
334                              ? n
335                              : n.querySelector?.('iframe[name="editor-canvas"], .editor-canvas__iframe');
336
337                if ( iframe ) {
338                    outerMO.disconnect();
339                    watchIframe( iframe );
340                    return;
341                }
342
343                // Also catch shortcode wrapper added directly to the outer document (API v2, no iframe)
344                const wrapper =
345                          n.matches?.(WRAPPER)
346                              ? n
347                              : n.querySelector?.(WRAPPER);
348
349                if ( wrapper ) {
350                    outerMO.disconnect();
351                    initLanguageSwitchers( document );
352                    return;
353                }
354
355            }
356        }
357    } );
358    outerMO.observe( document, { childList: true, subtree: true } );
359
360    function watchIframe( iframe ) {
361        // Try immediately (some builds inject srcdoc synchronously)
362        tryAttachInside();
363
364        // Also on load/navigate (Gutenberg may reload the canvas)
365        iframe.addEventListener( 'load', tryAttachInside );
366
367        function tryAttachInside() {
368            let doc;
369            try {
370                doc = iframe.contentDocument || iframe.contentWindow?.document;
371            } catch (e) {
372                console.warn('Cannot access iframe content due to cross-origin restrictions', e);
373                return;
374            }
375            if ( !doc ) return;
376
377            // If wrapper is already there, init once and stop.
378            const hit = doc.querySelector(WRAPPER);
379            if ( hit ) {
380                initLanguageSwitchers( doc );
381                return;
382            }
383
384            // Observe INSIDE the iframe until wrapper appears
385            const innerMO = new MutationObserver( ( muts ) => {
386                for ( const mm of muts ) {
387                    for ( const nn of mm.addedNodes ) {
388                        if ( nn.nodeType !== 1 ) continue;
389                        if (
390                            nn.matches?.(WRAPPER) ||
391                            nn.querySelector?.(WRAPPER)
392                        ) {
393                            innerMO.disconnect();
394                            initLanguageSwitchers( doc );
395                            return;
396                        }
397                    }
398                }
399                if ( doc.querySelector(WRAPPER) ) {
400                    innerMO.disconnect();
401                    initLanguageSwitchers( doc );
402                }
403            } );
404
405            innerMO.observe( doc, { childList: true, subtree: true } );
406        }
407    }
408}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.