https://www.audubonamherstgolf.com/wp-content/themes/cet-wp-theme-indigo/js/navigation.js?ver=1.0.78
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.