@lmfaole/basics 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,12 +31,167 @@ Storybook Test coverage is enabled through the Vitest addon. In the Storybook UI
31
31
 
32
32
  The Visual Tests panel is provided by `@chromatic-com/storybook`. To run cloud visual checks, connect the addon to a Chromatic project from the Storybook UI.
33
33
 
34
+ GitHub Actions now splits Storybook automation by purpose:
35
+
36
+ - `CI` runs the browser-backed Storybook test suite on pull requests and pushes to `main`.
37
+ - `Storybook Preview` builds Storybook for pull requests and uploads `storybook-static` as an artifact.
38
+ - `Storybook Pages` deploys the built Storybook from `main` to GitHub Pages.
39
+ - `Chromatic` publishes Storybook builds on branch pushes when the `CHROMATIC_PROJECT_TOKEN` repository secret is configured.
40
+
41
+ ## Changesets
42
+
43
+ For changes that affect the published package, run:
44
+
45
+ ```sh
46
+ pnpm changeset
47
+ ```
48
+
49
+ Commit the generated Markdown file under `.changeset/` with the rest of your work. If a change is repo-only and does not affect the published package, no changeset file is needed.
50
+
34
51
  ## Releasing
35
52
 
36
- 1. Update `package.json` to the version you want to ship.
37
- 2. Publish the package to npm with `npm publish --access public --registry=https://registry.npmjs.org`.
38
- 3. Push a matching git tag such as `v0.1.0`.
39
- 4. GitHub Actions will run the test suite, pack the published files, and create a GitHub Release with the tarball attached.
53
+ Merging changesets into `main` causes the `Release` workflow to open or update a `chore: release` pull request. Merging that release pull request will:
54
+
55
+ 1. run `pnpm release`
56
+ 2. publish the package to npm with trusted publishing from GitHub Actions
57
+ 3. create the matching `vX.Y.Z` git tag and GitHub Release
58
+ 4. attach the packed tarball to the GitHub Release
59
+
60
+ Trusted publishing must be configured on npm for `@lmfaole/basics` against the GitHub Actions workflow file `.github/workflows/release.yml` in the `lmfaole/basics` repository. No `NPM_TOKEN` repository secret is needed once trusted publishing is active.
61
+
62
+ If a release needs to be retried after the workflow changes land, use the `Release` workflow's `publish_current_version` manual input to publish the current `package.json` version from `main` when it is still unpublished.
63
+
64
+ ## Commits
65
+
66
+ Use Conventional Commits for commit messages and pull request titles. The GitHub workflow accepts `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, and `test`, with an optional scope such as `feat(tabs): add keyboard support`.
67
+
68
+ ## Basic Popover
69
+
70
+ ```html
71
+ <basic-popover data-label="Filtre" data-anchor-trigger data-position-area="bottom">
72
+ <button type="button" data-popover-open>Toggle popover</button>
73
+
74
+ <section data-popover-panel>
75
+ <h2 data-popover-title>Filtre</h2>
76
+ <p>Popover body.</p>
77
+ <button type="button" data-popover-close>Close</button>
78
+ </section>
79
+ </basic-popover>
80
+
81
+ <script type="module">
82
+ import "@lmfaole/basics/components/basic-popover/register";
83
+ </script>
84
+ ```
85
+
86
+ The element upgrades popover trigger-and-panel markup into an accessible non-modal overlay without adding any styles of its own.
87
+
88
+ ### Attributes
89
+
90
+ - `data-label`: fallback accessible name when the popover has no `aria-label`, `aria-labelledby`, or `[data-popover-title]`.
91
+ - `data-anchor-trigger`: uses the opener as the popover's implicit anchor so consumer CSS can position the panel relative to the trigger.
92
+ - `data-position-area`: sets the CSS anchor-positioning area used when `data-anchor-trigger` is enabled. Defaults to `bottom`.
93
+ - `data-position-try-fallbacks`: optional comma-separated fallback list used when the default anchored placement would overflow. By default the component derives a sensible sequence from `data-position-area`.
94
+
95
+ ### Behavior
96
+
97
+ - Uses the native Popover API in auto mode for outside-click and `Esc` dismissal.
98
+ - Syncs `aria-expanded`, `aria-controls`, and `data-open` between the trigger and panel.
99
+ - Restores focus to the opener when dismissal should return to it, while preserving focus on an outside control the user explicitly clicked.
100
+ - When `data-anchor-trigger` is set, opening the popover passes the trigger as the Popover API `source`, establishes the panel's implicit anchor, and applies the configured `position-area`.
101
+ - When `data-anchor-trigger` is set and `data-position-try-fallbacks` is not provided, the component derives a fallback sequence from the default placement:
102
+ `bottom` or `top` start with `flip-block`, while `left` or `right` start with `flip-inline`.
103
+ - `[data-popover-close]` controls can dismiss the panel from inside the overlay.
104
+
105
+ ### Markup Contract
106
+
107
+ - Provide one descendant `[data-popover-panel]`.
108
+ - Use `[data-popover-open]` on buttons that should toggle the panel.
109
+ - Use `[data-popover-title]` for the popover heading when you want it to become the accessible name.
110
+ - If you set `data-anchor-trigger`, you can tune the default anchored placement with `data-position-area` and optionally override its overflow fallbacks with `data-position-try-fallbacks`.
111
+ - Keep layout and styling outside the package; the component only manages semantics and open or close behavior.
112
+
113
+ ## Basic Dialog
114
+
115
+ ```html
116
+ <basic-dialog data-label="Bekreft handling" data-backdrop-close>
117
+ <button type="button" data-dialog-open>Open dialog</button>
118
+
119
+ <dialog data-dialog-panel>
120
+ <h2 data-dialog-title>Bekreft handling</h2>
121
+ <p>Dialog body.</p>
122
+ <button type="button" data-dialog-close>Cancel</button>
123
+ <button type="button" data-dialog-close data-dialog-close-value="confirmed">
124
+ Confirm
125
+ </button>
126
+ </dialog>
127
+ </basic-dialog>
128
+
129
+ <script type="module">
130
+ import "@lmfaole/basics/components/basic-dialog/register";
131
+ </script>
132
+ ```
133
+
134
+ The element upgrades native `<dialog>` markup into an accessible modal flow without adding any styles of its own.
135
+
136
+ ### Attributes
137
+
138
+ - `data-label`: fallback accessible name when the dialog has no `aria-label`, `aria-labelledby`, or `[data-dialog-title]`.
139
+ - `data-backdrop-close`: allows clicks on the dialog backdrop to close the modal.
140
+
141
+ ### Behavior
142
+
143
+ - Uses the native dialog element's modal behavior via `showModal()`.
144
+ - Restores focus to the element that opened the dialog when the modal closes.
145
+ - `Esc` closes the modal through the platform's dialog behavior.
146
+ - `[data-dialog-close]` controls can optionally set `data-dialog-close-value` to pass a close value.
147
+
148
+ ### Markup Contract
149
+
150
+ - Provide one descendant `<dialog data-dialog-panel>`.
151
+ - Use `[data-dialog-open]` on buttons that should open the modal.
152
+ - Use `[data-dialog-title]` for the dialog heading when you want it to become the accessible name.
153
+ - Keep layout and styling outside the package; the component only manages semantics and open or close behavior.
154
+
155
+ ## Basic Accordion
156
+
157
+ ```html
158
+ <basic-accordion>
159
+ <h3><button type="button" data-accordion-trigger>Oversikt</button></h3>
160
+ <section data-accordion-panel>
161
+ <p>Viser en kort oppsummering.</p>
162
+ </section>
163
+
164
+ <h3><button type="button" data-accordion-trigger>Implementasjon</button></h3>
165
+ <section data-accordion-panel>
166
+ <p>Viser implementasjonsdetaljer.</p>
167
+ </section>
168
+ </basic-accordion>
169
+
170
+ <script type="module">
171
+ import "@lmfaole/basics/components/basic-accordion/register";
172
+ </script>
173
+ ```
174
+
175
+ The element upgrades existing trigger-and-panel markup into an accessible accordion without adding any styles of its own.
176
+
177
+ ### Attributes
178
+
179
+ - `data-multiple`: allows multiple panels to stay open at the same time.
180
+ - `data-collapsible`: allows the last open panel in single mode to close.
181
+
182
+ ### Behavior
183
+
184
+ - Missing trigger and panel ids are generated automatically.
185
+ - `aria-expanded`, `aria-controls`, `aria-labelledby`, `hidden`, and `data-open` stay in sync with the current state.
186
+ - `ArrowUp`, `ArrowDown`, `Home`, and `End` move focus between enabled triggers.
187
+ - `Enter` and `Space` toggle the focused item.
188
+ - Disabled triggers are skipped during keyboard navigation.
189
+
190
+ ### Markup Contract
191
+
192
+ - Provide matching counts of `[data-accordion-trigger]` and `[data-accordion-panel]` descendants in the same order.
193
+ - Prefer `<button>` elements for triggers, usually inside your own heading elements.
194
+ - Keep layout and styling outside the package; the component only manages semantics, state, and keyboard behavior.
40
195
 
41
196
  ## Basic Tabs
42
197
 
@@ -0,0 +1,50 @@
1
+ export interface AccordionItemState {
2
+ disabled: boolean;
3
+ open?: boolean;
4
+ }
5
+
6
+ /**
7
+ * Public tag name registered by `defineAccordion`.
8
+ */
9
+ export const ACCORDION_TAG_NAME: "basic-accordion";
10
+
11
+ /**
12
+ * Returns the initially open accordion item indexes based on explicit state and
13
+ * the root element's single-open or collapsible behavior.
14
+ */
15
+ export function getInitialOpenAccordionIndexes(
16
+ itemStates: AccordionItemState[],
17
+ options?: {
18
+ multiple?: boolean;
19
+ collapsible?: boolean;
20
+ },
21
+ ): number[];
22
+
23
+ /**
24
+ * Returns the next enabled accordion trigger index, wrapping around the list
25
+ * when needed.
26
+ */
27
+ export function findNextEnabledAccordionIndex(
28
+ itemStates: AccordionItemState[],
29
+ startIndex: number,
30
+ direction: number,
31
+ ): number;
32
+
33
+ /**
34
+ * Custom element that upgrades existing trigger-and-panel markup into an
35
+ * accessible accordion interface.
36
+ *
37
+ * Attributes:
38
+ * - `data-multiple`: allows multiple panels to stay open
39
+ * - `data-collapsible`: allows the last open panel in single mode to close
40
+ */
41
+ export class AccordionElement extends HTMLElement {
42
+ static observedAttributes: string[];
43
+ }
44
+
45
+ /**
46
+ * Registers the `basic-accordion` custom element if it is not already defined.
47
+ */
48
+ export function defineAccordion(
49
+ registry?: CustomElementRegistry,
50
+ ): typeof AccordionElement;
@@ -0,0 +1,387 @@
1
+ const ElementBase = globalThis.Element ?? class {};
2
+ const HTMLElementBase = globalThis.HTMLElement ?? class {};
3
+ const HTMLButtonElementBase = globalThis.HTMLButtonElement ?? class {};
4
+
5
+ export const ACCORDION_TAG_NAME = "basic-accordion";
6
+
7
+ const TRIGGER_SELECTOR = "[data-accordion-trigger]";
8
+ const PANEL_SELECTOR = "[data-accordion-panel]";
9
+
10
+ let nextAccordionInstanceId = 1;
11
+
12
+ function collectOwnedElements(root, scope, selector) {
13
+ return Array.from(scope.querySelectorAll(selector)).filter(
14
+ (element) => element instanceof HTMLElementBase && element.closest(ACCORDION_TAG_NAME) === root,
15
+ );
16
+ }
17
+
18
+ function isAccordionItemDisabled(trigger) {
19
+ return trigger.hasAttribute("disabled") || trigger.getAttribute("aria-disabled") === "true";
20
+ }
21
+
22
+ function findFirstEnabledAccordionIndex(itemStates) {
23
+ for (let index = 0; index < itemStates.length; index += 1) {
24
+ if (!itemStates[index]?.disabled) {
25
+ return index;
26
+ }
27
+ }
28
+
29
+ return -1;
30
+ }
31
+
32
+ function findLastEnabledAccordionIndex(itemStates) {
33
+ for (let index = itemStates.length - 1; index >= 0; index -= 1) {
34
+ if (!itemStates[index]?.disabled) {
35
+ return index;
36
+ }
37
+ }
38
+
39
+ return -1;
40
+ }
41
+
42
+ export function getInitialOpenAccordionIndexes(
43
+ itemStates,
44
+ { multiple = false, collapsible = false } = {},
45
+ ) {
46
+ const explicitOpenIndexes = [];
47
+
48
+ for (let index = 0; index < itemStates.length; index += 1) {
49
+ const itemState = itemStates[index];
50
+
51
+ if (itemState?.open && !itemState.disabled) {
52
+ explicitOpenIndexes.push(index);
53
+ }
54
+ }
55
+
56
+ if (multiple) {
57
+ return explicitOpenIndexes;
58
+ }
59
+
60
+ if (explicitOpenIndexes.length > 0) {
61
+ return [explicitOpenIndexes[0]];
62
+ }
63
+
64
+ if (collapsible) {
65
+ return [];
66
+ }
67
+
68
+ const firstEnabledIndex = findFirstEnabledAccordionIndex(itemStates);
69
+ return firstEnabledIndex === -1 ? [] : [firstEnabledIndex];
70
+ }
71
+
72
+ export function findNextEnabledAccordionIndex(itemStates, startIndex, direction) {
73
+ if (itemStates.length === 0) {
74
+ return -1;
75
+ }
76
+
77
+ const step = direction < 0 ? -1 : 1;
78
+ let nextIndex = startIndex;
79
+
80
+ for (let checked = 0; checked < itemStates.length; checked += 1) {
81
+ nextIndex += step;
82
+
83
+ if (nextIndex < 0) {
84
+ nextIndex = itemStates.length - 1;
85
+ } else if (nextIndex >= itemStates.length) {
86
+ nextIndex = 0;
87
+ }
88
+
89
+ if (!itemStates[nextIndex]?.disabled) {
90
+ return nextIndex;
91
+ }
92
+ }
93
+
94
+ return -1;
95
+ }
96
+
97
+ export class AccordionElement extends HTMLElementBase {
98
+ static observedAttributes = ["data-collapsible", "data-multiple"];
99
+
100
+ #instanceId = `${ACCORDION_TAG_NAME}-${nextAccordionInstanceId++}`;
101
+ #triggers = [];
102
+ #panels = [];
103
+ #openIndexes = new Set();
104
+ #focusIndex = -1;
105
+ #eventsBound = false;
106
+
107
+ connectedCallback() {
108
+ if (!this.#eventsBound) {
109
+ this.addEventListener("click", this.#handleClick);
110
+ this.addEventListener("keydown", this.#handleKeyDown);
111
+ this.#eventsBound = true;
112
+ }
113
+
114
+ this.#sync({ resetOpen: true });
115
+ }
116
+
117
+ disconnectedCallback() {
118
+ if (!this.#eventsBound) {
119
+ return;
120
+ }
121
+
122
+ this.removeEventListener("click", this.#handleClick);
123
+ this.removeEventListener("keydown", this.#handleKeyDown);
124
+ this.#eventsBound = false;
125
+ }
126
+
127
+ attributeChangedCallback() {
128
+ this.#sync({ resetOpen: true });
129
+ }
130
+
131
+ #handleClick = (event) => {
132
+ if (!(event.target instanceof ElementBase)) {
133
+ return;
134
+ }
135
+
136
+ const trigger = event.target.closest(TRIGGER_SELECTOR);
137
+
138
+ if (
139
+ !(trigger instanceof HTMLElementBase)
140
+ || trigger.closest(ACCORDION_TAG_NAME) !== this
141
+ ) {
142
+ return;
143
+ }
144
+
145
+ const triggerIndex = this.#triggers.indexOf(trigger);
146
+
147
+ if (triggerIndex === -1) {
148
+ return;
149
+ }
150
+
151
+ this.#toggleIndex(triggerIndex, { focus: true });
152
+ };
153
+
154
+ #handleKeyDown = (event) => {
155
+ if (!(event.target instanceof ElementBase)) {
156
+ return;
157
+ }
158
+
159
+ const currentTrigger = event.target.closest(TRIGGER_SELECTOR);
160
+
161
+ if (
162
+ !(currentTrigger instanceof HTMLElementBase)
163
+ || currentTrigger.closest(ACCORDION_TAG_NAME) !== this
164
+ ) {
165
+ return;
166
+ }
167
+
168
+ const itemStates = this.#getItemStates();
169
+ const currentIndex = this.#triggers.indexOf(currentTrigger);
170
+ let nextIndex = -1;
171
+
172
+ if (currentIndex === -1 || currentIndex >= itemStates.length) {
173
+ return;
174
+ }
175
+
176
+ switch (event.key) {
177
+ case "ArrowDown":
178
+ nextIndex = findNextEnabledAccordionIndex(itemStates, currentIndex, 1);
179
+ break;
180
+ case "ArrowUp":
181
+ nextIndex = findNextEnabledAccordionIndex(itemStates, currentIndex, -1);
182
+ break;
183
+ case "Home":
184
+ nextIndex = findFirstEnabledAccordionIndex(itemStates);
185
+ break;
186
+ case "End":
187
+ nextIndex = findLastEnabledAccordionIndex(itemStates);
188
+ break;
189
+ case " ":
190
+ case "Enter":
191
+ event.preventDefault();
192
+ this.#toggleIndex(currentIndex, { focus: true });
193
+ return;
194
+ default:
195
+ return;
196
+ }
197
+
198
+ if (nextIndex === -1) {
199
+ return;
200
+ }
201
+
202
+ event.preventDefault();
203
+ this.#focusIndex = nextIndex;
204
+ this.#applyState({ focus: true });
205
+ };
206
+
207
+ #getItemStates() {
208
+ const pairCount = Math.min(this.#triggers.length, this.#panels.length);
209
+
210
+ return this.#triggers.slice(0, pairCount).map((trigger, index) => ({
211
+ disabled: isAccordionItemDisabled(trigger),
212
+ open: trigger.hasAttribute("data-open")
213
+ || trigger.getAttribute("aria-expanded") === "true"
214
+ || this.#panels[index]?.hasAttribute("data-open"),
215
+ }));
216
+ }
217
+
218
+ #isCollapsible() {
219
+ return this.hasAttribute("data-collapsible");
220
+ }
221
+
222
+ #isMultiple() {
223
+ return this.hasAttribute("data-multiple");
224
+ }
225
+
226
+ #getNextFocusableIndex(itemStates) {
227
+ for (const openIndex of this.#openIndexes) {
228
+ if (!itemStates[openIndex]?.disabled) {
229
+ return openIndex;
230
+ }
231
+ }
232
+
233
+ return findFirstEnabledAccordionIndex(itemStates);
234
+ }
235
+
236
+ #sync({ resetOpen = false } = {}) {
237
+ this.#triggers = collectOwnedElements(this, this, TRIGGER_SELECTOR);
238
+ this.#panels = collectOwnedElements(this, this, PANEL_SELECTOR);
239
+
240
+ const itemStates = this.#getItemStates();
241
+
242
+ if (resetOpen) {
243
+ this.#openIndexes = new Set(
244
+ getInitialOpenAccordionIndexes(itemStates, {
245
+ multiple: this.#isMultiple(),
246
+ collapsible: this.#isCollapsible(),
247
+ }),
248
+ );
249
+ } else {
250
+ const nextOpenIndexes = Array.from(this.#openIndexes).filter(
251
+ (index) => index >= 0 && index < itemStates.length && !itemStates[index]?.disabled,
252
+ );
253
+
254
+ if (!this.#isMultiple() && nextOpenIndexes.length > 1) {
255
+ nextOpenIndexes.splice(1);
256
+ }
257
+
258
+ if (
259
+ !this.#isMultiple()
260
+ && nextOpenIndexes.length === 0
261
+ && !this.#isCollapsible()
262
+ ) {
263
+ const fallbackIndex = findFirstEnabledAccordionIndex(itemStates);
264
+
265
+ if (fallbackIndex !== -1) {
266
+ nextOpenIndexes.push(fallbackIndex);
267
+ }
268
+ }
269
+
270
+ this.#openIndexes = new Set(nextOpenIndexes);
271
+ }
272
+
273
+ if (resetOpen || itemStates[this.#focusIndex]?.disabled || this.#focusIndex >= itemStates.length) {
274
+ this.#focusIndex = this.#getNextFocusableIndex(itemStates);
275
+ }
276
+
277
+ this.#applyState();
278
+ }
279
+
280
+ #applyState({ focus = false } = {}) {
281
+ const pairCount = Math.min(this.#triggers.length, this.#panels.length);
282
+ const baseId = this.id || this.#instanceId;
283
+
284
+ for (let index = 0; index < this.#triggers.length; index += 1) {
285
+ const trigger = this.#triggers[index];
286
+ const panel = index < pairCount ? this.#panels[index] : null;
287
+ const disabled = index >= pairCount || isAccordionItemDisabled(trigger);
288
+ const open = !disabled && this.#openIndexes.has(index);
289
+ const focusable = !disabled && index === this.#focusIndex;
290
+
291
+ if (!trigger.id) {
292
+ trigger.id = `${baseId}-trigger-${index + 1}`;
293
+ }
294
+
295
+ if (trigger instanceof HTMLButtonElementBase && !trigger.hasAttribute("type")) {
296
+ trigger.type = "button";
297
+ }
298
+
299
+ trigger.setAttribute("aria-expanded", String(open));
300
+ trigger.tabIndex = focusable ? 0 : -1;
301
+ trigger.toggleAttribute("data-open", open);
302
+
303
+ if (panel) {
304
+ if (!panel.id) {
305
+ panel.id = `${baseId}-panel-${index + 1}`;
306
+ }
307
+
308
+ trigger.setAttribute("aria-controls", panel.id);
309
+ } else {
310
+ trigger.removeAttribute("aria-controls");
311
+ }
312
+ }
313
+
314
+ for (let index = 0; index < this.#panels.length; index += 1) {
315
+ const panel = this.#panels[index];
316
+ const trigger = this.#triggers[index];
317
+ const open = index < pairCount
318
+ && !isAccordionItemDisabled(trigger)
319
+ && this.#openIndexes.has(index);
320
+
321
+ if (!panel.id) {
322
+ panel.id = `${baseId}-panel-${index + 1}`;
323
+ }
324
+
325
+ panel.setAttribute("role", "region");
326
+
327
+ if (trigger?.id) {
328
+ panel.setAttribute("aria-labelledby", trigger.id);
329
+ } else {
330
+ panel.removeAttribute("aria-labelledby");
331
+ }
332
+
333
+ panel.hidden = !open;
334
+ panel.toggleAttribute("data-open", open);
335
+ }
336
+
337
+ if (focus && this.#focusIndex !== -1) {
338
+ this.#triggers[this.#focusIndex]?.focus();
339
+ }
340
+ }
341
+
342
+ #toggleIndex(index, { focus = false } = {}) {
343
+ const itemStates = this.#getItemStates();
344
+
345
+ if (index < 0 || index >= itemStates.length || itemStates[index]?.disabled) {
346
+ return;
347
+ }
348
+
349
+ const nextOpenIndexes = new Set(this.#openIndexes);
350
+ const isOpen = nextOpenIndexes.has(index);
351
+
352
+ if (this.#isMultiple()) {
353
+ if (isOpen) {
354
+ nextOpenIndexes.delete(index);
355
+ } else {
356
+ nextOpenIndexes.add(index);
357
+ }
358
+ } else if (isOpen) {
359
+ if (!this.#isCollapsible()) {
360
+ this.#focusIndex = index;
361
+ this.#applyState({ focus });
362
+ return;
363
+ }
364
+
365
+ nextOpenIndexes.clear();
366
+ } else {
367
+ nextOpenIndexes.clear();
368
+ nextOpenIndexes.add(index);
369
+ }
370
+
371
+ this.#openIndexes = nextOpenIndexes;
372
+ this.#focusIndex = index;
373
+ this.#applyState({ focus });
374
+ }
375
+ }
376
+
377
+ export function defineAccordion(registry = globalThis.customElements) {
378
+ if (!registry?.get || !registry?.define) {
379
+ return AccordionElement;
380
+ }
381
+
382
+ if (!registry.get(ACCORDION_TAG_NAME)) {
383
+ registry.define(ACCORDION_TAG_NAME, AccordionElement);
384
+ }
385
+
386
+ return AccordionElement;
387
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { defineAccordion } from "./index.js";
2
+
3
+ defineAccordion();
@@ -0,0 +1,36 @@
1
+ export const DIALOG_TAG_NAME: "basic-dialog";
2
+
3
+ /**
4
+ * Normalizes the root `data-backdrop-close` attribute into a boolean flag.
5
+ */
6
+ export function normalizeDialogBackdropClose(
7
+ value?: string | null,
8
+ ): boolean;
9
+
10
+ /**
11
+ * Normalizes unsupported or empty labels back to the default `"Dialog"`.
12
+ */
13
+ export function normalizeDialogLabel(
14
+ value?: string | null,
15
+ ): string;
16
+
17
+ /**
18
+ * Custom element that upgrades native `<dialog>` markup into a modal dialog
19
+ * flow with open and close triggers.
20
+ *
21
+ * Attributes:
22
+ * - `data-label`: fallback accessible name when the dialog has no title
23
+ * - `data-backdrop-close`: allows clicks on the dialog backdrop to close it
24
+ */
25
+ export class DialogElement extends HTMLElement {
26
+ static observedAttributes: string[];
27
+ showModal(opener?: HTMLElement | null): boolean;
28
+ close(returnValue?: string): boolean;
29
+ }
30
+
31
+ /**
32
+ * Registers the `basic-dialog` custom element if it is not already defined.
33
+ */
34
+ export function defineDialog(
35
+ registry?: CustomElementRegistry,
36
+ ): typeof DialogElement;