@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 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;
@@ -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 };
@@ -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
+ }