PageSourceSearch

https://www.iowaattorneygeneral.gov/system/blockeditor/websites/1/pages/plugins/faqs/index.js

js iowaattorneygeneral.gov collected 2026-10-02 02:40:48 UTC 5,610 bytes, 210 lines download raw bytes

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.