1const setupMenu = () => { 2 /** @type {HTMLElement} */ 3 const $header = document.querySelector('.Header'); 4 if (!$header) { 5 console.warn('Cannot find `.Header` element'); 6 return; 7 } 8 9 /** Open header menu */ 10 const openMenu = ($link, $menu, options = {}) => { 11 $link.setAttribute('aria-expanded', 'true'); 12 $menu.hidden = false; 13 // Fix Safari triggering `focus-visible` state on click events 14 if (options.isMouse) { 15 $menu.dataset.focusVisible = false; 16 } 17 $menu.focus({preventScroll: true, focusVisible: options.isKeyboard}); 18 $header.dispatchEvent(new CustomEvent('menu-open')); 19 }; 20 21 /** Close header menu */ 22 const closeMenu = ($link, $menu) => { 23 $link.setAttribute('aria-expanded', 'false'); 24 $menu.hidden = true; 25 delete $menu.dataset.focusVisible; 26 $header.dispatchEvent(new CustomEvent('menu-close')); 27 }; 28 29 /** Toggle header menu (open or closed) */ 30 const toggleMenu = ($link, options = {}) => { 31 const id = $link.getAttribute('aria-controls'); 32 /** @type {HTMLElement} */ 33 const $menu = document.querySelector(`#${id}`); 34 if (!$menu) return false; 35 36 const $otherLinks = Array.from( 37 document.querySelectorAll('.Header__nav a[aria-controls]') 38 ).filter(l => l !== $link && l.getAttribute('aria-expanded') === 'true'); 39 40 $otherLinks.forEach(l => { 41 const otherId = l.getAttribute('aria-controls'); 42 const $otherMenu = document.querySelector(`#${otherId}`); 43 if ($otherMenu) closeMenu(l, $otherMenu); 44 }); 45 46 globalThis.safeViewTransition(() => { 47 if ($link.getAttribute('aria-expanded') === 'true') { 48 closeMenu($link, $menu, options); 49 } else { 50 openMenu($link, $menu, options); 51 } 52 }); 53 }; 54 55 // Ensure attributes are set on Header-Menu elements 56 /** @type {Array<HTMLElement>} */ 57 const $menus = Array.from($header.querySelectorAll('.Header-Menu')); 58 $menus.forEach(($menu) => { 59 $menu.tabIndex = -1; 60 $menu.hidden = true; 61 }); 62 63 /** @type {Array<HTMLElement>} */ 64 const $headerLinks = Array.from( 65 $header.querySelectorAll('.Header__nav a[aria-controls]') 66 ); 67 68 for (const $link of $headerLinks) { 69 // Set default state 70 $link.setAttribute('aria-expanded', 'false'); 71 // Handle keyboard events to open/close menu 72 $link.addEventListener('keydown', (ev) => { 73 if ([' ', 'Enter'].includes(ev.key)) { 74 ev.preventDefault(); 75 toggleMenu($link, {isKeyboard: true}); 76 } 77 }); 78 // Handle pointer event to open/close menu 79 $link.addEventListener('click', (ev) => { 80 ev.preventDefault(); 81 toggleMenu($link, {isMouse: true}); 82 }); 83 } 84 85 // Handle custom events 86 $header.addEventListener('search-open', () => { 87 for (const $link of $headerLinks) { 88 if ($link.getAttribute('aria-expanded') === 'true') { 89 toggleMenu($link); 90 } 91 } 92 }); 93 94 document.addEventListener('click', ev => { 95 const isClickInside = $header.contains(ev.target); 96 if (!isClickInside) { 97 $headerLinks.forEach($link => { 98 if ($link.getAttribute('aria-expanded') === 'true') { 99 const id = $link.getAttribute('aria-controls'); 100 const $menu = document.querySelector(`#${id}`); 101 if ($menu) closeMenu($link, $menu); 102 } 103 }); 104 } 105 }); 106}; 107 108// Activate on document ready 109setupMenu();
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.