@data-slot/collapsible 0.2.4
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 +145 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# @data-slot/collapsible
|
|
2
|
+
|
|
3
|
+
Headless collapsible (show/hide) component for vanilla JavaScript. Accessible, unstyled, tiny.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @data-slot/collapsible
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<div data-slot="collapsible">
|
|
15
|
+
<button data-slot="collapsible-trigger">Toggle Content</button>
|
|
16
|
+
<div data-slot="collapsible-content" hidden>
|
|
17
|
+
Hidden content revealed on click.
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<script type="module">
|
|
22
|
+
import { create } from "@data-slot/collapsible";
|
|
23
|
+
|
|
24
|
+
const controllers = create();
|
|
25
|
+
</script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## API
|
|
29
|
+
|
|
30
|
+
### `create(scope?)`
|
|
31
|
+
|
|
32
|
+
Auto-discover and bind all collapsible instances in a scope (defaults to `document`).
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { create } from "@data-slot/collapsible";
|
|
36
|
+
|
|
37
|
+
const controllers = create(); // Returns CollapsibleController[]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `createCollapsible(root, options?)`
|
|
41
|
+
|
|
42
|
+
Create a controller for a specific element.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createCollapsible } from "@data-slot/collapsible";
|
|
46
|
+
|
|
47
|
+
const collapsible = createCollapsible(element, {
|
|
48
|
+
defaultOpen: false,
|
|
49
|
+
onOpenChange: (open) => console.log(open),
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Options
|
|
54
|
+
|
|
55
|
+
| Option | Type | Default | Description |
|
|
56
|
+
|--------|------|---------|-------------|
|
|
57
|
+
| `defaultOpen` | `boolean` | `false` | Initial open state |
|
|
58
|
+
| `onOpenChange` | `(open: boolean) => void` | `undefined` | Callback when open state changes (not called on init) |
|
|
59
|
+
|
|
60
|
+
### Controller
|
|
61
|
+
|
|
62
|
+
| Method/Property | Description |
|
|
63
|
+
|-----------------|-------------|
|
|
64
|
+
| `open()` | Open the collapsible |
|
|
65
|
+
| `close()` | Close the collapsible |
|
|
66
|
+
| `toggle()` | Toggle the collapsible |
|
|
67
|
+
| `isOpen` | Current open state (readonly `boolean`) |
|
|
68
|
+
| `destroy()` | Cleanup all event listeners |
|
|
69
|
+
|
|
70
|
+
## Markup Structure
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<div data-slot="collapsible">
|
|
74
|
+
<button data-slot="collapsible-trigger">Toggle</button>
|
|
75
|
+
<div data-slot="collapsible-content">Content</div>
|
|
76
|
+
</div>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Both `collapsible-trigger` and `collapsible-content` are required.
|
|
80
|
+
|
|
81
|
+
## Styling
|
|
82
|
+
|
|
83
|
+
Use `data-state` attributes for CSS styling (available on both root and content):
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
/* Hidden state */
|
|
87
|
+
[data-slot="collapsible-content"][hidden] {
|
|
88
|
+
display: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Or use data-state */
|
|
92
|
+
[data-slot="collapsible"][data-state="closed"] [data-slot="collapsible-content"] {
|
|
93
|
+
display: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Animate using data-state on content */
|
|
97
|
+
[data-slot="collapsible-content"] {
|
|
98
|
+
overflow: hidden;
|
|
99
|
+
transition: max-height 0.3s;
|
|
100
|
+
max-height: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
[data-slot="collapsible-content"][data-state="open"] {
|
|
104
|
+
max-height: 500px;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
With Tailwind:
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<div data-slot="collapsible" class="group">
|
|
112
|
+
<button data-slot="collapsible-trigger" class="flex items-center gap-2">
|
|
113
|
+
<span>Show more</span>
|
|
114
|
+
<svg class="group-data-[state=open]:rotate-180 transition-transform">...</svg>
|
|
115
|
+
</button>
|
|
116
|
+
<div data-slot="collapsible-content" class="hidden group-data-[state=open]:block">
|
|
117
|
+
Content here
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Accessibility
|
|
123
|
+
|
|
124
|
+
The component automatically handles:
|
|
125
|
+
|
|
126
|
+
- `aria-expanded` state on trigger
|
|
127
|
+
- `aria-controls` linking trigger to content
|
|
128
|
+
- `role="region"` on content
|
|
129
|
+
- `aria-labelledby` linking content back to trigger
|
|
130
|
+
- Unique ID generation for trigger and content
|
|
131
|
+
- Disabled trigger support (respects `disabled` attribute and `aria-disabled="true"`)
|
|
132
|
+
|
|
133
|
+
## Events
|
|
134
|
+
|
|
135
|
+
Listen for changes via custom events:
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
element.addEventListener("collapsible:change", (e) => {
|
|
139
|
+
console.log("Collapsible open:", e.detail.open);
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=(e,t)=>e.querySelector(`[data-slot="${t}"]`),t=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)];let n=0;const r=(e,t)=>e.id||=`${t}-${++n}`,i=(e,t,n)=>{n===null?e.removeAttribute(`aria-${t}`):e.setAttribute(`aria-${t}`,String(n))};function a(e,t,n,r){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)}const o=(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n})),s=new WeakSet;function c(t,n={}){let{defaultOpen:c=!1,onOpenChange:l}=n,u=e(t,`collapsible-trigger`),d=e(t,`collapsible-content`);if(!u||!d)throw Error(`Collapsible requires trigger and content slots`);let f=c,p=[],m=r(d,`collapsible-content`),h=r(u,`collapsible-trigger`);u.setAttribute(`aria-controls`,m),d.setAttribute(`role`,`region`),d.setAttribute(`aria-labelledby`,h);let g=e=>{t.setAttribute(`data-state`,e),d.setAttribute(`data-state`,e)},_=e=>{f!==e&&(f=e,i(u,`expanded`,f),d.hidden=!f,g(f?`open`:`closed`),o(t,`collapsible:change`,{open:f}),l?.(f))};i(u,`expanded`,f),d.hidden=!f,g(f?`open`:`closed`),p.push(a(u,`click`,()=>{u.hasAttribute(`disabled`)||u.getAttribute(`aria-disabled`)===`true`||_(!f)}));let v={open:()=>_(!0),close:()=>_(!1),toggle:()=>_(!f),get isOpen(){return f},destroy:()=>{p.forEach(e=>e()),p.length=0,s.delete(t)}};return v}function l(e=document){let n=[];for(let r of t(e,`collapsible`)){if(s.has(r))continue;s.add(r),n.push(c(r))}return n}exports.create=l,exports.createCollapsible=c;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface CollapsibleOptions {
|
|
3
|
+
/** Initial open state */
|
|
4
|
+
defaultOpen?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Callback when open state changes.
|
|
7
|
+
* Note: Not called on initial render, only on subsequent state changes.
|
|
8
|
+
*/
|
|
9
|
+
onOpenChange?: (open: boolean) => void;
|
|
10
|
+
}
|
|
11
|
+
interface CollapsibleController {
|
|
12
|
+
/** Open the collapsible */
|
|
13
|
+
open(): void;
|
|
14
|
+
/** Close the collapsible */
|
|
15
|
+
close(): void;
|
|
16
|
+
/** Toggle the collapsible */
|
|
17
|
+
toggle(): void;
|
|
18
|
+
/** Current open state */
|
|
19
|
+
readonly isOpen: boolean;
|
|
20
|
+
/** Cleanup all event listeners */
|
|
21
|
+
destroy(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a collapsible controller for a root element
|
|
25
|
+
*
|
|
26
|
+
* Expected markup:
|
|
27
|
+
* ```html
|
|
28
|
+
* <div data-slot="collapsible">
|
|
29
|
+
* <button data-slot="collapsible-trigger">Toggle</button>
|
|
30
|
+
* <div data-slot="collapsible-content">Content here</div>
|
|
31
|
+
* </div>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function createCollapsible(root: Element, options?: CollapsibleOptions): CollapsibleController;
|
|
35
|
+
/**
|
|
36
|
+
* Find and bind all collapsible components in a scope
|
|
37
|
+
* Returns array of controllers for programmatic access
|
|
38
|
+
*/
|
|
39
|
+
declare function create(scope?: ParentNode): CollapsibleController[];
|
|
40
|
+
//#endregion
|
|
41
|
+
export { CollapsibleController, CollapsibleOptions, create, createCollapsible };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface CollapsibleOptions {
|
|
3
|
+
/** Initial open state */
|
|
4
|
+
defaultOpen?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Callback when open state changes.
|
|
7
|
+
* Note: Not called on initial render, only on subsequent state changes.
|
|
8
|
+
*/
|
|
9
|
+
onOpenChange?: (open: boolean) => void;
|
|
10
|
+
}
|
|
11
|
+
interface CollapsibleController {
|
|
12
|
+
/** Open the collapsible */
|
|
13
|
+
open(): void;
|
|
14
|
+
/** Close the collapsible */
|
|
15
|
+
close(): void;
|
|
16
|
+
/** Toggle the collapsible */
|
|
17
|
+
toggle(): void;
|
|
18
|
+
/** Current open state */
|
|
19
|
+
readonly isOpen: boolean;
|
|
20
|
+
/** Cleanup all event listeners */
|
|
21
|
+
destroy(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a collapsible controller for a root element
|
|
25
|
+
*
|
|
26
|
+
* Expected markup:
|
|
27
|
+
* ```html
|
|
28
|
+
* <div data-slot="collapsible">
|
|
29
|
+
* <button data-slot="collapsible-trigger">Toggle</button>
|
|
30
|
+
* <div data-slot="collapsible-content">Content here</div>
|
|
31
|
+
* </div>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function createCollapsible(root: Element, options?: CollapsibleOptions): CollapsibleController;
|
|
35
|
+
/**
|
|
36
|
+
* Find and bind all collapsible components in a scope
|
|
37
|
+
* Returns array of controllers for programmatic access
|
|
38
|
+
*/
|
|
39
|
+
declare function create(scope?: ParentNode): CollapsibleController[];
|
|
40
|
+
//#endregion
|
|
41
|
+
export { CollapsibleController, CollapsibleOptions, create, createCollapsible };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=(e,t)=>e.querySelector(`[data-slot="${t}"]`),t=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)];let n=0;const r=(e,t)=>e.id||=`${t}-${++n}`,i=(e,t,n)=>{n===null?e.removeAttribute(`aria-${t}`):e.setAttribute(`aria-${t}`,String(n))};function a(e,t,n,r){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)}const o=(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n})),s=new WeakSet;function c(t,n={}){let{defaultOpen:c=!1,onOpenChange:l}=n,u=e(t,`collapsible-trigger`),d=e(t,`collapsible-content`);if(!u||!d)throw Error(`Collapsible requires trigger and content slots`);let f=c,p=[],m=r(d,`collapsible-content`),h=r(u,`collapsible-trigger`);u.setAttribute(`aria-controls`,m),d.setAttribute(`role`,`region`),d.setAttribute(`aria-labelledby`,h);let g=e=>{t.setAttribute(`data-state`,e),d.setAttribute(`data-state`,e)},_=e=>{f!==e&&(f=e,i(u,`expanded`,f),d.hidden=!f,g(f?`open`:`closed`),o(t,`collapsible:change`,{open:f}),l?.(f))};i(u,`expanded`,f),d.hidden=!f,g(f?`open`:`closed`),p.push(a(u,`click`,()=>{u.hasAttribute(`disabled`)||u.getAttribute(`aria-disabled`)===`true`||_(!f)}));let v={open:()=>_(!0),close:()=>_(!1),toggle:()=>_(!f),get isOpen(){return f},destroy:()=>{p.forEach(e=>e()),p.length=0,s.delete(t)}};return v}function l(e=document){let n=[];for(let r of t(e,`collapsible`)){if(s.has(r))continue;s.add(r),n.push(c(r))}return n}export{l as create,c as createCollapsible};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@data-slot/collapsible",
|
|
3
|
+
"version": "0.2.4",
|
|
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
|
+
"devDependencies": {
|
|
28
|
+
"@data-slot/core": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/bejamas/data-slot",
|
|
33
|
+
"directory": "packages/collapsible"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"headless",
|
|
37
|
+
"ui",
|
|
38
|
+
"collapsible",
|
|
39
|
+
"vanilla",
|
|
40
|
+
"data-slot"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT"
|
|
43
|
+
}
|