1// Custom Dropdown Manager 2class CustomDropdownManager { 3 constructor() { 4 this.activeDropdown = null; 5 this.activeToggle = null; 6 this.dropdownsContainer = null; 7 this.retryCount = 0; 8 this.maxRetries = 5; 9 this.init(); 10 } 11 12 init() { 13 // Wait for everything to be fully loaded 14 if (document.querySelectorAll(".custom-dropdown-menu").length === 0) { 15 this.retryCount++; 16 if (this.retryCount < this.maxRetries) { 17 setTimeout(() => this.init(), 100); 18 } 19 return; 20 } 21 22 // Create a container for dropdowns at the body level 23 this.dropdownsContainer = document.createElement("div"); 24 this.dropdownsContainer.id = "custom-dropdowns-container"; 25 this.dropdownsContainer.style.cssText = 26 "position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10000;"; 27 document.body.appendChild(this.dropdownsContainer); 28 29 // Move all existing dropdowns into this container 30 const dropdowns = document.querySelectorAll(".custom-dropdown-menu"); 31 dropdowns.forEach((dropdown) => { 32 this.dropdownsContainer.appendChild(dropdown); 33 }); 34 35 // Manage all custom dropdowns 36 const toggles = document.querySelectorAll(".custom-dropdown-toggle"); 37 toggles.forEach((toggle) => { 38 toggle.addEventListener("click", (e) => { 39 e.preventDefault(); 40 e.stopPropagation(); 41 42 const dropdownId = toggle.id + "Menu"; 43 const dropdown = document.getElementById(dropdownId); 44 45 if (dropdown) { 46 if ( 47 this.activeDropdown === dropdown && 48 dropdown.classList.contains("open") 49 ) { 50 this.closeDropdown(dropdown); 51 } else { 52 this.closeAllDropdowns(); 53 this.openDropdown(dropdown, toggle); 54 } 55 } 56 }); 57 }); 58 59 // Close dropdowns by clicking elsewhere 60 document.addEventListener("click", (e) => { 61 if ( 62 !e.target.closest(".custom-dropdown-menu") && 63 !e.target.closest(".custom-dropdown-toggle") 64 ) { 65 this.closeAllDropdowns(); 66 } 67 }); 68 69 // Close dropdowns with the Escape key 70 document.addEventListener("keydown", (e) => { 71 if (e.key === "Escape") { 72 this.closeAllDropdowns(); 73 } 74 }); 75 76 // Close dropdowns on scroll 77 window.addEventListener( 78 "scroll", 79 () => { 80 this.closeAllDropdowns(); 81 }, 82 true, 83 ); // true to capture scroll in all elements 84 85 // Recalculate position on resize 86 window.addEventListener("resize", () => { 87 if (this.activeDropdown && this.activeToggle) { 88 this.positionDropdown(this.activeDropdown, this.activeToggle); 89 } 90 }); 91 } 92 93 openDropdown(dropdown, toggle) { 94 this.activeDropdown = dropdown; 95 this.activeToggle = toggle; 96 dropdown.style.display = "block"; 97 this.positionDropdown(dropdown, toggle); 98 99 // Force a reflow 100 dropdown.offsetHeight; 101 102 requestAnimationFrame(() => { 103 dropdown.classList.add("open"); 104 }); 105 } 106 107 closeDropdown(dropdown) { 108 dropdown.classList.remove("open"); 109 // Wait for the end of the animation before hiding 110 setTimeout(() => { 111 if (!dropdown.classList.contains("open")) { 112 dropdown.style.display = "none"; 113 } 114 }, 300); 115 if (this.activeDropdown === dropdown) { 116 this.activeDropdown = null; 117 this.activeToggle = null; 118 } 119 } 120 121 closeAllDropdowns() { 122 document.querySelectorAll(".custom-dropdown-menu").forEach((dropdown) => { 123 this.closeDropdown(dropdown); 124 }); 125 } 126 127 getZoomLevel() { 128 // Get the HTML zoom level (defined by media queries) 129 return parseFloat(getComputedStyle(document.documentElement).zoom) || 1; 130 } 131 132 positionDropdown(dropdown, toggle) { 133 const rect = toggle.getBoundingClientRect(); 134 const windowWidth = window.innerWidth; 135 const windowHeight = window.innerHeight; 136 const margin = 10; 137 const zoom = this.getZoomLevel(); 138 139 // Make temporarily visible to measure 140 dropdown.style.visibility = "hidden"; 141 dropdown.style.display = "block"; 142 const dropdownWidth = dropdown.offsetWidth; 143 const dropdownHeight = dropdown.offsetHeight; 144 dropdown.style.display = ""; 145 dropdown.style.visibility = ""; 146 147 // Compensate zoom for fixed positions 148 // getBoundingClientRect() returns zoomed values, but position: fixed uses non-zoomed coordinates 149 let top = (rect.bottom + 15) / zoom; 150 let left = (rect.left + rect.width / 2) / zoom - dropdownWidth / 2; 151 152 // Check if the dropdown overflows on the left 153 if (left < margin) { 154 left = margin; 155 } 156 157 // Check if the dropdown overflows on the right (windowWidth is non-zoomed for position fixed) 158 if (left + dropdownWidth > windowWidth / zoom - margin) { 159 left = windowWidth / zoom - dropdownWidth - margin; 160 } 161 162 // Check if the dropdown overflows at the bottom 163 if (top + dropdownHeight > windowHeight / zoom - margin) { 164 // Try to position it above 165 top = rect.top / zoom - dropdownHeight - 15; 166 167 // If it overflows at the top 168 if (top < margin) { 169 // Position it at the maximum visible at the bottom 170 top = Math.min( 171 (rect.bottom + 15) / zoom, 172 windowHeight / zoom - dropdownHeight - margin, 173 ); 174 // Last resort: at least margin from the top 175 if (top < margin) { 176 top = margin; 177 } 178 } 179 } 180 181 dropdown.style.top = top + "px"; 182 dropdown.style.left = left + "px"; 183 } 184} 185 186// Initialization on DOM load 187if (document.readyState === "loading") { 188 document.addEventListener("DOMContentLoaded", () => { 189 window.customDropdownManager = new CustomDropdownManager(); 190 }); 191} else { 192 window.customDropdownManager = new CustomDropdownManager(); 193} 194 195// Bootstrap Modal: Scroll to top instantly when modal opens 196document.addEventListener("show.bs.modal", function () { 197 // Scroll to top without smooth animation 198 window.scrollTo({ top: 0, behavior: "auto" }); 199});
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.