PageSourceSearch

https://www.iowaattorneygeneral.gov/system/blockeditor/websites/1/pages/plugins/media-slider/index.js

js iowaattorneygeneral.gov collected 2026-10-02 02:40:47 UTC 11,039 bytes, 357 lines download raw bytes

1
2import utilities from "/core/modules/system/dependencies/blockeditor/assets/pages/layout/js/plugin-utilities-53e391bb3a38dcb760494d9e37e79e3a.js";
3
4export default {
5	name: "media-slider",
6	displayName: "Media Slider",
7	version: "2.0.0",
8	settings: {"_groups":[{"fields":["itemsPerSlide","gap","aspectRatio"],"label":"Layout","id":"layout"},{"fields":["accentColor","borderColor","rounded"],"label":"Appearance","id":"appearance"},{"fields":["autoplay","autoplaySpeed","loop","pauseOnHover"],"label":"Behavior","id":"behavior"},{"fields":["showArrows","showDots","showCounter"],"label":"Controls","id":"controls"}],"itemsPerSlide":{"group":"layout","min":1,"max":6,"label":"Items Per View","type":"number","default":3},"gap":{"group":"layout","min":0,"unit":"px","max":48,"label":"Gap Between Items","type":"number","default":16},"aspectRatio":{"group":"layout","label":"Card Aspect Ratio","options":[{"label":"16:9 (Landscape)","value":"16/9"},{"label":"4:3 (Standard)","value":"4/3"},{"label":"1:1 (Square)","value":"1/1"},{"label":"3:4 (Portrait)","value":"3/4"},{"label":"Auto (Content Height)","value":"auto"}],"type":"select","default":"3/4"},"accentColor":{"group":"appearance","label":"Accent Color","type":"color","default":"#000000"},"borderColor":{"group":"appearance","label":"Border Color","type":"color","default":"#e5e7eb"},"rounded":{"group":"appearance","label":"Rounded Corners","type":"boolean","default":true},"autoplay":{"group":"behavior","label":"Autoplay","type":"boolean","default":false},"autoplaySpeed":{"group":"behavior","min":1000,"unit":"ms","max":10000,"label":"Autoplay Speed","step":500,"type":"number","default":3000},"loop":{"group":"behavior","label":"Loop Items","type":"boolean","default":true},"pauseOnHover":{"group":"behavior","label":"Pause on Hover","type":"boolean","default":true},"showArrows":{"group":"controls","label":"Show Navigation Arrows","type":"boolean","default":true},"showDots":{"group":"controls","label":"Show Dots Indicator","type":"boolean","default":true},"showCounter":{"group":"controls","label":"Show Item Counter","type":"boolean","default":false}},
9	
10	mount: function(element, options) {
11		const opts = {
12			itemsPerSlide: parseInt(options.itemsPerSlide) || 3,
13			gap: parseInt(options.gap) || 16,
14			aspectRatio: options.aspectRatio || "3/4",
15			accentColor: options.accentColor || "#000000",
16			autoplay: options.autoplay === true || options.autoplay === "true",
17			autoplaySpeed: parseInt(options.autoplaySpeed) || 3000,
18			loop: options.loop !== false && options.loop !== "false",
19			pauseOnHover: options.pauseOnHover !== false && options.pauseOnHover !== "false",
20			showArrows: options.showArrows !== false && options.showArrows !== "false",
21			showDots: options.showDots !== false && options.showDots !== "fal
21se",
22			showCounter: options.showCounter === true || options.showCounter === "true"
23		};
24
25		const track = element.querySelector(".media-slider-track");
26		const items = track.querySelectorAll(".slider-item");
27
28		if (items.length === 0){
29			return {};
30		}
31
32		let currentIndex = 0;
33		let autoplayTimer = null;
34		let touchStartX = 0;
35		let touchEndX = 0;
36
37		// Apply styles
38		element.style.position = "relative";
39		element.style.overflow = "hidden";
40
41		track.style.display = "flex";
42		track.style.gap = `${opts.gap}px`;
43		track.style.transition = "transform 0.4s cubic-bezier(0.4, 0, 0.2, 1)";
44		track.style.willChange = "transform";
45
46		// Calculate item width based on itemsPerSlide and gap
47		const itemWidth = `calc((100% - ${opts.gap * (opts.itemsPerSlide - 1)}px) / ${opts.itemsPerSlide})`;
48
49		items.forEach(item => {
50			item.style.flexShrink = "0";
51			item.style.width = itemWidth;
52
53			const media = item.querySelector(".item-media");
54
55			if (
56				media &&
57				opts.aspectRatio !== "auto"
58			) {
59				media.style.aspectRatio = opts.aspectRatio;
60			}
61		});
62
63		// Navigation
64		const goToIndex = (index) => {
65			currentIndex = Math.max(0, Math.min(index, items.length - opts.itemsPerSlide));
66
67			// Use pixel-based calculation for precision
68			if (items.length > 0) {
69				const firstItem = items[0];
70				const itemWidth = firstItem.offsetWidth;
71				const totalGap = opts.gap;
72				const moveDistance = currentIndex * (itemWidth + totalGap);
73
74				track.style.transform = `translateX(-${moveDistance}px)`;
75			}
76
77			updateControls();
78
79			// Announce to screen readers
80			const liveRegion = element.querySelector(".slider-live-region");
81
82			if (liveRegion) {
83				liveRegion.textContent = `Showing items ${currentIndex + 1} to ${Math.min(currentIndex + opts.itemsPerSlide, items.length)} of ${items.length}`;
84			}
85		};
86
87		const nextSlide = () => {
88			if (currentIndex < items.length - opts.itemsPerSlide) {
89				goToIndex(currentIndex + 1);
90			}
91			else if (opts.loop) {
92				goToIndex(0);
93			}
94		};
95
96		const prevSlide = () => {
97			if (currentIndex > 0) {
98				goToIndex(currentIndex - 1);
99			}
100			else if (opts.loop) {
101				goToIndex(items.length - opts.itemsPerSlide);
102			}
103		};
104
105		// Controls
106		const updateControls = () => {
107			if (opts.showArrows) {
108				const prevBtn = element.querySelector(".slider-prev");
109				const nextBtn = element.querySelector(".slider-next");
110
111				if (!opts.loop) {
112					prevBtn.disabled = currentIndex === 0;
113					nextBtn.disabled = currentIndex >= items.length - opts.itemsPerSlide;
114				}
115			}
116
117			if (opts.showDots) {
118				const dots = element.querySelectorAll(".slider-dot");
119
120				dots.forEach((dot, i) => {
121					dot.classList.toggle("active", i === currentIndex);
122					dot.setAttribute("aria-current", i === currentIndex ? "true" : "false");
123				});
124			}
125
126			if (opts.showCounter) {
127				const counter = element.querySelector(".slider-counter");
128
129				if (counter) {
130					counter.textContent = `${currentIndex + 1}-${Math.min(currentIndex + opts.itemsPerSlide, items.length)} / ${items.length}`;
131				}
132			}
133		};
134
135		// Create controls
136		const controlsContainer = document.createElement("div");
137		controlsContainer.className = "slider-controls";
138
139		// Arrows
140		if (
141			opts.showArrows &&
142			items.length > opts.itemsPerSlide
143		) {
144			const prevBtn = document.createElement("button");
145
146			prevBtn.className = "slider-prev";
147			prevBtn.innerHTML = `
148				<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
149					<path d="M15 18l-6-6 6-6"/>
150				</svg>
151			`;
152			prevBtn.setAttribute("aria-label", "Previous items");
153			prevBtn.onclick = () => {
154				prevSlide();
155
156				resetAutoplay();
157			};
158
159			const nextBtn = document.createElement("button");
160
161			nextBtn.className = "slider-next";
162			nextBtn.innerHTML = `
163				<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
164					<path d="M9 18l6-6-6-6"/>
165				</svg>
166			`;
167			nextBtn.setAttribute("aria-label", "Next items");
168			nextBtn.onclick = () => {
169				nextSlide();
170
171				resetAutoplay();
172			};
173
174			element.appendChild(prevBtn);
175			element.appendChild(nextBtn);
176		}
177
178		// Dots
179		if (opts.showDots && items.length > opts.itemsPerSlide) {
180			const dotsContainer = document.createElement("div");
181
182			dotsContainer.className = "slider-dots";
183			dotsContainer.setAttribute("role", "tablist");
184			dotsContainer.setAttribute("aria-label", "Item navigation");
185
186			const totalDots = items.length - opts.itemsPerSlide + 1;
187
188			for (let i = 0; i < totalDots; i++) {
189				const dot = document.createElement("button");
190
191				dot.className = "slider-dot";
192				dot.setAttribute("role", "tab");
193				dot.setAttribute("aria-label", `Go to item ${i + 1}`);
194				dot.setAttribute("aria-current", i === 0 ? "true" : "false");
195				dot.onclick = () => {
196					goToIndex(i);
197
198					resetAutoplay();
199				};
200
201				dotsContainer.appendChild(dot);
202			}
203
204			controlsContainer.appendChild(dotsContainer);
205		}
206
207		// Counter
208		if (opts.showCounter) {
209			const counter = document.createElement("div");
210
211			counter.className = "slider-counter";
212			counter.setAttribute("aria-live", "polite");
213			counter.textContent = `1-${Math.min(opts.itemsPerSlide, items.length)} / ${items.length}`;
214
215			controlsContainer.appendChild(counter);
216		}
217
218		element.appendChild(controlsContainer);
219
220		// Live region for screen readers
221		const liveRegion = document.createElement("div");
222
223		liveRegion.className = "slider-live-region";
224		liveRegion.setAttribute("aria-live", "polite");
225		liveRegion.setAttribute("aria-atomic", "true");
226
227		element.appendChild(liveRegion);
228
229		// Apply accent color via CSS custom property
230		const accentColorLight = utilities.lightenColor(opts.accentColor, 25);
231
232		element.style.setProperty("--accent-color", opts.accentColor);
233		element.style.setProperty("--accent-color-light", accentColorLight);
234
235		// Border Color
236		element.style.setProperty("--ms-card-border", options.borderColor || "");
237
238		// Autoplay
239		const startAutoplay = () => {
240			// Clear any existing timer first
241			stopAutoplay();
242
243			if (
244				opts.autoplay &&
245				items.length > opts.itemsPerSlide
246			) {
247				autoplayTimer = setInterval(nextSlide, opts.autoplaySpeed);
248			}
249		};
250
251		const stopAutoplay = () => {
252			if (autoplayTimer) {
253				clearInterval(autoplayTimer);
254
255				autoplayTimer = null;
256			}
257		};
258
259		const resetAutoplay = () => {
260			if (
261				opts.autoplay &&
262				items.length > opts.itemsPerSlide
263			) {
264				stopAutoplay();
265
266				// Add a small delay before restarting to prevent immediate firing
267				setTimeout(startAutoplay, 100);
268			}
269		};
270
271		// Touch support
272		const handleTouchStart = (e) => {
273			touchStartX = e.touches[0].clientX;
274		};
275
276		const handleTouchMove = (e) => {
277			touchEndX = e.touches[0].clientX;
278		};
279
280		const handleTouchEnd = () => {
281			const diff = touchStartX - touchEndX;
282			const threshold = 50;
283
284			if (Math.abs(diff) > threshold) {
285				if (diff > 0) {
286					nextSlide();
287				}
288				else {
289					prevSlide();
290				}
291
292				resetAutoplay();
293			}
294		};
295
296		track.addEventListener("touchstart", handleTouchStart, { passive: true });
297		track.addEventListener("touchmove", handleTouchMove, { passive: true });
298		track.addEventListener("touchend", handleTouchEnd);
299
300		// Keyboard navigation
301		element.setAttribute("tabindex", "0");
302		element.setAttribute("role", "region");
303		element.setAttribute("aria-label", "Media Slider");
304		element.setAttribute("aria-roledescription", "carousel");
305
306		element.addEventListener("keydown", (e) => {
307			if (e.key === "ArrowLeft") {
308				e.preventDefault();
309
310				prevSlide();
311
312				resetAutoplay();
313			}
314			else if (e.key === "ArrowRight") {
315				e.preventDefault();
316
317				nextSlide();
318
319				resetAutoplay();
320			}
321		});
322
323		// Pause on hover
324		if (opts.pauseOnHover) {
325			element.addEventListener("mouseenter", stopAutoplay);
326			element.addEventListener("mouseleave", startAutoplay);
327		}
328
329		// Set initial position
330		goToIndex(0);
331
332		updateControls();
333
334		startAutoplay();
335
336		
337
338		// Cleanup
339		return {
340			destroy: () => {
341				stopAutoplay();
342
343				track.removeEventListener("touchstart", handleTouchStart);
344				track.removeEventListener("touchmove", handleTouchMove);
345				track.removeEventListener("touchend", handleTouchEnd);
346			}
347		};
348	},
349	unmount: function(element, instance) {
350		if (
351			instance &&
352			instance.destroy
353		) {
354			instance.destroy();
355		}
356	}
357};

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.