1(function () { 2 'use strict'; 3 4 function initMainNav() { 5 const nav = document.getElementById('main-nav'); 6 7 if (!nav) { 8 return; 9 } 10 11 if (nav.dataset.navigationInitialized === 'true') { 12 return; 13 } 14 15 nav.dataset.navigationInitialized = 'true'; 16 17 const mobileBreakpoint = 640; 18 19 /* 20 * Main mobile navigation toggle. 21 */ 22 const navToggle = document.createElement('button'); 23 24 navToggle.type = 'button'; 25 navToggle.className = 'main-nav-toggle'; 26 navToggle.setAttribute('aria-expanded', 'false'); 27 navToggle.setAttribute('aria-controls', 'main-nav'); 28 29 navToggle.innerHTML = ` 30 <span class="main-nav-toggle__label">Menu</span> 31 <span class="main-nav-toggle__icon" aria-hidden="true"></span> 32 `; 33 34 nav.parentNode.insertBefore(navToggle, nav); 35 36 37 /** 38 * Get the direct label element for a menu item. 39 * 40 * This may be either: 41 * - a real link 42 * - a non-link placeholder label 43 */ 44 function getItemLabel(item) { 45 return item.querySelector( 46 ':scope > a, :scope > .menu-item-label' 47 ); 48 } 49 50 51 /** 52 * Reset viewport correction on a submenu. 53 */ 54 function resetSubmenuPosition(submenu) { 55 if (!submenu) { 56 return; 57 } 58 59 submenu.style.removeProperty('--submenu-shift'); 60 } 61 62 63 /** 64 * Keep a desktop dropdown inside the viewport. 65 */ 66 function positionSubmenu(item) { 67 if (window.innerWidth <= mobileBreakpoint) { 68 return; 69 } 70 71 const submenu = item.querySelector(':scope > .sub-menu'); 72 73 if (!submenu) { 74 return; 75 } 76 77 resetSubmenuPosition(submenu); 78 79 const viewportPadding = 16; 80 const rect = submenu.getBoundingClientRect(); 81 82 let shift = 0; 83 84 if (rect.right > window.innerWidth - viewportPadding) { 85 shift -= rect.right - (window.innerWidth - viewportPadding); 86 } 87 88 if (rect.left + shift < viewportPadding) { 89 shift += viewportPadding - (rect.left + shift); 90 } 91 92 if (shift !== 0) { 93 submenu.style.setProperty( 94 '--submenu-shift', 95 `${shift}px` 96 ); 97 } 98 } 99 100 101 /** 102 * Close a single submenu. 103 */ 104 function closeSubmenu(item, returnFocus = false) { 105 const button = item.querySelector( 106 ':scope > .submenu-toggle' 107 ); 108 109 const label = getItemLabel(item);
110 const submenu = item.querySelector(':scope > .sub-menu'); 111 112 item.classList.remove('submenu-open'); 113 114 resetSubmenuPosition(submenu); 115 116 if (!button) { 117 return; 118 } 119 120 button.setAttribute('aria-expanded', 'false'); 121 122 if (label) { 123 button.setAttribute( 124 'aria-label', 125 `Open submenu for ${label.textContent.trim()}` 126 ); 127 } 128 129 if (returnFocus) { 130 button.focus(); 131 } 132 } 133 134 135 /** 136 * Close sibling submenus at the same menu level. 137 */ 138 function closeSiblingSubmenus(currentItem) { 139 const parent = currentItem.parentElement; 140 141 if (!parent) { 142 return; 143 } 144 145 parent 146 .querySelectorAll(':scope > .submenu-open') 147 .forEach(function (item) { 148 if (item === currentItem) { 149 return; 150 } 151 152 closeSubmenu(item); 153 }); 154 } 155 156 157 /** 158 * Close the mobile navigation. 159 */ 160 function closeMainNav(returnFocus = false) { 161 nav.classList.remove('main-nav-open'); 162 navToggle.setAttribute('aria-expanded', 'false'); 163 164 if (returnFocus) { 165 navToggle.focus(); 166 } 167 } 168 169 170 /* 171 * Mobile navigation toggle. 172 */ 173 navToggle.addEventListener('click', function () { 174 const isOpen = nav.classList.toggle('main-nav-open'); 175 176 navToggle.setAttribute( 177 'aria-expanded', 178 isOpen ? 'true' : 'false' 179 ); 180 }); 181 182 183 /* 184 * Add submenu toggle buttons. 185 */ 186 const parentItems = nav.querySelectorAll( 187 '.menu-item-has-children' 188 ); 189 190 parentItems.forEach(function (item, index) { 191 const label = getItemLabel(item);
192 const submenu = item.querySelector(':scope > .sub-menu'); 193 194 if (!label || !submenu) { 195 return; 196 } 197 198 const itemId = item.id 199 ? item.id.replace('menu-item-', '') 200 : index + 1; 201 202 const submenuId = `nav-submenu-${itemId}`; 203 const labelText = label.textContent.trim(); 204 205 submenu.id = submenuId; 206 207 const button = document.createElement('button'); 208 209 button.type = 'button'; 210 button.className = 'submenu-toggle'; 211 212 button.setAttribute('aria-expanded', 'false'); 213 button.setAttribute('aria-controls', submenuId); 214 button.setAttribute( 215 'aria-label', 216 `Open submenu for ${labelText}` 217 ); 218 219 button.innerHTML = 220 '<span aria-hidden="true"></span>'; 221 222 label.insertAdjacentElement('afterend', button); 223 224 button.addEventListener('click', function () { 225 const isCurrentlyOpen = 226 item.classList.contains('submenu-open'); 227 228 if (!isCurrentlyOpen) { 229 closeSiblingSubmenus(item); 230 } 231 232 const isOpen = 233 item.classList.toggle('submenu-open'); 234 235 button.setAttribute( 236 'aria-expanded', 237 isOpen ? 'true' : 'false' 238 ); 239 240 button.setAttribute( 241 'aria-label', 242 `${isOpen ? 'Close' : 'Open'} submenu for ${labelText}` 243 ); 244 245 if (isOpen) { 246 requestAnimationFrame(function () { 247 positionSubmenu(item); 248 }); 249 } else { 250 resetSubmenuPosition(submenu); 251 } 252 }); 253 }); 254 255 256 /* 257 * Escape closes the nearest open submenu. 258 */ 259 nav.addEventListener('keydown', function (event) { 260 if (event.key !== 'Escape') { 261 return; 262 } 263 264 const openItem = 265 event.target.closest('.submenu-open'); 266 267 if (openItem && nav.contains(openItem)) { 268 event.preventDefault(); 269 270 closeSubmenu(openItem, true); 271 272 return; 273 } 274 275 if (nav.classList.contains('main-nav-open')) { 276 event.preventDefault(); 277 278 closeMainNav(true); 279 } 280 }); 281 282 283 /* 284 * Clicking outside the navigation closes open menus. 285 */ 286 document.addEventListener('click', function (event) { 287 if ( 288 nav.contains(event.target) || 289 navToggle.contains(event.target) 290 ) { 291 return; 292 } 293 294 nav 295 .querySelectorAll('.submenu-open') 296 .forEach(function (item) { 297 closeSubmenu(item); 298 }); 299 }); 300 301 302 /* 303 * Recalculate open dropdown positioning after resize. 304 */ 305 window.addEventListener('resize', function () { 306 nav 307 .querySelectorAll( 308 '.menu > .submenu-open' 309 ) 310 .forEach(function (item) { 311 positionSubmenu(item); 312 }); 313 }); 314 } 315 316 317 if (document.readyState === 'loading') { 318 document.addEventListener( 319 'DOMContentLoaded', 320 initMainNav 321 ); 322 } else { 323 initMainNav(); 324 } 325})();
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.