@data-slot/slider 0.2.9
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 +156 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +74 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# @data-slot/slider
|
|
2
|
+
|
|
3
|
+
Headless slider component for vanilla JavaScript. Supports single value and range sliders with full keyboard navigation and ARIA compliance.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @data-slot/slider
|
|
9
|
+
# or
|
|
10
|
+
npm install @data-slot/slider
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### HTML Structure
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<!-- Single value slider -->
|
|
19
|
+
<div data-slot="slider" data-default-value="50">
|
|
20
|
+
<div class="slider-control">
|
|
21
|
+
<div data-slot="slider-track">
|
|
22
|
+
<div data-slot="slider-range"></div>
|
|
23
|
+
</div>
|
|
24
|
+
<div data-slot="slider-thumb"></div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- Range slider (two thumbs) -->
|
|
29
|
+
<div data-slot="slider" data-default-value="25,75">
|
|
30
|
+
<div class="slider-control">
|
|
31
|
+
<div data-slot="slider-track">
|
|
32
|
+
<div data-slot="slider-range"></div>
|
|
33
|
+
</div>
|
|
34
|
+
<div data-slot="slider-thumb"></div>
|
|
35
|
+
<div data-slot="slider-thumb"></div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### JavaScript
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
import { create, createSlider } from "@data-slot/slider";
|
|
44
|
+
|
|
45
|
+
// Auto-discover and bind all [data-slot="slider"] elements
|
|
46
|
+
const controllers = create();
|
|
47
|
+
|
|
48
|
+
// Or target a specific element
|
|
49
|
+
const slider = createSlider(element, {
|
|
50
|
+
defaultValue: 50,
|
|
51
|
+
min: 0,
|
|
52
|
+
max: 100,
|
|
53
|
+
step: 1,
|
|
54
|
+
onValueChange: (value) => console.log("Changed:", value),
|
|
55
|
+
onValueCommit: (value) => console.log("Committed:", value),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Programmatic control
|
|
59
|
+
slider.setValue(75);
|
|
60
|
+
console.log(slider.value); // 75
|
|
61
|
+
|
|
62
|
+
// Cleanup
|
|
63
|
+
slider.destroy();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Data Attributes
|
|
67
|
+
|
|
68
|
+
| Attribute | Description | Default |
|
|
69
|
+
|-----------|-------------|---------|
|
|
70
|
+
| `data-default-value` | Initial value (`50` or `25,75` for range) | `min` |
|
|
71
|
+
| `data-min` | Minimum value | `0` |
|
|
72
|
+
| `data-max` | Maximum value | `100` |
|
|
73
|
+
| `data-step` | Step increment | `1` |
|
|
74
|
+
| `data-large-step` | Large step for PageUp/PageDown | `step * 10` |
|
|
75
|
+
| `data-orientation` | `horizontal` or `vertical` | `horizontal` |
|
|
76
|
+
| `data-disabled` | Disable the slider | - |
|
|
77
|
+
|
|
78
|
+
## Events
|
|
79
|
+
|
|
80
|
+
### Outbound Events (on root)
|
|
81
|
+
|
|
82
|
+
| Event | Detail | Description |
|
|
83
|
+
|-------|--------|-------------|
|
|
84
|
+
| `slider:change` | `{ value: number \| [number, number] }` | Fires during value changes |
|
|
85
|
+
| `slider:commit` | `{ value: number \| [number, number] }` | Fires when interaction ends |
|
|
86
|
+
|
|
87
|
+
### Inbound Events (on root)
|
|
88
|
+
|
|
89
|
+
| Event | Detail | Description |
|
|
90
|
+
|-------|--------|-------------|
|
|
91
|
+
| `slider:set` | `{ value: number \| [number, number] }` | Set value programmatically |
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
// Listen for changes
|
|
95
|
+
root.addEventListener("slider:change", (e) => {
|
|
96
|
+
console.log("Value:", e.detail.value);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Set value from outside
|
|
100
|
+
root.dispatchEvent(new CustomEvent("slider:set", {
|
|
101
|
+
detail: { value: 50 }
|
|
102
|
+
}));
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Keyboard Navigation
|
|
106
|
+
|
|
107
|
+
| Key | Action |
|
|
108
|
+
|-----|--------|
|
|
109
|
+
| `ArrowRight` / `ArrowUp` | Increase by step |
|
|
110
|
+
| `ArrowLeft` / `ArrowDown` | Decrease by step |
|
|
111
|
+
| `PageUp` | Increase by largeStep |
|
|
112
|
+
| `PageDown` | Decrease by largeStep |
|
|
113
|
+
| `Home` | Set to min |
|
|
114
|
+
| `End` | Set to max |
|
|
115
|
+
| `Shift+Arrow` | Move by largeStep |
|
|
116
|
+
|
|
117
|
+
## Styling
|
|
118
|
+
|
|
119
|
+
The component sets data attributes and inline styles for CSS hooks:
|
|
120
|
+
|
|
121
|
+
```css
|
|
122
|
+
/* Root state */
|
|
123
|
+
[data-slot="slider"][data-orientation="horizontal"] { ... }
|
|
124
|
+
[data-slot="slider"][data-orientation="vertical"] { ... }
|
|
125
|
+
[data-slot="slider"][data-disabled] { ... }
|
|
126
|
+
[data-slot="slider"][data-dragging] { ... }
|
|
127
|
+
|
|
128
|
+
/* Thumb positioning (set automatically) */
|
|
129
|
+
[data-slot="slider-thumb"] {
|
|
130
|
+
position: absolute;
|
|
131
|
+
/* left: X% (horizontal) or bottom: X% (vertical) */
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Range positioning (set automatically) */
|
|
135
|
+
[data-slot="slider-range"] {
|
|
136
|
+
position: absolute;
|
|
137
|
+
/* left + width (horizontal) or bottom + height (vertical) */
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Thumb dragging state */
|
|
141
|
+
[data-slot="slider-thumb"][data-dragging] { ... }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Accessibility
|
|
145
|
+
|
|
146
|
+
Each thumb element receives:
|
|
147
|
+
- `role="slider"`
|
|
148
|
+
- `tabindex="0"`
|
|
149
|
+
- `aria-valuemin` / `aria-valuemax` / `aria-valuenow`
|
|
150
|
+
- `aria-orientation`
|
|
151
|
+
- `aria-disabled` (when disabled)
|
|
152
|
+
- `aria-label` (from `data-label` or auto-generated for range)
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
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}"]`)],n=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)],r=new WeakMap;function i(e,t,n){if(typeof process<`u`&&process.env?.NODE_ENV===`production`)return;let i=r.get(e);i||(i=new Set,r.set(e,i)),!i.has(t)&&(i.add(t),console.warn(`[@data-slot] ${n}`))}function a(e){let t=`data-${e.replace(/([A-Z])/g,`-$1`).toLowerCase()}`,n=`data-${e}`;return t===n?[t]:[t,n]}function o(e,t){for(let n of a(t))if(e.hasAttribute(n))return e.getAttribute(n);return null}function s(e,t){return a(t).some(t=>e.hasAttribute(t))}const c=new Set([``,`true`,`1`,`yes`]),l=new Set([`false`,`0`,`no`]);function u(e,t){if(!s(e,t))return;let n=o(e,t);if(n===null)return;let r=n.toLowerCase();if(c.has(r))return!0;if(l.has(r))return!1;i(e,t,`Invalid boolean value "${n}" for data-${t}. Expected: true/false/1/0/yes/no or empty.`)}function d(e,t){let n=o(e,t);if(n===null||n===``)return;let r=Number(n);if(Number.isNaN(r)||!Number.isFinite(r)){i(e,t,`Invalid number value "${n}" for data-${t}.`);return}return r}function f(e,t){if(s(e,t))return o(e,t)??void 0}function p(e,t,n){let r=o(e,t);if(r!==null){if(n.includes(r))return r;i(e,t,`Invalid value "${r}" for data-${t}. Expected one of: ${n.join(`, `)}.`)}}let m=0;const h=(e,t)=>e.id||=`${t}-${++m}`,g=(e,t,n)=>{n===null?e.removeAttribute(`aria-${t}`):e.setAttribute(`aria-${t}`,String(n))};function _(e,t,n,r){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)}const v=(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n})),y=[`horizontal`,`vertical`];function b(e){if(!e)return;let t=e.split(`,`).map(e=>parseFloat(e.trim()));if(!t.some(e=>isNaN(e))){if(t.length===2)return[t[0],t[1]];if(t.length===1)return t[0]}}function x(e){return Array.isArray(e)}function S(e,t,n,r){let i=Math.round((e-t)/r)*r+t,a=r.toString().split(`.`)[1]?.length??0,o=parseFloat(i.toFixed(a));return Math.min(n,Math.max(t,o))}function C(e,t,n){return n===t?0:(e-t)/(n-t)*100}function w(e,t,n){return e/100*(n-t)+t}function T(n,r={}){let i=e(n,`slider-track`),a=t(n,`slider-thumb`),o=e(n,`slider-range`);if(!i||a.length===0)throw Error(`Slider requires slider-track and at least one slider-thumb`);let s=i.parentElement;if(!s)throw Error(`Slider track must have a parent element (control)`);let c=r.min??d(n,`min`)??0,l=r.max??d(n,`max`)??100;c>l&&([c,l]=[l,c]);let m=r.step??d(n,`step`)??1;m<=0&&(m=1);let T=r.largeStep??d(n,`largeStep`)??m*10,E=r.orientation??p(n,`orientation`,y)??`horizontal`,D=r.disabled??u(n,`disabled`)??!1,O=r.onValueChange,k=r.onValueCommit,A=b(f(n,`defaultValue`)),j=r.defaultValue??A??c,M=a.length>=2;M&&!x(j)?j=[c,j]:!M&&x(j)&&(j=j[1]);let N=x(j)?[S(j[0],c,l,m),S(j[1],c,l,m)]:S(j,c,l,m),P=[],F=null,I=0,L=null,R=null;n.setAttribute(`data-orientation`,E);let z=e=>{e?n.setAttribute(`data-disabled`,``):n.removeAttribute(`data-disabled`);for(let t of a)g(t,`disabled`,e),t.tabIndex=e?-1:0};z(D);let B=(e,t)=>{e.setAttribute(`role`,`slider`),e.tabIndex=D?-1:0,h(e,`slider-thumb`),g(e,`orientation`,E);let n=e.hasAttribute(`aria-label`)||e.hasAttribute(`aria-labelledby`),r=e.dataset.label;r?g(e,`label`,r):!n&&M&&g(e,`label`,t===0?`Minimum`:`Maximum`)};for(let e=0;e<a.length;e++)B(a[e],e);let V=E===`horizontal`,H=()=>{if(x(N)){let[e,t]=N,n=C(e,c,l),r=C(t,c,l);a[0]&&(g(a[0],`valuenow`,String(e)),g(a[0],`valuemin`,String(c)),g(a[0],`valuemax`,String(t)),V?(a[0].style.left=`${n}%`,a[0].style.bottom=``):(a[0].style.bottom=`${n}%`,a[0].style.left=``)),a[1]&&(g(a[1],`valuenow`,String(t)),g(a[1],`valuemin`,String(e)),g(a[1],`valuemax`,String(l)),V?(a[1].style.left=`${r}%`,a[1].style.bottom=``):(a[1].style.bottom=`${r}%`,a[1].style.left=``)),o&&(V?(o.style.left=`${n}%`,o.style.width=`${r-n}%`,o.style.bottom=``,o.style.height=``):(o.style.bottom=`${n}%`,o.style.height=`${r-n}%`,o.style.left=``,o.style.width=``))}else{let e=C(N,c,l);a[0]&&(g(a[0],`valuenow`,String(N)),g(a[0],`valuemin`,String(c)),g(a[0],`valuemax`,String(l)),V?(a[0].style.left=`${e}%`,a[0].style.bottom=``):(a[0].style.bottom=`${e}%`,a[0].style.left=``)),o&&(V?(o.style.left=`0%`,o.style.width=`${e}%`,o.style.bottom=``,o.style.height=``):(o.style.bottom=`0%`,o.style.height=`${e}%`,o.style.left=``,o.style.width=``))}x(N)?n.setAttribute(`data-value`,`${N[0]},${N[1]}`):n.setAttribute(`data-value`,String(N))},U=(e,t)=>x(e)&&x(t)?e[0]===t[0]&&e[1]===t[1]:e===t,W=(e,t=!0)=>{let r;if(x(e)){let[t,n]=e;t=S(t,c,l,m),n=S(n,c,l,m),t>n&&([t,n]=[n,t]),r=[t,n]}else r=S(e,c,l,m);let i=!U(r,N);return i?(N=r,H(),t&&(v(n,`slider:change`,{value:N}),O?.(N)),!0):!1};H();let G=e=>{let t=i.getBoundingClientRect();if(V&&t.width===0||!V&&t.height===0)return null;let n;return n=V?(e.clientX-t.left)/t.width*100:(t.bottom-e.clientY)/t.height*100,n=Math.max(0,Math.min(100,n)),w(n,c,l)},K=e=>{if(!e||!(e instanceof HTMLElement))return null;let t=a.indexOf(e);if(t!==-1)return t;for(let t=0;t<a.length;t++)if(a[t].contains(e))return t;return null},q=e=>{if(!x(N))return 0;let[t,n]=N,r=Math.abs(e-t),i=Math.abs(e-n);return r===i?I:r<i?0:1},J=(e,t)=>{if(x(N)){let[n,r]=N;if(e===0){let e=S(t,c,r,m);return W([e,r])}else{let e=S(t,n,l,m);return W([n,e])}}else return W(t)},Y=e=>{if(D)return;e.preventDefault();let t=G(e);if(t===null)return;let r=K(e.target);r===null&&(r=q(t)),F=r,I=r,n.setAttribute(`data-dragging`,``),a[r]?.setAttribute(`data-dragging`,``),a[r]?.focus(),R=s.style.touchAction,s.style.touchAction=`none`,J(r,t),s.setPointerCapture(e.pointerId)},X=e=>{if(F===null||D)return;e.preventDefault();let t=G(e);t!==null&&J(F,t)},Z=e=>{if(F!==null){n.removeAttribute(`data-dragging`);for(let e of a)e.removeAttribute(`data-dragging`);s.style.touchAction=R??``,R=null,v(n,`slider:commit`,{value:N}),k?.(N),F=null;try{s.releasePointerCapture(e.pointerId)}catch{}}};P.push(_(s,`pointerdown`,Y)),P.push(_(s,`pointermove`,X)),P.push(_(s,`pointerup`,Z)),P.push(_(s,`pointercancel`,Z));let Q=e=>{if(D)return;let t=e.target,n=a.indexOf(t);if(n===-1)return;let r=0,i=null;switch(e.key){case`ArrowRight`:if(!V)return;r=m;break;case`ArrowLeft`:if(!V)return;r=-m;break;case`ArrowUp`:if(V)return;r=m;break;case`ArrowDown`:if(V)return;r=-m;break;case`PageUp`:r=T;break;case`PageDown`:r=-T;break;case`Home`:i=c;break;case`End`:i=l;break;default:return}e.shiftKey&&e.key.startsWith(`Arrow`)&&(r=r>0?T:r<0?-T:0),e.preventDefault(),I=n,L===null&&(L=x(N)?[N[0],N[1]]:N);let o=x(N)?N[n]??N[0]:N,s=i===null?o+r:i;J(n,s)},$=()=>{L!==null&&(U(L,N)||(v(n,`slider:commit`,{value:N}),k?.(N)),L=null)};for(let e of a)P.push(_(e,`keydown`,Q)),P.push(_(e,`blur`,$));let ee=e=>{let t=e,r=t.detail,i;if(typeof r==`number`||Array.isArray(r)?i=r:r&&typeof r==`object`&&`value`in r&&(i=r.value),i!==void 0){let e=W(i);e&&(v(n,`slider:commit`,{value:N}),k?.(N))}};P.push(_(n,`slider:set`,ee));let te={setValue:e=>{W(e)},get value(){return N},get min(){return c},get max(){return l},get disabled(){return D},destroy:()=>{P.forEach(e=>e()),P.length=0}};return te}const E=new WeakSet;function D(e=document){let t=[];for(let r of n(e,`slider`)){if(E.has(r))continue;E.add(r),t.push(T(r))}return t}exports.create=D,exports.createSlider=T;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface SliderOptions {
|
|
3
|
+
/** Initial value(s) - number or [min, max] for range */
|
|
4
|
+
defaultValue?: number | [number, number];
|
|
5
|
+
/** Minimum value */
|
|
6
|
+
min?: number;
|
|
7
|
+
/** Maximum value */
|
|
8
|
+
max?: number;
|
|
9
|
+
/** Step increment */
|
|
10
|
+
step?: number;
|
|
11
|
+
/** Larger step for PageUp/PageDown/Shift+Arrow */
|
|
12
|
+
largeStep?: number;
|
|
13
|
+
/** Slider orientation */
|
|
14
|
+
orientation?: "horizontal" | "vertical";
|
|
15
|
+
/** Disable the slider */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** Callback when value changes during interaction */
|
|
18
|
+
onValueChange?: (value: number | [number, number]) => void;
|
|
19
|
+
/** Callback when interaction ends (pointer release, blur) */
|
|
20
|
+
onValueCommit?: (value: number | [number, number]) => void;
|
|
21
|
+
}
|
|
22
|
+
interface SliderController {
|
|
23
|
+
/** Set value programmatically */
|
|
24
|
+
setValue(value: number | [number, number]): void;
|
|
25
|
+
/** Current value(s) */
|
|
26
|
+
readonly value: number | [number, number];
|
|
27
|
+
/** Min value */
|
|
28
|
+
readonly min: number;
|
|
29
|
+
/** Max value */
|
|
30
|
+
readonly max: number;
|
|
31
|
+
/** Whether slider is disabled */
|
|
32
|
+
readonly disabled: boolean;
|
|
33
|
+
/** Cleanup all event listeners */
|
|
34
|
+
destroy(): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a slider controller for a root element
|
|
38
|
+
*
|
|
39
|
+
* ## Events
|
|
40
|
+
* - **Outbound** `slider:change` (on root): Fires during value changes.
|
|
41
|
+
* `event.detail: { value: number | [number, number] }`
|
|
42
|
+
* - **Outbound** `slider:commit` (on root): Fires when interaction ends (pointer release, blur).
|
|
43
|
+
* `event.detail: { value: number | [number, number] }`
|
|
44
|
+
* - **Inbound** `slider:set` (on root): Set value programmatically.
|
|
45
|
+
* `event.detail: { value: number | [number, number] }`
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```js
|
|
49
|
+
* // Listen for value changes
|
|
50
|
+
* root.addEventListener("slider:change", (e) => console.log(e.detail.value));
|
|
51
|
+
* // Set value from outside
|
|
52
|
+
* root.dispatchEvent(new CustomEvent("slider:set", { detail: { value: 50 } }));
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Expected markup:
|
|
56
|
+
* ```html
|
|
57
|
+
* <div data-slot="slider" data-default-value="50">
|
|
58
|
+
* <div class="slider-control">
|
|
59
|
+
* <div data-slot="slider-track">
|
|
60
|
+
* <div data-slot="slider-range"></div>
|
|
61
|
+
* </div>
|
|
62
|
+
* <div data-slot="slider-thumb"></div>
|
|
63
|
+
* </div>
|
|
64
|
+
* </div>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
declare function createSlider(root: Element, options?: SliderOptions): SliderController;
|
|
68
|
+
/**
|
|
69
|
+
* Find and bind all slider components in a scope
|
|
70
|
+
* Returns array of controllers for programmatic access
|
|
71
|
+
*/
|
|
72
|
+
declare function create(scope?: ParentNode): SliderController[];
|
|
73
|
+
//#endregion
|
|
74
|
+
export { SliderController, SliderOptions, create, createSlider };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface SliderOptions {
|
|
3
|
+
/** Initial value(s) - number or [min, max] for range */
|
|
4
|
+
defaultValue?: number | [number, number];
|
|
5
|
+
/** Minimum value */
|
|
6
|
+
min?: number;
|
|
7
|
+
/** Maximum value */
|
|
8
|
+
max?: number;
|
|
9
|
+
/** Step increment */
|
|
10
|
+
step?: number;
|
|
11
|
+
/** Larger step for PageUp/PageDown/Shift+Arrow */
|
|
12
|
+
largeStep?: number;
|
|
13
|
+
/** Slider orientation */
|
|
14
|
+
orientation?: "horizontal" | "vertical";
|
|
15
|
+
/** Disable the slider */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** Callback when value changes during interaction */
|
|
18
|
+
onValueChange?: (value: number | [number, number]) => void;
|
|
19
|
+
/** Callback when interaction ends (pointer release, blur) */
|
|
20
|
+
onValueCommit?: (value: number | [number, number]) => void;
|
|
21
|
+
}
|
|
22
|
+
interface SliderController {
|
|
23
|
+
/** Set value programmatically */
|
|
24
|
+
setValue(value: number | [number, number]): void;
|
|
25
|
+
/** Current value(s) */
|
|
26
|
+
readonly value: number | [number, number];
|
|
27
|
+
/** Min value */
|
|
28
|
+
readonly min: number;
|
|
29
|
+
/** Max value */
|
|
30
|
+
readonly max: number;
|
|
31
|
+
/** Whether slider is disabled */
|
|
32
|
+
readonly disabled: boolean;
|
|
33
|
+
/** Cleanup all event listeners */
|
|
34
|
+
destroy(): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a slider controller for a root element
|
|
38
|
+
*
|
|
39
|
+
* ## Events
|
|
40
|
+
* - **Outbound** `slider:change` (on root): Fires during value changes.
|
|
41
|
+
* `event.detail: { value: number | [number, number] }`
|
|
42
|
+
* - **Outbound** `slider:commit` (on root): Fires when interaction ends (pointer release, blur).
|
|
43
|
+
* `event.detail: { value: number | [number, number] }`
|
|
44
|
+
* - **Inbound** `slider:set` (on root): Set value programmatically.
|
|
45
|
+
* `event.detail: { value: number | [number, number] }`
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```js
|
|
49
|
+
* // Listen for value changes
|
|
50
|
+
* root.addEventListener("slider:change", (e) => console.log(e.detail.value));
|
|
51
|
+
* // Set value from outside
|
|
52
|
+
* root.dispatchEvent(new CustomEvent("slider:set", { detail: { value: 50 } }));
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Expected markup:
|
|
56
|
+
* ```html
|
|
57
|
+
* <div data-slot="slider" data-default-value="50">
|
|
58
|
+
* <div class="slider-control">
|
|
59
|
+
* <div data-slot="slider-track">
|
|
60
|
+
* <div data-slot="slider-range"></div>
|
|
61
|
+
* </div>
|
|
62
|
+
* <div data-slot="slider-thumb"></div>
|
|
63
|
+
* </div>
|
|
64
|
+
* </div>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
declare function createSlider(root: Element, options?: SliderOptions): SliderController;
|
|
68
|
+
/**
|
|
69
|
+
* Find and bind all slider components in a scope
|
|
70
|
+
* Returns array of controllers for programmatic access
|
|
71
|
+
*/
|
|
72
|
+
declare function create(scope?: ParentNode): SliderController[];
|
|
73
|
+
//#endregion
|
|
74
|
+
export { SliderController, SliderOptions, create, createSlider };
|
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}"]`)],n=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)],r=new WeakMap;function i(e,t,n){if(typeof process<`u`&&process.env?.NODE_ENV===`production`)return;let i=r.get(e);i||(i=new Set,r.set(e,i)),!i.has(t)&&(i.add(t),console.warn(`[@data-slot] ${n}`))}function a(e){let t=`data-${e.replace(/([A-Z])/g,`-$1`).toLowerCase()}`,n=`data-${e}`;return t===n?[t]:[t,n]}function o(e,t){for(let n of a(t))if(e.hasAttribute(n))return e.getAttribute(n);return null}function s(e,t){return a(t).some(t=>e.hasAttribute(t))}const c=new Set([``,`true`,`1`,`yes`]),l=new Set([`false`,`0`,`no`]);function u(e,t){if(!s(e,t))return;let n=o(e,t);if(n===null)return;let r=n.toLowerCase();if(c.has(r))return!0;if(l.has(r))return!1;i(e,t,`Invalid boolean value "${n}" for data-${t}. Expected: true/false/1/0/yes/no or empty.`)}function d(e,t){let n=o(e,t);if(n===null||n===``)return;let r=Number(n);if(Number.isNaN(r)||!Number.isFinite(r)){i(e,t,`Invalid number value "${n}" for data-${t}.`);return}return r}function f(e,t){if(s(e,t))return o(e,t)??void 0}function p(e,t,n){let r=o(e,t);if(r!==null){if(n.includes(r))return r;i(e,t,`Invalid value "${r}" for data-${t}. Expected one of: ${n.join(`, `)}.`)}}let m=0;const h=(e,t)=>e.id||=`${t}-${++m}`,g=(e,t,n)=>{n===null?e.removeAttribute(`aria-${t}`):e.setAttribute(`aria-${t}`,String(n))};function _(e,t,n,r){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)}const v=(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n})),y=[`horizontal`,`vertical`];function b(e){if(!e)return;let t=e.split(`,`).map(e=>parseFloat(e.trim()));if(!t.some(e=>isNaN(e))){if(t.length===2)return[t[0],t[1]];if(t.length===1)return t[0]}}function x(e){return Array.isArray(e)}function S(e,t,n,r){let i=Math.round((e-t)/r)*r+t,a=r.toString().split(`.`)[1]?.length??0,o=parseFloat(i.toFixed(a));return Math.min(n,Math.max(t,o))}function C(e,t,n){return n===t?0:(e-t)/(n-t)*100}function w(e,t,n){return e/100*(n-t)+t}function T(n,r={}){let i=e(n,`slider-track`),a=t(n,`slider-thumb`),o=e(n,`slider-range`);if(!i||a.length===0)throw Error(`Slider requires slider-track and at least one slider-thumb`);let s=i.parentElement;if(!s)throw Error(`Slider track must have a parent element (control)`);let c=r.min??d(n,`min`)??0,l=r.max??d(n,`max`)??100;c>l&&([c,l]=[l,c]);let m=r.step??d(n,`step`)??1;m<=0&&(m=1);let T=r.largeStep??d(n,`largeStep`)??m*10,E=r.orientation??p(n,`orientation`,y)??`horizontal`,D=r.disabled??u(n,`disabled`)??!1,O=r.onValueChange,k=r.onValueCommit,A=b(f(n,`defaultValue`)),j=r.defaultValue??A??c,M=a.length>=2;M&&!x(j)?j=[c,j]:!M&&x(j)&&(j=j[1]);let N=x(j)?[S(j[0],c,l,m),S(j[1],c,l,m)]:S(j,c,l,m),P=[],F=null,I=0,L=null,R=null;n.setAttribute(`data-orientation`,E);let z=e=>{e?n.setAttribute(`data-disabled`,``):n.removeAttribute(`data-disabled`);for(let t of a)g(t,`disabled`,e),t.tabIndex=e?-1:0};z(D);let B=(e,t)=>{e.setAttribute(`role`,`slider`),e.tabIndex=D?-1:0,h(e,`slider-thumb`),g(e,`orientation`,E);let n=e.hasAttribute(`aria-label`)||e.hasAttribute(`aria-labelledby`),r=e.dataset.label;r?g(e,`label`,r):!n&&M&&g(e,`label`,t===0?`Minimum`:`Maximum`)};for(let e=0;e<a.length;e++)B(a[e],e);let V=E===`horizontal`,H=()=>{if(x(N)){let[e,t]=N,n=C(e,c,l),r=C(t,c,l);a[0]&&(g(a[0],`valuenow`,String(e)),g(a[0],`valuemin`,String(c)),g(a[0],`valuemax`,String(t)),V?(a[0].style.left=`${n}%`,a[0].style.bottom=``):(a[0].style.bottom=`${n}%`,a[0].style.left=``)),a[1]&&(g(a[1],`valuenow`,String(t)),g(a[1],`valuemin`,String(e)),g(a[1],`valuemax`,String(l)),V?(a[1].style.left=`${r}%`,a[1].style.bottom=``):(a[1].style.bottom=`${r}%`,a[1].style.left=``)),o&&(V?(o.style.left=`${n}%`,o.style.width=`${r-n}%`,o.style.bottom=``,o.style.height=``):(o.style.bottom=`${n}%`,o.style.height=`${r-n}%`,o.style.left=``,o.style.width=``))}else{let e=C(N,c,l);a[0]&&(g(a[0],`valuenow`,String(N)),g(a[0],`valuemin`,String(c)),g(a[0],`valuemax`,String(l)),V?(a[0].style.left=`${e}%`,a[0].style.bottom=``):(a[0].style.bottom=`${e}%`,a[0].style.left=``)),o&&(V?(o.style.left=`0%`,o.style.width=`${e}%`,o.style.bottom=``,o.style.height=``):(o.style.bottom=`0%`,o.style.height=`${e}%`,o.style.left=``,o.style.width=``))}x(N)?n.setAttribute(`data-value`,`${N[0]},${N[1]}`):n.setAttribute(`data-value`,String(N))},U=(e,t)=>x(e)&&x(t)?e[0]===t[0]&&e[1]===t[1]:e===t,W=(e,t=!0)=>{let r;if(x(e)){let[t,n]=e;t=S(t,c,l,m),n=S(n,c,l,m),t>n&&([t,n]=[n,t]),r=[t,n]}else r=S(e,c,l,m);let i=!U(r,N);return i?(N=r,H(),t&&(v(n,`slider:change`,{value:N}),O?.(N)),!0):!1};H();let G=e=>{let t=i.getBoundingClientRect();if(V&&t.width===0||!V&&t.height===0)return null;let n;return n=V?(e.clientX-t.left)/t.width*100:(t.bottom-e.clientY)/t.height*100,n=Math.max(0,Math.min(100,n)),w(n,c,l)},K=e=>{if(!e||!(e instanceof HTMLElement))return null;let t=a.indexOf(e);if(t!==-1)return t;for(let t=0;t<a.length;t++)if(a[t].contains(e))return t;return null},q=e=>{if(!x(N))return 0;let[t,n]=N,r=Math.abs(e-t),i=Math.abs(e-n);return r===i?I:r<i?0:1},J=(e,t)=>{if(x(N)){let[n,r]=N;if(e===0){let e=S(t,c,r,m);return W([e,r])}else{let e=S(t,n,l,m);return W([n,e])}}else return W(t)},Y=e=>{if(D)return;e.preventDefault();let t=G(e);if(t===null)return;let r=K(e.target);r===null&&(r=q(t)),F=r,I=r,n.setAttribute(`data-dragging`,``),a[r]?.setAttribute(`data-dragging`,``),a[r]?.focus(),R=s.style.touchAction,s.style.touchAction=`none`,J(r,t),s.setPointerCapture(e.pointerId)},X=e=>{if(F===null||D)return;e.preventDefault();let t=G(e);t!==null&&J(F,t)},Z=e=>{if(F!==null){n.removeAttribute(`data-dragging`);for(let e of a)e.removeAttribute(`data-dragging`);s.style.touchAction=R??``,R=null,v(n,`slider:commit`,{value:N}),k?.(N),F=null;try{s.releasePointerCapture(e.pointerId)}catch{}}};P.push(_(s,`pointerdown`,Y)),P.push(_(s,`pointermove`,X)),P.push(_(s,`pointerup`,Z)),P.push(_(s,`pointercancel`,Z));let Q=e=>{if(D)return;let t=e.target,n=a.indexOf(t);if(n===-1)return;let r=0,i=null;switch(e.key){case`ArrowRight`:if(!V)return;r=m;break;case`ArrowLeft`:if(!V)return;r=-m;break;case`ArrowUp`:if(V)return;r=m;break;case`ArrowDown`:if(V)return;r=-m;break;case`PageUp`:r=T;break;case`PageDown`:r=-T;break;case`Home`:i=c;break;case`End`:i=l;break;default:return}e.shiftKey&&e.key.startsWith(`Arrow`)&&(r=r>0?T:r<0?-T:0),e.preventDefault(),I=n,L===null&&(L=x(N)?[N[0],N[1]]:N);let o=x(N)?N[n]??N[0]:N,s=i===null?o+r:i;J(n,s)},$=()=>{L!==null&&(U(L,N)||(v(n,`slider:commit`,{value:N}),k?.(N)),L=null)};for(let e of a)P.push(_(e,`keydown`,Q)),P.push(_(e,`blur`,$));let ee=e=>{let t=e,r=t.detail,i;if(typeof r==`number`||Array.isArray(r)?i=r:r&&typeof r==`object`&&`value`in r&&(i=r.value),i!==void 0){let e=W(i);e&&(v(n,`slider:commit`,{value:N}),k?.(N))}};P.push(_(n,`slider:set`,ee));let te={setValue:e=>{W(e)},get value(){return N},get min(){return c},get max(){return l},get disabled(){return D},destroy:()=>{P.forEach(e=>e()),P.length=0}};return te}const E=new WeakSet;function D(e=document){let t=[];for(let r of n(e,`slider`)){if(E.has(r))continue;E.add(r),t.push(T(r))}return t}export{D as create,T as createSlider};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@data-slot/slider",
|
|
3
|
+
"version": "0.2.9",
|
|
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/slider"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"headless",
|
|
37
|
+
"ui",
|
|
38
|
+
"slider",
|
|
39
|
+
"range",
|
|
40
|
+
"vanilla",
|
|
41
|
+
"data-slot"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT"
|
|
44
|
+
}
|