1(function ($, window, document) { 2 'use strict'; 3 4 $(function () { 5 var $masthead = $('#masthead'); 6 7 if (!$masthead.length) { 8 return; 9 } 10 11 var $followWrap = $masthead.parent('.followWrap'); 12 var $navWrapper = $('#primary-nav-wrapper'); 13 var $toggle = $('#nav-toggle'); 14 var $nav = $('#site-navigation'); 15 16 if ( 17 !$followWrap.length || 18 !$navWrapper.length || 19 !$toggle.length || 20 !$nav.length 21 ) { 22 return; 23 } 24 25 var MOBILE_BREAKPOINT = 1140; 26 var BRANDING_MIN_WIDTH = 270; 27 var rafId = 0; 28 29 function getStableTitleWidth() { 30 var titleEl = $masthead.find('.site-text-wrapper .site-title').get(0); 31 32 if (!titleEl) { 33 return null; 34 } 35 36 var hadCollapseClass = $masthead.hasClass('header-needs-collapse'); 37 var navWasHidden = $nav.is('[hidden]'); 38 var navClass = $nav.attr('class') || ''; 39 var toggleExpanded = $toggle.attr('aria-expanded'); 40 41 $masthead.removeClass('header-needs-collapse'); 42 $nav.removeAttr('hidden'); 43 $nav.removeClass('is-open is-collapsed'); 44 45 var width = titleEl.getBoundingClientRect().width; 46 47 if (hadCollapseClass) { 48 $masthead.addClass('header-needs-collapse'); 49 } 50 51 $nav.attr('class', navClass); 52 53 if (navWasHidden) { 54 $nav.attr('hidden', 'hidden'); 55 } else { 56 $nav.removeAttr('hidden'); 57 } 58 59 if (typeof toggleExpanded !== 'undefined') { 60 $toggle.attr('aria-expanded', toggleExpanded); 61 } 62 63 return Math.ceil(width); 64 } 65 66 function shouldCollapseEarly() { 67 if (window.innerWidth <= MOBILE_BREAKPOINT) { 68 return true; 69 } 70 71 var titleWidth = getStableTitleWidth(); 72 73 if (titleWidth === null) { 74 return false; 75 } 76 77 return titleWidth <= BRANDING_MIN_WIDTH; 78 } 79 80 function isCollapsedView() { 81 return shouldCollapseEarly(); 82 } 83 84 function isDesktopView() { 85 return window.innerWidth >= 1141 && !isCollapsedView(); 86 } 87 88 function resetDesktopSubmenuDirections() { 89 $nav.find('li.submenu-open-left').removeClass('submenu-open-left'); 90 } 91 92 function positionDesktopSubmenu($item) { 93 if (!isDesktopView()) { 94 return; 95 } 96 97 var $submenu = $item.children('.sub-menu'); 98 99 if (!$submenu.length) { 100 return; 101 } 102 103 $item.removeClass('submenu-open-left'); 104 105 var submenuEl = $submenu.get(0); 106 if (!submenuEl) { 107 return; 108 } 109 110 var previousVisibility = submenuEl.style.visibility; 111 var previousOpacity = submenuEl.style.opacity; 112 var previousPointerEvents = submenuEl.style.pointerEvents; 113 var previousDisplay = submenuEl.style.display; 114 115 submenuEl.style.visibility = 'hidden'; 116 submenuEl.style.opacity = '0'; 117 submenuEl.style.pointerEvents = 'none'; 118 submenuEl.style.display = 'block'; 119 120 var itemRect = $item.get(0).getBoundingClientRect(); 121 var submenuRect = submenuEl.getBoundingClientRect(); 122 var viewportWidth = document.documentElement.clientWidth; 123 var gutter = 8; 124 125 var isTopLevel = $item.parent('.onepress-menu').length > 0; 126 127 if (isTopLevel) { 128 var projectedRight = itemRect.left + submenuRect.width; 129 130 if (projectedRight > viewportWidth - gutter) { 131 $item.addClass('submenu-open-left'); 132 } 133 } else { 134 var projectedNestedRight = itemRect.right + submenuRect.width; 135 var projectedNestedLeft = itemRect.left - submenuRect.width; 136 137 if ( 138 projectedNestedRight > viewportWidth - gutter && 139 projectedNestedLeft >= gutter 140 ) { 141 $item.addClass('submenu-open-left'); 142 } 143 } 144 145 submenuEl.style.visibility = previousVisibility; 146 submenuEl.style.opacity = previousOpacity; 147 submenuEl.style.pointerEvents = previousPointerEvents; 148 submenuEl.style.display = previousDisplay; 149 } 150 151 function refreshDesktopSubmenuDirections() { 152 if (!isDesktopView()) { 153 resetDesktopSubmenuDirections(); 154 return; 155 } 156 157 $nav.find('li.menu-item-has-children').each(function () { 158 positionDesktopSubmenu($(this)); 159 }); 160 } 161 162 function syncFollowWrapHeight() { 163 window.cancelAnimationFrame(rafId); 164 165 rafId = window.requestAnimationFrame(function () { 166 var mastheadEl = $masthead.get(0); 167 var wrapEl = $followWrap.get(0); 168 169 if (!mastheadEl || !wrapEl) { 170 return; 171 } 172
173 var height = Math.ceil(mastheadEl.getBoundingClientRect().height); 174 175 if (height > 0) { 176 wrapEl.style.height = height + 'px'; 177 wrapEl.style.setProperty('--masthead-sticky-height', height + 'px'); 178 document.documentElement.style.setProperty('--masthead-sticky-height', height + 'px'); 179 } 180 181 syncNavViewportMetrics(); 182 }); 183 } 184 185 function getBrandbarHeight() { 186 var brandbar = document.getElementById('brandbar-tsu'); 187 188 if (!brandbar) { 189 return 0; 190 } 191 192 return Math.ceil(brandbar.getBoundingClientRect().height); 193 } 194 195 function getVisibleViewportHeight() { 196 if (window.visualViewport && window.visualViewport.height) { 197 return Math.ceil(window.visualViewport.height); 198 } 199 200 return Math.ceil(window.innerHeight || document.documentElement.clientHeight || 0); 201 } 202 203 function syncNavViewportMetrics() { 204 window.requestAnimationFrame(function () { 205 var mastheadEl = $masthead.get(0); 206 207 if (!mastheadEl) { 208 return; 209 } 210 211 var root = document.documentElement; 212 var mastheadHeight = Math.ceil(mastheadEl.getBoundingClientRect().height); 213 var brandbarHeight = getBrandbarHeight(); 214 var visibleViewportHeight = getVisibleViewportHeight(); 215 var navGap = 16; // 1rem breathing room 216 var panelMaxHeight = Math.max( 217 160, 218 visibleViewportHeight - brandbarHeight - mastheadHeight - navGap 219 ); 220 221 root.style.setProperty('--brandbar-height', brandbarHeight + 'px'); 222 root.style.setProperty('--nav-visible-viewport-height', visibleViewportHeight + 'px'); 223 root.style.setProperty('--nav-panel-max-height', panelMaxHeight + 'px'); 224 root.style.setProperty('--masthead-sticky-height', mastheadHeight + 'px'); 225 226 if ($followWrap.length) { 227 $followWrap.get(0).style.setProperty('--masthead-sticky-height', mastheadHeight + 'px'); 228 } 229 }); 230 } 231 232 function setToggleState(isOpen) { 233 $toggle.attr('aria-expanded', isOpen ? 'true' : 'false'); 234 $toggle.attr( 235 'aria-label', 236 isOpen 237 ? 'Close primary navigation menu' 238 : 'Open primary navigation menu' 239 ); 240 241 $nav.toggleClass('is-open', isOpen); 242 $nav.toggleClass('is-collapsed', !isOpen); 243 244 if (isOpen) { 245 $nav.removeAttr('hidden'); 246 } else { 247 $nav.attr('hidden', 'hidden'); 248 closeAllSubmenus(); 249 } 250 251 syncFollowWrapHeight(); 252 } 253 254 function closeMenu(restoreFocus) { 255 if (!isCollapsedView()) { 256 return; 257 } 258 259 setToggleState(false); 260 261 if (restoreFocus) { 262 $toggle.trigger('focus'); 263 } 264 } 265 266 function toggleMenu() { 267 if (!isCollapsedView()) { 268 return; 269 } 270 271 var isOpen = $toggle.attr('aria-expanded') === 'true'; 272 setToggleState(!isOpen); 273 } 274 275 function syncForViewport() { 276 var collapsed = isCollapsedView(); 277 278 $masthead.toggleClass('header-needs-collapse', collapsed); 279 280 if (collapsed) { 281 var isOpen = $toggle.attr('aria-expanded') === 'true'; 282 setToggleState(isOpen); 283 } else { 284 $toggle.attr('aria-expanded', 'false'); 285 $toggle.attr('aria-label', 'Open primary navigation menu'); 286 $nav.removeClass('is-open is-collapsed'); 287 $nav.removeAttr('hidden'); 288 closeAllSubmenus(true); 289 } 290 291 setupSubmenuArrows(); 292 syncFollowWrapHeight(); 293 refreshDesktopSubmenuDirections(); 294 } 295 296 function deferredSync() { 297 window.setTimeout(function () { 298 syncForViewport(); 299 syncFollowWrapHeight(); 300 }, 0); 301 } 302 303 function setupSubmenuArrows() { 304 $nav.find('.nav-toggle-subarrow').each(function (index) { 305 var $arrow = $(this); 306 var $item = $arrow.parent('li'); 307 var $submenu = $item.children('.sub-menu'); 308 var $link = $item.children('a').first(); 309 var submenuId = $submenu.attr('id'); 310 var labelText = $.trim($link.text()); 311 312 if (!$item.length || !$submenu.length) { 313 return; 314 } 315 316 if (!submenuId) { 317 submenuId = 'submenu-' + index; 318 $submenu.attr('id', submenuId); 319 } 320 321 $arrow.attr({ 322 tabindex: '0', 323 role: 'button', 324 'aria-expanded': $item.hasClass('nav-toggle-dropdown') ? 'true' : 'false', 325 'aria-controls': submenuId, 326 'aria-label': labelText 327 ? 'Toggle ' + labelText + ' submenu' 328 : 'Toggle submenu' 329 }); 330 }); 331 } 332 333 function closeSubmenu($item) { 334 var $arrow = $item.children('.nav-toggle-subarrow'); 335 336 $item.removeClass('nav-toggle-dropdown'); 337 $arrow.attr('aria-expanded', 'false'); 338 339 $item 340 .find('> .sub-menu li.nav-toggle-dropdown') 341 .removeClass('nav-toggle-dropdown'); 342 $item 343 .find('> .sub-menu .nav-toggle-subarrow[aria-expanded="true"]') 344 .attr('aria-expanded', 'false'); 345 346 syncFollowWrapHeight(); 347 } 348 349 function openSubmenu($item) { 350 var $submenu = $item.children('.sub-menu'); 351 var $arrow = $item.children('.nav-toggle-subarrow'); 352 353 if (!$submenu.length) { 354 return; 355 } 356 357 $item 358 .siblings('.nav-toggle-dropdown') 359 .each(function () { 360 closeSubmenu($(this)); 361 }); 362 363 $item.addClass('nav-toggle-dropdown'); 364 $arrow.attr('aria-expanded', 'true'); 365 366 syncFollowWrapHeight(); 367 } 368 369 function toggleSubmenu($item) { 370 if ($item.hasClass('nav-toggle-dropdown')) { 371 closeSubmenu($item); 372 } else { 373 openSubmenu($item); 374 } 375 } 376 377 function closeAllSubmenus(forceDesktopCleanup) { 378 $nav.find('li.nav-toggle-dropdown').removeClass('nav-toggle-dropdown'); 379 $nav.find('.nav-toggle-subarrow').attr('aria-expanded', 'false'); 380 381 if (forceDesktopCleanup) { 382 $nav.find('.sub-menu').removeAttr('style'); 383 } 384 385 syncFollowWrapHeight(); 386 } 387 388 $(window).on('load resize orientationchange', function () { 389 syncForViewport(); 390 setupSubmenuArrows(); 391 deferredSync(); 392 syncNavViewportMetrics(); 393 }); 394 395 $(document).on('hero_ready header_view_changed', function () { 396 syncForViewport(); 397 setupSubmenuArrows(); 398 deferredSync(); 399 syncNavViewportMetrics(); 400 }); 401 402 if ('MutationObserver' in window) { 403 var wrapObserver = new MutationObserver(function (mutations) { 404 for (var i = 0; i < mutations.length; i++) { 405 if ( 406 mutations[i].type === 'attributes' && 407 mutations[i].attributeName === 'style' 408 ) { 409 deferredSync(); 410 break; 411 } 412 } 413 }); 414 415 wrapObserver.observe($followWrap.get(0), { 416 attributes: true, 417 attributeFilter: ['style'] 418 }); 419 } 420 421 if ('ResizeObserver' in window) { 422 var mastheadObserver = new ResizeObserver(function () { 423 syncForViewport(); 424 syncFollowWrapHeight(); 425 syncNavViewportMetrics(); 426 }); 427 428 mastheadObserver.observe($masthead.get(0)); 429 } 430
431 if (window.visualViewport) { 432 window.visualViewport.addEventListener('resize', function () { 433 syncForViewport(); 434 syncFollowWrapHeight(); 435 syncNavViewportMetrics(); 436 }); 437 438 window.visualViewport.addEventListener('scroll', function () { 439 syncNavViewportMetrics(); 440 }); 441 } 442 443 /* 444 * Hamburger toggle 445 */ 446 $toggle.on('click', function (event) { 447 event.preventDefault(); 448 toggleMenu(); 449 }); 450 451 $toggle.on('keydown', function (event) { 452 if (!isCollapsedView()) { 453 return; 454 } 455 456 if (event.key === 'Enter' || event.key === ' ') { 457 event.preventDefault(); 458 toggleMenu(); 459 } 460 }); 461 462 /* 463 * Close whole collapsed nav when focus leaves the nav wrapper. 464 */ 465 $navWrapper.on('focusout', function () { 466 window.setTimeout(function () { 467 var activeElement = document.activeElement; 468 469 if (!$navWrapper.get(0).contains(activeElement)) { 470 closeAllSubmenus(false); 471 472 if (isCollapsedView()) { 473 closeMenu(false); 474 } 475 } 476 }, 0); 477 }); 478 479 /* 480 * ESC closes the whole collapsed nav, unless handled by a submenu arrow. 481 */ 482 $navWrapper.on('keydown', function (event) { 483 if (!isCollapsedView()) { 484 return; 485 } 486 487 if ( 488 event.key === 'Escape' && 489 !$(event.target).hasClass('nav-toggle-subarrow') 490 ) { 491 event.preventDefault(); 492 closeMenu(true); 493 } 494 }); 495 496 $(document).on('click', function (event) { 497 var target = event.target; 498 499 if (!$navWrapper.get(0).contains(target)) { 500 closeAllSubmenus(false); 501 502 if (isCollapsedView()) { 503 closeMenu(false); 504 } 505 } 506 }); 507 508 /* 509 * Make submenu arrows reliably focusable before click so wrapper focusout 510 * logic does not close the whole nav unexpectedly. 511 */ 512 $nav.on('mousedown', '.nav-toggle-subarrow', function (event) { 513 if (!isCollapsedView()) { 514 return; 515 } 516 517 event.preventDefault(); 518 event.stopPropagation(); 519 520 $(this).trigger('focus'); 521 }); 522 523 /* 524 * Collapsed/mobile submenu toggle via subarrow click/tap. 525 * Parent links remain links; only the arrow toggles submenu state. 526 */ 527 $nav.on('click', '.nav-toggle-subarrow', function (event) { 528 event.preventDefault(); 529 event.stopPropagation(); 530 531 var $arrow = $(this); 532 var $item = $arrow.parent('li'); 533 var $submenu = $item.children('.sub-menu'); 534 535 if (!$item.length || !$submenu.length) { 536 return; 537 } 538 539 toggleSubmenu($item); 540 }); 541 542 /* 543 * Keyboard support for collapsed/mobile submenu arrows. 544 * - Enter / Space toggles the submenu 545 * - Escape collapses the submenu and returns focus to the arrow 546 */ 547 $nav.on('keydown', '.nav-toggle-subarrow', function (event) { 548 var $arrow = $(this); 549 var $item = $arrow.parent('li'); 550 var $submenu = $item.children('.sub-menu'); 551 552 if (!$item.length || !$submenu.length) { 553 return; 554 } 555 556 if (event.key === 'Enter' || event.key === ' ') { 557 event.preventDefault(); 558 event.stopPropagation(); 559 toggleSubmenu($item); 560 return; 561 } 562 563 if (event.key === 'Escape') { 564 event.preventDefault(); 565 event.stopPropagation(); 566 closeSubmenu($item); 567 $arrow.trigger('focus'); 568 } 569 }); 570 571 /* 572 * Shift+Tab from the first submenu focusable returns to the submenu arrow, 573 * so keyboard users can collapse it with Enter/Space or Escape. 574 */ 575 $nav.on('keydown', '.sub-menu a, .sub-menu button, .sub-menu [tabindex]:not([tabindex="-1"])', function (event) { 576 if (!isCollapsedView()) { 577 return; 578 } 579 580 if (event.key !== 'Tab' || !event.shiftKey) { 581 return; 582 } 583 584 var $current = $(this); 585 var $submenu = $current.closest('.sub-menu'); 586 var $parentItem = $submenu.parent('li'); 587 var $arrow = $parentItem.children('.nav-toggle-subarrow'); 588 589 if (!$arrow.length) { 590 return; 591 } 592 593 var $focusables = $submenu 594 .find('a, button, [tabindex]:not([tabindex="-1"])') 595 .filter(':visible'); 596 597 if ($focusables.get(0) === this) { 598 event.preventDefault(); 599 $arrow.trigger('focus'); 600 } 601 }); 602 603 $nav.on('mouseenter focusin', 'li.menu-item-has-children', function () { 604 if (!isDesktopView()) { 605 return; 606 } 607 608 positionDesktopSubmenu($(this)); 609 }); 610 611 $(window).on('load resize orientationchange', function () { 612 refreshDesktopSubmenuDirections(); 613 }); 614 615 $(document).on('hero_ready header_view_changed', function () { 616 refreshDesktopSubmenuDirections(); 617 }); 618 619 setupSubmenuArrows(); 620 syncForViewport(); 621 syncFollowWrapHeight(); 622 }); 623})(jQuery, window, document);
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.