PageSourceSearch

https://www.audubonamherstgolf.com/wp-content/themes/cet-wp-theme-indigo/js/navigation.js?ver=1.0.78

js audubonamherstgolf.com collected 2026-10-04 10:45:19 UTC 10,645 bytes, 406 lines download raw bytes

1/**
2 * File navigation.js.
3 *
4 * Handles toggling the navigation menu for small screens and enables
5 * keyboard support for dropdown menus. Also controls the collapsible
6 * header search form.
7 */
8
9( () => {
10	/* =========================
11	 * Utilities
12	 * ========================= */
13	const mqMobile = window.matchMedia( '(max-width: 1399px)' ),
14		q = ( sel, root = document ) => root.querySelector( sel ),
15		qa = ( sel, root = document ) => Array.from( root.querySelectorAll( sel ) ),
16
17		setAriaExpanded = ( el, value ) => {
18			if ( ! el ) {
19				return;
20			}
21			el.setAttribute( 'aria-expanded', value ? 'true' : 'false' );
22		},
23
24		debounce = ( fn, wait = 150 ) => {
25			let t;
26			return ( ...args ) => {
27				clearTimeout( t );
28				t = setTimeout( () => fn.apply( null, args ), wait );
29			};
30		},
31
32		/* =========================
33         * Primary Navigation
34         * ========================= */
35		siteNavigation = document.querySelector( '[data-nav]' ),
36		menuButton = siteNavigation.querySelector( '[data-toggle]' ),
37		menu = siteNavigation.querySelector( 'ul' ),
38		backButton = document.getElementById( 'back-button' );
39
40	let isAdminBarCalculated = false;
41	calculateAdminBarHeight();
42
43	if ( siteNavigation && menuButton && menu ) {
44		// Initial ARIA state
45		setAriaExpanded( menuButton, false );
46
47		document.addEventListener( 'click', ( e ) => {
48			//toggle humburger
49			if ( e.target.closest( '[data-toggle]' ) ) {
50				if ( ! isAdminBarCalculated ) {
51					calculateAdminBarHeight();
52				}
53
54				siteNavigation.classList.toggle( 'toggled' );
55				document.body.classList.toggle( 'no-scroll' );
56
57				const expanded = menuButton.getAttribute( 'aria-expanded' ) === 'true';
58				setAriaExpanded( menuButton, ! expanded );
59			}
60
61			//toggle dropdown
62			const a = e.target.closest( 'a' );
63			if ( a ) {
64				const li = a.closest( 'li.menu-item-has-children' );
65
66				if ( li && a.parentElement === li ) {
67					e.preventDefault();
68					onToggleClick.call( li, e );
69					return;
70				}
71			}
72
73			// Click-away & back button behavior
74			if ( mqMobile.matches ) {
75				if ( backButton && backButton.contains( e.target ) ) {
76					closeLastSubmenuLevel();
77				}
78			} else if ( ! menu.contains( e.target ) ) {
79				closeAllSubmenus( null );
80			}
81		} );
82
83		// ESC closes any open submenus
84		document.addEventListener( 'keydown', ( e ) => {
85			if ( e.key === 'Escape' ) {
86				if ( ! siteNavigation || ! menu ) {
87					return;
88				}
89
90				const hasOpenSubmenu = menu.querySelector( 'li.open' );
91				if ( hasOpenSubmenu ) {
92					closeLastSubmenuLevel();
93				} else if ( siteNavigation.classList.contains( 'toggled' ) ) {
94					siteNavigation.classList.remove( 'toggled' );
95					document.body.classList.remove( 'no-scroll' );
96					setAriaExpanded( menuButton, false );
97				}
98			}
99		} );
100
101		// Recalculate admin bar + close menu on desktop
102		window.addEventListener( 'resize',
103			debounce( () => {
104				calculateAdminBarHeight(); // recalc on resize
105				closeAllSubmenus( null );
106				if ( ! mqMobile.matches ) {
107					siteNavigation.classList.remove( 'toggled' );
108					document.body.classList.remove( 'no-scroll' );
109					setAriaExpanded( menuButton, false );
110				}
111			}, 150 )
112		);
113	}
114
115	/**
116	 * Toggle dropdowns
117	 *
118	 * @param  e
119	 */
120	function onToggleClick( e ) {
121		e.preventDefault();
122		const link = this.querySelector( ':scope > a' ),
123			siblings = qa( ':scope > li', this.parentElement ).filter( ( el ) => el !== this ),
124			willOpen = ! this.classList.contains( 'open' );
125
126		if ( willOpen ) {
127			if ( this.parentElement === menu ) {
128				// --- TOP-LEVEL ITEM ---
129				// Keep only this branch open, close everything else
130				closeAllSubmenus( this );
131			} else {
132				// --- NESTED ITEM (submenu / subsubmenu) ---
133				// Close only siblings at the same level (and their open descendants)
134				for ( const sib of siblings ) {
135					if ( sib.classList.contains( 'open' ) ) {
136						sib.classList.remove( 'open' );
137
138						const sibLink = sib.querySelector( ':scope > a' );
139						if ( sibLink ) {
140							setAriaExpanded( sibLink, false );
141						}
142
143						// Close any open submenus inside that sibling
144						const nestedOpen = qa( 'li.open', sib );
145						for ( const nested of nestedOpen ) {
146							nested.classList.remove( 'open' );
147							const nestedLink = nested.querySelector( ':scope > a' );
148							if ( nestedLink ) {
149								setAriaExpanded( nestedLink, false );
150							}
151						}
152					}
153				}
154			}
155		} else {
156			// We are closing this item: also close all open descendants
157			const openDescendants = qa( 'li.open', this );
158			for ( const li of openDescendants ) {
159				li.classList.remove( 'open' );
160				const liLink = li.querySelector( ':scope > a' );
161				if ( liLink ) {
162					setAriaExpanded( liLink, false );
163				}
164			}
165		} if ( willOpen ) {
166			if ( this.parentElement === menu ) { // Top leve item
167				closeAllSubmenus( this );
168			} else {  // nested item
169				for ( const sib of siblings ) {
170					if ( sib.classList.contains( 'open' ) ) {
171						sib.classList.remove( 'open' );
172
173						const sibLink = sib.querySelector( ':scope > a' );
174						if ( sibLink ) {
175							setAriaExpanded( sibLink, false );
176						}
177
178						const nestedOpen = qa( 'li.open', sib );
179						for ( const nested of nestedOpen ) {
180							nested.classList.remove( 'open' );
181							const nestedLink = nested.querySelector( ':scope > a' );
182							if ( nestedLink ) {
183								setAriaExpanded( nestedLink, false );
184							}
185						}
186					}
187				}
188			}
189		} else {
190			const openDescendants = qa( 'li.open', this );
191			for ( const li of openDescendants ) {
192				li.classList.remove( 'open' );
193				const liLink = li.querySelector( ':scope > a' );
194				if ( liLink ) {
195					setAriaExpanded( liLink, false );
196				}
197			}
198		}
199
200		this.classList.toggle( 'open', willOpen );
201		if ( link ) {
202			setAriaExpanded( link, willOpen );
203		}
204
205		if ( backButton ) {
206			backButton.style.setProperty( 'display', willOpen ? 'flex' : 'none' );
207		}
208
209		if ( mqMobile.matches ) {
210			for ( const sib of siblings ) {
211				sib.classList.toggle( 'hidden', willOpen );
212			}
213		}
214	}
215
216	/**
217	 * Close all open submenus, optionally preserving `exceptLi`.
218	 *
219	 * @param  exceptLi
220	 */
221	function closeAllSubmenus( exceptLi ) {
222		if ( ! menu ) {
223			return;
224		}
225		const openItems = qa( 'li.open', menu );
226
227		for ( const li of openItems ) {
228			if ( li === exceptLi ) {
229				continue;
230			}
231			li.classList.remove( 'open' );
232			const a = li.querySelector( ':scope > a' );
233			if ( a ) {
234				setAriaExpanded( a, false );
235			}
236		}
237
238		const allItems = qa( 'li', menu );
239		for ( const item of allItems ) {
240			item.classList.remove( 'hidden' );
241		}
242
243		const backButton = document.getElementById( 'back-button' );
244		if ( backButton ) {
245			backButton.style.setProperty( 'display', 'none' );
246		}
247	}
248
249	/**
250	 * Close last submenu level on mobile
251	 *
252	 * @param  exceptLi
253	 */
254
255	function closeLastSubmenuLevel() {
256		if ( ! menu ) {
257			return;
258		}
259
260		const openItems = qa( 'li.open', menu );
261		if ( ! openItems.length ) {
262			return;
263		}
264
265		// The last one in DOM order is the deepest open item
266		const target = openItems[ openItems.length - 1 ],
267
268			toClose = [ target, ...qa( 'li.open', target ) ];
269
270		for ( const li of toClose ) {
271			li.classList.remove( 'open' );
272			const link = li.querySelector( ':scope > a' );
273			if ( link ) {
274				setAriaExpanded( link, false );
275			}
276		}
277
278		// On mobile: unhide siblings at this level
279		if ( mqMobile.matches ) {
280			const siblings = qa( ':scope > li', target.parentElement ).filter(
281				( el ) => el !== target
282			);
283			for ( const sib of siblings ) {
284				sib.classList.remove( 'hidden' );
285			}
286		}
287
288		// Check if there are still any open submenus left
289		const stillOpen = qa( 'li.open', menu );
290
291		if ( backButton ) {
292			if ( stillOpen.length ) {
293				backButton.style.setProperty( 'display', 'flex' );
294			} else {
295				backButton.style.setProperty( 'display', 'none' );
296
297				const allItems = qa( 'li', menu );
298				for ( const item of allItems ) {
299					item.classList.remove( 'hidden' );
300				}
301			}
302		}
303	}
304
305	/**
306	 * Calculate WP admin bar height and apply CSS var + margin-top.
307	 */
308	function calculateAdminBarHeight() {
309		const wpAdminBar = document.getElementById( 'wpadminbar' );
310		if ( wpAdminBar ) {
311			const html = document.documentElement;
312			// Set CSS var for use in styles
313			document.body.style.setProperty( '--admin-bar-height', `${ wpAdminBar.offsetHeight }px` );
314			// Ensure page content is pushed below admin bar
315			html.style.setProperty( 'margin-top', `${ wpAdminBar.offsetHeight }px`, 'important' );
316		} else {
317			document.body.style.setProperty( '--admin-bar-height', '0px' );
318		}
319		isAdminBarCalculated = true;
320	}
321
322	/* =========================
323	 * Header Search Form
324	 * ========================= */
325	qa( '.header-search-form' ).forEach( ( form ) => {
326		const btn = q( '.search-submit', form ),
327			input = q( '.search-field', form );
328
329		if ( ! btn ) {
330			return;
331		}
332
333		form.classList.add( 'is-collapsed' );
334		setAriaExpanded( btn, false );
335
336		// Expand on button click (first click only when collapsed)
337		btn.addEventListener( 'click', ( e ) => {
338			if ( form.classList.contains( 'is-collapsed' ) ) {
339				e.preventDefault();
340				form.classList.remove( 'is-collapsed' );
341				setAriaExpanded( btn, true );
342				if ( input ) {
343					input.focus();
344				}
345			}
346		} );
347
348		// Collapse on ESC within the form
349		form.addEventListener( 'keydown', ( e ) => {
350			if ( e.key === 'Escape' || e.keyCode === 27 ) {
351				form.classList.add( 'is-collapsed' );
352				setAriaExpanded( btn, false );
353				btn.focus();
354			}
355		} );
356
357		// Click-away collapses the form
358		document.addEventListener( 'click', ( e ) => {
359			if ( ! form.contains( e.target ) ) {
360				form.classList.add( 'is-collapsed' );
361				setAriaExpanded( btn, false );
362			}
363		} );
364	} );
365
366	/* =========================
367	 * Logic to change sticky header
368	 * BG color on scroll
369	 * ========================= */
370	const ScrollManager = {
371		// Configuration
372		el: document.querySelector( '.site-header.sticky' ),
373		threshold: 50,
374		ticking: false,
375		lastScrollY: 0,
376
377		// The logic to update the UI
378		updateUI() {
379			this.el.classList.toggle( 'scrolled', this.lastScrollY > this.threshold );
380			this.ticking = false;
381		},
382
383		// The scroll event handler
384		onScroll() {
385			this.lastScrollY = window.scrollY;
386
387			if ( ! this.ticking ) {
388				// Use bind(this) so 'this' inside updateUI still refers to ScrollManager
389				window.requestAnimationFrame( this.updateUI.bind( this ) );
390				this.ticking = true;
391			}
392		},
393
394		// Entry point
395		init() {
396			if ( ! this.el ) return;
397			window.addEventListener( 'scroll', () => this.onScroll(), { passive: true } );
398
399			// Run once on load in case the user refreshes mid-page
400			this.onScroll(); 
401		}
402	};
403
404	ScrollManager.init();
405
406} )();

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.