PageSourceSearch

https://www.alsting.fr/wp-content/themes/travelify/library/js/functions.js?ver=3.1.2

js alsting.fr collected 2026-10-03 00:49:13 UTC 7,976 bytes, 304 lines download raw bytes

1/**
2 * Travelify theme behaviour.
3 *
4 * Plain DOM APIs, no jQuery. Loaded in the footer.
5 */
6( function () {
7	'use strict';
8
9	function prefersReducedMotion() {
10		return window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
11	}
12
13	/**
14	 * Back to top.
15	 *
16	 * Fades in past 1000px, as the jQuery version did, and scrolls to the top
17	 * of the document when activated.
18	 */
19	function initBackToTop() {
20		var widget = document.querySelector( '.back-to-top' );
21
22		if ( ! widget ) {
23			return;
24		}
25
26		var link = widget.querySelector( 'a' );
27
28		widget.classList.add( 'back-to-top--ready' );
29
30		var visible = false;
31		var ticking = false;
32
33		function update() {
34			var shouldShow = ( window.pageYOffset || document.documentElement.scrollTop ) > 1000;
35
36			if ( shouldShow !== visible ) {
37				visible = shouldShow;
38				widget.classList.toggle( 'is-visible', visible );
39			}
40
41			ticking = false;
42		}
43
44		window.addEventListener( 'scroll', function () {
45			if ( ! ticking ) {
46				window.requestAnimationFrame( update );
47				ticking = true;
48			}
49		}, { passive: true } );
50
51		update();
52
53		if ( ! link ) {
54			return;
55		}
56
57		link.addEventListener( 'click', function ( event ) {
58			event.preventDefault();
59
60			window.scrollTo( {
61				top: 0,
62				behavior: prefersReducedMotion() ? 'auto' : 'smooth'
63			} );
64
65			// Send focus somewhere sensible rather than leaving it at the bottom.
66			var target = document.getElementById( 'branding' );
67
68			if ( target ) {
69				target.setAttribute( 'tabindex', '-1' );
70				target.focus( { preventScroll: true } );
71			}
72		} );
73	}
74
75	/**
76	 * Mobile navigation.
77	 *
78	 * Replaces the <select> that TinyNav built below 768px. TinyNav shipped
79	 * bundled inside the old functions.min.js -- the standalone tinynav.js in
80	 * library/js was dead code, which is easy to misread as the menu being
81	 * absent. It worked; it was just a <select> with no sub-menu structure and
82	 * no accessible semantics.
83	 */
84	function initMobileMenu() {
85		var nav = document.getElementById( 'main-nav' );
86
87		if ( ! nav ) {
88			return;
89		}
90
91		var menu = nav.querySelector( 'ul' );
92
93		if ( ! menu ) {
94			return;
95		}
96
97		var strings = window.travelifyScreenReaderText || {};
98
99		var toggle = document.createElement( 'button' );
100		toggle.className = 'menu-toggle';
101		toggle.type = 'button';
102		toggle.setAttribute( 'aria-expanded', 'false' );
103		toggle.setAttribute( 'aria-controls', menu.id || 'travelify-primary-menu' );
104		var label = document.createElement( 'span' );
105		label.className = 'menu-toggle-text';
106		label.textContent = strings.menu || 'Menu';
107
108		var icon = document.createElement( 'span' );
109		icon.className = 'menu-toggle-icon';
110		icon.setAttribute( 'aria-hidden', 'true' );
111
112		toggle.appendChild( icon );
113		toggle.appendChild( label );
114
115		if ( ! menu.id ) {
116			menu.id = 'travelify-primary-menu';
117		}
118
119		menu.parentNode.insertBefore( toggle, menu );
120
121		/*
122		 * Whether the menu is on screen is decided here, not in the stylesheet.
123		 *
124		 * The CSS rules that hide the list and reveal it again on .is-open only
125		 * exist in this version of style.css. Where that file is out of step --
126		 * a child theme carrying its own copy, a minify or CDN layer serving a
127		 * cached one -- the class still toggles but nothing moves, and the
128		 * button is dead. Setting display from here means the menu opens
129		 * regardless of which stylesheet arrived.
130		 *
131		 * MOBILE_BREAKPOINT mirrors the max-width in style.css; keep them equal.
132		 */
133		var MOBILE_BREAKPOINT = 767;
134
135		function isMobile() {
136			return window.innerWidth <= MOBILE_BREAKPOINT;
137		}
138
139		function applyMenuState( open ) {
140			if ( isMobile() ) {
141				toggle.style.display = 'flex';
142				menu.style.display = open ? 'block' : 'none';
143			} else {
144				// Desktop: hand both back to the stylesheet.
145				toggle.style.display = 'none';
146				menu.style.display = '';
147			}
148		}
149
150		function setOpen( open ) {
151			nav.classList.toggle( 'is-open', open );
152			toggle.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
153			applyMenuState( open );
154		}
155
156		setOpen( false );
157
158		toggle.addEventListener( 'click', function () {
159			setOpen( ! nav.classList.contains( 'is-open' ) );
160		} );
161
162		// Sub-menu toggles: hover cannot open a dropdown on a touch screen.
163		Array.prototype.forEach.call( menu.querySelectorAll( 'li' ), function ( item ) {
164			var submenu = item.querySelector( 'ul' );
165
166			if ( ! submenu ) {
167				return;
168			}
169
170			item.classList.add( 'has-submenu' );
171
172			var link = item.querySelector( 'a' );
173			var button = document.createElement( 'button' );
174
175			button.className = 'submenu-toggle';
176			button.type = 'button';
177			button.setAttribute( 'aria-expanded', 'false' );
178			button.setAttribute(
179				'aria-label',
180				( strings.expand || 'Open sub-menu of' ) + ' ' + ( link ? link.textContent.trim() : '' )
181			);
182			var chevron = document.createElement( 'span' );
183			chevron.setAttribute( 'aria-hidden', 'true' );
184			button.appendChild( chevron );
185
186			button.addEventListener( 'click', function () {
187				var open = item.classList.toggle( 'submenu-open' );
188				button.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
189
190				// As above: do not depend on the stylesheet to reveal it.
191				if ( isMobile() ) {
192					submenu.style.display = open ? 'block' : 'none';
193				}
194				button.setAttribute(
195					'aria-label',
196					( open ? ( strings.collapse || 'Close sub-menu of' ) : ( strings.expand || 'Open sub-menu of' ) ) + ' ' + ( link ? link.textContent.trim() : '' )
197				);
198			} );
199
200			if ( link ) {
201				link.parentNode.insertBefore( button, link.nextSibling );
202			} else {
203				item.insertBefore( button, item.firstChild );
204			}
205		} );
206
207		// Escape closes whatever is open and returns focus to the toggle.
208		nav.addEventListener( 'keydown', function ( event ) {
209			if ( event.key !== 'Escape' ) {
210				return;
211			}
212
213			if ( nav.classList.contains( 'is-open' ) ) {
214				setOpen( false );
215				toggle.focus();
216			}
217		} );
218
219		// Collapse when the layout goes back to the desktop menu.
220		if ( window.matchMedia ) {
221			var desktop = window.matchMedia( '(min-width: 768px)' );
222
223			var reset = function () {
224				setOpen( false );
225
226				Array.prototype.forEach.call( menu.querySelectorAll( '.has-submenu' ), function ( item ) {
227					item.classList.remove( 'submenu-open' );
228
229					var button = item.querySelector( '.submenu-toggle' );
230					var sub = item.querySelector( 'ul' );
231
232					if ( button ) {
233						button.setAttribute( 'aria-expanded', 'false' );
234					}
235
236					// Clear the inline display so the stylesheet governs again.
237					if ( sub ) {
238						sub.style.display = '';
239					}
240				} );
241			};
242
243			if ( desktop.addEventListener ) {
244				desktop.addEventListener( 'change', reset );
245			} else if ( desktop.addListener ) {
246				desktop.addListener( reset );
247			}
248
249			window.addEventListener( 'resize', function () {
250				applyMenuState( nav.classList.contains( 'is-open' ) );
251			} );
252		}
253	}
254
255	/**
256	 * Keyboard support for the desktop dropdowns.
257	 *
258	 * The stylesheet opens sub-menus on :focus-within, which covers tabbing.
259	 * This adds the class as well so older engines behave the same way.
260	 */
261	function initDesktopMenuFocus() {
262		var nav = document.getElementById( 'main-nav' );
263
264		if ( ! nav ) {
265			return;
266		}
267
268		Array.prototype.forEach.call( nav.querySelectorAll( 'a' ), function ( link ) {
269			link.addEventListener( 'focus', function () {
270				var item = link.parentNode;
271
272				while ( item && item !== nav ) {
273					if ( item.tagName === 'LI' ) {
274						item.classList.add( 'focus' );
275					}
276					item = item.parentNode;
277				}
278			} );
279
280			link.addEventListener( 'blur', function () {
281				var item = link.parentNode;
282
283				while ( item && item !== nav ) {
284					if ( item.tagName === 'LI' ) {
285						item.classList.remove( 'focus' );
286					}
287					item = item.parentNode;
288				}
289			} );
290		} );
291	}
292
293	function init() {
294		initBackToTop();
295		initMobileMenu();
296		initDesktopMenuFocus();
297	}
298
299	if ( document.readyState !== 'loading' ) {
300		init();
301	} else {
302		document.addEventListener( 'DOMContentLoaded', init );
303	}
304}() );

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.