1(function ($, win, doc) { 2 if (!$) return; 3 4 var NAV_BP = 1279; 5 var SCROLL_DELTA = 4; 6 var reduceMotion = 7 typeof win.matchMedia === "function" && 8 win.matchMedia("(prefers-reduced-motion: reduce)").matches; 9 10 /** @param {JQuery<Element>} $header */ 11 function initSiteHeader($header) { 12 var $body = $("body"); 13 var $overlay = $header.find(".js-site-header-overlay"); 14 var $panels = $header.find(".js-site-header-megamenu"); 15 var $panelTriggers = $header.find(".js-site-header-panel-trigger"); 16 var $mobile = $header.find(".js-site-header-mobile"); 17 var $menuToggle = $header.find(".js-site-header-menu-toggle"); 18 var $search = $header.find(".js-site-header-search"); 19 var $searchOpen = $header.find(".js-site-header-search-open"); 20 var $searchClose = $header.find(".js-site-header-search-close"); 21 var $mobileTriggers = $header.find(".js-site-header-mobile-trigger"); 22 var $searchInput = $header.find("#site-header-search-input"); 23 var $announcement = $header.find(".site-header__announcement"); 24 var hasAnnouncement = $announcement.length > 0; 25 var searchOpenedFromMenu = false;
26 var lockedScrollY = 0; 27 var lastScrollY = win.scrollY || 0; 28 var ticking = false; 29 var smoothedDelta = 4; 30 var shrinkTimer = null; 31 var SMOOTH_FACTOR = 0.2; // lower = more smoothing (0.1â0.3 works well) 32 var SHRINK_DEBOUNCE = 80; // ms before actually applying shrunk class 33 var ns = ".siteHeader"; 34 var ANNOUNCE_HIDE_AFTER = 12; 35 var announceResizeTimer = null; 36 37 function isDesktop() { 38 return win.innerWidth > NAV_BP; 39 } 40 41 function isHeaderActive() { 42 return ( 43 $header.hasClass("is-panel-open") || 44 $header.hasClass("is-menu-open") || 45 $header.hasClass("is-search-open") 46 ); 47 } 48 49 function syncPageLock() { 50 var shouldLock = isHeaderActive(); 51 52 if (shouldLock && !$body.hasClass("site-header-lock")) { 53 lockedScrollY = win.scrollY || doc.documentElement.scrollTop || 0; 54 $body.addClass("site-header-lock"); 55 $body.css("top", -lockedScrollY + "px"); 56 lastScrollY = lockedScrollY; 57 } else if (!shouldLock && $body.hasClass("site-header-lock")) { 58 $body.removeClass("site-header-lock"); 59 $body.css("top", ""); 60 win.scrollTo(0, lockedScrollY); 61 lastScrollY = lockedScrollY; 62 } 63 } 64 65 function closePanels() { 66 $panelTriggers.attr("aria-expanded", "false"); 67 $header.find(".site-header__primary-item--has-panel").removeClass("is-open"); 68 $panels.attr("hidden", "hidden").removeClass("is-open"); 69 $header.removeClass("is-panel-open"); 70 $overlay.attr("hidden", "hidden"); 71 syncPageLock(); 72 } 73 74 function closeMobileSections() { 75 $mobileTriggers.attr("aria-expanded", "false"); 76 $header.find(".site-header__mobile-section--has-panel").removeClass("is-open"); 77 $mobile.find(".site-header__mobile-submenu").attr("hidden", "hidden"); 78 } 79 80 function updateMenuToggleState() { 81 var isOpen = 82 $header.hasClass("is-menu-open") || $header.hasClass("is-search-open"); 83 84 $menuToggle.attr("aria-expanded", isOpen ? "true" : "false"); 85 $menuToggle.attr( 86 "aria-label", 87 isOpen ? "Close menu or search" : "Open menu" 88 ); 89 } 90 91 function closeMobileMenu() { 92 $mobile.attr("hidden", "hidden"); 93 $header.removeClass("is-menu-open"); 94 closeMobileSections(); 95 updateMenuToggleState(); 96 syncPageLock(); 97 } 98 99 function openMobileMenu() { 100 if ($header.hasClass("is-search-open")) { 101 searchOpenedFromMenu = false;
102 $search.attr("hidden", "hidden"); 103 $header.removeClass("is-search-open"); 104 $searchOpen.attr("aria-expanded", "false"); 105 } 106 closePanels(); 107 closeMobileSections(); 108 $header.addClass("is-menu-open"); 109 $mobile.removeAttr("hidden"); 110 updateMenuToggleState(); 111 syncPageLock(); 112 } 113 114 function closeSearch() { 115 $searchOpen.attr("aria-expanded", "false"); 116 $search.attr("hidden", "hidden"); 117 $header.removeClass("is-search-open"); 118 if (!$header.hasClass("is-panel-open") && $overlay.length) { 119 $overlay.attr("hidden", "hidden"); 120 } 121 if (searchOpenedFromMenu) { 122 closeMobileMenu(); 123 searchOpenedFromMenu = false; 124 } 125 126 updateMenuToggleState(); 127 syncPageLock(); 128 } 129 130 function openSearch(fromMenu) { 131 closePanels(); 132 if (!isDesktop() && !fromMenu) { 133 closeMobileMenu(); 134 } 135 searchOpenedFromMenu = !isDesktop() && !!fromMenu; 136 $header.addClass("is-search-open"); 137 $searchOpen.attr("aria-expanded", "true"); 138 $search.removeAttr("hidden"); 139 if (isDesktop() && $overlay.length) { 140 $overlay[0].removeAttribute("hidden"); 141 } 142 updateMenuToggleState(); 143 syncPageLock(); 144 win.setTimeout(function () { 145 $searchInput.trigger("focus"); 146 }, reduceMotion ? 0 : 50); 147 } 148 149 function closeMobileOverlay() { 150 searchOpenedFromMenu = false;
151 $searchOpen.attr("aria-expanded", "false"); 152 $search.attr("hidden", "hidden"); 153 $header.removeClass("is-search-open"); 154 closeMobileMenu(); 155 updateMenuToggleState(); 156 syncPageLock(); 157 } 158 159 function setAnnouncementHeight() { 160 if (!hasAnnouncement) return; 161 162 var height = $announcement[0].offsetHeight; 163 $header[0].style.setProperty("--announcement-h", height + "px"); 164 } 165 166 function updateAnnouncement(scrollY) { 167 if (!hasAnnouncement) return; 168 169 if (scrollY <= 0) { 170 $header.removeClass("is-announcement-hidden"); 171 return; 172 } 173 174 if (scrollY > ANNOUNCE_HIDE_AFTER) { 175 $header.addClass("is-announcement-hidden"); 176 } 177 } 178 179 function closeAll() { 180 searchOpenedFromMenu = false; 181 $panelTriggers.attr("aria-expanded", "false"); 182 $header.find(".site-header__primary-item--has-panel").removeClass("is-open"); 183 $panels.attr("hidden", "hidden").removeClass("is-open"); 184 $header.removeClass("is-panel-open is-menu-open is-search-open"); 185 $searchOpen.attr("aria-expanded", "false"); 186 $mobile.attr("hidden", "hidden"); 187 $search.attr("hidden", "hidden"); 188 closeMobileSections(); 189 $overlay.attr("hidden", "hidden"); 190 updateMenuToggleState(); 191 syncPageLock(); 192 } 193 194 function openPanel($trigger) { 195 var panelId = $trigger.attr("aria-controls"); 196 if (!panelId) return; 197 198 var panelEl = doc.getElementById(panelId); 199 var $item = $trigger.closest(".site-header__primary-item--has-panel"); 200 201 if (!panelEl || !$item.length) return; 202 203 closeSearch(); 204 closeMobileMenu(); 205 206 $panelTriggers.attr("aria-expanded", "false"); 207 $header.find(".site-header__primary-item--has-panel").removeClass("is-open"); 208 209 $panels.each(function () { 210 this.setAttribute("hidden", ""); 211 this.classList.remove("is-open"); 212 }); 213 214 $item.addClass("is-open"); 215 $trigger.attr("aria-expanded", "true"); 216 panelEl.removeAttribute("hidden"); 217 panelEl.classList.add("is-open"); 218 $header.addClass("is-panel-open"); 219 220 if ($overlay.length) { 221 $overlay[0].removeAttribute("hidden"); 222 } 223 224 syncPageLock(); 225 } 226 227 function onScroll() { 228 if ($body.hasClass("site-header-lock")) return; 229 var y = win.scrollY || 0; 230 var rawDelta = y - lastScrollY; 231 232 // Exponential moving average smooths out jitter 233 smoothedDelta = smoothedDelta * (1 - SMOOTH_FACTOR) + rawDelta * SMOOTH_FACTOR; 234 235 if (y <= 0) { 236 $header.removeClass("site-header--shrunk is-scrolled is-announcement-hidden"); 237 smoothedDelta = 0; 238 if (shrinkTimer) { clearTimeout(shrinkTimer); shrinkTimer = null; } 239 } else { 240 $header.addClass("is-scrolled"); 241 if ( 242 !$header.hasClass("is-panel-open") && 243 !$header.hasClass("is-menu-open") && 244 !$header.hasClass("is-search-open") 245 ) { 246 if (smoothedDelta > SCROLL_DELTA) { 247 // Debounce the shrink so slow scroll can't flicker it 248 if (!shrinkTimer) { 249 shrinkTimer = setTimeout(function () { 250 $header.addClass("site-header--shrunk"); 251 shrinkTimer = null; 252 }, SHRINK_DEBOUNCE); 253 } 254 } else if (smoothedDelta < -SCROLL_DELTA) { 255 if (shrinkTimer) { clearTimeout(shrinkTimer); shrinkTimer = null; } 256 $header.removeClass("site-header--shrunk"); 257 } 258 } 259 } 260 261 updateAnnouncement(y); 262 lastScrollY = y; 263 ticking = false; 264 } 265 266 $panelTriggers.on("click" + ns, function (evt) { 267 if (!isDesktop()) return; 268 evt.preventDefault(); 269 evt.stopImmediatePropagation(); 270 var $trigger = $(this); 271 var $item = $trigger.closest(".site-header__primary-item--has-panel"); 272 273 if ($item.hasClass("is-open")) { 274 closePanels(); 275 return; 276 } 277 278 openPanel($trigger); 279 }); 280 281 $menuToggle.on("click" + ns, function (evt) { 282 if (isDesktop()) return; 283 evt.preventDefault(); 284 285 if ( 286 $header.hasClass("is-search-open") || 287 $header.hasClass("is-menu-open") 288 ) { 289 closeMobileOverlay(); 290 return; 291 } 292 293 openMobileMenu(); 294 }); 295 296 $mobileTriggers.on("click" + ns, function () { 297 var $section = $(this).closest(".site-header__mobile-section--has-panel"); 298 var $sub = $section.find(".site-header__mobile-submenu"); 299 var isOpen = $section.hasClass("is-open"); 300 301 $header.find(".site-header__mobile-section--has-panel").not($section).each(function () { 302 $(this).removeClass("is-open"); 303 $(this).find(".js-site-header-mobile-trigger").attr("aria-expanded", "false"); 304 $(this).find(".site-header__mobile-submenu").attr("hidden", "hidden"); 305 }); 306 307 if (isOpen) { 308 $section.removeClass("is-open"); 309 $(this).attr("aria-expanded", "false"); 310 $sub.attr("hidden", "hidden"); 311 } else { 312 $section.addClass("is-open"); 313 $(this).attr("aria-expanded", "true"); 314 $sub.removeAttr("hidden"); 315 } 316 }); 317 318 $searchOpen.on("click" + ns, function (evt) { 319 evt.preventDefault(); 320 evt.stopPropagation(); 321 322 if ($header.hasClass("is-search-open")) { 323 if (isDesktop()) { 324 closeSearch(); 325 } else { 326 closeMobileOverlay(); 327 } 328 return; 329 } 330 331 openSearch(!isDesktop() && $header.hasClass("is-menu-open")); 332 }); 333 334 $searchClose.on("click" + ns, function (evt) { 335 evt.preventDefault(); 336 evt.stopPropagation(); 337 if (isDesktop()) { 338 closeSearch(); 339 } else { 340 closeMobileOverlay(); 341 } 342 }); 343 344 $overlay.on("click" + ns, function (evt) { 345 if (evt.target !== evt.currentTarget) return; 346 347 if ($header.hasClass("is-search-open")) { 348 closeSearch(); 349 return; 350 } 351 352 closePanels(); 353 }); 354 355 $(doc).on("click" + ns, function (evt) { 356 if (!$header.hasClass("is-search-open") || isDesktop()) return; 357 if ($header[0].contains(evt.target)) return; 358 359 closeSearch(); 360 }); 361 362 $(doc).on("keydown" + ns, function (evt) { 363 if (evt.key === "Escape") closeAll(); 364 }); 365 366 $(win).on("resize" + ns, function () { 367 if (isDesktop()) { 368 closeMobileMenu(); 369 } else { 370 closePanels(); 371 } 372 373 if (hasAnnouncement) { 374 win.clearTimeout(announceResizeTimer); 375 announceResizeTimer = win.setTimeout(setAnnouncementHeight, 150); 376 } 377 }); 378 379 $(win).on("scroll" + ns, function () { 380 if ($body.hasClass("site-header-lock")) return; 381 if (!ticking) { 382 ticking = true; 383 win.requestAnimationFrame(onScroll); 384 } 385 }); 386 387 function canScrollWithinHeader(target, evt) { 388 if (!(target instanceof Element) || !$header[0].contains(target)) return false;
389 390 if ($mobile[0] && $mobile[0].contains(target)) return true; 391 392 var deltaY = evt.type === "wheel" ? evt.deltaY : 0; 393 var node = target; 394 395 while (node && node !== $header[0]) { 396 var style = win.getComputedStyle(node); 397 var overflowY = style.overflowY; 398 399 if ( 400 (overflowY === "auto" || overflowY === "scroll") && 401 node.scrollHeight > node.clientHeight + 1 402 ) { 403 if (evt.type === "touchmove") return true; 404 405 if (deltaY < 0 && node.scrollTop > 0) return true; 406 if ( 407 deltaY > 0 && 408 node.scrollTop + node.clientHeight < node.scrollHeight - 1 409 ) { 410 return true; 411 } 412 413 return false; 414 } 415 416 node = node.parentElement; 417 } 418 419 return false; 420 } 421 422 function preventPageScroll(evt) { 423 if (!isHeaderActive()) return; 424 425 var target = evt.target; 426 if (canScrollWithinHeader(target, evt)) return; 427 428 evt.preventDefault(); 429 } 430 431 doc.addEventListener("wheel", preventPageScroll, { passive: false, capture: true }); 432 doc.addEventListener("touchmove", preventPageScroll, { passive: false, capture: true }); 433 434 updateMenuToggleState(); 435 if (hasAnnouncement) { 436 setAnnouncementHeight(); 437 if ((win.scrollY || 0) > ANNOUNCE_HIDE_AFTER) { 438 $header.addClass("is-announcement-hidden"); 439 } 440 } 441 onScroll(); 442 } 443 444 $(function () { 445 $(".js-site-header").each(function () { 446 initSiteHeader($(this)); 447 }); 448 }); 449})(typeof jQuery !== "undefined" ? jQuery : null, 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.