Newer
Older
gnexus-ui-kit / src / js / components / accordion.js
const initializedRoots = new WeakSet();

function getPanel(details) {
	return details.querySelector(".accordion-panel");
}

function prepareOpenPanel(details) {
	const panel = getPanel(details);

	if(!panel) {
		return;
	}

	panel.style.height = "auto";
	panel.style.opacity = "1";
	panel.style.transform = "translateY(0)";
}

function expand(details) {
	const panel = getPanel(details);

	if(!panel || details.dataset.animating === "true") {
		return;
	}

	details.dataset.animating = "true";
	details.open = true;
	panel.style.height = "0px";
	panel.style.opacity = "0";
	panel.style.transform = "translateY(-8px)";

	requestAnimationFrame(() => {
		panel.style.height = `${panel.scrollHeight}px`;
		panel.style.opacity = "1";
		panel.style.transform = "translateY(0)";
	});

	panel.addEventListener("transitionend", event => {
		if(event.propertyName !== "height") {
			return;
		}

		panel.style.height = "auto";
		delete details.dataset.animating;
	}, { once: true });
}

function collapse(details) {
	const panel = getPanel(details);

	if(!panel || details.dataset.animating === "true") {
		return;
	}

	details.dataset.animating = "true";
	panel.style.height = `${panel.scrollHeight}px`;
	panel.style.opacity = "1";
	panel.style.transform = "translateY(0)";

	requestAnimationFrame(() => {
		panel.style.height = "0px";
		panel.style.opacity = "0";
		panel.style.transform = "translateY(-8px)";
	});

	panel.addEventListener("transitionend", event => {
		if(event.propertyName !== "height") {
			return;
		}

		details.open = false;
		panel.style.height = "";
		panel.style.opacity = "";
		panel.style.transform = "";
		delete details.dataset.animating;
	}, { once: true });
}

function toggle(details) {
	if(details.open) {
		collapse(details);
	} else {
		expand(details);
	}
}

function init(root = document) {
	if(initializedRoots.has(root)) {
		return;
	}

	root.querySelectorAll(".accordion-item[open], .disclosure[open]").forEach(prepareOpenPanel);

	root.addEventListener("click", event => {
		const summary = event.target.closest(".accordion-summary");

		if(!summary) {
			return;
		}

		const details = summary.closest(".accordion-item, .disclosure");

		if(!details) {
			return;
		}

		event.preventDefault();
		toggle(details);
	});

	initializedRoots.add(root);
}

export default {
	init,
	expand,
	collapse,
	toggle
};