PageSourceSearch

https://physicsguy.com/star-trek/components/quick-nav-dropdown/quick-nav-dropdown.js

js physicsguy.com collected 2026-10-02 13:21:37 UTC 5,993 bytes, 164 lines download raw bytes

1// /star-trek/components/quick-nav-dropdown/quick-nav-dropdown.js
2
3// Start of the IIFE
4(function () {
5    "use strict";
6
7    const TEMPLATE_URL = new URL(
8        "./quick-nav-dropdown.html",
9        import.meta.url
10    ).pathname;
11
12    const templatePromise = fetch(TEMPLATE_URL)
13        .then(response => {
14            if (!response.ok) {
15                throw new Error(`Failed to load template: ${response.status}`);
16            }
17            return response.text();
18        })
19        .then(htmlString => {
20            const template = document.createElement("template");
21            template.innerHTML = htmlString.trim();
22            // Remove all dev-only elements
23            template.content.querySelectorAll('[data-dev-only="true"]')
24                .forEach(el => el.remove());
25            return template;
26        })
27        .catch(err => {
28            console.error("Error loading quick-nav-dropdown template:", err);
29        });
30
31    class QuickNavDropdown extends HTMLElement {
32        constructor() {
33            super();
34        }
35
36        connectedCallback() {
37            if (!this.innerHTML) {
38                templatePromise.then(template => {
39                    this.appendChild(template.content.cloneNode(true));
40                    // Cache the overlay element
41                    this.overlay = this.querySelector("#mobile-nav-overlay");
42                    // Cache the quick-nav-overlay element
43                    this.quickNavOverlay = this.querySelector(".quick-nav-overlay");
44                    // Set up event listeners
45                    this.setupEventListeners();
46                    // Apply initial open attribute if present
47                    if (this.hasAttribute("open")) {
48                        this.overlay.classList.add("active");
49                        document.body.style.overflow = "hidden";
50                        this.updateMaxHeight();
51                    }
52                    this._applyCurrentPage();
53                    this._applyRootPath();
54                });
55            }
56        }
57
58        static get observedAttributes() {
59            return ["open", "current-page", "root-path"];
60        }
61
62        attributeChangedCallback(name, oldValue, newValue) {
63            if (name === "open") {
64                if (!this.overlay) return;
65                if (newValue !== null) {
66                    this.overlay.classList.add("active");
67                    document.body.style.overflow = "hidden";
68                } else {
69                    this.overlay.classList.remove("active");
70                    document.body.style.overflow = "";
71                }
72                this.updateMaxHeight();
73            } else if (name === "current-page") {
74                this._applyCurrentPage();
75            } else if (name === "root-path") {
76                this._applyRootPath();
77            }
78        }
79
80        setupEventListeners() {
81            // Toggle buttons
82            this.querySelectorAll(".bar-button.open, .bar-button.close").forEach(btn => {
83                btn.addEventListener("click", () => this.toggle());
84            });
85
86            // Click outside to close
87            this.overlay.addEventListener("click", (e) => {
88                if (e.target === this.overlay) {
89                    this.close();
90                }
91            });
92
93            // Close when clicking a link inside quick-nav-panel
94            this.querySelector(".quick-nav-overlay").addEventListener("click", (e) => {
95                if (e.target.tagName === "A" || e.target.closest("a")) {
96                    this.close();
97                }
98            });
99
100            // Update max-height on resize and scroll
101            window.addEventListener("resize", () => this.updateMaxHeight());
102            window.addEventListener("scroll", () => this.updateMaxHeight());
103        }
104
105        updateMaxHeight() {
106            if (this.quickNavOverlay && this.overlay.classList.contains("active")) {
107                const rect = this.quickNavOverlay.getBoundingClientRect();
108                const availableHeight = window.innerHeight - rect.top;
109                this.quickNavOverlay.style.maxHeight = `${availableHeight}px`;
110            } else if (this.quickNavOverlay) {
111                this.quickNavOverlay.style.maxHeight = '';
112            }
113        }
114
115        toggle() {
116            if (this.overlay.classList.contains("active")) {
117                this.close();
118            } else {
119                this.open();
120            }
121        }
122
123        open() {
124            this.overlay.classList.add("active");
125            document.body.style.overflow = "hidden";
126            this.setAttribute("open", "");
127            this.updateMaxHeight();
128        }
129
130        close() {
131            this.overlay.classList.remove("active");
132            document.body.style.overflow = "";
133            this.removeAttribute("open");
134        }
135
136        _applyCurrentPage() {
137            // Propagate to inner quick-nav-panel
138            const innerNav = this.querySelector("quick-nav-panel");
139            if (innerNav) {
140                const currentPage = this.getAttribute("current-page");
141                if (currentPage !== null) {
142                    innerNav.setAttribute("current-page", currentPage);
143                } else {
144                    innerNav.removeAttribute("current-page");
145                }
146            }
147        }
148
149        /**
150         * Finds the Web Component of type <quick-nav-panel> and sets it's
151         * root-path to match this Web Component's root-path
152         */
153        _applyRootPath() {
154            const rootPath = this.getAttribute("root-path") || "/";
155            // Find <quick-nav-panel> element(s) and set their root-path attribute
156            this.querySelectorAll("quick-nav-panel")
157                .forEach(quickNavPanel => {
158                    quickNavPanel.setAttribute("root-path", rootPath);
159                });
160        }
161    }
162
163    customElements.define("quick-nav-dropdown", QuickNavDropdown);
164})(); // End of the IIFE

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.