@primitiv-ui/web 0.1.43 → 0.1.44

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
@@ -6,9 +6,15 @@ framework and no runtime dependencies beyond
6
6
  server-rendered stack (HTMX, Rails, Django, Laravel, Spring …), and inside
7
7
  React, Vue, Angular or any other framework.
8
8
 
9
- > **Status: publish-ready, first release pending.** Tabs is the first
10
- > component. See [RFC 0033](../../docs/rfcs/0033-web-components.md) for the
11
- > design.
9
+ > **Status: published.** Tabs and Accordion so far. See
10
+ > [RFC 0033](../../docs/rfcs/0033-web-components.md) for the design.
11
+
12
+ ## Components
13
+
14
+ | Component | Elements | Change event | First release |
15
+ |---|---|---|---|
16
+ | Tabs | `primitiv-tabs`, `-list`, `-trigger`, `-content` | `primitiv-tabs-change` | 0.1.42 |
17
+ | Accordion | `primitiv-accordion`, `-item`, `-trigger`, `-content`, `-trigger-icon` | `primitiv-accordion-change` | 0.1.44 |
12
18
 
13
19
  ## Use it
14
20
 
@@ -40,8 +46,9 @@ Each `dist/cdn/<component>.js` imports the code components share from
40
46
  downloads it once. `dist/primitiv-web.js` is the alternative: every element in
41
47
  one file. Per-component files ship from 0.1.43.
42
48
 
43
- The main entry (`@primitiv-ui/web`, `/tabs`) exports the classes and
44
- `defineTabs(registry?)` with **no side effects**.
49
+ The main entry (`@primitiv-ui/web`, `/tabs`, `/accordion`) exports the classes
50
+ and `defineTabs(registry?)` / `defineAccordion(registry?)` with **no side
51
+ effects**.
45
52
 
46
53
  **Render `hidden` on inactive panels yourself.** Until the module loads, CSS
47
54
  cannot tell which panel is active, so markup that omits it shows every panel.
