1import {setAriaExpanded, setElementHidden} from '../core/aria.js'; 2 3const closeDelayMs = 180; 4 5const setSubmenuOpenState = (submenu, isOpen) => { 6 submenu.classList.toggle('is-open', isOpen); 7 8 const trigger = submenu.querySelector('.header-submenu__trigger'); 9 const panel = submenu.querySelector('.header-submenu__panel'); 10 setAriaExpanded(trigger, isOpen); 11 setElementHidden(panel, !isOpen); 12}; 13 14const closeSubmenus = (submenus, exceptSubmenu = null) => { 15 submenus.forEach((submenu) => { 16 if (submenu !== exceptSubmenu) { 17 setSubmenuOpenState(submenu, false); 18 } 19 }); 20}; 21 22const closeMegaMenuDropdowns = () => { 23 document.querySelectorAll('[data-mega-menu-dropdown].is-open').forEach((dropdown) => { 24 dropdown.classList.remove('is-open'); 25 setAriaExpanded(dropdown.querySelector('.mega-menu-dropdown__trigger'), false); 26 setElementHidden(dropdown.querySelector('.mega-menu-dropdown__panel'), true); 27 }); 28}; 29 30export const initHeaderSubmenus = () => { 31 const submenus = Array.from(document.querySelectorAll('[data-header-submenu]')); 32 if (!submenus.length) { 33 return; 34 } 35 36 closeSubmenus(submenus); 37 38 submenus.forEach((submenu) => {
39 let closeTimeout = null; 40 let openedByHover = false; 41 const trigger = submenu.querySelector('.header-submenu__trigger'); 42 43 const clearCloseTimeout = () => { 44 if (closeTimeout !== null) { 45 window.clearTimeout(closeTimeout); 46 closeTimeout = null; 47 } 48 }; 49 50 const scheduleClose = () => { 51 clearCloseTimeout(); 52 closeTimeout = window.setTimeout(() => { 53 if (submenu.contains(document.activeElement)) { 54 closeTimeout = null; 55 return; 56 } 57 58 setSubmenuOpenState(submenu, false); 59 closeTimeout = null; 60 }, closeDelayMs); 61 }; 62 63 submenu.addEventListener('mouseenter', () => { 64 clearCloseTimeout(); 65 openedByHover = !submenu.classList.contains('is-open'); 66 closeSubmenus(submenus, submenu); 67 closeMegaMenuDropdowns(); 68 setSubmenuOpenState(submenu, true); 69 }); 70 71 submenu.addEventListener('mouseleave', () => { 72 openedByHover = false; 73 scheduleClose(); 74 }); 75 76 submenu.addEventListener('focusin', () => { 77 clearCloseTimeout(); 78 if (document.activeElement === trigger) { 79 return; 80 } 81 82 openedByHover = false; 83 closeSubmenus(submenus, submenu); 84 closeMegaMenuDropdowns(); 85 setSubmenuOpenState(submenu, true); 86 }); 87 88 submenu.addEventListener('focusout', (event) => { 89 const nextFocusedElement = event.relatedTarget; 90 if (!(nextFocusedElement instanceof Node) || !submenu.contains(nextFocusedElement)) { 91 scheduleClose(); 92 } 93 }); 94 95 if (trigger instanceof HTMLElement) { 96 trigger.addEventListener('click', (event) => { 97 clearCloseTimeout(); 98 const shouldOpen = event.detail > 0 && openedByHover 99 ? true 100 : !submenu.classList.contains('is-open'); 101 openedByHover = false; 102 closeSubmenus(submenus, submenu); 103 if (shouldOpen) { 104 closeMegaMenuDropdowns(); 105 } 106 setSubmenuOpenState(submenu, shouldOpen); 107 }); 108 } 109 }); 110 111 document.addEventListener('click', (event) => { 112 const target = event.target; 113 if (!(target instanceof Node)) { 114 return; 115 } 116 117 const clickedInsideSubmenu = submenus.some((submenu) => submenu.contains(target)); 118 if (!clickedInsideSubmenu) { 119 closeSubmenus(submenus); 120 } 121 }); 122 123 document.addEventListener('keydown', (event) => { 124 if (event.key === 'Escape') { 125 const activeElement = document.activeElement; 126 const activeSubmenu = activeElement instanceof Node 127 ? submenus.find((submenu) => submenu.classList.contains('is-open') && submenu.contains(activeElement)) 128 : null; 129 const activeTrigger = activeSubmenu?.querySelector('.header-submenu__trigger'); 130 closeSubmenus(submenus); 131 if (activeTrigger instanceof HTMLElement) { 132 activeTrigger.focus(); 133 } 134 } 135 }); 136};
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.