PageSourceSearch

https://des-gens.net/wp-content/themes/s/js/navigation.js?ver=1.2.5

js des-gens.net collected 2026-09-24 22:20:24 UTC 8,541 bytes, 259 lines download raw bytes

1/**
2 * File navigation.js.
3 *
4 * Handles toggling the navigation menu for small screens, smooth scroll,
5 * and enables TAB key navigation support for dropdown menus.
6 */
7( function() {
8	const siteNavigation = document.getElementById( 'site-navigation' );
9
10	// Return early if the navigation doesn't exist.
11	if ( ! siteNavigation ) {
12		return;
13	}
14
15	const button = siteNavigation.getElementsByTagName( 'button' )[ 0 ];
16	const menu = siteNavigation.getElementsByTagName( 'ul' )[ 0 ];
17
18	// Hide menu toggle button if menu is empty and return early.
19	if ( 'undefined' === typeof menu ) {
20		if ( button ) {
21			button.style.display = 'none';
22		}
23		return;
24	}
25
26	if ( ! menu.classList.contains( 'nav-menu' ) ) {
27		menu.classList.add( 'nav-menu' );
28	}
29
30	// Toggle the .toggled class and the aria-expanded value each time the button is clicked.
31	if ( button ) {
32		button.addEventListener( 'click', function() {
33			siteNavigation.classList.toggle( 'toggled' );
34			document.body.classList.toggle( 'menu-open' );
35
36			if ( button.getAttribute( 'aria-expanded' ) === 'true' ) {
37				button.setAttribute( 'aria-expanded', 'false' );
38			} else {
39				button.setAttribute( 'aria-expanded', 'true' );
40			}
41		} );
42	}
43
44	// Handle sub-menu toggle using event delegation (works for dynamic content)
45	// This ensures it works even if menu is loaded dynamically
46	menu.addEventListener( 'click', function( e ) {
47		// Find the clicked link (could be the link itself or a child element)
48		let clickedLink = e.target;
49		
50		// If target is not a link, try to find the closest link
51		if ( clickedLink.tagName !== 'A' ) {
52			clickedLink = clickedLink.closest( 'a' );
53		}
54		
55		if ( ! clickedLink ) {
56			return;
57		}
58		
59		// Check if this link is inside a menu item with children
60		const menuItem = clickedLink.closest( '.menu-item-has-children, .page_item_has_children' );
61		
62		if ( menuItem ) {
63			const isMobile = window.innerWidth <= 1024;
64			
65			// Check if this link is inside a sub-menu
66			const isSubMenuLink = clickedLink.closest( '.sub-menu' ) !== null;
67			
68			if ( isMobile ) {
69				// If link is NOT in sub-menu, it's a parent link - toggle submenu
70				// If link IS in sub-menu, allow normal navigation
71				if ( ! isSubMenuLink ) {
72					// This is a parent link - toggle submenu
73					e.preventDefault();
74					e.stopImmediatePropagation();
75					
76					// Toggle submenu
77					const isOpen = menuItem.classList.contains( 'submenu-open' );
78					const allMenuItemsWithChildren = menu.querySelectorAll( '.menu-item-has-children, .page_item_has_children' );
79					
80					// Close all other submenus first
81					allMenuItemsWithChildren.forEach( function( otherItem ) {
82						if ( otherItem !== menuItem ) {
83							otherItem.classList.remove( 'submenu-open' );
84						}
85					} );
86					
87					// Toggle current submenu
88					if ( isOpen ) {
89						menuItem.classList.remove( 'submenu-open' );
90					} else {
91						menuItem.classList.add( 'submenu-open' );
92					}
93					
94					// Force a reflow to ensure CSS transition works
95					void menuItem.offsetHeight;
96					
97					return false;
98				}
99				// If it's a sub-menu link, let it navigate normally (don't prevent default)
100			} else {
101				// On desktop, prevent navigation for placeholder links
102				const href = clickedLink.getAttribute( 'href' );
103				const isPlaceholderLink = ! href || href === '#' || href === '';
104				if ( isPlaceholderLink ) {
105					e.preventDefault();
106				}
107			}
108		}
109	}, true ); // Use capture phase to run before other handlers
110
111	// Smooth scroll for anchor links (but skip links with sub-menus on mobile)
112	const menuLinks = menu.querySelectorAll( 'a[href^="#"]' );
113	menuLinks.forEach( function( link ) {
114		link.addEventListener( 'click', function( e ) {
115			// Skip if default was already prevented (e.g., by sub-menu handler)
116			if ( e.defaultPrevented ) {
117				return;
118			}
119			
120			// IMPORTANT: Skip if this link has a sub-menu and we're on mobile
121			const parentItem = link.closest( '.menu-item-has-children, .page_item_has_children' );
122			if ( parentItem && window.innerWidth <= 1024 ) {
123				// Sub-menu handler should handle it
124				return;
125			}
126			
127			const href = this.getAttribute( 'href' );
128			
129			// Only handle if it's a hash link (not just #)
130			if ( href && href !== '#' && href.length > 1 ) {
131				const targetId = href.substring( 1 );
132				// Try to find element by ID
133				const targetElement = document.getElementById( targetId ) || 
134					document.querySelector( '[data-section="' + targetId + '"]' ) ||
135					document.querySelector( 'section[id*="' + targetId + '"]' );
136				
137				if ( targetElement ) {
138					e.preventDefault();
139					
140					// Close mobile menu if open
141					if ( button && button.getAttribute( 'aria-expanded' ) === 'true' ) {
142						siteNavigation.classList.remove( 'toggled' );
143						document.body.classList.remove( 'menu-open' );
144						button.setAttribute( 'aria-expanded', 'false' );
145					}
146					
147					// Calculate offset for fixed header
148					const headerElement = document.getElementById( 'masthead' );
149					let headerOffset = 0;
150
151					if ( headerElement ) {
152						const headerStyles = window.getComputedStyle( headerElement );
153						const headerPosition = headerStyles.getPropertyValue( 'position' );
154
155						if ( headerPosition === 'fixed' || headerPosition === 'sticky' ) {
156							headerOffset = headerElement.offsetHeight;
157						}
158					}
159
160					const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - headerOffset;
161					
162					window.scrollTo( {
163						top: targetPosition,
164						behavior: 'smooth'
165					} );
166				}
167			}
168		}, false ); // Use bubble phase (after capture phase handlers)
169	} );
170
171	// Header scroll effect (optional - add background when scrolled)
172	const header = document.getElementById( 'masthead' );
173	if ( header ) {
174		window.addEventListener( 'scroll', function() {
175			const currentScroll = window.pageYOffset;
176			
177			if ( currentScroll > 50 ) {
178				header.classList.add( 'scrolled' );
179			} else {
180				header.classList.remove( 'scrolled' );
181			}
182		} );
183	}
184
185	// Remove the .toggled class and set aria-expanded to false when the user clicks outside the navigation.
186	document.addEventListener( 'click', function( event ) {
187		const isClickInside = siteNavigation.contains( event.target );
188
189		if ( ! isClickInside && siteNavigation.classList.contains( 'toggled' ) ) {
190			siteNavigation.classList.remove( 'toggled' );
191			document.body.classList.remove( 'menu-open' );
192			if ( button ) {
193				button.setAttribute( 'aria-expanded', 'false' );
194			}
195		}
196	} );
197
198	// Get all the link elements within the menu.
199	const links = menu.getElementsByTagName( 'a' );
200
201	// Handle window resize - close submenus when switching to desktop
202	window.addEventListener( 'resize', function() {
203		// If switched to desktop, close all submenus
204		if ( window.innerWidth > 1024 ) {
205			const allMenuItemsWithChildren = menu.querySelectorAll( '.menu-item-has-children, .page_item_has_children' );
206			allMenuItemsWithChildren.forEach( function( menuItem ) {
207				menuItem.classList.remove( 'submenu-open' );
208			} );
209		}
210	} );
211
212	// Get all the link elements with children within the menu.
213	const linksWithChildren = menu.querySelectorAll( '.menu-item-has-children > a, .page_item_has_children > a' );
214
215	// Toggle focus each time a menu link is focused or blurred.
216	for ( const link of links ) {
217		link.addEventListener( 'focus', toggleFocus, true );
218		link.addEventListener( 'blur', toggleFocus, true );
219	}
220
221	// Toggle focus each time a menu link with children receive a touch event.
222	// BUT: Skip if we're on mobile - sub-menu toggle handles it
223	for ( const link of linksWithChildren ) {
224		link.addEventListener( 'touchstart', function( e ) {
225			// On mobile, don't use focus toggle - use sub-menu toggle instead
226			if ( window.innerWidth > 1024 ) {
227				toggleFocus.call( this, e );
228			}
229		}, false );
230	}
231
232	/**
233	 * Sets or removes .focus class on an element.
234	 */
235	function toggleFocus() {
236		if ( event.type === 'focus' || event.type === 'blur' ) {
237			let self = this;
238			// Move up through the ancestors of the current link until we hit .nav-menu.
239			while ( ! self.classList.contains( 'nav-menu' ) ) {
240				// On li elements toggle the class .focus.
241				if ( 'li' === self.tagName.toLowerCase() ) {
242					self.classList.toggle( 'focus' );
243				}
244				self = self.parentNode;
245			}
246		}
247
248		if ( event.type === 'touchstart' ) {
249			const menuItem = this.parentNode;
250			event.preventDefault();
251			for ( const link of menuItem.parentNode.children ) {
252				if ( menuItem !== link ) {
253					link.classList.remove( 'focus' );
254				}
255			}
256			menuItem.classList.toggle( 'focus' );
257		}
258	}
259}() );

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.