PageSourceSearch

https://www.pointsoflight.org/wp-content/themes/points-of-light/assets/js/header.js?ver=1789989442

js pointsoflight.org collected 2026-09-24 08:51:30 UTC 13,578 bytes, 449 lines download raw bytes

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.