@flexilla/accordion
Version:
A versatile and interactive accordion component for creating collapsible sections in web applications, conserving space and improving user experience
299 lines (298 loc) • 13.1 kB
JavaScript
var b = Object.defineProperty;
var A = (n, t, e) => t in n ? b(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
var r = (n, t, e) => A(n, typeof t != "symbol" ? t + "" : t, e);
const f = (n, t = document.body) => t.querySelector(n), l = (n, t = document.body) => {
const e = u(n, t);
return Array.from(e).find((i) => i.parentElement === t);
}, u = (n, t = document.body) => Array.from(t.querySelectorAll(n)), y = ({
element: n,
callback: t,
type: e,
keysCheck: i
}) => {
const s = getComputedStyle(n), o = s.transition;
if (o !== "none" && o !== "" && !i.includes(o)) {
const a = "transitionend", c = () => {
n.removeEventListener(a, c), t();
};
n.addEventListener(a, c, { once: !0 });
} else
t();
}, I = ({
element: n,
callback: t
}) => {
y({
element: n,
callback: t,
type: "transition",
keysCheck: ["all 0s ease 0s", "all"]
});
}, w = (n, t, e) => {
const i = new CustomEvent(t, { detail: e });
n.dispatchEvent(i);
}, x = ({
container: n,
attributeToWatch: t,
onChildAdded: e
}) => {
const i = new MutationObserver((s) => {
for (const o of s)
if (o.type === "childList" && Array.from(o.addedNodes).some(
(a) => a instanceof HTMLElement && a.hasAttribute(t)
)) {
e();
break;
}
});
return i.observe(n, {
childList: !0
}), () => {
i.disconnect();
};
}, E = (n) => {
const t = l("[data-accordion-trigger]", n), e = l("[data-accordion-content]", n), i = n.hasAttribute("data-default-open");
if (!(t instanceof HTMLButtonElement)) throw new Error("The element does't have a Valid Trigger");
if (!(e instanceof HTMLDivElement)) throw new Error("No Valid Content Element");
const s = n.getAttribute("data-accordion-value") ?? "", o = t.getAttribute("aria-expanded") === "true";
return { accordionTriggerElement: t, accordionContentElement: e, accordionItemValue: s, isItemExpanded: o, defaultOpened: i };
}, m = (n, t) => {
n.setAttribute("aria-hidden", t === "open" ? "false" : "true"), n.setAttribute("data-state", t);
}, T = (n, t = "close", e = "0px") => {
n.style.height = t === "open" ? "auto" : e, m(n, t);
}, C = (n) => {
if (n.getAttribute("data-state") === "open") return;
m(n, "open");
const t = n.scrollHeight;
n.style.height = `${t}px`, I({
element: n,
callback: () => {
n.getAttribute("data-state") === "open" && (n.style.height = "auto");
}
});
}, L = (n, t = "0px") => {
n.getAttribute("data-state") !== "close" && (n.style.height = `${n.scrollHeight}px`, n.offsetHeight, n.style.height = t, m(n, "close"));
}, O = (n, t, e) => {
const s = u("[data-accordion-item]", e).filter((d) => d.parentElement === e), o = Array.from(s).indexOf(n.closest("[data-accordion-item]")), a = t ? o - 1 : o + 1;
return l("[data-accordion-trigger]", s[a]) ?? (t ? l("[data-accordion-trigger]", s[s.length - 1]) : l("[data-accordion-trigger]", s[0]));
}, S = (n, t) => {
const e = document.activeElement;
if (!(e instanceof HTMLElement)) return;
e.matches("[data-accordion-trigger]") && (n.key === "ArrowUp" || n.key === "ArrowDown") && (n.preventDefault(), O(e, n.key === "ArrowUp", t).focus());
}, g = (n, t) => {
n.ariaExpanded = t === "open" ? "true" : "false";
}, k = ({ collapsible: n, triggerElement: t, state: e, onInit: i }) => {
i ? (T(n, e), g(t, e)) : e === "open" ? (g(t, "open"), C(n)) : (g(t, "close"), L(n));
};
class p {
static initGlobalRegistry() {
window.$flexillaInstances || (window.$flexillaInstances = {});
}
static register(t, e, i) {
return this.initGlobalRegistry(), window.$flexillaInstances[t] || (window.$flexillaInstances[t] = []), this.getInstance(t, e) || (window.$flexillaInstances[t].push({ element: e, instance: i }), i);
}
static getInstance(t, e) {
var i, s;
return this.initGlobalRegistry(), (s = (i = window.$flexillaInstances[t]) == null ? void 0 : i.find(
(o) => o.element === e
)) == null ? void 0 : s.instance;
}
static removeInstance(t, e) {
this.initGlobalRegistry(), window.$flexillaInstances[t] && (window.$flexillaInstances[t] = window.$flexillaInstances[t].filter(
(i) => i.element !== e
));
}
}
const h = class h {
/**
* Creates an instance of Accordion
* @param {string | HTMLElement} accordion - Selector string or HTMLElement for the accordion container
* @param {AccordionOptions} [options={}] - Configuration options for the accordion
*/
constructor(t, e = {}) {
r(this, "accordionEl");
r(this, "options");
r(this, "items");
r(this, "eventListeners", []);
r(this, "cleanupObserver", null);
r(this, "reload", () => {
this.cleanup(), this.items = u("[data-accordion-item]", this.accordionEl).filter((t) => t.parentElement && t.parentElement === this.accordionEl), this.initAccordion();
});
r(this, "triggerItemState", (t, e, i) => {
this.options.preventClosingAll && (this.options.accordionType === "single" && i || this.options.accordionType === "multiple" && this.items.filter((s) => s.getAttribute("data-state") === "open").length === 1 && i) || (this.setItemState(t, e), this.options.accordionType === "single" && this.closeOther({ current: t }), this.dispatchedEvent(t));
});
/**
* Cleans up the accordion instance by removing event listeners, data attributes, and references.
* @public
* @example
* ```typescript
* const accordion = new Accordion('#myAccordion');
* // ... use accordion ...
* accordion.cleanup(); // Remove all event listeners and clean up resources
* ```
*/
r(this, "cleanup", () => {
this.accordionEl && (this.items.forEach((t) => {
t && t.hasAttribute("data-state") && t.removeAttribute("data-state");
}), this.eventListeners.forEach(({ element: t, type: e, listener: i }) => {
t && t.removeEventListener && t.removeEventListener(e, i);
}), this.cleanupObserver && (this.cleanupObserver(), this.cleanupObserver = null), this.eventListeners = [], this.items = [], p.removeInstance("accordion", this.accordionEl));
});
if (this.accordionEl = typeof t == "string" ? f(t) : t, !this.accordionEl)
throw new Error(`Accordion element not found: ${typeof t == "string" ? `No element matches selector "${t}"` : "Provided HTMLElement is null or undefined"}`);
const i = p.getInstance("accordion", this.accordionEl);
if (i)
return i;
this.options = {
accordionType: this.accordionEl.dataset.accordionType || e.accordionType || "single",
preventClosingAll: this.accordionEl.hasAttribute("data-prevent-closing-all") || e.preventClosingAll || !1,
defaultValue: this.accordionEl.dataset.defaultValue || e.defaultValue || "",
allowCloseFromContent: this.accordionEl.hasAttribute("data-allow-close-from-content") || e.allowCloseFromContent || !1,
onChangeItem: e.onChangeItem
}, this.items = u("[data-accordion-item]", this.accordionEl).filter((s) => s.parentElement && s.parentElement === this.accordionEl), this.initAccordion();
}
initAccordion() {
if (!this.accordionEl) return;
const { accordionType: t, defaultValue: e, preventClosingAll: i } = this.options;
let s = l(`[data-accordion-item][data-accordion-value="${e}"]`, this.accordionEl);
if (t === "single")
this.options.preventClosingAll && !(s instanceof HTMLElement) && (s = this.items[0]), this.closeOther({ current: s }), s && this.setItemState(s, "open", !0);
else {
this.closeAll(!0);
const a = this.items.some((c) => c.getAttribute("data-state") === "open");
if (i && !a) this.setItemState(this.items[0], "open", !0);
else {
const c = this.items.filter((d) => d.getAttribute("data-state") === "open");
for (const d of c) this.setItemState(d, "open", !0);
}
}
this.addEventListeners();
const o = (a) => {
S(a, this.accordionEl);
};
this.accordionEl.addEventListener("keydown", o), this.eventListeners.push({ element: this.accordionEl, type: "keydown", listener: o }), p.register("accordion", this.accordionEl, this), this.cleanupObserver = x({
container: this.accordionEl,
attributeToWatch: "data-accordion-item",
onChildAdded: this.reload
});
}
destroy() {
this.accordionEl && (this.items.forEach((t) => {
t && t.hasAttribute("data-state") && t.removeAttribute("data-state");
}), this.eventListeners.forEach(({ element: t, type: e, listener: i }) => {
t && t.removeEventListener && t.removeEventListener(e, i);
}), this.eventListeners = [], this.items = [], p.removeInstance("accordion", this.accordionEl), this.cleanupObserver && (this.cleanupObserver(), this.cleanupObserver = null));
}
setItemState(t, e, i) {
t.setAttribute("data-state", e);
const { accordionContentElement: s, accordionTriggerElement: o } = E(t);
k({
collapsible: s,
triggerElement: o,
state: e,
onInit: i
});
}
closeOther({ current: t, onInit: e }) {
this.items.forEach((i) => {
i !== t && (e && this.options.accordionType === "multiple" ? i.hasAttribute("data-default-open") ? this.setItemState(i, "open") : this.setItemState(i, "close") : this.setItemState(i, "close"));
});
}
closeAll(t) {
this.closeOther({ onInit: t });
}
dispatchedEvent(t) {
const { accordionContentElement: e, accordionTriggerElement: i, isItemExpanded: s, accordionItemValue: o } = E(t);
this.options.onChangeItem && this.options.onChangeItem({
expandedItem: { accordionItem: this.accordionEl, trigger: i, content: e, value: o, isExpanded: s }
}), w(this.accordionEl, "change-item", {
targetElement: { trigger: i, content: e, isExpanded: s },
items: this.items
});
}
addEventListeners() {
this.items.forEach((t) => {
const e = f("[data-accordion-trigger]", t), i = f("[data-accordion-content]", t), s = () => this.triggerItemState(t, "close", !0), o = (a) => {
a.preventDefault();
const c = t.getAttribute("data-state") === "open";
let d = c ? "close" : "open";
this.triggerItemState(t, d, c);
};
e && (e.addEventListener("click", o), this.eventListeners.push({ element: e, type: "click", listener: o })), this.options.allowCloseFromContent && i && (i.addEventListener("click", s), this.eventListeners.push({ element: i, type: "click", listener: s }));
});
}
/**
* Shows/expands an accordion item by its ID
* @public
* @param {string} id - The value/ID of the accordion item to show
* @example
* ```typescript
* const accordion = new Accordion('#myAccordion');
* accordion.show('section1'); // Expands the accordion item with value="section1"
* ```
*/
show(t) {
const e = l(`[data-accordion-item][data-accordion-value="${t}"]`, this.accordionEl);
!e || e.getAttribute("data-state") === "open" || (this.options.accordionType === "single" && this.closeOther({ current: e }), this.setItemState(e, "open"), this.dispatchedEvent(e));
}
/**
* Hides/collapses an accordion item by its ID
* @public
* @param {string} id - The value/ID of the accordion item to hide
* @example
* ```typescript
* const accordion = new Accordion('#myAccordion');
* accordion.hide('section1'); // Collapses the accordion item with value="section1"
* ```
*/
hide(t) {
const e = l(`[data-accordion-item][data-accordion-value="${t}"]`, this.accordionEl);
if (!(!e || !(e.getAttribute("data-state") === "open"))) {
if (this.options.preventClosingAll) {
const s = this.items.filter((o) => o.getAttribute("data-state") === "open");
if (s.length === 1 && e === s[0])
return;
}
this.setItemState(e, "close"), this.dispatchedEvent(e);
}
}
};
/**
* Automatically initializes all accordion components matching the selector
* @static
* @param {string} [selector="[data-fx-accordion]"] - The selector to find accordion elements
* @example
* ```typescript
* // Initialize all accordion elements with data-fx-accordion attribute
* Accordion.autoInit();
*
* // Initialize accordions with custom selector
* Accordion.autoInit('.custom-accordion');
* ```
*/
r(h, "autoInit", (t = "[data-fx-accordion]") => {
const e = u(t, document.documentElement);
for (const i of e) new h(i);
}), /**
* Shortcut method to create a new Accordion instance
* @static
* @param {string | HTMLElement} accordion - Selector string or HTMLElement for the accordion container
* @param {AccordionOptions} [options={}] - Configuration options for the accordion
* @example
* ```typescript
* // Initialize with selector
* const accordion1 = Accordion.init('#myAccordion');
*
* // Initialize with HTMLElement and options
* const element = document.querySelector('#multiAccordion');
* const accordion2 = Accordion.init(element, {
* accordionType: 'multiple',
* preventClosingAll: true
* });
* ```
*/
r(h, "init", (t, e = {}) => new h(t, e));
let v = h;
export {
v as Accordion
};