PageSourceSearch

https://gapfy.io/js/mobile-nav.9cyi5geele.js

js gapfy.io collected 2026-09-24 15:04:09 UTC 5,108 bytes, 118 lines download raw bytes

1/*
2 * Gapfy mobile nav — slide-in overlay menu on viewports under
3 * 1024px. The hamburger button in SiteNavbar.razor flips
4 * .has-mobile-open on the <header>, which the CSS uses to slide
5 * .site-nav-links in from the right (and overlay the page).
6 *
7 * Closes on:
8 *   - hamburger click again (toggle)
9 *   - ESC key
10 *   - click on backdrop
11 *   - click on any nav link (so navigating closes the drawer)
12 *
13 * Body scroll is locked while the drawer is open so scrolling the
14 * overlay does not bleed into the page underneath.
15 *
16 * Why event delegation instead of per-element listeners:
17 * This is a Blazor Web App with enhanced navigation. When the user
18 * follows a link, Blazor fetches the new page and MERGES it into the
19 * live DOM rather than reloading. During that merge the <header> can be
20 * preserved, recreated, or have JS-injected nodes (like the backdrop)
21 * stripped — none of which is under our control. Per-element listeners
22 * wired once and a "bound" flag get silently lost or skipped after such
23 * a swap, which is exactly how the mobile menu "stops working" after the
24 * first in-app navigation. By delegating every click off document (which
25 * Blazor never replaces) and creating the backdrop on demand when the
26 * drawer opens, the menu keeps working through any number of enhanced
27 * navigations with no re-scan and no dependency on the enhancedload event.
28 */
29(function () {
30    if (typeof window === 'undefined' || typeof document === 'undefined') return;
31
32    var OPEN_CLASS = 'has-mobile-open';
33    var BODY_LOCK_CLASS = 'has-mobile-nav-open';
34
35    // The backdrop is not in the server-rendered markup (keeps the Razor
36    // clean). Create it lazily the first time a header opens, or re-create
37    // it if an enhanced-navigation merge stripped the previously injected
38    // one. Either way the open header always ends up with a backdrop.
39    function ensureBackdrop(header) {
40        var backdrop = header.querySelector('.site-nav-mobile-backdrop');
41        if (!backdrop) {
42            backdrop = document.createElement('div');
43            backdrop.className = 'site-nav-mobile-backdrop';
44            backdrop.setAttribute('aria-hidden', 'true');
45            header.appendChild(backdrop);
46        }
47        return backdrop;
48    }
49
50    function open(header) {
51        ensureBackdrop(header);
52        header.classList.add(OPEN_CLASS);
53        document.body.classList.add(BODY_LOCK_CLASS);
54        var toggle = header.querySelector('.site-nav-mobile-toggle');
55        if (toggle) toggle.setAttribute('aria-expanded', 'true');
56    }
57
58    function close(header) {
59        header.classList.remove(OPEN_CLASS);
60        document.body.classList.remove(BODY_LOCK_CLASS);
61        var toggle = header.querySelector('.site-nav-mobile-toggle');
62        if (toggle) toggle.setAttribute('aria-expanded', 'false');
63    }
64
65    function closeAll() {
66        var openHeaders = document.querySelectorAll('.site-navbar.' + OPEN_CLASS);
67        for (var i = 0; i < openHeaders.length; i++) close(openHeaders[i]);
68    }
69
70    // Single delegated click handler. Lives on document, so it survives
71    // every DOM swap Blazor performs during enhanced navigation.
72    document.addEventListener('click', function (e) {
73        var target = e.target;
74        // e.target can be a text node / SVG in some browsers; guard closest.
75        if (!target || typeof target.closest !== 'function') return;
76
77        // Hamburger (or the icon inside it) — toggle the drawer.
78        var toggle = target.closest('.site-nav-mobile-toggle');
79        if (toggle) {
80            var header = toggle.closest('.site-navbar');
81            if (!header) return;
82            e.preventDefault();
83            e.stopPropagation();
84            if (header.classList.contains(OPEN_CLASS)) close(header); else open(header);
85            return;
86        }
87
88        // Backdrop — close the header it belongs to.
89        var backdrop = target.closest('.site-nav-mobile-backdrop');
90        if (backdrop) {
91            var h = backdrop.closest('.site-navbar');
92            if (h) close(h);
93            return;
94        }
95
96        // Link inside the drawer — close so navigation feels natural.
97        // Dropdown triggers are <button>, not <a>, so they keep acting as
98        // in-drawer accordions and do not match here.
99        var link = target.closest('.site-nav-links a');
100        if (link) {
101            var hd = link.closest('.site-navbar');
102            if (hd && hd.classList.contains(OPEN_CLASS)) close(hd);
103        }
104    });
105
106    // ESC closes any open drawer.
107    document.addEventListener('keydown', function (e) {
108        if (e.key === 'Escape' || e.keyCode === 27) closeAll();
109    });
110
111    // Auto-close if the viewport grows past the mobile breakpoint while the
112    // drawer is open — otherwise the desktop layout shows both the drawer
113    // and the inline nav at once.
114    var mql = window.matchMedia('(min-width: 1024px)');
115    function onMql() { if (mql.matches) closeAll(); }
116    if (mql.addEventListener) mql.addEventListener('change', onMql);
117    else if (mql.addListener) mql.addListener(onMql);
118})();

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.