stylescape
Version:
Stylescape is a visual identity framework developed by Scape Agency.
208 lines • 7.9 kB
JavaScript
export class AccordionManager {
constructor(selectorOrElement, options = {}) {
this.items = [];
this.handleClick = (event) => {
const header = event.currentTarget;
const index = this.items.findIndex((item) => item.header === header);
if (index !== -1) {
this.toggle(index);
}
};
this.handleKeydown = (event) => {
const header = event.currentTarget;
const index = this.items.findIndex((item) => item.header === header);
switch (event.key) {
case "Enter":
case " ":
event.preventDefault();
this.toggle(index);
break;
case "ArrowDown":
event.preventDefault();
this.focusHeader((index + 1) % this.items.length);
break;
case "ArrowUp":
event.preventDefault();
this.focusHeader((index - 1 + this.items.length) % this.items.length);
break;
case "Home":
event.preventDefault();
this.focusHeader(0);
break;
case "End":
event.preventDefault();
this.focusHeader(this.items.length - 1);
break;
}
};
this.container =
typeof selectorOrElement === "string"
? document.querySelector(selectorOrElement)
: selectorOrElement;
this.options = {
allowMultiple: options.allowMultiple ?? false,
defaultOpen: options.defaultOpen ?? -1,
animationDuration: options.animationDuration ?? 300,
activeHeaderClass: options.activeHeaderClass ?? "accordion-header--active",
activeContentClass: options.activeContentClass ?? "accordion-content--active",
onOpen: options.onOpen ?? (() => { }),
onClose: options.onClose ?? (() => { }),
contentSelector: options.contentSelector ?? "[data-ss-accordion-content]",
};
if (!this.container) {
console.warn("[Stylescape] AccordionManager container not found");
return;
}
this.init();
}
open(index) {
if (index < 0 || index >= this.items.length)
return;
if (!this.options.allowMultiple) {
this.items.forEach((item, i) => {
if (i !== index && item.isOpen) {
this.closeItem(i);
}
});
}
this.openItem(index);
}
close(index) {
if (index < 0 || index >= this.items.length)
return;
this.closeItem(index);
}
toggle(index) {
if (index < 0 || index >= this.items.length)
return;
if (this.items[index].isOpen) {
this.close(index);
}
else {
this.open(index);
}
}
openAll() {
this.items.forEach((_, i) => this.openItem(i));
}
closeAll() {
this.items.forEach((_, i) => this.closeItem(i));
}
isOpen(index) {
return this.items[index]?.isOpen ?? false;
}
destroy() {
this.items.forEach((item) => {
item.header.removeEventListener("click", this.handleClick);
item.header.removeEventListener("keydown", this.handleKeydown);
});
this.items = [];
}
static initAccordions() {
const managers = [];
const accordions = document.querySelectorAll('[data-ss="accordion"]');
accordions.forEach((el) => {
const multiple = el.dataset.ssAccordionMultiple === "true";
const defaultOpen = el.dataset.ssAccordionDefaultOpen;
managers.push(new AccordionManager(el, {
allowMultiple: multiple,
defaultOpen: defaultOpen ? parseInt(defaultOpen, 10) : -1,
}));
});
return managers;
}
init() {
if (!this.container)
return;
const headers = this.container.querySelectorAll("[data-ss-accordion-header], .accordion-header");
headers.forEach((header, index) => {
const content = this.findContent(header);
if (!content)
return;
const id = `accordion-content-${index}-${Date.now()}`;
content.id = content.id || id;
header.setAttribute("aria-expanded", "false");
header.setAttribute("aria-controls", content.id);
content.setAttribute("role", "region");
content.setAttribute("aria-labelledby", header.id || `accordion-header-${index}`);
if (!header.hasAttribute("tabindex")) {
header.setAttribute("tabindex", "0");
}
content.hidden = true;
content.style.overflow = "hidden";
this.items.push({
header,
content,
isOpen: false,
});
header.addEventListener("click", this.handleClick);
header.addEventListener("keydown", this.handleKeydown);
});
this.openDefaults();
}
findContent(header) {
const nextSibling = header.nextElementSibling;
if (nextSibling?.matches(this.options.contentSelector)) {
return nextSibling;
}
const parent = header.parentElement;
return (parent?.querySelector(this.options.contentSelector) ??
null);
}
openDefaults() {
const defaults = this.options.defaultOpen;
if (Array.isArray(defaults)) {
defaults.forEach((i) => this.openItem(i));
}
else if (defaults >= 0) {
this.openItem(defaults);
}
}
openItem(index) {
const item = this.items[index];
if (!item || item.isOpen)
return;
item.header.setAttribute("aria-expanded", "true");
item.header.classList.add(this.options.activeHeaderClass);
item.content.hidden = false;
item.content.classList.add(this.options.activeContentClass);
const height = item.content.scrollHeight;
item.content.style.height = "0px";
requestAnimationFrame(() => {
item.content.style.transition = `height ${this.options.animationDuration}ms ease`;
item.content.style.height = `${height}px`;
setTimeout(() => {
item.content.style.height = "";
item.content.style.transition = "";
}, this.options.animationDuration);
});
item.isOpen = true;
this.options.onOpen(item.header, item.content, index);
}
closeItem(index) {
const item = this.items[index];
if (!item || !item.isOpen)
return;
item.header.setAttribute("aria-expanded", "false");
item.header.classList.remove(this.options.activeHeaderClass);
item.content.classList.remove(this.options.activeContentClass);
const height = item.content.scrollHeight;
item.content.style.height = `${height}px`;
requestAnimationFrame(() => {
item.content.style.transition = `height ${this.options.animationDuration}ms ease`;
item.content.style.height = "0px";
setTimeout(() => {
item.content.hidden = true;
item.content.style.height = "";
item.content.style.transition = "";
}, this.options.animationDuration);
});
item.isOpen = false;
this.options.onClose(item.header, item.content, index);
}
focusHeader(index) {
this.items[index]?.header.focus();
}
}
export default AccordionManager;
//# sourceMappingURL=AccordionManager.js.map