@data-slot/tabs 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 +219 -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 +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# @data-slot/tabs
|
|
2
|
+
|
|
3
|
+
Headless tabs component for vanilla JavaScript. Accessible, unstyled, tiny.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @data-slot/tabs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<div data-slot="tabs" data-default-value="one">
|
|
15
|
+
<div data-slot="tabs-list">
|
|
16
|
+
<button data-slot="tabs-trigger" data-value="one">Tab One</button>
|
|
17
|
+
<button data-slot="tabs-trigger" data-value="two">Tab Two</button>
|
|
18
|
+
<button data-slot="tabs-trigger" data-value="three">Tab Three</button>
|
|
19
|
+
<div data-slot="tabs-indicator"></div>
|
|
20
|
+
</div>
|
|
21
|
+
<div data-slot="tabs-content" data-value="one">Content One</div>
|
|
22
|
+
<div data-slot="tabs-content" data-value="two">Content Two</div>
|
|
23
|
+
<div data-slot="tabs-content" data-value="three">Content Three</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<script type="module">
|
|
27
|
+
import { create } from "@data-slot/tabs";
|
|
28
|
+
|
|
29
|
+
const controllers = create();
|
|
30
|
+
</script>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API
|
|
34
|
+
|
|
35
|
+
### `create(scope?)`
|
|
36
|
+
|
|
37
|
+
Auto-discover and bind all tabs instances in a scope (defaults to `document`).
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { create } from "@data-slot/tabs";
|
|
41
|
+
|
|
42
|
+
const controllers = create(); // Returns TabsController[]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `createTabs(root, options?)`
|
|
46
|
+
|
|
47
|
+
Create a controller for a specific element.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { createTabs } from "@data-slot/tabs";
|
|
51
|
+
|
|
52
|
+
const tabs = createTabs(element, {
|
|
53
|
+
defaultValue: "one",
|
|
54
|
+
orientation: "horizontal",
|
|
55
|
+
onValueChange: (value) => console.log(value),
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Options
|
|
60
|
+
|
|
61
|
+
| Option | Type | Default | Description |
|
|
62
|
+
|--------|------|---------|-------------|
|
|
63
|
+
| `defaultValue` | `string` | First trigger's value | Initial selected tab |
|
|
64
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Tab orientation for keyboard nav |
|
|
65
|
+
| `onValueChange` | `(value: string) => void` | `undefined` | Callback when selected tab changes |
|
|
66
|
+
|
|
67
|
+
### Controller
|
|
68
|
+
|
|
69
|
+
| Method/Property | Description |
|
|
70
|
+
|-----------------|-------------|
|
|
71
|
+
| `select(value)` | Select a tab by value |
|
|
72
|
+
| `value` | Currently selected value (readonly `string`) |
|
|
73
|
+
| `destroy()` | Cleanup all event listeners |
|
|
74
|
+
|
|
75
|
+
## Markup Structure
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<div data-slot="tabs" data-default-value="initial-tab">
|
|
79
|
+
<div data-slot="tabs-list">
|
|
80
|
+
<button data-slot="tabs-trigger" data-value="unique-id">Label</button>
|
|
81
|
+
<!-- Optional animated indicator -->
|
|
82
|
+
<div data-slot="tabs-indicator"></div>
|
|
83
|
+
</div>
|
|
84
|
+
<div data-slot="tabs-content" data-value="unique-id">Panel content</div>
|
|
85
|
+
</div>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Optional Slots
|
|
89
|
+
|
|
90
|
+
- `tabs-indicator` - Animated highlight that follows the selected tab
|
|
91
|
+
|
|
92
|
+
## Styling
|
|
93
|
+
|
|
94
|
+
### Basic Styling
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
/* Hidden panels */
|
|
98
|
+
[data-slot="tabs-content"][hidden] {
|
|
99
|
+
display: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Active trigger */
|
|
103
|
+
[data-slot="tabs-trigger"][aria-selected="true"] {
|
|
104
|
+
font-weight: bold;
|
|
105
|
+
border-bottom: 2px solid currentColor;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Or use data-state */
|
|
109
|
+
[data-slot="tabs-trigger"][data-state="active"] {
|
|
110
|
+
color: blue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
[data-slot="tabs-trigger"][data-state="inactive"] {
|
|
114
|
+
color: gray;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Animated Indicator
|
|
119
|
+
|
|
120
|
+
The indicator receives CSS variables for positioning:
|
|
121
|
+
|
|
122
|
+
```css
|
|
123
|
+
[data-slot="tabs-indicator"] {
|
|
124
|
+
position: absolute;
|
|
125
|
+
left: var(--active-tab-left);
|
|
126
|
+
width: var(--active-tab-width);
|
|
127
|
+
height: 2px;
|
|
128
|
+
background: currentColor;
|
|
129
|
+
transition: left 0.2s, width 0.2s;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* Vertical orientation */
|
|
133
|
+
[data-slot="tabs-list"][aria-orientation="vertical"] [data-slot="tabs-indicator"] {
|
|
134
|
+
top: var(--active-tab-top);
|
|
135
|
+
height: var(--active-tab-height);
|
|
136
|
+
width: 2px;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### CSS Variables
|
|
141
|
+
|
|
142
|
+
| Variable | Description |
|
|
143
|
+
|----------|-------------|
|
|
144
|
+
| `--active-tab-left` | Left offset of active trigger |
|
|
145
|
+
| `--active-tab-width` | Width of active trigger |
|
|
146
|
+
| `--active-tab-top` | Top offset of active trigger |
|
|
147
|
+
| `--active-tab-height` | Height of active trigger |
|
|
148
|
+
|
|
149
|
+
### Tailwind Example
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<div data-slot="tabs">
|
|
153
|
+
<div data-slot="tabs-list" class="relative flex border-b">
|
|
154
|
+
<button
|
|
155
|
+
data-slot="tabs-trigger"
|
|
156
|
+
data-value="one"
|
|
157
|
+
class="px-4 py-2 aria-selected:text-blue-600"
|
|
158
|
+
>
|
|
159
|
+
Tab One
|
|
160
|
+
</button>
|
|
161
|
+
<div
|
|
162
|
+
data-slot="tabs-indicator"
|
|
163
|
+
class="absolute bottom-0 h-0.5 bg-blue-600 transition-all"
|
|
164
|
+
style="left: var(--active-tab-left); width: var(--active-tab-width)"
|
|
165
|
+
></div>
|
|
166
|
+
</div>
|
|
167
|
+
<div data-slot="tabs-content" data-value="one" class="p-4">
|
|
168
|
+
Content
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Keyboard Navigation
|
|
174
|
+
|
|
175
|
+
### Horizontal Orientation
|
|
176
|
+
|
|
177
|
+
| Key | Action |
|
|
178
|
+
|-----|--------|
|
|
179
|
+
| `ArrowLeft` | Select previous tab |
|
|
180
|
+
| `ArrowRight` | Select next tab |
|
|
181
|
+
| `Home` | Select first tab |
|
|
182
|
+
| `End` | Select last tab |
|
|
183
|
+
|
|
184
|
+
### Vertical Orientation
|
|
185
|
+
|
|
186
|
+
| Key | Action |
|
|
187
|
+
|-----|--------|
|
|
188
|
+
| `ArrowUp` | Select previous tab |
|
|
189
|
+
| `ArrowDown` | Select next tab |
|
|
190
|
+
| `Home` | Select first tab |
|
|
191
|
+
| `End` | Select last tab |
|
|
192
|
+
|
|
193
|
+
## Accessibility
|
|
194
|
+
|
|
195
|
+
The component automatically handles:
|
|
196
|
+
|
|
197
|
+
- `role="tablist"` on list
|
|
198
|
+
- `role="tab"` on triggers
|
|
199
|
+
- `role="tabpanel"` on content
|
|
200
|
+
- `aria-orientation` on list
|
|
201
|
+
- `aria-selected` on triggers
|
|
202
|
+
- `aria-controls` linking triggers to panels
|
|
203
|
+
- `aria-labelledby` linking panels to triggers
|
|
204
|
+
- `tabindex` management (only selected tab is in tab order)
|
|
205
|
+
|
|
206
|
+
## Events
|
|
207
|
+
|
|
208
|
+
Listen for changes via custom events:
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
element.addEventListener("tabs:change", (e) => {
|
|
212
|
+
console.log("Selected tab:", e.detail.value);
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT
|
|
219
|
+
|
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`)),l=`a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])`;function u(e,t={}){let{onValueChange:n,orientation:r=`horizontal`,activationMode:i=`auto`}=t,a=(0,c.getPart)(e,`tabs-list`),o=(0,c.getParts)(e,`tabs-trigger`),s=(0,c.getParts)(e,`tabs-content`),u=(0,c.getPart)(e,`tabs-indicator`);if(!a||o.length===0)throw Error(`Tabs requires tabs-list and at least one tabs-trigger`);let f=new Map;for(let e of s){let t=(e.dataset.value||``).trim();t&&f.set(t,e)}let p=[],m=new Map,h=new Map;for(let e of o){let t=(e.dataset.value||``).trim();if(!t)continue;let n=e.hasAttribute(`disabled`)||e.dataset.disabled!==void 0||e.getAttribute(`aria-disabled`)===`true`,r=f.get(t),i={el:e,value:t,disabled:n,panel:r};p.push(i),m.set(t,i),h.set(e,i)}let g=p.filter(e=>!e.disabled),_=new Map;g.forEach((e,t)=>_.set(e.value,t));let v=g[0]?.value||``,y=e,b=(t.defaultValue??y.dataset.defaultValue??``).trim(),x=m.get(b),S=x&&!x.disabled?b:v,C=[];a.setAttribute(`role`,`tablist`),r===`vertical`&&(0,c.setAria)(a,`orientation`,`vertical`);for(let e of p){let{el:t,disabled:n,panel:r}=e;t.setAttribute(`role`,`tab`);let i=(0,c.ensureId)(t,`tab`);if(t.tagName===`BUTTON`&&!t.hasAttribute(`type`)&&(t.type=`button`),n&&(t.setAttribute(`aria-disabled`,`true`),t.tagName===`BUTTON`&&(t.disabled=!0)),r){r.setAttribute(`role`,`tabpanel`),r.tabIndex=-1;let e=(0,c.ensureId)(r,`tabpanel`);t.setAttribute(`aria-controls`,e),r.setAttribute(`aria-labelledby`,i)}}let w=()=>{if(!u)return;let e=m.get(S);if(!e)return;let t=a.getBoundingClientRect(),n=e.el.getBoundingClientRect();u.style.setProperty(`--active-tab-left`,`${n.left-t.left}px`),u.style.setProperty(`--active-tab-width`,`${n.width}px`),u.style.setProperty(`--active-tab-top`,`${n.top-t.top}px`),u.style.setProperty(`--active-tab-height`,`${n.height}px`)},T=(t,r=!1)=>{if(t=t.trim(),S===t&&!r)return;let i=m.get(t);if(!i||i.disabled)if(r){if(t=v,!t)return}else return;let a=S!==t;S=t;for(let e of p){let n=e.value===t;(0,c.setAria)(e.el,`selected`,n),e.el.tabIndex=n&&!e.disabled?0:-1,e.el.dataset.state=n?`active`:`inactive`}for(let e of s){let n=(e.dataset.value||``).trim();if(!n)continue;let r=n===t;e.hidden=!r,e.dataset.state=r?`active`:`inactive`}e.setAttribute(`data-value`,t),u&&w(),a&&!r&&((0,c.emit)(e,`tabs:change`,{value:t}),n?.(t))};if(T(S,!0),u){let e=()=>requestAnimationFrame(w);C.push((0,c.on)(window,`resize`,e)),C.push((0,c.on)(a,`scroll`,e));let t=new ResizeObserver(e);t.observe(a),C.push(()=>t.disconnect())}C.push((0,c.on)(a,`click`,e=>{let t=e.target.closest?.(`[data-slot="tabs-trigger"]`);if(!t)return;let n=h.get(t);n&&!n.disabled&&T(n.value)}));let E=r===`horizontal`,D=E?`ArrowLeft`:`ArrowUp`,O=E?`ArrowRight`:`ArrowDown`;C.push((0,c.on)(a,`keydown`,e=>{let t=e.target.closest?.(`[data-slot="tabs-trigger"]`);if(!t)return;let n=h.get(t);if(!n||g.length===0)return;if(e.key===`Enter`||e.key===` `){e.preventDefault(),n.disabled||T(n.value);return}if(E&&e.key===`ArrowDown`&&n.value===S){let t=n.panel;if(t){e.preventDefault();let n=t.querySelector(l);(n||t).focus();return}}let r=_.get(n.value)??-1;r===-1&&(r=_.get(S)??0);let a=r;switch(e.key){case D:a=r-1,a<0&&(a=g.length-1);break;case O:a=r+1,a>=g.length&&(a=0);break;case`Home`:a=0;break;case`End`:a=g.length-1;break;default:return}e.preventDefault();let o=g[a];o&&(o.el.focus(),i===`auto`&&T(o.value))}));let k={select:e=>T(e),get value(){return S},updateIndicator:w,destroy:()=>{C.forEach(e=>e()),C.length=0,d.delete(e)}};return k}const d=new WeakSet;function f(e=document){let t=[];for(let n of(0,c.getRoots)(e,`tabs`)){if(d.has(n))continue;d.add(n),t.push(u(n))}return t}exports.create=f,exports.createTabs=u;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface TabsOptions {
|
|
3
|
+
/** Initial selected tab value */
|
|
4
|
+
defaultValue?: string;
|
|
5
|
+
/** Callback when selected tab changes */
|
|
6
|
+
onValueChange?: (value: string) => void;
|
|
7
|
+
/** Tab orientation for keyboard navigation */
|
|
8
|
+
orientation?: "horizontal" | "vertical";
|
|
9
|
+
/**
|
|
10
|
+
* Activation mode for keyboard navigation
|
|
11
|
+
* - "auto": Arrow keys select tabs immediately (default)
|
|
12
|
+
* - "manual": Arrow keys move focus, Enter/Space activates
|
|
13
|
+
*/
|
|
14
|
+
activationMode?: "auto" | "manual";
|
|
15
|
+
}
|
|
16
|
+
interface TabsController {
|
|
17
|
+
/** Select a tab by value */
|
|
18
|
+
select(value: string): void;
|
|
19
|
+
/** Currently selected value */
|
|
20
|
+
readonly value: string;
|
|
21
|
+
/** Update indicator position (call after layout changes) */
|
|
22
|
+
updateIndicator(): void;
|
|
23
|
+
/** Cleanup all event listeners */
|
|
24
|
+
destroy(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a tabs controller for a root element
|
|
28
|
+
*
|
|
29
|
+
* Expected markup:
|
|
30
|
+
* ```html
|
|
31
|
+
* <div data-slot="tabs" data-default-value="two">
|
|
32
|
+
* <div data-slot="tabs-list">
|
|
33
|
+
* <button data-slot="tabs-trigger" data-value="one">Tab One</button>
|
|
34
|
+
* <button data-slot="tabs-trigger" data-value="two">Tab Two</button>
|
|
35
|
+
* </div>
|
|
36
|
+
* <div data-slot="tabs-content" data-value="one">Content One</div>
|
|
37
|
+
* <div data-slot="tabs-content" data-value="two">Content Two</div>
|
|
38
|
+
* </div>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function createTabs(root: Element, options?: TabsOptions): TabsController;
|
|
42
|
+
/**
|
|
43
|
+
* Find and bind all tabs components in a scope
|
|
44
|
+
* Returns array of controllers for programmatic access
|
|
45
|
+
*/
|
|
46
|
+
declare function create(scope?: ParentNode): TabsController[];
|
|
47
|
+
//#endregion
|
|
48
|
+
export { TabsController, TabsOptions, create, createTabs };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface TabsOptions {
|
|
3
|
+
/** Initial selected tab value */
|
|
4
|
+
defaultValue?: string;
|
|
5
|
+
/** Callback when selected tab changes */
|
|
6
|
+
onValueChange?: (value: string) => void;
|
|
7
|
+
/** Tab orientation for keyboard navigation */
|
|
8
|
+
orientation?: "horizontal" | "vertical";
|
|
9
|
+
/**
|
|
10
|
+
* Activation mode for keyboard navigation
|
|
11
|
+
* - "auto": Arrow keys select tabs immediately (default)
|
|
12
|
+
* - "manual": Arrow keys move focus, Enter/Space activates
|
|
13
|
+
*/
|
|
14
|
+
activationMode?: "auto" | "manual";
|
|
15
|
+
}
|
|
16
|
+
interface TabsController {
|
|
17
|
+
/** Select a tab by value */
|
|
18
|
+
select(value: string): void;
|
|
19
|
+
/** Currently selected value */
|
|
20
|
+
readonly value: string;
|
|
21
|
+
/** Update indicator position (call after layout changes) */
|
|
22
|
+
updateIndicator(): void;
|
|
23
|
+
/** Cleanup all event listeners */
|
|
24
|
+
destroy(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a tabs controller for a root element
|
|
28
|
+
*
|
|
29
|
+
* Expected markup:
|
|
30
|
+
* ```html
|
|
31
|
+
* <div data-slot="tabs" data-default-value="two">
|
|
32
|
+
* <div data-slot="tabs-list">
|
|
33
|
+
* <button data-slot="tabs-trigger" data-value="one">Tab One</button>
|
|
34
|
+
* <button data-slot="tabs-trigger" data-value="two">Tab Two</button>
|
|
35
|
+
* </div>
|
|
36
|
+
* <div data-slot="tabs-content" data-value="one">Content One</div>
|
|
37
|
+
* <div data-slot="tabs-content" data-value="two">Content Two</div>
|
|
38
|
+
* </div>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function createTabs(root: Element, options?: TabsOptions): TabsController;
|
|
42
|
+
/**
|
|
43
|
+
* Find and bind all tabs components in a scope
|
|
44
|
+
* Returns array of controllers for programmatic access
|
|
45
|
+
*/
|
|
46
|
+
declare function create(scope?: ParentNode): TabsController[];
|
|
47
|
+
//#endregion
|
|
48
|
+
export { TabsController, TabsOptions, create, createTabs };
|
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";const s=`a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])`;function c(i,c={}){let{onValueChange:u,orientation:d=`horizontal`,activationMode:f=`auto`}=c,p=n(i,`tabs-list`),m=r(i,`tabs-trigger`),h=r(i,`tabs-content`),g=n(i,`tabs-indicator`);if(!p||m.length===0)throw Error(`Tabs requires tabs-list and at least one tabs-trigger`);let _=new Map;for(let e of h){let t=(e.dataset.value||``).trim();t&&_.set(t,e)}let v=[],y=new Map,b=new Map;for(let e of m){let t=(e.dataset.value||``).trim();if(!t)continue;let n=e.hasAttribute(`disabled`)||e.dataset.disabled!==void 0||e.getAttribute(`aria-disabled`)===`true`,r=_.get(t),i={el:e,value:t,disabled:n,panel:r};v.push(i),y.set(t,i),b.set(e,i)}let x=v.filter(e=>!e.disabled),S=new Map;x.forEach((e,t)=>S.set(e.value,t));let C=x[0]?.value||``,w=i,T=(c.defaultValue??w.dataset.defaultValue??``).trim(),E=y.get(T),D=E&&!E.disabled?T:C,O=[];p.setAttribute(`role`,`tablist`),d===`vertical`&&o(p,`orientation`,`vertical`);for(let e of v){let{el:n,disabled:r,panel:i}=e;n.setAttribute(`role`,`tab`);let a=t(n,`tab`);if(n.tagName===`BUTTON`&&!n.hasAttribute(`type`)&&(n.type=`button`),r&&(n.setAttribute(`aria-disabled`,`true`),n.tagName===`BUTTON`&&(n.disabled=!0)),i){i.setAttribute(`role`,`tabpanel`),i.tabIndex=-1;let e=t(i,`tabpanel`);n.setAttribute(`aria-controls`,e),i.setAttribute(`aria-labelledby`,a)}}let k=()=>{if(!g)return;let e=y.get(D);if(!e)return;let t=p.getBoundingClientRect(),n=e.el.getBoundingClientRect();g.style.setProperty(`--active-tab-left`,`${n.left-t.left}px`),g.style.setProperty(`--active-tab-width`,`${n.width}px`),g.style.setProperty(`--active-tab-top`,`${n.top-t.top}px`),g.style.setProperty(`--active-tab-height`,`${n.height}px`)},A=(t,n=!1)=>{if(t=t.trim(),D===t&&!n)return;let r=y.get(t);if(!r||r.disabled)if(n){if(t=C,!t)return}else return;let a=D!==t;D=t;for(let e of v){let n=e.value===t;o(e.el,`selected`,n),e.el.tabIndex=n&&!e.disabled?0:-1,e.el.dataset.state=n?`active`:`inactive`}for(let e of h){let n=(e.dataset.value||``).trim();if(!n)continue;let r=n===t;e.hidden=!r,e.dataset.state=r?`active`:`inactive`}i.setAttribute(`data-value`,t),g&&k(),a&&!n&&(e(i,`tabs:change`,{value:t}),u?.(t))};if(A(D,!0),g){let e=()=>requestAnimationFrame(k);O.push(a(window,`resize`,e)),O.push(a(p,`scroll`,e));let t=new ResizeObserver(e);t.observe(p),O.push(()=>t.disconnect())}O.push(a(p,`click`,e=>{let t=e.target.closest?.(`[data-slot="tabs-trigger"]`);if(!t)return;let n=b.get(t);n&&!n.disabled&&A(n.value)}));let j=d===`horizontal`,M=j?`ArrowLeft`:`ArrowUp`,N=j?`ArrowRight`:`ArrowDown`;O.push(a(p,`keydown`,e=>{let t=e.target.closest?.(`[data-slot="tabs-trigger"]`);if(!t)return;let n=b.get(t);if(!n||x.length===0)return;if(e.key===`Enter`||e.key===` `){e.preventDefault(),n.disabled||A(n.value);return}if(j&&e.key===`ArrowDown`&&n.value===D){let t=n.panel;if(t){e.preventDefault();let n=t.querySelector(s);(n||t).focus();return}}let r=S.get(n.value)??-1;r===-1&&(r=S.get(D)??0);let i=r;switch(e.key){case M:i=r-1,i<0&&(i=x.length-1);break;case N:i=r+1,i>=x.length&&(i=0);break;case`Home`:i=0;break;case`End`:i=x.length-1;break;default:return}e.preventDefault();let a=x[i];a&&(a.el.focus(),f===`auto`&&A(a.value))}));let P={select:e=>A(e),get value(){return D},updateIndicator:k,destroy:()=>{O.forEach(e=>e()),O.length=0,l.delete(i)}};return P}const l=new WeakSet;function u(e=document){let t=[];for(let n of i(e,`tabs`)){if(l.has(n))continue;l.add(n),t.push(c(n))}return t}export{u as create,c as createTabs};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@data-slot/tabs",
|
|
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": ["dist"],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsdown"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@data-slot/core": "workspace:*"
|
|
27
|
+
},
|
|
28
|
+
"keywords": ["headless", "ui", "tabs", "vanilla", "data-slot"],
|
|
29
|
+
"license": "MIT"
|
|
30
|
+
}
|