@data-slot/toggle-group 0.2.10
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 +153 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @data-slot/toggle-group
|
|
2
|
+
|
|
3
|
+
A group of toggle buttons with shared state, supporting single or multiple selection.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @data-slot/toggle-group
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Markup
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<!-- Single selection (default) -->
|
|
17
|
+
<div data-slot="toggle-group" data-default-value="center">
|
|
18
|
+
<button data-slot="toggle-group-item" data-value="left">Left</button>
|
|
19
|
+
<button data-slot="toggle-group-item" data-value="center">Center</button>
|
|
20
|
+
<button data-slot="toggle-group-item" data-value="right">Right</button>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<!-- Multiple selection -->
|
|
24
|
+
<div data-slot="toggle-group" data-multiple data-default-value="bold italic">
|
|
25
|
+
<button data-slot="toggle-group-item" data-value="bold">B</button>
|
|
26
|
+
<button data-slot="toggle-group-item" data-value="italic">I</button>
|
|
27
|
+
<button data-slot="toggle-group-item" data-value="underline">U</button>
|
|
28
|
+
</div>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### JavaScript
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
import { create, createToggleGroup } from "@data-slot/toggle-group";
|
|
35
|
+
|
|
36
|
+
// Auto-discover and bind all toggle-group elements
|
|
37
|
+
const controllers = create();
|
|
38
|
+
|
|
39
|
+
// Or target a specific element
|
|
40
|
+
const group = createToggleGroup(element, {
|
|
41
|
+
defaultValue: "center",
|
|
42
|
+
multiple: false,
|
|
43
|
+
orientation: "horizontal",
|
|
44
|
+
loop: true,
|
|
45
|
+
onValueChange: (value) => console.log(value),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Programmatic control
|
|
49
|
+
group.setValue("left");
|
|
50
|
+
group.toggle("bold");
|
|
51
|
+
console.log(group.value); // ["left"]
|
|
52
|
+
|
|
53
|
+
// Cleanup
|
|
54
|
+
group.destroy();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Options
|
|
58
|
+
|
|
59
|
+
| Option | Type | Default | Description |
|
|
60
|
+
|--------|------|---------|-------------|
|
|
61
|
+
| `defaultValue` | `string \| string[]` | `[]` | Initial selected value(s). For multiple values, use array or space-separated string. |
|
|
62
|
+
| `multiple` | `boolean` | `false` | Allow multiple selections. |
|
|
63
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Orientation for keyboard navigation. |
|
|
64
|
+
| `loop` | `boolean` | `true` | Wrap keyboard focus at ends. |
|
|
65
|
+
| `disabled` | `boolean` | `false` | Disable the entire group. |
|
|
66
|
+
| `onValueChange` | `(value: string[]) => void` | - | Callback when selection changes. |
|
|
67
|
+
|
|
68
|
+
## Data Attributes
|
|
69
|
+
|
|
70
|
+
### Root Element
|
|
71
|
+
|
|
72
|
+
| Attribute | Description |
|
|
73
|
+
|-----------|-------------|
|
|
74
|
+
| `data-slot="toggle-group"` | Required. Identifies the root element. |
|
|
75
|
+
| `data-default-value` | Initial selected value(s). Space-separated for multiple values. |
|
|
76
|
+
| `data-multiple` | Enable multiple selection mode. |
|
|
77
|
+
| `data-orientation` | `"horizontal"` or `"vertical"` for keyboard navigation. |
|
|
78
|
+
| `data-disabled` | Disable the entire group. |
|
|
79
|
+
|
|
80
|
+
### Item Elements
|
|
81
|
+
|
|
82
|
+
| Attribute | Description |
|
|
83
|
+
|-----------|-------------|
|
|
84
|
+
| `data-slot="toggle-group-item"` | Required. Identifies an item. |
|
|
85
|
+
| `data-value` | Required. The value associated with this item. |
|
|
86
|
+
| `data-disabled` | Disable this specific item. |
|
|
87
|
+
|
|
88
|
+
### State Attributes (set by component)
|
|
89
|
+
|
|
90
|
+
| Attribute | Values | Description |
|
|
91
|
+
|-----------|--------|-------------|
|
|
92
|
+
| `aria-pressed` | `"true"` \| `"false"` | Whether item is pressed. |
|
|
93
|
+
| `data-state` | `"on"` \| `"off"` | Visual state for styling. |
|
|
94
|
+
| `data-value` (on root) | Space-separated values | Current selection. |
|
|
95
|
+
|
|
96
|
+
## Events
|
|
97
|
+
|
|
98
|
+
### Outbound
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Listen for changes
|
|
102
|
+
root.addEventListener("toggle-group:change", (e) => {
|
|
103
|
+
console.log(e.detail.value); // string[]
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Inbound
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Set selection from outside
|
|
111
|
+
root.dispatchEvent(new CustomEvent("toggle-group:set", {
|
|
112
|
+
detail: { value: "bold" }
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
// Or with array
|
|
116
|
+
root.dispatchEvent(new CustomEvent("toggle-group:set", {
|
|
117
|
+
detail: ["bold", "italic"]
|
|
118
|
+
}));
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Keyboard Navigation
|
|
122
|
+
|
|
123
|
+
| Key | Action |
|
|
124
|
+
|-----|--------|
|
|
125
|
+
| `ArrowRight` / `ArrowDown` | Move to next item (based on orientation) |
|
|
126
|
+
| `ArrowLeft` / `ArrowUp` | Move to previous item (based on orientation) |
|
|
127
|
+
| `Home` | Move to first item |
|
|
128
|
+
| `End` | Move to last item |
|
|
129
|
+
| `Enter` / `Space` | Toggle current item |
|
|
130
|
+
|
|
131
|
+
## Styling
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
/* Style pressed items */
|
|
135
|
+
[data-slot="toggle-group-item"][data-state="on"] {
|
|
136
|
+
background: #333;
|
|
137
|
+
color: white;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Style disabled items */
|
|
141
|
+
[data-slot="toggle-group-item"][aria-disabled="true"] {
|
|
142
|
+
opacity: 0.5;
|
|
143
|
+
cursor: not-allowed;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Accessibility
|
|
148
|
+
|
|
149
|
+
- Root has `role="group"`
|
|
150
|
+
- Items have `aria-pressed` attribute
|
|
151
|
+
- Disabled items have `aria-disabled="true"` and native `disabled`
|
|
152
|
+
- Supports roving tabindex for keyboard navigation
|
|
153
|
+
- Add `aria-label` or `aria-labelledby` to root for context
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)],t=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)],n=new WeakMap;function r(e,t,r){if(typeof process<`u`&&process.env?.NODE_ENV===`production`)return;let i=n.get(e);i||(i=new Set,n.set(e,i)),!i.has(t)&&(i.add(t),console.warn(`[@data-slot] ${r}`))}function i(e){let t=`data-${e.replace(/([A-Z])/g,`-$1`).toLowerCase()}`,n=`data-${e}`;return t===n?[t]:[t,n]}function a(e,t){for(let n of i(t))if(e.hasAttribute(n))return e.getAttribute(n);return null}function o(e,t){return i(t).some(t=>e.hasAttribute(t))}const s=new Set([``,`true`,`1`,`yes`]),c=new Set([`false`,`0`,`no`]);function l(e,t){if(!o(e,t))return;let n=a(e,t);if(n===null)return;let i=n.toLowerCase();if(s.has(i))return!0;if(c.has(i))return!1;r(e,t,`Invalid boolean value "${n}" for data-${t}. Expected: true/false/1/0/yes/no or empty.`)}function u(e,t){if(o(e,t))return a(e,t)??void 0}function d(e,t,n){let i=a(e,t);if(i!==null){if(n.includes(i))return i;r(e,t,`Invalid value "${i}" for data-${t}. Expected one of: ${n.join(`, `)}.`)}}let f=0;const p=(e,t)=>e.id||=`${t}-${++f}`,m=(e,t,n)=>{n===null?e.removeAttribute(`aria-${t}`):e.setAttribute(`aria-${t}`,String(n))};function h(e,t,n,r){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)}const g=(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n})),_=[`horizontal`,`vertical`];function v(t,n={}){let r=e(t,`toggle-group-item`);if(r.length===0)throw Error(`ToggleGroup requires at least one toggle-group-item`);let i=n.multiple??l(t,`multiple`)??!1,a=n.orientation??d(t,`orientation`,_)??`horizontal`,o=n.loop??l(t,`loop`)??!0,s=n.disabled??l(t,`disabled`)??!1,c=n.onValueChange,f=()=>{if(n.defaultValue!==void 0)return Array.isArray(n.defaultValue)?n.defaultValue:n.defaultValue.split(/\s+/).filter(Boolean);let e=u(t,`defaultValue`);return e?e.split(/\s+/).filter(Boolean):[]},v=f(),b=[],x=new Map,S=new Map,C=[];for(let e of r){let t=(e.dataset.value||``).trim();if(!t){C.push(e);continue}let n=e.hasAttribute(`disabled`)||e.hasAttribute(`data-disabled`)||e.getAttribute(`aria-disabled`)===`true`,r={el:e,value:t,disabled:n};b.push(r),x.set(t,r),S.set(e,r)}for(let e of C)e.tabIndex=-1,e.setAttribute(`aria-disabled`,`true`);if(b.length===0)throw Error(`ToggleGroup requires at least one toggle-group-item with a data-value attribute`);let w=new Set;for(let e of v)if(x.has(e)&&(w.add(e),!i))break;let T=[],E=()=>t.hasAttribute(`disabled`)||t.hasAttribute(`data-disabled`)||t.getAttribute(`aria-disabled`)===`true`,D=e=>e.el.hasAttribute(`disabled`)||e.el.hasAttribute(`data-disabled`)||e.el.getAttribute(`aria-disabled`)===`true`,O=()=>b.filter(e=>!D(e));t.setAttribute(`role`,`group`),s&&t.setAttribute(`aria-disabled`,`true`),a===`vertical`&&m(t,`orientation`,`vertical`),i&&(t.dataset.multiple=``);for(let e of b){let{el:t,disabled:n}=e;p(t,`toggle-group-item`),t.tagName===`BUTTON`&&!t.hasAttribute(`type`)&&(t.type=`button`),n&&(t.setAttribute(`aria-disabled`,`true`),t.tagName===`BUTTON`&&(t.disabled=!0))}let k=(e,n=!1)=>{let r=!n&&(e.size!==w.size||[...e].some(e=>!w.has(e)));w=e;for(let e of b){let t=w.has(e.value);m(e.el,`pressed`,t),e.el.dataset.state=t?`on`:`off`}A();let i=[...w];t.setAttribute(`data-value`,i.join(` `)),r&&(g(t,`toggle-group:change`,{value:i}),c?.(i))},A=()=>{let e=O(),t;for(let n of e)if(w.has(n.value)){t=n;break}!t&&e.length>0&&(t=e[0]);for(let e of b)e.el.tabIndex=e===t?0:-1};k(w,!0);let j=e=>{let t=new Set(w);i?t.has(e)?t.delete(e):t.add(e):t.has(e)?t.clear():(t.clear(),t.add(e)),k(t)},M=e=>{let t=Array.isArray(e)?e:e.split(/\s+/).filter(Boolean),n=new Set;for(let e of t)if(x.has(e)&&(n.add(e),!i))break;k(n)};T.push(h(t,`click`,e=>{if(E())return;let t=e.target.closest?.(`[data-slot="toggle-group-item"]`);if(!t)return;let n=S.get(t);!n||D(n)||j(n.value)}));let N=a===`horizontal`,P=N?`ArrowLeft`:`ArrowUp`,F=N?`ArrowRight`:`ArrowDown`;return T.push(h(t,`keydown`,e=>{if(E())return;let t=e.target.closest?.(`[data-slot="toggle-group-item"]`);if(!t)return;let n=S.get(t);if(!n)return;if(e.key===`Enter`||e.key===` `){D(n)&&e.preventDefault();return}let r=O();if(r.length===0)return;let i=r.findIndex(e=>e.el===t);i===-1&&(i=0);let a=i;switch(e.key){case P:a=i-1,a<0&&(a=o?r.length-1:0);break;case F:a=i+1,a>=r.length&&(a=o?0:r.length-1);break;case`Home`:a=0;break;case`End`:a=r.length-1;break;default:return}e.preventDefault();let s=r[a];if(s){for(let e of b)e.el.tabIndex=e===s?0:-1;s.el.focus()}})),T.push(h(t,`toggle-group:set`,e=>{if(E())return;let t=e,n=t.detail,r;if(typeof n==`string`)r=n;else if(Array.isArray(n))r=n;else if(n&&typeof n==`object`&&`value`in n)r=n.value;else return;M(r)})),{setValue:e=>M(e),toggle:e=>j(e),get value(){return[...w]},destroy:()=>{T.forEach(e=>e()),T.length=0,y.delete(t)}}}const y=new WeakSet;function b(e=document){let n=[];for(let r of t(e,`toggle-group`)){if(y.has(r))continue;y.add(r),n.push(v(r))}return n}exports.create=b,exports.createToggleGroup=v;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface ToggleGroupOptions {
|
|
3
|
+
/** Initial selected value(s) - string for single, string[] for multiple */
|
|
4
|
+
defaultValue?: string | string[];
|
|
5
|
+
/** Allow multiple selections (default: false) */
|
|
6
|
+
multiple?: boolean;
|
|
7
|
+
/** Orientation for keyboard navigation */
|
|
8
|
+
orientation?: "horizontal" | "vertical";
|
|
9
|
+
/** Wrap keyboard focus at ends (default: true) */
|
|
10
|
+
loop?: boolean;
|
|
11
|
+
/** Disabled state for entire group */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Callback when value changes */
|
|
14
|
+
onValueChange?: (value: string[]) => void;
|
|
15
|
+
}
|
|
16
|
+
interface ToggleGroupController {
|
|
17
|
+
/** Set value(s) programmatically */
|
|
18
|
+
setValue(value: string | string[]): void;
|
|
19
|
+
/** Toggle a specific item */
|
|
20
|
+
toggle(value: string): void;
|
|
21
|
+
/** Current selected value(s) */
|
|
22
|
+
readonly value: string[];
|
|
23
|
+
/** Cleanup all event listeners */
|
|
24
|
+
destroy(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a toggle-group controller for a root element
|
|
28
|
+
*
|
|
29
|
+
* ## Events
|
|
30
|
+
* - **Outbound** `toggle-group:change` (on root): Fires when selection changes.
|
|
31
|
+
* `event.detail: { value: string[] }`
|
|
32
|
+
* - **Inbound** `toggle-group:set` (on root): Set selection programmatically.
|
|
33
|
+
* `event.detail: { value: string | string[] } | string | string[]`
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```js
|
|
37
|
+
* // Listen for changes
|
|
38
|
+
* root.addEventListener("toggle-group:change", (e) => console.log(e.detail.value));
|
|
39
|
+
* // Set selection from outside
|
|
40
|
+
* root.dispatchEvent(new CustomEvent("toggle-group:set", { detail: { value: "bold" } }));
|
|
41
|
+
* root.dispatchEvent(new CustomEvent("toggle-group:set", { detail: ["bold", "italic"] }));
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Expected markup:
|
|
45
|
+
* ```html
|
|
46
|
+
* <div data-slot="toggle-group" data-default-value="center">
|
|
47
|
+
* <button data-slot="toggle-group-item" data-value="left">Left</button>
|
|
48
|
+
* <button data-slot="toggle-group-item" data-value="center">Center</button>
|
|
49
|
+
* <button data-slot="toggle-group-item" data-value="right">Right</button>
|
|
50
|
+
* </div>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function createToggleGroup(root: Element, options?: ToggleGroupOptions): ToggleGroupController;
|
|
54
|
+
/**
|
|
55
|
+
* Find and bind all toggle-group instances in a scope
|
|
56
|
+
* Returns array of controllers for programmatic access
|
|
57
|
+
*/
|
|
58
|
+
declare function create(scope?: ParentNode): ToggleGroupController[];
|
|
59
|
+
//#endregion
|
|
60
|
+
export { ToggleGroupController, ToggleGroupOptions, create, createToggleGroup };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface ToggleGroupOptions {
|
|
3
|
+
/** Initial selected value(s) - string for single, string[] for multiple */
|
|
4
|
+
defaultValue?: string | string[];
|
|
5
|
+
/** Allow multiple selections (default: false) */
|
|
6
|
+
multiple?: boolean;
|
|
7
|
+
/** Orientation for keyboard navigation */
|
|
8
|
+
orientation?: "horizontal" | "vertical";
|
|
9
|
+
/** Wrap keyboard focus at ends (default: true) */
|
|
10
|
+
loop?: boolean;
|
|
11
|
+
/** Disabled state for entire group */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Callback when value changes */
|
|
14
|
+
onValueChange?: (value: string[]) => void;
|
|
15
|
+
}
|
|
16
|
+
interface ToggleGroupController {
|
|
17
|
+
/** Set value(s) programmatically */
|
|
18
|
+
setValue(value: string | string[]): void;
|
|
19
|
+
/** Toggle a specific item */
|
|
20
|
+
toggle(value: string): void;
|
|
21
|
+
/** Current selected value(s) */
|
|
22
|
+
readonly value: string[];
|
|
23
|
+
/** Cleanup all event listeners */
|
|
24
|
+
destroy(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a toggle-group controller for a root element
|
|
28
|
+
*
|
|
29
|
+
* ## Events
|
|
30
|
+
* - **Outbound** `toggle-group:change` (on root): Fires when selection changes.
|
|
31
|
+
* `event.detail: { value: string[] }`
|
|
32
|
+
* - **Inbound** `toggle-group:set` (on root): Set selection programmatically.
|
|
33
|
+
* `event.detail: { value: string | string[] } | string | string[]`
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```js
|
|
37
|
+
* // Listen for changes
|
|
38
|
+
* root.addEventListener("toggle-group:change", (e) => console.log(e.detail.value));
|
|
39
|
+
* // Set selection from outside
|
|
40
|
+
* root.dispatchEvent(new CustomEvent("toggle-group:set", { detail: { value: "bold" } }));
|
|
41
|
+
* root.dispatchEvent(new CustomEvent("toggle-group:set", { detail: ["bold", "italic"] }));
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Expected markup:
|
|
45
|
+
* ```html
|
|
46
|
+
* <div data-slot="toggle-group" data-default-value="center">
|
|
47
|
+
* <button data-slot="toggle-group-item" data-value="left">Left</button>
|
|
48
|
+
* <button data-slot="toggle-group-item" data-value="center">Center</button>
|
|
49
|
+
* <button data-slot="toggle-group-item" data-value="right">Right</button>
|
|
50
|
+
* </div>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function createToggleGroup(root: Element, options?: ToggleGroupOptions): ToggleGroupController;
|
|
54
|
+
/**
|
|
55
|
+
* Find and bind all toggle-group instances in a scope
|
|
56
|
+
* Returns array of controllers for programmatic access
|
|
57
|
+
*/
|
|
58
|
+
declare function create(scope?: ParentNode): ToggleGroupController[];
|
|
59
|
+
//#endregion
|
|
60
|
+
export { ToggleGroupController, ToggleGroupOptions, create, createToggleGroup };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)],t=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)],n=new WeakMap;function r(e,t,r){if(typeof process<`u`&&process.env?.NODE_ENV===`production`)return;let i=n.get(e);i||(i=new Set,n.set(e,i)),!i.has(t)&&(i.add(t),console.warn(`[@data-slot] ${r}`))}function i(e){let t=`data-${e.replace(/([A-Z])/g,`-$1`).toLowerCase()}`,n=`data-${e}`;return t===n?[t]:[t,n]}function a(e,t){for(let n of i(t))if(e.hasAttribute(n))return e.getAttribute(n);return null}function o(e,t){return i(t).some(t=>e.hasAttribute(t))}const s=new Set([``,`true`,`1`,`yes`]),c=new Set([`false`,`0`,`no`]);function l(e,t){if(!o(e,t))return;let n=a(e,t);if(n===null)return;let i=n.toLowerCase();if(s.has(i))return!0;if(c.has(i))return!1;r(e,t,`Invalid boolean value "${n}" for data-${t}. Expected: true/false/1/0/yes/no or empty.`)}function u(e,t){if(o(e,t))return a(e,t)??void 0}function d(e,t,n){let i=a(e,t);if(i!==null){if(n.includes(i))return i;r(e,t,`Invalid value "${i}" for data-${t}. Expected one of: ${n.join(`, `)}.`)}}let f=0;const p=(e,t)=>e.id||=`${t}-${++f}`,m=(e,t,n)=>{n===null?e.removeAttribute(`aria-${t}`):e.setAttribute(`aria-${t}`,String(n))};function h(e,t,n,r){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)}const g=(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n})),_=[`horizontal`,`vertical`];function v(t,n={}){let r=e(t,`toggle-group-item`);if(r.length===0)throw Error(`ToggleGroup requires at least one toggle-group-item`);let i=n.multiple??l(t,`multiple`)??!1,a=n.orientation??d(t,`orientation`,_)??`horizontal`,o=n.loop??l(t,`loop`)??!0,s=n.disabled??l(t,`disabled`)??!1,c=n.onValueChange,f=()=>{if(n.defaultValue!==void 0)return Array.isArray(n.defaultValue)?n.defaultValue:n.defaultValue.split(/\s+/).filter(Boolean);let e=u(t,`defaultValue`);return e?e.split(/\s+/).filter(Boolean):[]},v=f(),b=[],x=new Map,S=new Map,C=[];for(let e of r){let t=(e.dataset.value||``).trim();if(!t){C.push(e);continue}let n=e.hasAttribute(`disabled`)||e.hasAttribute(`data-disabled`)||e.getAttribute(`aria-disabled`)===`true`,r={el:e,value:t,disabled:n};b.push(r),x.set(t,r),S.set(e,r)}for(let e of C)e.tabIndex=-1,e.setAttribute(`aria-disabled`,`true`);if(b.length===0)throw Error(`ToggleGroup requires at least one toggle-group-item with a data-value attribute`);let w=new Set;for(let e of v)if(x.has(e)&&(w.add(e),!i))break;let T=[],E=()=>t.hasAttribute(`disabled`)||t.hasAttribute(`data-disabled`)||t.getAttribute(`aria-disabled`)===`true`,D=e=>e.el.hasAttribute(`disabled`)||e.el.hasAttribute(`data-disabled`)||e.el.getAttribute(`aria-disabled`)===`true`,O=()=>b.filter(e=>!D(e));t.setAttribute(`role`,`group`),s&&t.setAttribute(`aria-disabled`,`true`),a===`vertical`&&m(t,`orientation`,`vertical`),i&&(t.dataset.multiple=``);for(let e of b){let{el:t,disabled:n}=e;p(t,`toggle-group-item`),t.tagName===`BUTTON`&&!t.hasAttribute(`type`)&&(t.type=`button`),n&&(t.setAttribute(`aria-disabled`,`true`),t.tagName===`BUTTON`&&(t.disabled=!0))}let k=(e,n=!1)=>{let r=!n&&(e.size!==w.size||[...e].some(e=>!w.has(e)));w=e;for(let e of b){let t=w.has(e.value);m(e.el,`pressed`,t),e.el.dataset.state=t?`on`:`off`}A();let i=[...w];t.setAttribute(`data-value`,i.join(` `)),r&&(g(t,`toggle-group:change`,{value:i}),c?.(i))},A=()=>{let e=O(),t;for(let n of e)if(w.has(n.value)){t=n;break}!t&&e.length>0&&(t=e[0]);for(let e of b)e.el.tabIndex=e===t?0:-1};k(w,!0);let j=e=>{let t=new Set(w);i?t.has(e)?t.delete(e):t.add(e):t.has(e)?t.clear():(t.clear(),t.add(e)),k(t)},M=e=>{let t=Array.isArray(e)?e:e.split(/\s+/).filter(Boolean),n=new Set;for(let e of t)if(x.has(e)&&(n.add(e),!i))break;k(n)};T.push(h(t,`click`,e=>{if(E())return;let t=e.target.closest?.(`[data-slot="toggle-group-item"]`);if(!t)return;let n=S.get(t);!n||D(n)||j(n.value)}));let N=a===`horizontal`,P=N?`ArrowLeft`:`ArrowUp`,F=N?`ArrowRight`:`ArrowDown`;return T.push(h(t,`keydown`,e=>{if(E())return;let t=e.target.closest?.(`[data-slot="toggle-group-item"]`);if(!t)return;let n=S.get(t);if(!n)return;if(e.key===`Enter`||e.key===` `){D(n)&&e.preventDefault();return}let r=O();if(r.length===0)return;let i=r.findIndex(e=>e.el===t);i===-1&&(i=0);let a=i;switch(e.key){case P:a=i-1,a<0&&(a=o?r.length-1:0);break;case F:a=i+1,a>=r.length&&(a=o?0:r.length-1);break;case`Home`:a=0;break;case`End`:a=r.length-1;break;default:return}e.preventDefault();let s=r[a];if(s){for(let e of b)e.el.tabIndex=e===s?0:-1;s.el.focus()}})),T.push(h(t,`toggle-group:set`,e=>{if(E())return;let t=e,n=t.detail,r;if(typeof n==`string`)r=n;else if(Array.isArray(n))r=n;else if(n&&typeof n==`object`&&`value`in n)r=n.value;else return;M(r)})),{setValue:e=>M(e),toggle:e=>j(e),get value(){return[...w]},destroy:()=>{T.forEach(e=>e()),T.length=0,y.delete(t)}}}const y=new WeakSet;function b(e=document){let n=[];for(let r of t(e,`toggle-group`)){if(y.has(r))continue;y.add(r),n.push(v(r))}return n}export{b as create,v as createToggleGroup};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@data-slot/toggle-group",
|
|
3
|
+
"version": "0.2.10",
|
|
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/toggle-group"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"headless",
|
|
37
|
+
"ui",
|
|
38
|
+
"toggle-group",
|
|
39
|
+
"vanilla",
|
|
40
|
+
"data-slot"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT"
|
|
43
|
+
}
|