import { emit, listen } from "../../shared/helpers"; /** PettyCollapsible — single disclosure wrapper on native details element. */ export class PettyCollapsible extends HTMLElement { static observedAttributes = ["disabled"]; #cleanup = (): void => {}; get detailsElement(): HTMLDetailsElement | null { return this.querySelector("details"); } get isOpen(): boolean { return this.detailsElement?.open ?? false; } /** Opens the collapsible. */ open(): void { const d = this.detailsElement; if (d && !this.hasAttribute("disabled")) d.open = true; } /** Closes the collapsible. */ close(): void { const d = this.detailsElement; if (d) d.open = false; } /** Toggles the collapsible open/closed state. */ toggle(): void { if (this.isOpen) this.close(); else this.open(); } connectedCallback(): void { const d = this.detailsElement; if (!d) return; this.#syncState(); this.#applyDisabled(); this.#cleanup = listen(d, [["toggle", this.#handleToggle]]); } disconnectedCallback(): void { this.#cleanup(); } attributeChangedCallback(name: string): void { if (name === "disabled") this.#applyDisabled(); } #handleToggle = (): void => { this.#syncState(); emit(this, "toggle", { open: this.isOpen }); }; #syncState(): void { const d = this.detailsElement; this.dataset.state = d?.open ? "open" : "closed"; const summary = d?.querySelector("summary"); if (summary) summary.setAttribute("aria-expanded", String(d?.open ?? false)); } #applyDisabled(): void { const disabled = this.hasAttribute("disabled"); const summary = this.detailsElement?.querySelector("summary"); if (!summary) return; if (disabled) { summary.setAttribute("aria-disabled", "true"); summary.addEventListener("click", this.#preventToggle); } else { summary.removeAttribute("aria-disabled"); summary.removeEventListener("click", this.#preventToggle); } } #preventToggle = (e: Event): void => { e.preventDefault(); }; }