1(() => { 2 const SELECTORS = { 3 header: '[data-marketing-header]', 4 menuButton: '[data-menu-toggle]', 5 nav: '[data-marketing-nav]', 6 dropdown: '[data-nav-dropdown]', 7 dropdownButton: '[data-dropdown-toggle]' 8 }; 9 10 const CLASSES = { 11 menuOpen: 'is-menu-open', 12 dropdownOpen: 'is-open', 13 bodyMenuOpen: 'marketing-menu-open' 14 }; 15 16 const mobileMediaQuery = window.matchMedia('(max-width: 991px)'); 17 const hoverMediaQuery = window.matchMedia('(hover: hover) and (pointer: fine)'); 18 19 const initMarketingHeader = () => { 20 const header = document.querySelector(SELECTORS.header); 21 if (!(header instanceof HTMLElement)) { 22 return; 23 } 24 25 const menuButton = header.querySelector(SELECTORS.menuButton); 26 const nav = header.querySelector(SELECTORS.nav); 27 const dropdowns = Array.from(document.querySelectorAll(SELECTORS.dropdown)).filter( 28 (dropdown) => dropdown instanceof HTMLElement 29 ); 30 31 const setDropdownOpen = (dropdown, isOpen) => { 32 const button = dropdown.querySelector(SELECTORS.dropdownButton); 33 if (!(button instanceof HTMLElement)) { 34 return; 35 } 36 37 dropdown.classList.toggle(CLASSES.dropdownOpen, isOpen); 38 if (dropdown instanceof HTMLDetailsElement) { 39 dropdown.open = isOpen; 40 } 41 button.setAttribute('aria-expanded', String(isOpen)); 42 }; 43 44 const closeDropdowns = (exceptDropdown = null) => { 45 dropdowns.forEach((dropdown) => { 46 if (dropdown === exceptDropdown) { 47 return; 48 } 49 50 setDropdownOpen(dropdown, false); 51 }); 52 }; 53 54 const setMenuOpen = (isOpen) => { 55 header.classList.toggle(CLASSES.menuOpen, isOpen); 56 document.body.classList.toggle(CLASSES.bodyMenuOpen, isOpen); 57 58 if (menuButton instanceof HTMLButtonElement) { 59 menuButton.setAttribute('aria-expanded', String(isOpen)); 60 } 61 62 if (!isOpen) { 63 closeDropdowns(); 64 } 65 }; 66 67 dropdowns.forEach((dropdown) => { 68 setDropdownOpen(dropdown, false); 69 70 const button = dropdown.querySelector(SELECTORS.dropdownButton); 71 if (!(button instanceof HTMLElement)) { 72 return; 73 } 74 75 button.addEventListener('click', (event) => { 76 // The same handler drives buttons and native disclosure summaries. 77 if (dropdown instanceof HTMLDetailsElement) event.preventDefault(); 78 const isOpen = dropdown.classList.contains(CLASSES.dropdownOpen); 79 closeDropdowns(dropdown); 80 setDropdownOpen(dropdown, !isOpen); 81 }); 82 83 dropdown.addEventListener('mouseenter', () => { 84 if (!hoverMediaQuery.matches) { 85 return; 86 } 87 88 closeDropdowns(dropdown); 89 setDropdownOpen(dropdown, true); 90 }); 91 92 dropdown.addEventListener('mouseleave', () => { 93 if (!hoverMediaQuery.matches) { 94 return; 95 } 96 97 const activeElement = document.activeElement; 98 if (activeElement instanceof HTMLElement && dropdown.contains(activeElement)) { 99 return; 100 } 101 102 setDropdownOpen(dropdown, false); 103 }); 104 105 dropdown.addEventListener('focusout', (event) => { 106 const nextFocused = event.relatedTarget; 107 // Safari may blur the trigger without focusing a tapped link. Closing 108 // here would hide the link before its click. Only a known focus 109 // destination outside is an exit; outside clicks and Escape still close. 110 if (!(nextFocused instanceof Node) || dropdown.contains(nextFocused)) { 111 return; 112 } 113 114 setDropdownOpen(dropdown, false); 115 }); 116 }); 117 118 if (menuButton instanceof HTMLButtonElement) { 119 menuButton.addEventListener('click', () => { 120 setMenuOpen(!header.classList.contains(CLASSES.menuOpen)); 121 }); 122 } 123 124 if (nav instanceof HTMLElement) { 125 nav.addEventListener('click', (event) => { 126 if (!mobileMediaQuery.matches) { 127 return; 128 } 129 130 const target = event.target; 131 if (!(target instanceof Element) || !target.closest('a')) { 132 return; 133 } 134 135 setMenuOpen(false); 136 }); 137 } 138 139 document.addEventListener('click', (event) => { 140 const target = event.target; 141 if ( 142 target instanceof Node && 143 (header.contains(target) || dropdowns.some((dropdown) => dropdown.contains(target))) 144 ) { 145 return; 146 } 147 148 setMenuOpen(false); 149 }); 150 151 document.addEventListener('keydown', (event) => { 152 if (event.key !== 'Escape') { 153 return; 154 } 155 156 setMenuOpen(false); 157 }); 158 159 mobileMediaQuery.addEventListener('change', (event) => { 160 if (!event.matches) { 161 setMenuOpen(false); 162 } 163 }); 164 }; 165 166 if (document.readyState === 'loading') { 167 document.addEventListener('DOMContentLoaded', initMarketingHeader, { once: true }); 168 } else { 169 initMarketingHeader(); 170 } 171})();
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.