@@ -50,6 +57,42 @@ cannot tell which panel is active, so markup that omits it shows every panel.
50
57
  index }`) — call `preventDefault()` to veto a change — and `primitiv-tabs-show`
51
58
  on a panel as it becomes active, for lazy loading.
52
59
 
60
+ ### Accordion
61
+
62
+ ```html
63
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@primitiv-ui/web@<version>/dist/cdn/accordion.js"></script>
64
+
65
+ <primitiv-accordion value="shipping">
66
+ <primitiv-accordion-item value="shipping">
67
+ <h3>
68
+ <primitiv-accordion-trigger><button type="button">Shipping</button></primitiv-accordion-trigger>
69
+ </h3>
70
+ <primitiv-accordion-content>Free on orders over £50.</primitiv-accordion-content>
71
+ </primitiv-accordion-item>
72
+ <primitiv-accordion-item value="returns">
73
+ <h3>
74
+ <primitiv-accordion-trigger><button type="button">Returns</button></primitiv-accordion-trigger>
75
+ </h3>
76
+ <primitiv-accordion-content hidden>30 days, no questions asked.</primitiv-accordion-content>
77
+ </primitiv-accordion-item>
78
+ </primitiv-accordion>
79
+ ```
80
+
81
+ - **`value` lists the open items, separated by spaces** (so item values cannot
82
+ contain whitespace), and is reflected as items toggle. `multiple` lets more
83
+ than one be open; `orientation="horizontal"` switches the arrow pair. The
84
+ `value` property reads as an array and accepts an array or a string.
85
+ - **The heading is yours.** Wrap each trigger in a real `<h2>`–`<h6>` at the
86
+ level your outline needs; there is no header element.
87
+ - **`force-mount` on a panel** keeps it unhidden so styles can animate it,
88
+ hiding it with `aria-hidden` while closed instead — the registry stylesheet
89
+ needs it.
90
+ - **`role="none"` on a panel** opts it out of being a region landmark, for an
91
+ accordion with many items.
92
+ - **Listen with `primitiv-accordion-change`** (bubbles, cancelable, `detail: {
93
+ value }`, every item open afterwards) and `primitiv-accordion-show` on a panel
94
+ as its item opens. Render `hidden` on closed panels yourself, as for Tabs.
95
+
53
96
  **TypeScript** — opt into the tag map so DOM queries return the element classes:
54
97
  `import type {} from "@primitiv-ui/web/tags"` (npm only).
55
98
 
@@ -84,7 +127,7 @@ The elements write no `class` and no `style`. For the registry look, apply the
84
127
 
85
128
  ## Layers
86
129
 
87
- - **Controllers** (`TabsController`) hold a component's DOM behaviour —
130
+ - **Controllers** (`TabsController`, `AccordionController`) hold a component's DOM behaviour —
88
131
  listeners, focus, the ordered collection of parts — and attach to whatever
89
132
  elements register with them. They never own state; their host does.
90
133
  - **Element hosts** (`<primitiv-tabs>` …) are the public face: they keep state
@@ -0,0 +1,33 @@
1
+ import { type AccordionOrientation, type AccordionReadingDirection } from "@primitiv-ui/core";
2
+ import { TriggerController, type Trigger } from "../internal/TriggerController.js";
3
+ /**
4
+ * What an {@link AccordionController} asks of whoever hosts it. The controller
5
+ * owns behaviour and never owns state (RFC 0033 D28): the custom elements keep
6
+ * the open items in a reflected `value` attribute, the React adapter keeps them
7
+ * in React, and each answers these two calls from wherever it keeps them.
8
+ */
9
+ export type AccordionControllerHost = {
10
+ /** The state the controller cannot see for itself. */
11
+ state(): {
12
+ /** The values of the open items. */
13
+ expanded: readonly string[];
14
+ multiple: boolean;
15
+ orientation: AccordionOrientation;
16
+ dir: AccordionReadingDirection;
17
+ };
18
+ /** Apply a change the user asked for: `value` is every item open afterwards. */
19
+ change(value: string[]): void;
20
+ };
21
+ /**
22
+ * The DOM behaviour of an accordion: listening, focus, and the ordered
23
+ * collection of triggers. It attaches to whatever elements register with it, so
24
+ * the same code runs under the custom elements and under the React adapter,
25
+ * which renders its own markup (RFC 0033 §3.4). A disabled trigger is skipped by
26
+ * the arrow keys and never toggles.
27
+ */
28
+ export declare class AccordionController extends TriggerController {
29
+ #private;
30
+ constructor(root: HTMLElement, host: AccordionControllerHost);
31
+ protected onTriggerClick(trigger: Trigger): void;
32
+ protected onTriggerKeydown(trigger: Trigger, event: KeyboardEvent): void;
33
+ }
@@ -0,0 +1,41 @@
1
+ import { accordionKeydown, accordionToggle, } from "@primitiv-ui/core";
2
+ import { TriggerController } from "../internal/TriggerController.js";
3
+ /**
4
+ * The DOM behaviour of an accordion: listening, focus, and the ordered
5
+ * collection of triggers. It attaches to whatever elements register with it, so
6
+ * the same code runs under the custom elements and under the React adapter,
7
+ * which renders its own markup (RFC 0033 §3.4). A disabled trigger is skipped by
8
+ * the arrow keys and never toggles.
9
+ */
10
+ export class AccordionController extends TriggerController {
11
+ #host;
12
+ constructor(root, host) {
13
+ super(root);
14
+ this.#host = host;
15
+ }
16
+ /** Ask the host to toggle `value`, unless the toggle is a no-op. */
17
+ #toggle(value) {
18
+ const { expanded, multiple } = this.#host.state();
19
+ const next = accordionToggle({ disabled: this.disabled(), expanded, multiple }, value);
20
+ if (next !== null)
21
+ this.#host.change(next);
22
+ }
23
+ onTriggerClick(trigger) {
24
+ this.#toggle(trigger.value);
25
+ }
26
+ onTriggerKeydown(trigger, event) {
27
+ const { expanded, multiple, orientation, dir } = this.#host.state();
28
+ const ordered = this.ordered();
29
+ const values = ordered.map(([, t]) => t.value);
30
+ const outcome = accordionKeydown({ values, disabled: this.disabled(), expanded, multiple, orientation, dir }, trigger.value, event.key);
31
+ if (!outcome)
32
+ return;
33
+ // Enter and Space would also click the button; the toggle happens here once.
34
+ event.preventDefault();
35
+ if (outcome.toggle !== null)
36
+ this.#toggle(outcome.toggle);
37
+ // A focus target is always one of `values`, so its index is always valid.
38
+ if (outcome.focus !== null)
39
+ ordered[values.indexOf(outcome.focus)][0].focus();
40
+ }
41
+ }
@@ -0,0 +1,79 @@
1
+ import { ElementBase, type ElementRegistry } from "../internal/element.js";
2
+ import { PrimitivRootElement } from "../internal/RootElement.js";
3
+ import { AccordionController } from "./AccordionController.js";
4
+ export type { ElementRegistry };
5
+ /**
6
+ * `<primitiv-accordion>` — the state owner of a WAI-ARIA accordion.
7
+ *
8
+ * The root finds its own items, so an accordion nested in a panel keeps its
9
+ * own, and reconciles every part's ARIA from its reflected state whenever a part
10
+ * arrives, leaves, or changes an attribute the root reads (the lifecycle is
11
+ * `PrimitivRootElement`'s). `multiple` is read live at each toggle, so a change
12
+ * to it needs no reconcile.
13
+ *
14
+ * Each trigger sits in a heading you write (`<h3>`, at the level the page's
15
+ * outline needs): a real heading element is the header, so there is no element
16
+ * for it.
17
+ *
18
+ * @customElement primitiv-accordion
19
+ * @summary The state owner of a WAI-ARIA accordion.
20
+ * @attr {string} value - The open items' values, separated by spaces. Reflected: it changes as the user toggles items, so a server re-render or a history snapshot sees what is open. Item values therefore cannot contain whitespace.
21
+ * @attr {boolean} multiple - Lets more than one item be open at once. Without it, opening an item closes the open one.
22
+ * @attr {"vertical" | "horizontal"} orientation - Layout axis, which picks the arrow-key pair. Defaults to `vertical`.
23
+ * @fires {CustomEvent<{ value: string[] }>} primitiv-accordion-change - Bubbles, cancelable, fired before a user-driven toggle is applied, with every item open afterwards. Call `preventDefault()` to veto it.
24
+ */
25
+ export declare class PrimitivAccordionElement extends PrimitivRootElement {
26
+ #private;
27
+ protected readonly controller: AccordionController;
28
+ protected readonly reconcileOn: readonly string[];
29
+ /** The open items' values, reflected as the space-separated `value` attribute. */
30
+ get value(): string[];
31
+ /** Set the open items, as an array or a space-separated string. */
32
+ set value(next: string | readonly string[]);
33
+ protected reconcile(): void;
34
+ }
35
+ /**
36
+ * `<primitiv-accordion-item>` — one collapsible section. Structural: it pairs a
37
+ * trigger and a panel and carries the value the root's `value` lists.
38
+ *
39
+ * @customElement primitiv-accordion-item
40
+ * @attr {string} value - The item's value, listed in the root's `value` while it is open. No whitespace.
41
+ */
42
+ export declare class PrimitivAccordionItemElement extends ElementBase {
43
+ }
44
+ /**
45
+ * `<primitiv-accordion-trigger>` — an interactive part. It decorates exactly one
46
+ * child the consumer writes, a `<button>`, which receives `aria-expanded`,
47
+ * `aria-controls` and the state attributes, so the rendered DOM matches React's
48
+ * `<button aria-expanded>` (RFC 0033 D24). Put it inside a heading.
49
+ *
50
+ * @customElement primitiv-accordion-trigger
51
+ * @attr {boolean} disabled - Marks the item disabled: the button stays focusable but never toggles, and the arrow keys skip it.
52
+ */
53
+ export declare class PrimitivAccordionTriggerElement extends ElementBase {
54
+ }
55
+ /**
56
+ * `<primitiv-accordion-content>` — an item's panel. Structural: the root writes
57
+ * its ARIA and hides it while the item is closed.
58
+ *
59
+ * @customElement primitiv-accordion-content
60
+ * @attr {boolean} force-mount - Never sets `hidden`, so styles can animate the panel open and closed. A closed panel always carries `aria-hidden`, so it stays out of the accessibility tree either way.
61
+ * @fires {CustomEvent} primitiv-accordion-show - Bubbles from the panel as its item opens (not on first render), so a lazy panel can fetch its content.
62
+ */
63
+ export declare class PrimitivAccordionContentElement extends ElementBase {
64
+ }
65
+ /**
66
+ * `<primitiv-accordion-trigger-icon>` — a decorative indicator inside the
67
+ * trigger's button. The root hides it from assistive technology and mirrors its
68
+ * item's `data-state`, for a chevron that flips.
69
+ *
70
+ * @customElement primitiv-accordion-trigger-icon
71
+ */
72
+ export declare class PrimitivAccordionTriggerIconElement extends ElementBase {
73
+ }
74
+ /**
75
+ * Define the five Accordion elements on `registry` (the global `customElements`
76
+ * by default). Safe to call more than once, and a no-op where there is no
77
+ * registry (a server render).
78
+ */
79
+ export declare function defineAccordion(registry?: ElementRegistry | undefined): void;
@@ -0,0 +1,194 @@
1
+ import { deriveId } from "@primitiv-ui/core";
2
+ import { defineElements, ElementBase, owned, reportOnce, write } from "../internal/element.js";
3
+ import { PrimitivRootElement } from "../internal/RootElement.js";
4
+ import { AccordionController } from "./AccordionController.js";
5
+ // Stryker disable StringLiteral: static — evaluated once at import, so runtime mutant switching never reaches them; the names are pinned by the defineAccordion registry test instead.
6
+ const ROOT = "primitiv-accordion";
7
+ const ITEM = "primitiv-accordion-item";
8
+ const TRIGGER = "primitiv-accordion-trigger";
9
+ const CONTENT = "primitiv-accordion-content";
10
+ const ICON = "primitiv-accordion-trigger-icon";
11
+ // Stryker restore StringLiteral
12
+ /** Split a space-separated `value` into item values. */
13
+ function tokens(value) {
14
+ // Stryker disable next-line Regex: equivalent — splitting on single whitespace leaves empty tokens, which the filter drops.
15
+ return (value ?? "").split(/\s+/).filter(Boolean);
16
+ }
17
+ /**
18
+ * A trigger decorates exactly one element child — the `<button>` the consumer
19
+ * writes. Anything else is reported once and left undecorated rather than
20
+ * thrown (RFC 0033 D24).
21
+ */
22
+ function decoratable(trigger) {
23
+ if (trigger.childElementCount === 1)
24
+ return trigger.firstElementChild;
25
+ reportOnce(trigger, `<${TRIGGER}> needs exactly one element child (a <button>) to decorate.`);
26
+ return null;
27
+ }
28
+ /**
29
+ * `<primitiv-accordion>` — the state owner of a WAI-ARIA accordion.
30
+ *
31
+ * The root finds its own items, so an accordion nested in a panel keeps its
32
+ * own, and reconciles every part's ARIA from its reflected state whenever a part
33
+ * arrives, leaves, or changes an attribute the root reads (the lifecycle is
34
+ * `PrimitivRootElement`'s). `multiple` is read live at each toggle, so a change
35
+ * to it needs no reconcile.
36
+ *
37
+ * Each trigger sits in a heading you write (`<h3>`, at the level the page's
38
+ * outline needs): a real heading element is the header, so there is no element
39
+ * for it.
40
+ *
41
+ * @customElement primitiv-accordion
42
+ * @summary The state owner of a WAI-ARIA accordion.
43
+ * @attr {string} value - The open items' values, separated by spaces. Reflected: it changes as the user toggles items, so a server re-render or a history snapshot sees what is open. Item values therefore cannot contain whitespace.
44
+ * @attr {boolean} multiple - Lets more than one item be open at once. Without it, opening an item closes the open one.
45
+ * @attr {"vertical" | "horizontal"} orientation - Layout axis, which picks the arrow-key pair. Defaults to `vertical`.
46
+ * @fires {CustomEvent<{ value: string[] }>} primitiv-accordion-change - Bubbles, cancelable, fired before a user-driven toggle is applied, with every item open afterwards. Call `preventDefault()` to veto it.
47
+ */
48
+ export class PrimitivAccordionElement extends PrimitivRootElement {
49
+ /** The items open at the last reconcile, so an item opening fires primitiv-accordion-show. */
50
+ #shown = new Set();
51
+ #rendered = false;
52
+ controller = new AccordionController(this, this.#host());
53
+ reconcileOn = ["value", "disabled", "orientation", "force-mount"];
54
+ /** The open items' values, reflected as the space-separated `value` attribute. */
55
+ get value() {
56
+ return tokens(this.getAttribute("value"));
57
+ }
58
+ /** Set the open items, as an array or a space-separated string. */
59
+ set value(next) {
60
+ this.setAttribute("value", typeof next === "string" ? next : next.join(" "));
61
+ }
62
+ /** The controller's view of this element: state lives in reflected attributes. */
63
+ #host() {
64
+ return {
65
+ state: () => ({
66
+ expanded: this.value,
67
+ multiple: this.hasAttribute("multiple"),
68
+ orientation: this.#orientation(),
69
+ dir: this.direction(),
70
+ }),
71
+ change: (value) => {
72
+ if (this.announce("primitiv-accordion-change", { value }))
73
+ this.value = value;
74
+ },
75
+ };
76
+ }
77
+ #orientation() {
78
+ return this.getAttribute("orientation") === "horizontal" ? "horizontal" : "vertical";
79
+ }
80
+ reconcile() {
81
+ const expanded = new Set(this.value);
82
+ const rootId = this.rootId();
83
+ write(this, "data-orientation", this.#orientation());
84
+ const triggers = [];
85
+ for (const item of this.parts(ITEM)) {
86
+ const value = item.getAttribute("value");
87
+ if (value === null) {
88
+ reportOnce(item, `<${ITEM}> needs a value, which pairs it with the root's value.`);
89
+ continue;
90
+ }
91
+ const open = expanded.has(value);
92
+ const state = open ? "open" : "closed";
93
+ const [trigger] = owned(item, TRIGGER, ITEM);
94
+ const [panel] = owned(item, CONTENT, ITEM);
95
+ const button = trigger ? decoratable(trigger) : null;
96
+ if (button) {
97
+ const disabled = trigger.hasAttribute("disabled");
98
+ triggers.push({ value, element: button, disabled });
99
+ if (!button.id)
100
+ button.id = deriveId(rootId, "heading", value);
101
+ write(button, "aria-expanded", String(open));
102
+ write(button, "aria-disabled", String(disabled));
103
+ write(button, "data-disabled", String(disabled));
104
+ write(button, "data-state", state);
105
+ }
106
+ if (panel) {
107
+ if (!panel.id)
108
+ panel.id = deriveId(rootId, "panel", value);
109
+ const forced = panel.hasAttribute("force-mount");
110
+ // A role the markup already has is kept: `role="none"` opts a panel
111
+ // out of being a region landmark.
112
+ if (!panel.hasAttribute("role"))
113
+ panel.setAttribute("role", "region");
114
+ write(panel, "aria-labelledby", button ? button.id : null);
115
+ write(panel, "hidden", open || forced ? null : "");
116
+ // Every closed panel, not only a force-mount one: a stylesheet that
117
+ // animates the panel gives it a display that wins over `hidden` (the
118
+ // registry's does), which would leave an empty region exposed.
119
+ write(panel, "aria-hidden", open ? null : "true");
120
+ write(panel, "data-state", state);
121
+ // An item that opens announces it, so a lazy panel can fetch its
122
+ // content (`hx-trigger="primitiv-accordion-show once"`). The first
123
+ // render shows what the server already rendered, so it announces nothing.
124
+ if (this.#rendered && open && !this.#shown.has(value)) {
125
+ panel.dispatchEvent(new CustomEvent("primitiv-accordion-show", { bubbles: true }));
126
+ }
127
+ }
128
+ if (button)
129
+ write(button, "aria-controls", panel ? panel.id : null);
130
+ for (const icon of owned(item, ICON, ITEM)) {
131
+ write(icon, "aria-hidden", "true");
132
+ write(icon, "data-state", state);
133
+ }
134
+ }
135
+ // Replace the registration wholesale: a removed item, or a trigger whose
136
+ // child a framework re-rendered, simply is not registered again.
137
+ this.controller.setTriggers(triggers);
138
+ this.#shown = expanded;
139
+ this.#rendered = true;
140
+ }
141
+ }
142
+ /**
143
+ * `<primitiv-accordion-item>` — one collapsible section. Structural: it pairs a
144
+ * trigger and a panel and carries the value the root's `value` lists.
145
+ *
146
+ * @customElement primitiv-accordion-item
147
+ * @attr {string} value - The item's value, listed in the root's `value` while it is open. No whitespace.
148
+ */
149
+ export class PrimitivAccordionItemElement extends ElementBase {
150
+ }
151
+ /**
152
+ * `<primitiv-accordion-trigger>` — an interactive part. It decorates exactly one
153
+ * child the consumer writes, a `<button>`, which receives `aria-expanded`,
154
+ * `aria-controls` and the state attributes, so the rendered DOM matches React's
155
+ * `<button aria-expanded>` (RFC 0033 D24). Put it inside a heading.
156
+ *
157
+ * @customElement primitiv-accordion-trigger
158
+ * @attr {boolean} disabled - Marks the item disabled: the button stays focusable but never toggles, and the arrow keys skip it.
159
+ */
160
+ export class PrimitivAccordionTriggerElement extends ElementBase {
161
+ }
162
+ /**
163
+ * `<primitiv-accordion-content>` — an item's panel. Structural: the root writes
164
+ * its ARIA and hides it while the item is closed.
165
+ *
166
+ * @customElement primitiv-accordion-content
167
+ * @attr {boolean} force-mount - Never sets `hidden`, so styles can animate the panel open and closed. A closed panel always carries `aria-hidden`, so it stays out of the accessibility tree either way.
168
+ * @fires {CustomEvent} primitiv-accordion-show - Bubbles from the panel as its item opens (not on first render), so a lazy panel can fetch its content.
169
+ */
170
+ export class PrimitivAccordionContentElement extends ElementBase {
171
+ }
172
+ /**
173
+ * `<primitiv-accordion-trigger-icon>` — a decorative indicator inside the
174
+ * trigger's button. The root hides it from assistive technology and mirrors its
175
+ * item's `data-state`, for a chevron that flips.
176
+ *
177
+ * @customElement primitiv-accordion-trigger-icon
178
+ */
179
+ export class PrimitivAccordionTriggerIconElement extends ElementBase {
180
+ }
181
+ /**
182
+ * Define the five Accordion elements on `registry` (the global `customElements`
183
+ * by default). Safe to call more than once, and a no-op where there is no
184
+ * registry (a server render).
185
+ */
186
+ export function defineAccordion(registry = globalThis.customElements) {
187
+ defineElements(registry, [
188
+ [ROOT, PrimitivAccordionElement],
189
+ [ITEM, PrimitivAccordionItemElement],
190
+ [TRIGGER, PrimitivAccordionTriggerElement],
191
+ [CONTENT, PrimitivAccordionContentElement],
192
+ [ICON, PrimitivAccordionTriggerIconElement],
193
+ ]);
194
+ }
@@ -0,0 +1,2 @@
1
+ export { AccordionController, type AccordionControllerHost } from "./AccordionController.js";
2
+ export { defineAccordion, PrimitivAccordionContentElement, PrimitivAccordionElement, PrimitivAccordionItemElement, PrimitivAccordionTriggerElement, PrimitivAccordionTriggerIconElement, } from "./elements.js";
@@ -0,0 +1,2 @@
1
+ export { AccordionController } from "./AccordionController.js";
2
+ export { defineAccordion, PrimitivAccordionContentElement, PrimitivAccordionElement, PrimitivAccordionItemElement, PrimitivAccordionTriggerElement, PrimitivAccordionTriggerIconElement, } from "./elements.js";
@@ -0,0 +1 @@
1
+ import{a as b,e as C,f as O,g,h as n,i as v,j as H,k as f,l as R,m as M}from"./chunks/chunk-FKLOMAT4.js";var m=class extends M{#t;constructor(e,s){super(e),this.#t=s}#e(e){let{expanded:s,multiple:c}=this.#t.state(),r=C({disabled:this.disabled(),expanded:s,multiple:c},e);r!==null&&this.#t.change(r)}onTriggerClick(e){this.#e(e.value)}onTriggerKeydown(e,s){let{expanded:c,multiple:r,orientation:l,dir:d}=this.#t.state(),u=this.ordered(),p=u.map(([,o])=>o.value),t=O({values:p,disabled:this.disabled(),expanded:c,multiple:r,orientation:l,dir:d},e.value,s.key);t&&(s.preventDefault(),t.toggle!==null&&this.#e(t.toggle),t.focus!==null&&u[p.indexOf(t.focus)][0].focus())}};var k="primitiv-accordion",h="primitiv-accordion-item",w="primitiv-accordion-trigger",L="primitiv-accordion-content",S="primitiv-accordion-trigger-icon";function D(i){return(i??"").split(/\s+/).filter(Boolean)}function K(i){return i.childElementCount===1?i.firstElementChild:(v(i,`<${w}> needs exactly one element child (a <button>) to decorate.`),null)}var y=class extends R{#t=new Set;#e=!1;controller=new m(this,this.#i());reconcileOn=["value","disabled","orientation","force-mount"];get value(){return D(this.getAttribute("value"))}set value(e){this.setAttribute("value",typeof e=="string"?e:e.join(" "))}#i(){return{state:()=>({expanded:this.value,multiple:this.hasAttribute("multiple"),orientation:this.#o(),dir:this.direction()}),change:e=>{this.announce("primitiv-accordion-change",{value:e})&&(this.value=e)}}}#o(){return this.getAttribute("orientation")==="horizontal"?"horizontal":"vertical"}reconcile(){let e=new Set(this.value),s=this.rootId();n(this,"data-orientation",this.#o());let c=[];for(let r of this.parts(h)){let l=r.getAttribute("value");if(l===null){v(r,`<${h}> needs a value, which pairs it with the root's value.`);continue}let d=e.has(l),u=d?"open":"closed",[p]=f(r,w,h),[t]=f(r,L,h),o=p?K(p):null;if(o){let a=p.hasAttribute("disabled");c.push({value:l,element:o,disabled:a}),o.id||(o.id=b(s,"heading",l)),n(o,"aria-expanded",String(d)),n(o,"aria-disabled",String(a)),n(o,"data-disabled",String(a)),n(o,"data-state",u)}if(t){t.id||(t.id=b(s,"panel",l));let a=t.hasAttribute("force-mount");t.hasAttribute("role")||t.setAttribute("role","region"),n(t,"aria-labelledby",o?o.id:null),n(t,"hidden",d||a?null:""),n(t,"aria-hidden",d?null:"true"),n(t,"data-state",u),this.#e&&d&&!this.#t.has(l)&&t.dispatchEvent(new CustomEvent("primitiv-accordion-show",{bubbles:!0}))}o&&n(o,"aria-controls",t?t.id:null);for(let a of f(r,S,h))n(a,"aria-hidden","true"),n(a,"data-state",u)}this.controller.setTriggers(c),this.#t=e,this.#e=!0}},x=class extends g{},A=class extends g{},T=class extends g{},E=class extends g{};function I(i=globalThis.customElements){H(i,[[k,y],[h,x],[w,A],[L,T],[S,E]])}I();
@@ -0,0 +1 @@
1
+ function b(e,t,r){return`${e}-${t}-${r}`}function s({orientation:e,dir:t,homeEnd:r=!1,activate:n=!1}){let o={};if(e==="horizontal"||e==="both"){let i=t==="rtl"?"ArrowLeft":"ArrowRight",a=t==="rtl"?"ArrowRight":"ArrowLeft";o[i]="next",o[a]="prev"}return(e==="vertical"||e==="both")&&(o.ArrowDown="next",o.ArrowUp="prev"),r&&(o.Home="first",o.End="last"),n&&(o.Enter="activate",o[" "]="activate"),o}function c({action:e,navigable:t,currentKey:r}){let n=t.length;if(n===0)return null;switch(e){case"activate":return{target:r};case"first":return{target:t[0]};case"last":return{target:t[n-1]};case void 0:return null;case"next":case"prev":{let o=t.indexOf(r);if(o===-1)return null;let i=e==="next"?1:n-1;return{target:t[(o+i)%n]}}}}function y(e){return e.active??e.values[0]}function l(e,t){return e.disabled.has(t)||e.active===t?null:t}function f(e,t,r){let n=s({orientation:e.orientation,dir:e.dir,homeEnd:!0,activate:!0})[r],o=c({action:n,navigable:e.values,currentKey:t});if(!o)return null;let i=o.target,a=e.activationMode==="automatic"||n==="activate";return{focus:i,activate:a?l(e,i):null}}function m(e,t){return e.disabled.has(t)?null:e.expanded.includes(t)?e.expanded.filter(r=>r!==t):e.multiple?[...e.expanded,t]:[t]}function T(e,t,r){let n=s({orientation:e.orientation,dir:e.dir,homeEnd:!0,activate:!0})[r];if(n==="activate")return e.disabled.has(t)?null:{focus:null,toggle:t};let o=e.values.filter(a=>!e.disabled.has(a)),i=c({action:n,navigable:o,currentKey:t});return i&&{focus:i.target,toggle:null}}var g=globalThis.HTMLElement??class{};function D(e,t,r){e.getAttribute(t)!==r&&(r===null?e.removeAttribute(t):e.setAttribute(t,r))}var d=new WeakSet;function I(e,t){d.has(e)||(d.add(e),console.error(t,e))}function N(e,t){if(e)for(let[r,n]of t)e.get(r)||e.define(r,n)}function u(e,t,r){return[...e.querySelectorAll(t)].filter(n=>n.closest(r)===e)}var h=0,p=class extends g{#e;#t=new MutationObserver(()=>this.reconcile());connectedCallback(){this.controller.connect(),this.#t.observe(this,{subtree:!0,childList:!0,attributes:!0,attributeFilter:[...this.reconcileOn]}),this.reconcile()}disconnectedCallback(){this.controller.disconnect(),this.#t.disconnect()}rootId(){return this.id||(this.#e??=`${this.localName}-${++h}`)}direction(){return this.matches(":dir(rtl)")?"rtl":"ltr"}parts(t){return u(this,t,this.localName)}announce(t,r){return this.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:r}))}};var v=class{#e;#t=new Map;#r=null;constructor(t){this.#e=t}registerTrigger(t,r,n={}){this.#t.set(r,{value:t,disabled:n.disabled??!1})}setTriggers(t){this.#t.clear();for(let{value:r,element:n,disabled:o}of t)this.registerTrigger(r,n,{disabled:o})}unregisterTrigger(t){this.#t.delete(t)}connect(){if(this.#r)return;this.#r=new AbortController;let{signal:t}=this.#r;this.#e.addEventListener("click",r=>this.#n(r,this.onTriggerClick),{signal:t}),this.#e.addEventListener("keydown",r=>this.#n(r,this.onTriggerKeydown),{signal:t})}disconnect(){this.#r?.abort(),this.#r=null}ordered(){return[...this.#t].sort(([t],[r])=>t.compareDocumentPosition(r)&Node.DOCUMENT_POSITION_FOLLOWING?-1:1)}disabled(){let t=new Set;for(let r of this.#t.values())r.disabled&&t.add(r.value);return t}#n(t,r){for(let[n,o]of this.#t)if(n.contains(t.target))return r.call(this,o,t)}};export{b as a,y as b,l as c,f as d,m as e,T as f,g,D as h,I as i,N as j,u as k,p as l,v as m};
package/dist/cdn/tabs.js CHANGED
@@ -1 +1 @@
1
- function b(i,t,e){return`${i}-${t}-${e}`}function O({orientation:i,dir:t,homeEnd:e=!1,activate:r=!1}){let o={};if(i==="horizontal"||i==="both"){let l=t==="rtl"?"ArrowLeft":"ArrowRight",d=t==="rtl"?"ArrowRight":"ArrowLeft";o[l]="next",o[d]="prev"}return(i==="vertical"||i==="both")&&(o.ArrowDown="next",o.ArrowUp="prev"),e&&(o.Home="first",o.End="last"),r&&(o.Enter="activate",o[" "]="activate"),o}function K({action:i,navigable:t,currentKey:e}){let r=t.length;if(r===0)return null;switch(i){case"activate":return{target:e};case"first":return{target:t[0]};case"last":return{target:t[r-1]};case void 0:return null;case"next":case"prev":{let o=t.indexOf(e);if(o===-1)return null;let l=i==="next"?1:r-1;return{target:t[(o+l)%r]}}}}function h(i){return i.active??i.values[0]}function g(i,t){return i.disabled.has(t)||i.active===t?null:t}function T(i,t,e){let r=O({orientation:i.orientation,dir:i.dir,homeEnd:!0,activate:!0})[e],o=K({action:r,navigable:i.values,currentKey:t});if(!o)return null;let l=o.target,d=i.activationMode==="automatic"||r==="activate";return{focus:l,activate:d?g(i,l):null}}var p=class{#o;#i;#t=new Map;#e=null;constructor(t,e){this.#o=t,this.#i=e}registerTrigger(t,e,r={}){this.#t.set(e,{value:t,disabled:r.disabled??!1})}setTriggers(t){this.#t.clear();for(let{value:e,element:r,disabled:o}of t)this.registerTrigger(e,r,{disabled:o})}unregisterTrigger(t){this.#t.delete(t)}connect(){if(this.#e)return;this.#e=new AbortController;let{signal:t}=this.#e;this.#o.addEventListener("click",e=>this.#l(e),{signal:t}),this.#o.addEventListener("keydown",e=>this.#s(e),{signal:t})}disconnect(){this.#e?.abort(),this.#e=null}#r(){return[...this.#t].sort(([t],[e])=>t.compareDocumentPosition(e)&Node.DOCUMENT_POSITION_FOLLOWING?-1:1)}#a(){let t=new Set;for(let e of this.#t.values())e.disabled&&t.add(e.value);return t}#l(t){let e=this.#n(t.target);if(!e)return;let{active:r}=this.#i.state(),o=g({disabled:this.#a(),active:r},e.value);if(o!==null){let l=this.#r().map(([,d])=>d.value);this.#i.activate(o,l.indexOf(o))}}#s(t){let e=this.#n(t.target);if(!e)return;let{active:r,orientation:o,dir:l,activationMode:d}=this.#i.state(),v=this.#r(),a=v.map(([,c])=>c.value),n=T({values:a,disabled:this.#a(),active:r,orientation:o,dir:l,activationMode:d},e.value,t.key);n&&(t.preventDefault(),n.activate!==null&&this.#i.activate(n.activate,a.indexOf(n.activate)),v[a.indexOf(n.focus)][0].focus())}#n(t){for(let[e,r]of this.#t)if(e.contains(t))return r}};var f=globalThis.HTMLElement??class{},m="primitiv-tabs",H="primitiv-tabs-list",E="primitiv-tabs-trigger",C="primitiv-tabs-content",S=0,L=new WeakSet;function I(i){let t=i.getAttribute("value");return i.childElementCount===1&&t!==null?{tab:i.firstElementChild,value:t}:(L.has(i)||(L.add(i),console.error(`<${E}> needs a value and exactly one element child (a <button>, an <a href> or a router link) to decorate.`,i)),null)}function s(i,t,e){i.getAttribute(t)!==e&&(e===null?i.removeAttribute(t):i.setAttribute(t,e))}var y=class extends f{#o;#i;#t=!1;#e=new p(this,this.#a());#r=new MutationObserver(()=>this.#c());connectedCallback(){this.#e.connect(),this.#r.observe(this,{subtree:!0,childList:!0,attributes:!0,attributeFilter:["value","disabled","label","orientation"]}),this.#c()}disconnectedCallback(){this.#e.disconnect(),this.#r.disconnect()}get value(){return this.getAttribute("value")??void 0}set value(t){this.setAttribute("value",t)}#a(){return{state:()=>({active:this.value,orientation:this.#s(),dir:this.matches(":dir(rtl)")?"rtl":"ltr",activationMode:this.#l()}),activate:(t,e)=>{let r=new CustomEvent("primitiv-tabs-change",{bubbles:!0,cancelable:!0,detail:{value:t,index:e}});this.dispatchEvent(r)&&(this.value=t)}}}#l(){let t=this.getAttribute("activation-mode");return t==="manual"?t:"automatic"}#s(){return this.getAttribute("orientation")==="vertical"?"vertical":"horizontal"}#n(t){return[...this.querySelectorAll(t)].filter(e=>e.closest(m)===this)}#d(){return this.id||(this.#o??=`${m}-${++S}`)}#c(){let t=this.#s(),e=this.value,r=this.#d();s(this,"data-orientation",t);for(let a of this.#n(H)){s(a,"role","tablist"),s(a,"aria-orientation",t),s(a,"data-orientation",t);let n=a.getAttribute("label");n!==null&&s(a,"aria-label",n)}let o=new Map;for(let a of this.#n(C)){let n=a.getAttribute("value");n!==null&&o.set(n,a)}let l=this.#n(E).flatMap(a=>{let n=I(a);return n?[{trigger:a,...n}]:[]}),d=h({values:l.map(a=>a.value),active:e});this.#e.setTriggers(l.map(({trigger:a,tab:n,value:c})=>({value:c,element:n,disabled:a.hasAttribute("disabled")})));for(let{trigger:a,tab:n,value:c}of l){let u=o.get(c),R=c===e,w=a.hasAttribute("disabled");n.id||(n.id=b(r,"trigger",c)),u&&!u.id&&(u.id=b(r,"panel",c)),s(n,"role","tab"),s(n,"aria-selected",String(R)),s(n,"aria-controls",u?u.id:null),s(n,"tabindex",c===d?"0":"-1"),s(n,"aria-disabled",w?"true":null),s(n,"data-disabled",w?"":null),s(n,"data-state",R?"active":"inactive"),s(n,"data-orientation",t),u&&s(u,"aria-labelledby",n.id)}for(let[a,n]of o){let c=a===e;s(n,"role","tabpanel"),s(n,"tabindex","0"),s(n,"hidden",c?null:""),s(n,"data-state",c?"active":"inactive"),s(n,"data-orientation",t)}let v=o.get(e);this.#t&&e!==this.#i&&v&&v.dispatchEvent(new CustomEvent("primitiv-tabs-show",{bubbles:!0})),this.#i=e,this.#t=!0}},x=class extends f{},A=class extends f{},M=class extends f{};function k(i=globalThis.customElements){if(!i)return;let t=[[m,y],[H,x],[E,A],[C,M]];for(let[e,r]of t)i.get(e)||i.define(e,r)}k();
1
+ import{a as h,b as M,c as A,d as C,g as b,h as a,i as O,j as H,l as w,m as R}from"./chunks/chunk-FKLOMAT4.js";var v=class extends R{#t;constructor(i,n){super(i),this.#t=n}onTriggerClick(i){let{active:n}=this.#t.state(),r=A({disabled:this.disabled(),active:n},i.value);if(r!==null){let l=this.ordered().map(([,d])=>d.value);this.#t.activate(r,l.indexOf(r))}}onTriggerKeydown(i,n){let{active:r,orientation:l,dir:d,activationMode:p}=this.#t.state(),u=this.ordered(),e=u.map(([,o])=>o.value),t=C({values:e,disabled:this.disabled(),active:r,orientation:l,dir:d,activationMode:p},i.value,n.key);t&&(n.preventDefault(),t.activate!==null&&this.#t.activate(t.activate,e.indexOf(t.activate)),u[e.indexOf(t.focus)][0].focus())}};var D="primitiv-tabs",I="primitiv-tabs-list",y="primitiv-tabs-trigger",L="primitiv-tabs-content";function K(s){let i=s.getAttribute("value");return s.childElementCount===1&&i!==null?{tab:s.firstElementChild,value:i}:(O(s,`<${y}> needs a value and exactly one element child (a <button>, an <a href> or a router link) to decorate.`),null)}var f=class extends w{#t;#e=!1;controller=new v(this,this.#a());reconcileOn=["value","disabled","label","orientation"];get value(){return this.getAttribute("value")??void 0}set value(i){this.setAttribute("value",i)}#a(){return{state:()=>({active:this.value,orientation:this.#i(),dir:this.direction(),activationMode:this.#n()}),activate:(i,n)=>{this.announce("primitiv-tabs-change",{value:i,index:n})&&(this.value=i)}}}#n(){let i=this.getAttribute("activation-mode");return i==="manual"?i:"automatic"}#i(){return this.getAttribute("orientation")==="vertical"?"vertical":"horizontal"}reconcile(){let i=this.#i(),n=this.value,r=this.rootId();a(this,"data-orientation",i);for(let e of this.parts(I)){a(e,"role","tablist"),a(e,"aria-orientation",i),a(e,"data-orientation",i);let t=e.getAttribute("label");t!==null&&a(e,"aria-label",t)}let l=new Map;for(let e of this.parts(L)){let t=e.getAttribute("value");t!==null&&l.set(t,e)}let d=this.parts(y).flatMap(e=>{let t=K(e);return t?[{trigger:e,...t}]:[]}),p=M({values:d.map(e=>e.value),active:n});this.controller.setTriggers(d.map(({trigger:e,tab:t,value:o})=>({value:o,element:t,disabled:e.hasAttribute("disabled")})));for(let{trigger:e,tab:t,value:o}of d){let c=l.get(o),x=o===n,E=e.hasAttribute("disabled");t.id||(t.id=h(r,"trigger",o)),c&&!c.id&&(c.id=h(r,"panel",o)),a(t,"role","tab"),a(t,"aria-selected",String(x)),a(t,"aria-controls",c?c.id:null),a(t,"tabindex",o===p?"0":"-1"),a(t,"aria-disabled",E?"true":null),a(t,"data-disabled",E?"":null),a(t,"data-state",x?"active":"inactive"),a(t,"data-orientation",i),c&&a(c,"aria-labelledby",t.id)}for(let[e,t]of l){let o=e===n;a(t,"role","tabpanel"),a(t,"tabindex","0"),a(t,"hidden",o?null:""),a(t,"data-state",o?"active":"inactive"),a(t,"data-orientation",i)}let u=l.get(n);this.#e&&n!==this.#t&&u&&u.dispatchEvent(new CustomEvent("primitiv-tabs-show",{bubbles:!0})),this.#t=n,this.#e=!0}},m=class extends b{},g=class extends b{},T=class extends b{};function k(s=globalThis.customElements){H(s,[[D,f],[I,m],[y,g],[L,T]])}k();