1 2export default { 3 name: "faqs", 4 displayName: "FAQs", 5 version: "1.1.0", 6 settings: {"allowMultiple":{"label":"Allow Multiple Open","type":"boolean","default":false},"openFirst":{"label":"Open First Item by Default","type":"boolean","default":true},"borderRadius":{"min":0,"unit":"px","max":500,"label":"Border Radius","type":"number","default":10}}, 7 8 mount: function (element, options) { 9 const allowMultiple = options.allowMultiple === true; 10 const openFirst = options.openFirst !== false; 11 12 element.style.setProperty("--border-radius", options.borderRadius + "px"); 13 14 // Generate unique ID for this accordion instance 15 const accordionId = `accordion-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; 16 17 // Open first item by default if option is enabled 18 if (openFirst) { 19 const firstItem = element.querySelector(".accordion-item"); 20 21 if (firstItem) { 22 firstItem.classList.add("active"); 23 } 24 } 25 26 27 const openContent = (item, content, animate) => { 28 if (!animate) { 29 content.style.maxHeight = "none"; 30 31 return; 32 } 33 34 content.style.maxHeight = content.scrollHeight + "px"; 35 36 content.addEventListener("transitionend", function done(e) { 37 if (e.propertyName !== "max-height") { 38 return; 39 } 40 41 content.removeEventListener("transitionend", done); 42 43 // Release the cap once open, so the panel keeps fitting content that grows later â inline editing, images, a narrower column. 44 if (item.classList.contains("active")) { 45 content.style.maxHeight = "none"; 46 } 47 }); 48 }; 49 50 const closeContent = (content, animate) => { 51 if (!animate) { 52 content.style.maxHeight = ""; 53 54 return; 55 } 56 57 // A transition needs a number to start from, so pin the current height and force a reflow before collapsing. 58 content.style.maxHeight = content.scrollHeight + "px"; 59 60 void content.offsetHeight; 61 62 content.style.maxHeight = 0; 63 }; 64 65 // Add click handlers to accordion items 66 const items = element.querySelectorAll(".accordion-item"); 67 68 items.forEach((item, index) => { 69 const header = item.querySelector(".accordion-header"); 70 const content = item.querySelector(".accordion-content"); 71 72 if ( 73 header && 74 content 75 ) { 76 // Generate unique IDs 77 const buttonId = `${accordionId}-button-${index}`; 78 const panelId = `${accordionId}-panel-${index}`; 79 80 // Convert header to button for accessibility 81 const button = document.createElement("button"); 82 83 button.type = "button"; 84 button.id = buttonId; 85 button.className = "accordion-header"; 86 button.innerHTML = header.innerHTML; 87 button.setAttribute("aria-expanded", item.classList.contains("active") ? "true" : "false"); 88 button.setAttribute("aria-controls", panelId); 89 90 // Copy styles from original header 91 const headerStyles = window.getComputedStyle(header); 92 93 button.style.cssText = header.style.cssText; 94 button.style.width = "100%"; 95 button.style.textAlign = "left"; 96 button.style.border = "none"; 97 button.style.background = "none"; 98 button.style.font = "inherit"; 99 button.style.cursor = "pointer"; 100 101 // Replace header with button 102 header.replaceWith(button); 103 104 // Set content attributes 105 content.id = panelId; 106 content.setAttribute("role", "region"); 107 content.setAttribute("aria-labelledby", buttonId); 108 content.setAttribute("aria-hidden", item.classList.contains("active") ? "false" : "true"); 109 110 // Items open on load (openFirst) start at full height, unanimated 111 if (item.classList.contains("active")) { 112 openContent(item, content, false); 113 } 114 115 // Add click handler 116 button.addEventListener("click", () => { 117 const isActive = item.classList.contains("active"); 118 119 // If not allowing multiple, close all other items 120 if ( 121 !allowMultiple && 122 !isActive 123 ) { 124 items.forEach(otherItem => { 125 if (otherItem !== item) { 126 otherItem.classList.remove("active"); 127 128 const otherButton = otherItem.querySelector(".accordion-header"); 129 const otherContent = otherItem.querySelector(".accordion-content"); 130 131 if (otherButton) { 132 otherButton.setAttribute("aria-expanded", "false"); 133 } 134 135 if (otherContent) { 136 otherContent.setAttribute("aria-hidden", "true"); 137 138 closeContent(otherContent, true); 139 } 140 } 141 }); 142 } 143 144 // Toggle current item 145 item.classList.toggle("active"); 146 147 const nowActive = item.classList.contains("active"); 148 149 button.setAttribute("aria-expanded", nowActive ? "true" : "false"); 150 151 content.setAttribute("aria-hidden", nowActive ? "false" : "true"); 152 153 if (nowActive) { 154 openContent(item, content, true); 155 } 156 else { 157 closeContent(content, true); 158 } 159 }); 160 161 // Keyboard navigation 162 button.addEventListener("keydown", (e) => { 163 const currentIndex = Array.from(items).indexOf(item); 164 165 let targetIndex = -1; 166 167 switch(e.key) { 168 case "ArrowDown": 169 e.preventDefault(); 170 171 targetIndex = (currentIndex + 1) % items.length; 172 173 break; 174 175 case "ArrowUp": 176 e.preventDefault(); 177 178 targetIndex = (currentIndex - 1 + items.length) % items.length; 179 180 break; 181 182 case "Home": 183 e.preventDefault(); 184 185 targetIndex = 0; 186 187 break; 188 189 case "End": 190 e.preventDefault(); 191 192 targetIndex = items.length - 1; 193 194 break; 195 } 196 197 if (targetIndex >= 0) { 198 const targetButton = items[targetIndex].querySelector(".accordion-header"); 199 200 if (targetButton) { 201 targetButton.focus(); 202 } 203 } 204 }); 205 } 206 }); 207 208 return {}; 209 } 210};
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.