@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 +154 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +1 -0
- package/package.json +38 -0
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;
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|