PageSourceSearch

https://store.curativemushrooms.com/wp-content/plugins/easy-accord…/Blocks/assets/js/sp-eap-accordion.js

js curativemushrooms.com collected 2026-09-25 00:31:21 UTC 15,035 bytes, 524 lines download raw bytes

1class EabHelpers {
2	constructor() {
3		this.headingSelector = '.sp-eab-accordion-heading';
4	}
5
6	qs(selector, el = false) {
7		if (el) {
8			return el.querySelector(selector);
9		}
10		return document.querySelector(selector);
11	}
12	qsAll(selector, el = false) {
13		if (el) {
14			return el.querySelectorAll(selector);
15		}
16		return document.querySelectorAll(selector);
17	}
18}
19class EabAccordion {
20	constructor(selectorEl, options = {}) {
21		this.container = selectorEl;
22		this.items = this.container.querySelectorAll(
23			':scope > .sp-eab-accordion-item'
24		);
25		this.options = Object.assign(
26			{
27				openMultiItemAtaTime: true,
28				defaultAccordionOpen: 'close-all',
29				selectedItemOpen: 1, // only works when defaultAccordionOpen ===  open-selected-item;
30				mode: 'vertical',
31				activeEvent: 'click',
32				transitionDuration: 600, // in ms
33				autoplayDuration: 3000, // in ms
34				autoplayPauseOnHover: true,
35				scrollToTopOnLoad: false,
36				scrollToTopOnClick: false,
37				accordionItemToUrl: false,
38				animationEffect: false,
39				applyAccessibility: true,
40			},
41			options
42		);
43		this.isVerticalMode = this.options.mode === 'vertical';
44		this.#accordionInit();
45		// autoplay constants.
46		this.currentIndex = 0;
47		this.autoplayInterval = null;
48		this.isAutoplayPaused = false;
49	}
50
51	//Ensure event listeners don't stack when the same DOM
52	//is initialized multiple times (e.g. block re-render / AJAX).
53	#removeBoundEvent(el, eventName, storeKey) {
54		if (!el) {
55			return;
56		}
57		const key = storeKey || `__eab_${eventName}_handler`;
58		const prev = el[key];
59		if (prev) {
60			el.removeEventListener(eventName, prev);
61			try {
62				delete el[key];
63			} catch (e) {
64				// ignore if delete fails
65			}
66		}
67	}
68
69	#bindEvent(el, eventName, handler, storeKey) {
70		if (!el || typeof handler !== 'function') {
71			return;
72		}
73		const key = storeKey || `__eab_${eventName}_handler`;
74		this.#removeBoundEvent(el, eventName, key);
75		el.addEventListener(eventName, handler);
76		el[key] = handler;
77	}
78
79	/* ----------------------------
80	 * ACCESSIBILITY SETUP
81	 * ----------------------------- */
82	#getFocusableElements(item) {
83		const selectors = `a[href], area[href], button, input:not([type="hidden"]), select, textarea, iframe`;
84		return [...item.querySelectorAll(selectors)].filter((el) => {
85			return (
86				!el.disabled &&
87				el.getAttribute('aria-hidden') !== 'true' &&
88				el.offsetParent !== null
89			);
90		});
91	}
92	#applyAccessibilityAttributes(item, index) {
93		const trigger = item.querySelector('.sp-eab-accordion-heading');
94		const content = item.querySelector('.sp-eab-accordion-content');
95		const uniqueId = item?.getAttribute('id');
96		const shortId = uniqueId?.slice(-6) || index;
97		const triggerId = `eab-trigger${shortId}`;
98		const contentId = `eab-content${shortId}`;
99		// --- Accessible roles & attributes ---
100		trigger.setAttribute('id', triggerId);
101		trigger.setAttribute('role', 'button');
102		trigger.setAttribute('tabindex', '0');
103		trigger.setAttribute('aria-controls', contentId);
104		trigger.setAttribute('aria-expanded', 'false');
105		trigger.setAttribute('aria-disabled', 'false');
106
107		content.setAttribute('id', contentId);
108		content.setAttribute('role', 'region');
109		content.setAttribute('aria-labelledby', triggerId);
110
111		// --- Keyboard Navigation ---
112		const onKeydown = (e) => {
113			const triggers = [
114				...this.container.querySelectorAll('.sp-eab-accordion-heading'),
115			];
116			if (e.key !== 'Tab') {
117				e.preventDefault();
118			}
119			switch (e.key) {
120				case 'Enter':
121				case ' ':
122					this.#toggleAccordionItem(item);
123					break;
124				case 'ArrowDown':
125				case 'ArrowRight':
126					if (triggers[index + 1]) {
127						triggers[index + 1].focus();
128					}
129					break;
130				case 'ArrowUp':
131				case 'ArrowLeft':
132					if (triggers[index - 1]) {
133						triggers[index - 1].focus();
134					}
135					break;
136				case 'Home':
137					triggers[0].focus();
138					break;
139				case 'End':
140					triggers[triggers?.length - 1].focus();
141					break;
142				case 'Tab':
143					// Only handle forward tab.
144					if (e.shiftKey) {
145						return;
146					}
147					if (
148						item.classList.contains('eab-expand') &&
149						this.#getFocusableElements(item)
150					) {
151						return;
152					}
153					const nextTrigger = triggers[index + 1];
154					if (nextTrigger) {
155						e.preventDefault();
156						nextTrigger.focus();
157					}
158					break;
159			}
160		};
161		this.#bindEvent(trigger, 'keydown', onKeydown, '__eabKeydownHandler');
162	}
163
164	#toggleAriaControls(item, state = 'true') {
165		const trigger = item.querySelector('.sp-eab-accordion-heading');
166		trigger.setAttribute('aria-expanded', state);
167	}
168
169	/* -------------------------
170	 * ACCORDION INIT
171	 * -------------------------- */
172	#accordionInit() {
173		// INITIALIZE EACH ACCORDION ITEM.
174		const activeEvent = this.options.activeEvent;
175		const defaultOpenItems = this.options.defaultAccordionOpen;
176		const isEabSlugExist =
177			window?.location?.href?.includes('eab-accordion-');
178		const cssString = this.isVerticalMode
179			? `max-height: 0; transition: max-height ${this.options.transitionDuration || 500}ms ease;`
180			: `width: 0; transition: width ${this.options.transitionDuration || 500}ms ease;`;
181
182		if (defaultOpenItems === 'close-all') {
183			this.collapseAll();
184		}
185
186		// LOOP THROUGH EACH ITEM.
187		this.items.forEach((item, index) => {
188			const content = item.querySelector('.sp-eab-accordion-content');
189			content.style.cssText = cssString;
190			// ADD EVENT LISTENER BASED ON ACTIVE EVENT TYPE.
191			const trigger = item.querySelector('.sp-eab-accordion-heading');
192			// Clean up any previously bound handlers to avoid double toggles.
193			this.#removeBoundEvent(
194				trigger,
195				'mouseenter',
196				'__eabToggleMouseenterHandler'
197			);
198			this.#removeBoundEvent(trigger, 'click', '__eabToggleClickHandler');
199			this.#removeBoundEvent(
200				item,
201				'mouseenter',
202				'__eabAutoplayPauseHandler'
203			);
204			this.#removeBoundEvent(
205				item,
206				'mouseleave',
207				'__eabAutoplayResumeHandler'
208			);
209			if (activeEvent === 'hover') {
210				const onTriggerMouseenter = () =>
211					this.#toggleAccordionItem(item);
212				this.#bindEvent(
213					trigger,
214					'mouseenter',
215					onTriggerMouseenter,
216					'__eabToggleMouseenterHandler'
217				);
218			} else if (activeEvent === 'click') {
219				const onTriggerClick = () => this.#toggleAccordionItem(item);
220				this.#bindEvent(
221					trigger,
222					'click',
223					onTriggerClick,
224					'__eabToggleClickHandler'
225				);
226			} else if (activeEvent === 'auto') {
227				const onTriggerClick = () => this.#toggleAccordionItem(item);
228				this.#bindEvent(
229					trigger,
230					'click',
231					onTriggerClick,
232					'__eabToggleClickHandler'
233				);
234				// PAUSE AUTOPLAY ON HOVER.
235				if (this.options.autoplayPauseOnHover) {
236					const onPause = () => this.#pauseAutoplay();
237					const onResume = () => this.#resumeAutoplay();
238					this.#bindEvent(
239						item,
240						'mouseenter',
241						onPause,
242						'__eabAutoplayPauseHandler'
243					);
244					this.#bindEvent(
245						item,
246						'mouseleave',
247						onResume,
248						'__eabAutoplayResumeHandler'
249					);
250				}
251			}
252
253			// DEFAULT OPEN ITEM SET FROM ACCORDION ITEM BLOCK ONLY WORK ON VERTICAL ACCORDION.
254			if (this.isVerticalMode && item.getAttribute('data-default-open')) {
255				this.#openAccordionItem(item);
256				// SCROLL TO TOP ON LOAD.
257				if (this.options.scrollToTopOnLoad) {
258					this.#scrollItemIntoView(item);
259				}
260			}
261			// ACCESSIBILITY INIT.
262			if (this.options.applyAccessibility) {
263				this.#applyAccessibilityAttributes(item, index);
264			}
265			// variations product.
266			const variationsProduct = item.querySelector('.variations_form');
267			if (variationsProduct) {
268				content.style.overflowY = 'auto';
269			}
270		});
271		// ACCORDION AUTOPLAY HANDLER.
272		if (activeEvent === 'auto') {
273			this.#startAutoplay();
274		}
275		// ACCORDION DEFAULT OPEN HANDLER.
276		if (isEabSlugExist) {
277			return; // skip default open if URL has accordion slug.
278		}
279		if (defaultOpenItems === 'open-all') {
280			this.expandAll();
281		} else if (defaultOpenItems === 'first-item') {
282			this.#openAccordionItem(this.items[0]);
283		} else if (defaultOpenItems === 'open-selected-item') {
284			const item = this.items[this.options.selectedItemOpen];
285			if (item) {
286				this.#openAccordionItem(item);
287			}
288		}
289	}
290
291	/* ---------------------------------------
292	 * TOGGLE ACCORDION ITEM
293	 * ---------------------------------------- */
294	#toggleAccordionItem(item) {
295		// UPDATE INDEX IF ACTIVATOR EVENT IS AUTOPLAY AND CLICKED ON ITEM.
296		if (this.options.activeEvent === 'auto') {
297			const index = Array.from(this.items).indexOf(item);
298			if (index !== -1) {
299				this.currentIndex = index;
300			}
301		}
302		// TOGGLE ITEM FUNCTIONALITY.
303		if (item.classList.contains('eab-expand')) {
304			this.#closeAccordionItem(item);
305		} else {
306			if (!this.options.openMultiItemAtaTime) {
307				this.collapseAll();
308			}
309			this.#openAccordionItem(item);
310			// if (this.options.accordionItemToUrl) {
311			// 	this.#accordionToUrl(item);
312			// }
313		}
314	}
315	/* -----------------------------------
316	 * HANDLE OPEN ACCORDION ITEM
317	 * ------------------------------------ */
318	#openAccordionItem(item) {
319		if (!item) {
320			return;
321		}
322		// APPLY CONTENT ANIMATION.
323		this.#handleContentAnimation(item, 'APPLY');
324		const content = item.querySelector('.sp-eab-accordion-content');
325		item.classList.add('eab-expand');
326		if (this.isVerticalMode) {
327			content.style.maxHeight = content.scrollHeight + 'px';
328		} else {
329			content.style.width = content.scrollWidth + 'px';
330		}
331		// ACCESSIBILITY TOGGLE.
332		if (this.options.applyAccessibility) {
333			this.#toggleAriaControls(item, 'true');
334		}
335	}
336	/* ------------------------------------
337	 * HANDLE CLOSE ACCORDION ITEM
338	 * ------------------------------------- */
339	#closeAccordionItem(item) {
340		// REMOVE CONTENT ANIMATION.
341		this.#handleContentAnimation(item, 'REMOVE');
342		const content = item.querySelector('.sp-eab-accordion-content');
343		item.classList.remove('eab-expand');
344		if (this.isVerticalMode) {
345			content.style.maxHeight = '0px';
346		} else {
347			content.style.width = '0px';
348		}
349		// ACCESSIBILITY TOGGLE.
350		if (this.options.applyAccessibility) {
351			this.#toggleAriaControls(item, 'false');
352		}
353	}
354	/* ---------------------------------
355	 * METHOD EXPAND ALL ACCORDION
356	 * --------------------------------- */
357	expandAll() {
358		this.items.forEach((item) => this.#openAccordionItem(item));
359	}
360	/* -------------------------------
361	 * METHOD COLLAPSE ALL ACCORDION
362	 * --------------------------------*/
363	collapseAll() {
364		this.items.forEach((item) => this.#closeAccordionItem(item));
365	}
366
367	/* --------------------------------
368	 * ACCORDION AUTOPLAY
369	 * --------------------------------- */
370	#startAutoplay() {
371		this.autoplayInterval = setInterval(() => {
372			if (this.isAutoplayPaused) {
373				return;
374			} // skip while paused
375
376			this.collapseAll();
377			const currentItem = this.items[this.currentIndex];
378			this.#openAccordionItem(currentItem);
379
380			this.currentIndex = (this.currentIndex + 1) % this.items.length;
381		}, this.options.autoplayDuration);
382	}
383
384	#pauseAutoplay() {
385		this.isAutoplayPaused = true;
386	}
387
388	#resumeAutoplay() {
389		this.isAutoplayPaused = false;
390	}
391
392	/* ----------------------------------
393	 * SCROLL OPENED ITEM TO TOP
394	 * ---------------------------------*/
395	#scrollItemIntoView(item) {
396		const offset = 300; // admin bar or extra spacing
397		const rect = item.getBoundingClientRect();
398		const scrollTop = window.pageYOffset + rect.top - offset;
399		window.scrollTo({ top: scrollTop, behavior: 'smooth' });
400	}
401	/* ----------------------------------
402	 * ACCORDION TO URL HASH ITEM
403	 * ----------------------------------*/
404	#accordionToUrl(item) {
405		const url = item
406			.querySelector('.sp-eab-accordion-header-wrapper')
407			?.getAttribute('data-slug');
408		if (url) {
409			const newLink = window.location.href.split('#')[0] + url;
410			window.location.href = newLink;
411		}
412	}
413	#accordionOpenByUrl(item) {
414		const slug = window.location?.href?.split('#')[1] || '';
415		const dataSlug = item
416			.querySelector('.sp-eab-accordion-header-wrapper')
417			?.getAttribute('data-slug');
418		if (dataSlug === `#${slug}`) {
419			this.#openAccordionItem(item);
420		}
421	}
422	/* -----------------------------------
423	 * APPLY CONTENT ANIMATION
424	 * ------------------------------------ */
425	#handleContentAnimation(item, action) {
426		const effect = this.options.animationEffect;
427		const animatedWrapper = item?.querySelector(
428			'.sp-eab-accordion-content-wrapper'
429		);
430		if (!effect || !animatedWrapper) {
431			return;
432		}
433		if (action === 'APPLY') {
434			animatedWrapper.classList.add('animated', effect);
435		} else if (action === 'REMOVE') {
436			animatedWrapper.classList.remove('animated', effect);
437		}
438	}
439}
440
441// ACCORDION SIDEBAR TAB SCRIPT.
442class EabTabAccordion extends EabHelpers {
443	// EabTabAccordion CLASS CONSTRUCTOR.
444	constructor(selectorEl, options = {}) {
445		super();
446		this.block = selectorEl;
447		this.options = Object.assign(
448			{
449				defaultOpenItem: "first-item",
450				selectedItemOpen: 1, // only works when defaultOpenItem ===  open-selected-item;
451				activeEvent: "click",
452				transitionDuration: 600, // in ms
453				autoplayDuration: 3000, // in ms
454				scrollToTopOnLoad: false,
455				accordionItemToUrl: false,
456				contentHeight: 'auto',
457				template: '',
458			},
459			options
460		);
461		// Elements
462		this.headings = this.qsAll(this.headingSelector, this.block);
463		this.allContents = this.qsAll('.sp-eab-sidebar-tab-item', this.block);
464		this.#initBlock();
465	}
466
467	// TAB BLOCK INIT.
468	#initBlock() {
469		this.headings.forEach((heading, index) => {
470			// event listener init.
471			const eventListener =
472				this.options.activeEvent === 'hover' ? 'mouseenter' : 'click';
473			heading.addEventListener(eventListener, () => {
474				this.#openTabItem(heading);
475			});
476			// default open items.
477			const itemNumber = index + 1;
478			if (
479				this.options.defaultOpenItem === 'first-item' &&
480				itemNumber === 1
481			) {
482				this.#openTabItem(heading);
483			}
484			if (
485				this.options.defaultOpenItem === 'open-selected-item' &&
486				itemNumber === parseInt(this.options.selectedItemOpen)
487			) {
488				this.#openTabItem(heading);
489			}
490		});
491		// navigation wrappers.
492		if (this.options.contentHeight === 'auto') {
493			const contentWrapper = this.qs(
494				'.sp-eab-sidebar-tabs-content',
495				this.block
496			);
497			const navWrapper = this.qs('.sp-eab-sidebar-tabs-nav', this.block);
498			contentWrapper.style.minHeight = `${navWrapper.offsetHeight}px`;
499		}
500	}
501
502	#openTabItem(heading) {
503		const contentId = heading?.getAttribute('data-tabid');
504		if (!contentId) {
505			return;
506		}
507		// heading update.
508		this.headings?.forEach((h) => h.classList.remove('eab-expand'));
509		heading.classList.add('eab-expand');
510		// content update.
511		const content = this.qs(`#${contentId}`, this.block);
512		this.allContents?.forEach((c) => c.classList.remove('eab-expand'));
513		content.classList.add('eab-expand');
514
515		if ('sidebar-tab-accordion-one' === this.options.template) {
516			const navWrapper = heading.closest('.sp-eab-sidebar-tabs-nav');
517			const contentWrapper = content.closest('.sp-eab-sidebar-tabs-content');
518			if (!navWrapper || !contentWrapper) {
519				return;
520			}
521			contentWrapper.style.borderBottomLeftRadius = contentWrapper?.offsetHeight > navWrapper?.offsetHeight ? '' : '0';
522		}
523	}
524}

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.