@studiocms/ui
Version:
The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.
99 lines (98 loc) • 3.64 kB
JavaScript
const findOwningAccordion = (accordionItem) => {
let current = accordionItem.parentElement;
while (current) {
if (current.classList.contains("sui-accordion")) {
return current;
}
current = current.parentElement;
}
return null;
};
const getDirectAccordionItems = (accordion) => {
const allItems = accordion.querySelectorAll(".sui-accordion-item");
const directItems = [];
for (let i = 0; i < allItems.length; i++) {
if (findOwningAccordion(allItems[i]) === accordion) {
directItems.push(allItems[i]);
}
}
return directItems;
};
const updateAccordionARIA = (item, isOpen) => {
const summary = item.querySelector(".sui-accordion-summary");
const details = item.querySelector(".sui-accordion-details");
if (summary) summary.setAttribute("aria-expanded", isOpen.toString());
if (details) details.setAttribute("aria-hidden", (!isOpen).toString());
};
const toggleAccordionItem = (accordionItem, accordion) => {
const isAlreadyOpen = accordionItem.dataset.open === "true";
const newState = !isAlreadyOpen;
if (accordion.dataset.multiple === "true") {
accordionItem.dataset.open = newState.toString();
} else {
const directItems = getDirectAccordionItems(accordion);
for (let i = 0; i < directItems.length; i++) {
const item = directItems[i];
const shouldBeOpen = item === accordionItem;
item.dataset.open = shouldBeOpen.toString();
updateAccordionARIA(item, shouldBeOpen);
}
return;
}
updateAccordionARIA(accordionItem, newState);
};
const focusAccordionItem = (accordion, direction, currentItem) => {
const items = getDirectAccordionItems(accordion);
const currentIndex = items.indexOf(currentItem);
if (currentIndex === -1) return;
const targetIndex = direction === "next" ? (currentIndex + 1) % items.length : (currentIndex - 1 + items.length) % items.length;
const targetSummary = items[targetIndex]?.querySelector(".sui-accordion-summary");
targetSummary?.focus();
};
const initializeAccordionStates = () => {
const allAccordionItems = document.querySelectorAll(".sui-accordion-item");
for (let i = 0; i < allAccordionItems.length; i++) {
const item = allAccordionItems[i];
const isOpen = item.dataset.open === "true";
updateAccordionARIA(item, isOpen);
}
};
let listenersAdded = false;
const handleAccordionInteraction = (event, isKeyboard = false) => {
const target = event.target;
const accordionSummary = target.closest(".sui-accordion-summary");
if (!accordionSummary) return;
const accordionItem = accordionSummary.closest(".sui-accordion-item");
const accordion = accordionItem && findOwningAccordion(accordionItem);
if (!accordionItem || !accordion) return;
if (isKeyboard) {
const keyEvent = event;
switch (keyEvent.key) {
case "Enter":
case " ":
event.preventDefault();
toggleAccordionItem(accordionItem, accordion);
break;
case "ArrowDown":
event.preventDefault();
focusAccordionItem(accordion, "next", accordionItem);
break;
case "ArrowUp":
event.preventDefault();
focusAccordionItem(accordion, "prev", accordionItem);
break;
}
} else {
event.preventDefault();
toggleAccordionItem(accordionItem, accordion);
}
};
const loadAccordions = () => {
initializeAccordionStates();
if (!listenersAdded) {
document.addEventListener("click", handleAccordionInteraction);
document.addEventListener("keydown", (e) => handleAccordionInteraction(e, true));
listenersAdded = true;
}
};
document.addEventListener("astro:page-load", loadAccordions);