/** * Accordion * ========= * * A framework-agnostic, class-based implementation of the WAI-ARIA Authoring * Practices Guide (APG) "Accordion" pattern: * * https://www.w3.org/WAI/ARIA/apg/patterns/accordion/ * * This file is intentionally written as a **reference / translation template**: * plain, strictly-typed vanilla TypeScript with heavy documentation so it can be * hand- or AI-translated into React, Vue, Svelte, etc. without losing any of the * accessibility semantics. Prefer clarity over cleverness here. * * --------------------------------------------------------------------------- * Accessibility contract (what the APG requires and what this class guarantees) * --------------------------------------------------------------------------- * * Structure * - Every accordion header is a NATIVE ` * *
*

Panel one content.

*
* *

* *

*
*

Panel two content.

*
* * ``` * * Notes on the markup: * - Each `.accordion__panel` is paired with the `.accordion__trigger` that * immediately precedes it (the button's heading is the panel's previous * element sibling). * - Mark an item as initially open with `data-expanded` on its trigger button. * - You do NOT need to write any ARIA attributes or ids yourself — the class * wires up `aria-expanded`, `aria-controls`, `role`, `aria-labelledby`, * `hidden`, and generates any missing ids. */ /** Options controlling accordion behaviour. All fields are optional. */ export interface AccordionOptions { /** * Allow several panels to be open at the same time. * When `true`, opening one panel never closes another and every panel is * always individually collapsible (so `allowToggle` is implied). * @default false */ allowMultiple?: boolean; /** * In single-open mode (`allowMultiple: false`), allow the currently-open * panel to be collapsed by clicking its own header (leaving nothing open). * When `false`, exactly one panel is always open and the open header is * marked `aria-disabled="true"`. * Ignored when `allowMultiple` is `true`. * @default true */ allowToggle?: boolean; /** * Enable the OPTIONAL APG arrow-key / Home / End focus movement between * headers. Disable to reproduce the minimal (Enter/Space/Tab only) example. * @default true */ arrowNavigation?: boolean; /** * When arrow navigation is on, wrap focus around the ends (Down on the last * header focuses the first, and vice versa). * @default true */ wrapFocus?: boolean; /** * Apply `role="region"` + `aria-labelledby` to each panel. Turn off to avoid * creating too many landmarks in accordions with many open panels. * @default true */ useRegionRole?: boolean; /** * Prefix used when generating element ids. Useful to keep ids stable/readable. * @default "accordion" */ idPrefix?: string; /** * Callback invoked on every expand/collapse — the same moments the * `accordion:toggle` `CustomEvent` fires (see {@link AccordionToggleDetail}). * Receives the affected item's index and its new expanded state (plus the * trigger/panel elements), so consumers who prefer a plain callback over * `addEventListener('accordion:toggle', ...)` don't have to wire one up * themselves. Like the event, it is NOT called during initial setup. * @default () => {} */ onToggle?: (detail: AccordionToggleDetail) => void; } /** Detail payload for the `accordion:toggle` custom event. */ export interface AccordionToggleDetail { /** Zero-based index of the affected item. */ index: number; /** Whether the item is now expanded. */ expanded: boolean; /** The trigger button element of the affected item. */ trigger: HTMLButtonElement; /** The panel element of the affected item. */ panel: HTMLElement; } /** Internal representation of one header/panel pair. */ interface AccordionItem { /** The heading wrapper element (e.g. `

`). */ readonly heading: HTMLElement; /** The native `