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.