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.