@data-slot/accordion 0.1.0

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 ADDED
@@ -0,0 +1,154 @@
1
+ # @data-slot/accordion
2
+
3
+ Headless accordion component for vanilla JavaScript. Accessible, unstyled, tiny.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @data-slot/accordion
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```html
14
+ <div data-slot="accordion">
15
+ <div data-slot="accordion-item" data-value="one">
16
+ <button data-slot="accordion-trigger">Section One</button>
17
+ <div data-slot="accordion-content">Content for section one</div>
18
+ </div>
19
+ <div data-slot="accordion-item" data-value="two">
20
+ <button data-slot="accordion-trigger">Section Two</button>
21
+ <div data-slot="accordion-content">Content for section two</div>
22
+ </div>
23
+ </div>
24
+
25
+ <script type="module">
26
+ import { create } from "@data-slot/accordion";
27
+
28
+ const controllers = create();
29
+ </script>
30
+ ```
31
+
32
+ ## API
33
+
34
+ ### `create(scope?)`
35
+
36
+ Auto-discover and bind all accordion instances in a scope (defaults to `document`).
37
+
38
+ ```typescript
39
+ import { create } from "@data-slot/accordion";
40
+
41
+ const controllers = create(); // Returns AccordionController[]
42
+ ```
43
+
44
+ ### `createAccordion(root, options?)`
45
+
46
+ Create a controller for a specific element.
47
+
48
+ ```typescript
49
+ import { createAccordion } from "@data-slot/accordion";
50
+
51
+ const accordion = createAccordion(element, {
52
+ multiple: true,
53
+ defaultValue: ["one"],
54
+ collapsible: true,
55
+ onValueChange: (values) => console.log(values),
56
+ });
57
+ ```
58
+
59
+ ### Options
60
+
61
+ | Option | Type | Default | Description |
62
+ | --------------- | --------------------------- | ----------- | ------------------------------------------------------- |
63
+ | `multiple` | `boolean` | `false` | Allow multiple items open at once |
64
+ | `defaultValue` | `string \| string[]` | `undefined` | Initially expanded item(s) |
65
+ | `collapsible` | `boolean` | `true` | Whether items can be fully collapsed (single mode only) |
66
+ | `onValueChange` | `(value: string[]) => void` | `undefined` | Callback when expanded items change |
67
+
68
+ ### Controller
69
+
70
+ | Method/Property | Description |
71
+ | ----------------- | ----------------------------------------------- |
72
+ | `expand(value)` | Expand an item by value |
73
+ | `collapse(value)` | Collapse an item by value |
74
+ | `toggle(value)` | Toggle an item by value |
75
+ | `value` | Currently expanded values (readonly `string[]`) |
76
+ | `destroy()` | Cleanup all event listeners |
77
+
78
+ ## Markup Structure
79
+
80
+ ```html
81
+ <div data-slot="accordion">
82
+ <div data-slot="accordion-item" data-value="unique-id">
83
+ <button data-slot="accordion-trigger">Trigger</button>
84
+ <div data-slot="accordion-content">Content</div>
85
+ </div>
86
+ </div>
87
+ ```
88
+
89
+ ## Styling
90
+
91
+ Use `data-state` attributes for CSS styling:
92
+
93
+ ```css
94
+ /* Closed state */
95
+ [data-slot="accordion-content"][data-state="closed"] {
96
+ display: none;
97
+ }
98
+
99
+ /* Open state */
100
+ [data-slot="accordion-content"][data-state="open"] {
101
+ display: block;
102
+ }
103
+
104
+ /* Animate with grid */
105
+ [data-slot="accordion-item"] {
106
+ display: grid;
107
+ grid-template-rows: auto 0fr;
108
+ transition: grid-template-rows 0.3s;
109
+ }
110
+
111
+ [data-slot="accordion-item"][data-state="open"] {
112
+ grid-template-rows: auto 1fr;
113
+ }
114
+
115
+ [data-slot="accordion-content"] {
116
+ overflow: hidden;
117
+ }
118
+ ```
119
+
120
+ With Tailwind:
121
+
122
+ ```html
123
+ <div
124
+ data-slot="accordion-item"
125
+ class="grid grid-rows-[auto_0fr] data-[state=open]:grid-rows-[auto_1fr] transition-[grid-template-rows]"
126
+ >
127
+ <button data-slot="accordion-trigger">...</button>
128
+ <div data-slot="accordion-content" class="overflow-hidden">...</div>
129
+ </div>
130
+ ```
131
+
132
+ ## Keyboard Navigation
133
+
134
+ | Key | Action |
135
+ | ----------------- | ------------------------------ |
136
+ | `Enter` / `Space` | Toggle focused item |
137
+ | `ArrowDown` | Move focus to next trigger |
138
+ | `ArrowUp` | Move focus to previous trigger |
139
+ | `Home` | Move focus to first trigger |
140
+ | `End` | Move focus to last trigger |
141
+
142
+ ## Events
143
+
144
+ Listen for changes via custom events:
145
+
146
+ ```javascript
147
+ element.addEventListener("accordion:change", (e) => {
148
+ console.log("Expanded items:", e.detail.value);
149
+ });
150
+ ```
151
+
152
+ ## License
153
+
154
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`@data-slot/core`));function l(e,t={}){let{multiple:n=!1,onValueChange:r,collapsible:i=!0}=t,a=(0,c.getParts)(e,`accordion-item`);if(a.length===0)throw Error(`Accordion requires at least one accordion-item`);let o=t.defaultValue?Array.isArray(t.defaultValue)?t.defaultValue:[t.defaultValue]:[],s=new Set(o),l=[],u=[];a.forEach(t=>{let a=t.dataset.value;if(!a)return;let o=(0,c.getPart)(t,`accordion-trigger`),f=(0,c.getPart)(t,`accordion-content`);if(!o||!f)return;u.push(o);let p=(0,c.ensureId)(f,`accordion-content`),m=(0,c.ensureId)(o,`accordion-trigger`);o.setAttribute(`aria-controls`,p),f.setAttribute(`aria-labelledby`,m),f.setAttribute(`role`,`region`),l.push((0,c.on)(o,`click`,()=>{let t=s.has(a);if(t){if(!i&&!n&&s.size===1)return;s.delete(a)}else n?s.add(a):s=new Set([a]);d(),(0,c.emit)(e,`accordion:change`,{value:[...s]}),r?.([...s])}))}),l.push((0,c.on)(e,`keydown`,e=>{let t=e.target,n=u.indexOf(t);if(n===-1)return;let r=n;switch(e.key){case`ArrowDown`:r=n+1,r>=u.length&&(r=0);break;case`ArrowUp`:r=n-1,r<0&&(r=u.length-1);break;case`Home`:r=0;break;case`End`:r=u.length-1;break;default:return}e.preventDefault(),u[r]?.focus()}));let d=()=>{a.forEach(e=>{let t=e.dataset.value;if(!t)return;let n=(0,c.getPart)(e,`accordion-trigger`),r=(0,c.getPart)(e,`accordion-content`);if(!n||!r)return;let i=s.has(t),a=n.getAttribute(`aria-expanded`)===`true`;if((0,c.setAria)(n,`expanded`,i),e.setAttribute(`data-state`,i?`open`:`closed`),r.setAttribute(`data-state`,i?`open`:`closed`),i)r.hidden=!1,r._accordionTimeout&&(clearTimeout(r._accordionTimeout),r._accordionTimeout=null);else if(a){r._accordionTimeout&&clearTimeout(r._accordionTimeout);let e=r.parentElement,t=n=>{if(n.propertyName===`grid-template-rows`){let n=r.getAttribute(`data-state`);n===`closed`&&(r.hidden=!0),e?.removeEventListener(`transitionend`,t),r._accordionTimeout&&(clearTimeout(r._accordionTimeout),r._accordionTimeout=null)}};e?(e.addEventListener(`transitionend`,t),r._accordionTimeout=setTimeout(()=>{let n=r.getAttribute(`data-state`);n===`closed`&&(r.hidden=!0),e.removeEventListener(`transitionend`,t),r._accordionTimeout=null},350)):r._accordionTimeout=setTimeout(()=>{r.hidden=!0,r._accordionTimeout=null},300)}else r.hidden=!0})};d();let f={expand:t=>{s.has(t)||(n?s.add(t):s=new Set([t]),d(),(0,c.emit)(e,`accordion:change`,{value:[...s]}),r?.([...s]))},collapse:t=>{s.has(t)&&(!i&&!n&&s.size===1||(s.delete(t),d(),(0,c.emit)(e,`accordion:change`,{value:[...s]}),r?.([...s])))},toggle:e=>{s.has(e)?f.collapse(e):f.expand(e)},get value(){return[...s]},destroy:()=>{l.forEach(e=>e()),l.length=0,a.forEach(e=>{let t=(0,c.getPart)(e,`accordion-content`);t&&t._accordionTimeout&&(clearTimeout(t._accordionTimeout),t._accordionTimeout=null)})}};return f}const u=new WeakSet;function d(e=document){let t=[];for(let n of(0,c.getRoots)(e,`accordion`)){if(u.has(n))continue;u.add(n),t.push(l(n))}return t}exports.create=d,exports.createAccordion=l;
@@ -0,0 +1,48 @@
1
+ //#region src/index.d.ts
2
+ interface AccordionOptions {
3
+ /** Allow multiple items open at once */
4
+ multiple?: boolean;
5
+ /** Initially expanded item(s) */
6
+ defaultValue?: string | string[];
7
+ /** Callback when expanded items change */
8
+ onValueChange?: (value: string[]) => void;
9
+ /** Whether items can be fully collapsed (only applies to single mode) */
10
+ collapsible?: boolean;
11
+ }
12
+ interface AccordionController {
13
+ /** Expand an item by value */
14
+ expand(value: string): void;
15
+ /** Collapse an item by value */
16
+ collapse(value: string): void;
17
+ /** Toggle an item by value */
18
+ toggle(value: string): void;
19
+ /** Currently expanded values */
20
+ readonly value: string[];
21
+ /** Cleanup all event listeners */
22
+ destroy(): void;
23
+ }
24
+ /**
25
+ * Create an accordion controller for a root element
26
+ *
27
+ * Expected markup:
28
+ * ```html
29
+ * <div data-slot="accordion">
30
+ * <div data-slot="accordion-item" data-value="one">
31
+ * <button data-slot="accordion-trigger">Item One</button>
32
+ * <div data-slot="accordion-content">Content One</div>
33
+ * </div>
34
+ * <div data-slot="accordion-item" data-value="two">
35
+ * <button data-slot="accordion-trigger">Item Two</button>
36
+ * <div data-slot="accordion-content">Content Two</div>
37
+ * </div>
38
+ * </div>
39
+ * ```
40
+ */
41
+ declare function createAccordion(root: Element, options?: AccordionOptions): AccordionController;
42
+ /**
43
+ * Find and bind all accordion components in a scope
44
+ * Returns array of controllers for programmatic access
45
+ */
46
+ declare function create(scope?: ParentNode): AccordionController[];
47
+ //#endregion
48
+ export { AccordionController, AccordionOptions, create, createAccordion };
@@ -0,0 +1,48 @@
1
+ //#region src/index.d.ts
2
+ interface AccordionOptions {
3
+ /** Allow multiple items open at once */
4
+ multiple?: boolean;
5
+ /** Initially expanded item(s) */
6
+ defaultValue?: string | string[];
7
+ /** Callback when expanded items change */
8
+ onValueChange?: (value: string[]) => void;
9
+ /** Whether items can be fully collapsed (only applies to single mode) */
10
+ collapsible?: boolean;
11
+ }
12
+ interface AccordionController {
13
+ /** Expand an item by value */
14
+ expand(value: string): void;
15
+ /** Collapse an item by value */
16
+ collapse(value: string): void;
17
+ /** Toggle an item by value */
18
+ toggle(value: string): void;
19
+ /** Currently expanded values */
20
+ readonly value: string[];
21
+ /** Cleanup all event listeners */
22
+ destroy(): void;
23
+ }
24
+ /**
25
+ * Create an accordion controller for a root element
26
+ *
27
+ * Expected markup:
28
+ * ```html
29
+ * <div data-slot="accordion">
30
+ * <div data-slot="accordion-item" data-value="one">
31
+ * <button data-slot="accordion-trigger">Item One</button>
32
+ * <div data-slot="accordion-content">Content One</div>
33
+ * </div>
34
+ * <div data-slot="accordion-item" data-value="two">
35
+ * <button data-slot="accordion-trigger">Item Two</button>
36
+ * <div data-slot="accordion-content">Content Two</div>
37
+ * </div>
38
+ * </div>
39
+ * ```
40
+ */
41
+ declare function createAccordion(root: Element, options?: AccordionOptions): AccordionController;
42
+ /**
43
+ * Find and bind all accordion components in a scope
44
+ * Returns array of controllers for programmatic access
45
+ */
46
+ declare function create(scope?: ParentNode): AccordionController[];
47
+ //#endregion
48
+ export { AccordionController, AccordionOptions, create, createAccordion };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{emit as e,ensureId as t,getPart as n,getParts as r,getRoots as i,on as a,setAria as o}from"@data-slot/core";function s(i,s={}){let{multiple:c=!1,onValueChange:l,collapsible:u=!0}=s,d=r(i,`accordion-item`);if(d.length===0)throw Error(`Accordion requires at least one accordion-item`);let f=s.defaultValue?Array.isArray(s.defaultValue)?s.defaultValue:[s.defaultValue]:[],p=new Set(f),m=[],h=[];d.forEach(r=>{let o=r.dataset.value;if(!o)return;let s=n(r,`accordion-trigger`),d=n(r,`accordion-content`);if(!s||!d)return;h.push(s);let f=t(d,`accordion-content`),_=t(s,`accordion-trigger`);s.setAttribute(`aria-controls`,f),d.setAttribute(`aria-labelledby`,_),d.setAttribute(`role`,`region`),m.push(a(s,`click`,()=>{let t=p.has(o);if(t){if(!u&&!c&&p.size===1)return;p.delete(o)}else c?p.add(o):p=new Set([o]);g(),e(i,`accordion:change`,{value:[...p]}),l?.([...p])}))}),m.push(a(i,`keydown`,e=>{let t=e.target,n=h.indexOf(t);if(n===-1)return;let r=n;switch(e.key){case`ArrowDown`:r=n+1,r>=h.length&&(r=0);break;case`ArrowUp`:r=n-1,r<0&&(r=h.length-1);break;case`Home`:r=0;break;case`End`:r=h.length-1;break;default:return}e.preventDefault(),h[r]?.focus()}));let g=()=>{d.forEach(e=>{let t=e.dataset.value;if(!t)return;let r=n(e,`accordion-trigger`),i=n(e,`accordion-content`);if(!r||!i)return;let a=p.has(t),s=r.getAttribute(`aria-expanded`)===`true`;if(o(r,`expanded`,a),e.setAttribute(`data-state`,a?`open`:`closed`),i.setAttribute(`data-state`,a?`open`:`closed`),a)i.hidden=!1,i._accordionTimeout&&(clearTimeout(i._accordionTimeout),i._accordionTimeout=null);else if(s){i._accordionTimeout&&clearTimeout(i._accordionTimeout);let e=i.parentElement,t=n=>{if(n.propertyName===`grid-template-rows`){let n=i.getAttribute(`data-state`);n===`closed`&&(i.hidden=!0),e?.removeEventListener(`transitionend`,t),i._accordionTimeout&&(clearTimeout(i._accordionTimeout),i._accordionTimeout=null)}};e?(e.addEventListener(`transitionend`,t),i._accordionTimeout=setTimeout(()=>{let n=i.getAttribute(`data-state`);n===`closed`&&(i.hidden=!0),e.removeEventListener(`transitionend`,t),i._accordionTimeout=null},350)):i._accordionTimeout=setTimeout(()=>{i.hidden=!0,i._accordionTimeout=null},300)}else i.hidden=!0})};g();let _={expand:t=>{p.has(t)||(c?p.add(t):p=new Set([t]),g(),e(i,`accordion:change`,{value:[...p]}),l?.([...p]))},collapse:t=>{p.has(t)&&(!u&&!c&&p.size===1||(p.delete(t),g(),e(i,`accordion:change`,{value:[...p]}),l?.([...p])))},toggle:e=>{p.has(e)?_.collapse(e):_.expand(e)},get value(){return[...p]},destroy:()=>{m.forEach(e=>e()),m.length=0,d.forEach(e=>{let t=n(e,`accordion-content`);t&&t._accordionTimeout&&(clearTimeout(t._accordionTimeout),t._accordionTimeout=null)})}};return _}const c=new WeakSet;function l(e=document){let t=[];for(let n of i(e,`accordion`)){if(c.has(n))continue;c.add(n),t.push(s(n))}return t}export{l as create,s as createAccordion};
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@data-slot/accordion",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsdown"
26
+ },
27
+ "dependencies": {
28
+ "@data-slot/core": "workspace:*"
29
+ },
30
+ "keywords": [
31
+ "headless",
32
+ "ui",
33
+ "accordion",
34
+ "vanilla",
35
+ "data-slot"
36
+ ],
37
+ "license": "MIT"
38
+ }