PageSourceSearch

https://un.dk/wp-content/plugins/easy-accordion-free/Blocks/assets/js/script.js?ver=3.1.10

js un.dk collected 2026-09-24 17:55:00 UTC 12,359 bytes, 502 lines download raw bytes

1// Product Quantity.
2function productQuantity(container = document) {
3	container.querySelectorAll('.eab-quantity-input').forEach((input) => {
4		input.addEventListener('change', () => {
5			const wrapper = input.closest('.eab-product-cart-wrapper');
6			if (!wrapper) {
7				return;
8			}
9
10			const button = wrapper.querySelector('.eab-add-to-cart');
11			if (!button) {
12				return;
13			}
14
15			const quantity = parseInt(input.value, 10) || 1;
16			button.dataset.quantity = quantity;
17		});
18	});
19}
20
21// SIDEBAR TABS ACCORDION BLOCK INIT.
22document.addEventListener('DOMContentLoaded', () => {
23	const tabsBlocks = document.querySelectorAll('.sp-eab-sidebar-tabs');
24	if (tabsBlocks?.length > 0) {
25		tabsBlocks.forEach((tabAccordion) => {
26			const options = tabAccordion.getAttribute('data-tab-settings');
27			let parsedOptions = {};
28			if (options && options.trim()) {
29				try {
30					parsedOptions = JSON.parse(options);
31				} catch (e) {
32					// Invalid JSON, use empty object
33					parsedOptions = {};
34				}
35			}
36			new EabTabAccordion(tabAccordion, parsedOptions);
37		});
38	}
39});
40
41document.addEventListener('DOMContentLoaded', () => {
42	const accordionWrappers = document.querySelectorAll(
43		'.sp-eab-regular-accordion,.sp-eab-post-accordion,.sp-eab-product-accordion'
44	);
45
46	if (!accordionWrappers.length) {
47		return;
48	}
49
50	accordionWrappers.forEach((accordionBlock) => {
51		const qs = (s) => accordionBlock.querySelector(s);
52		const qsa = (s) => accordionBlock.querySelectorAll(s);
53
54		/* -------------------------
55		 * ACCORDION INIT
56		 * ------------------------- */
57		const initAccordion = () => {
58			const el = qs('.sp-eab-accordion');
59			if (!el) {
60				return null;
61			}
62
63			let options = {};
64			const attrOptions = el.getAttribute('data-accordion-settings');
65			if (attrOptions && attrOptions.trim()) {
66				try {
67					options = JSON.parse(attrOptions);
68				} catch (e) {
69					// Invalid JSON, use empty object
70					options = {};
71				}
72			}
73			const accordion = new EabAccordion(el, options);
74
75			return accordion;
76		};
77
78		let accordion = initAccordion();
79	});
80});
81
82
83//Image effect.
84const applyEffectClasses = (item, isActive) => {
85	const effectType = item.dataset.imageEffect;
86	const hoverGrayScale = item.dataset.hoverGrayScale;
87	const bgEl = item.querySelector(
88		'.sp-eab-accordion-bg,.sp-eab-featured-image-wrapper'
89	);
90
91	if (!effectType || !bgEl) {
92		return;
93	}
94
95	// Remove all effect classes
96	item.classList.remove(
97		'eab-zoom-out-active',
98		'eab-zoom-out-normal',
99		'eab-zoom-out-onhover'
100	);
101
102	const useHoverGray = hoverGrayScale && !isActive;
103
104	let effectClass = '';
105
106	switch (effectType) {
107		case 'zoomOut':
108			if (useHoverGray) {
109				effectClass = 'eab-zoom-out-normal eab-zoom-out-onhover';
110			} else if (isActive) {
111				effectClass = 'eab-zoom-out-active';
112			} else {
113				effectClass = 'eab-zoom-out-normal';
114			}
115			break;
116		default:
117			effectClass = '';
118	}
119
120	// Apply classes
121	if (effectClass) {
122		effectClass.split(' ').forEach((cls) => item.classList.add(cls));
123	}
124};
125
126const getDeviceValuePx = (valueObj, device = 'Desktop', units = false) => {
127	if (!valueObj || !valueObj.device || !valueObj.unit) {
128		return 0;
129	}
130
131	const value = valueObj.device[device];
132	const unit = valueObj.unit[device];
133
134	if (value === null) {
135		return 0;
136	}
137
138	// Return proper string with correct unit
139	return unit === '%' && units ? `${value}%` : value;
140};
141
142// preloader init.
143document.addEventListener('DOMContentLoaded', () => {
144	const accordionBlocks = document.querySelectorAll(
145		'.sp-easy-accordion-block'
146	);
147	if (accordionBlocks.length === 0) {
148		return;
149	}
150	accordionBlocks?.forEach((accordion) => {
151		/**
152		 * Preloader.
153		 * Fades out preloader.
154		 */
155		const preloader = accordion?.querySelector('.sp-eab-preloader');
156		if (preloader) {
157			// Wait 0.5 seconds before showing fallback.
158			setTimeout(() => {
159				preloader.classList.remove('sp-d-block');
160				preloader.classList.add('sp-d-hidden');
161				accordion.classList.add('sp-eab-preloader-removed');
162			}, 500);
163		}
164	});
165});
166
167document.addEventListener('DOMContentLoaded', () => {
168	const accordions = Array.from(
169		document.querySelectorAll(
170			'.sp-eab-image-accordion,.sp-eab-product-accordion'
171		)
172	);
173
174	accordions.forEach((accordion) => {
175		const items = Array.from(
176			accordion.querySelectorAll('.sp-eab-image-accordion-item')
177		);
178		if (!items.length) {
179			return;
180		}
181
182		// --- Read data attributes ---
183		const defaultOpen = accordion.dataset.defaultOpen || 'first-item';
184		const activeEvent = accordion.dataset.activeEvent || 'click';
185		const selectedIndex =
186			parseInt(accordion.dataset.selectedAccordion, 10) - 1;
187		const scrollToActive = accordion.dataset.scrollToActiveItem;
188		const accessibility = accordion.dataset.eabAccessibility;
189		const itemToUrl = accordion.dataset.anchorLink;
190		const autoInterval = accordion.dataset.autoplayDelay;
191		const animation =
192			accordion.dataset.animation &&
193				accordion.dataset.animation !== 'none'
194				? accordion.dataset.animation
195				: null;
196
197		let activeIndex = -1;
198		let intervalRef = null;
199
200		const applyAnimation = (item, isActive) => {
201			const content = item.querySelector('.eab-animation');
202			if (content) {
203				// Only apply animation if it's not 'none'
204				if (animation && animation !== 'none') {
205					// Remove previous animation classes
206					content.classList.remove('animated', animation);
207					if (isActive) {
208						content.classList.add('animated', animation);
209					}
210				}
211			}
212		};
213
214		const updateAccessibility = () => {
215			if (!accessibility) {
216				return;
217			}
218			items.forEach((item, index) => {
219				const isActive = index === activeIndex;
220				const button = item.querySelector('.sp-eab-accordion-bg');
221
222				item.setAttribute('role', 'region');
223				if (button) {
224					button.setAttribute('role', 'button');
225					button.setAttribute('tabindex', '0');
226					button.setAttribute(
227						'aria-expanded',
228						isActive ? 'true' : 'false'
229					);
230					button.setAttribute(
231						'aria-label',
232						(
233							item.querySelector('.sp-eab-image-title')
234								?.textContent || ''
235						)
236							.replace(/\s+/g, ' ')
237							.trim()
238					);
239				}
240			});
241		};
242
243		// --- Helper: set active item ---
244		let allowUrlUpdate = false; // URL updates blocked until first open
245
246		const setActiveItem = (index, shouldScroll = false) => {
247			items.forEach((item, i) => {
248				const isActive = i === index;
249				if (
250					!(
251						accordion.classList.contains(
252							'sp-eab-accordion-slider'
253						) || accordion.classList.contains('sp-eab-carousel')
254					)
255				) {
256					item.classList.toggle('active', isActive);
257				}
258				applyEffectClasses(item, isActive);
259				applyAnimation(item, isActive);
260			});
261
262			const previousIndex = activeIndex;
263			activeIndex = index;
264			updateAccessibility();
265
266			// --- URL update logic ---
267			const activeItem = items[index];
268			if (activeItem && itemToUrl) {
269				const slug = activeItem.getAttribute('data-slug');
270				if (slug && allowUrlUpdate && previousIndex !== index) {
271					const base = window.location.href.split('#')[0];
272					history.replaceState(null, '', base + slug);
273				}
274			}
275
276			// Scroll
277			if (shouldScroll && scrollToActive && items[index]) {
278				items[index].scrollIntoView({
279					behavior: 'smooth',
280					block: 'center',
281					inline: 'nearest',
282				});
283			}
284
285			// After first active item: allow URL updates
286			allowUrlUpdate = true;
287		};
288
289		// --- Keyboard navigation ---
290		if (accessibility) {
291			items.forEach((item, index) => {
292				const button = item.querySelector('.sp-eab-accordion-bg');
293				if (!button) {
294					return;
295				}
296
297				button.addEventListener('keydown', (e) => {
298					switch (e.key) {
299						case 'ArrowRight':
300						case 'ArrowDown':
301							setActiveItem(
302								(activeIndex + 1) % items.length,
303								true
304							);
305							items[activeIndex % items.length]
306								.querySelector('.sp-eab-accordion-bg')
307								?.focus();
308							e.preventDefault();
309							break;
310						case 'ArrowLeft':
311						case 'ArrowUp':
312							setActiveItem(
313								(activeIndex - 1 + items.length) % items.length,
314								true
315							);
316							items[activeIndex % items.length]
317								.querySelector('.sp-eab-accordion-bg')
318								?.focus();
319							e.preventDefault();
320							break;
321						case 'Enter':
322						case ' ':
323							setActiveItem(index, true);
324							e.preventDefault();
325							break;
326					}
327				});
328			});
329		}
330
331		let isHashOpen = false;
332		const currentHash = window.location.hash;
333
334		if (currentHash && itemToUrl) {
335			const matchedIndex = items.findIndex(
336				(item) => item.getAttribute('data-slug') === currentHash
337			);
338
339			if (matchedIndex >= 0) {
340				isHashOpen = true;
341				activeIndex = matchedIndex;
342
343				// Open from URL but DO NOT update URL again
344				setTimeout(() => {
345					setActiveItem(matchedIndex, true);
346				}, 50);
347			}
348		}
349
350		if (!isHashOpen) {
351			switch (defaultOpen) {
352				case 'first-item':
353					activeIndex = 0;
354					break;
355				case 'close-all':
356					activeIndex = -1;
357					break;
358				case 'open-selected-item':
359					activeIndex =
360						selectedIndex >= 0 && selectedIndex < items.length
361							? selectedIndex
362							: 0;
363					break;
364				default:
365					activeIndex = 0;
366			}
367
368			if (activeIndex >= 0) {
369				setTimeout(() => setActiveItem(activeIndex, true), 300);
370			}
371		}
372
373		// --- Auto-play logic ---
374		let autoPlayIntervalRef = null;
375		let isPaused = false;
376
377		// --- Start / Resume autoplay ---
378		const startAutoPlay = () => {
379			clearInterval(autoPlayIntervalRef);
380			if (items.length <= 1 || isPaused) {
381				return;
382			}
383			autoPlayIntervalRef = setInterval(() => {
384				const nextIndex = (activeIndex + 1) % items.length;
385				setActiveItem(nextIndex, true);
386			}, autoInterval);
387		};
388
389		// --- Pause autoplay ---
390		const pauseAutoplay = () => {
391			isPaused = true;
392			clearInterval(autoPlayIntervalRef);
393		};
394
395		// --- Resume autoplay ---
396		const resumeAutoplay = () => {
397			if (!isPaused) {
398				return;
399			}
400			isPaused = false;
401			startAutoPlay();
402		};
403
404		if (activeEvent === 'auto') {
405			startAutoPlay();
406			accordion.addEventListener('mouseenter', pauseAutoplay);
407			accordion.addEventListener('mouseleave', resumeAutoplay);
408		}
409
410		// --- Click / Hover Event handling ---
411		const eventType = activeEvent === 'hover' ? 'mouseenter' : 'click';
412
413		items.forEach((item, index) => {
414			item.addEventListener(eventType, () => {
415				if (defaultOpen === 'close-all' && activeIndex === index) {
416					// Only remove active class if NOT slider
417					if (
418						!(
419							accordion.classList.contains(
420								'sp-eab-accordion-slider'
421							) || accordion.classList.contains('sp-eab-carousel')
422						)
423					) {
424						item.classList.remove('active');
425					}
426					applyEffectClasses(item, false);
427					activeIndex = -1;
428					updateAccessibility();
429				} else {
430					setActiveItem(index, true);
431				}
432
433				// Restart autoplay if enabled
434				if (activeEvent === 'auto') {
435					startAutoPlay();
436				}
437			});
438		});
439	});
440
441	// Add scripts for variation.
442	productQuantity();
443});
444
445const animateAccordionItemsOnView = (items, animationClass = 'none') => {
446	if (!animationClass || animationClass.toLowerCase() === 'none') {
447		return;
448	}
449	if (!('IntersectionObserver' in window)) {
450		// Fallback: animate immediately if IntersectionObserver not supported
451		items.forEach((item) => {
452			item.classList.add('animated', animationClass);
453		});
454		return;
455	}
456
457	const observer = new IntersectionObserver(
458		(entries, obs) => {
459			entries.forEach((entry) => {
460				if (entry.isIntersecting) {
461					entry.target.classList.add('animated', animationClass);
462					// Stop observing after animation
463					obs.unobserve(entry.target);
464				}
465			});
466		},
467		{
468			threshold: 0.2, // Trigger when 20% of the item is visible
469		}
470	);
471
472	items.forEach((item) => observer.observe(item));
473};
474
475// reload screen on resize tablet-or-desktop to mobile.
476document.addEventListener('DOMContentLoaded', () => {
477	if (window?.elementorFrontend?.isEditMode()) {
478		return;
479	}
480	// Function to determine the device category.
481	const getDeviceType = () => {
482		const width = window.innerWidth;
483		if (width <= 600) {
484			return 'mobile';
485		}
486		return 'tablet-or-desktop';
487	};
488
489	// Track initial device type
490	let currentDevice = getDeviceType();
491
492	window.addEventListener('resize', () => {
493		const newDevice = getDeviceType();
494		// Reload only if switching between mobile and tablet/desktop
495		if (newDevice !== currentDevice) {
496			location.reload();
497		}
498
499		// Update the current device type
500		currentDevice = newDevice;
501	});
502});

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.