@zag-js/slider 0.9.2 → 0.10.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/dist/{chunk-JG4OWFJP.mjs → chunk-NYN3VIY4.mjs} +8 -14
- package/dist/{chunk-CNR2557J.mjs → chunk-OCT2YBMN.mjs} +1 -1
- package/dist/{chunk-TMLAO23S.mjs → chunk-T6GXNUP7.mjs} +1 -1
- package/dist/index.js +8 -14
- package/dist/index.mjs +3 -3
- package/dist/slider.connect.js +8 -14
- package/dist/slider.connect.mjs +2 -2
- package/dist/slider.dom.d.ts +1 -1
- package/dist/slider.dom.js +8 -14
- package/dist/slider.dom.mjs +1 -1
- package/dist/slider.machine.js +8 -14
- package/dist/slider.machine.mjs +2 -2
- package/package.json +12 -11
- package/src/index.ts +5 -0
- package/src/slider.anatomy.ts +15 -0
- package/src/slider.connect.ts +276 -0
- package/src/slider.dom.ts +41 -0
- package/src/slider.machine.ts +210 -0
- package/src/slider.style.ts +165 -0
- package/src/slider.types.ts +182 -0
- package/src/slider.utils.ts +31 -0
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
} from "./chunk-IJAAAKZQ.mjs";
|
|
4
4
|
|
|
5
5
|
// src/slider.dom.ts
|
|
6
|
+
import { getRelativePoint } from "@zag-js/dom-event";
|
|
6
7
|
import { createScope } from "@zag-js/dom-query";
|
|
7
|
-
import { getRelativePointValue } from "@zag-js/dom-event";
|
|
8
8
|
import { dispatchInputValueEvent } from "@zag-js/form-utils";
|
|
9
9
|
import { getPercentValue } from "@zag-js/numeric-range";
|
|
10
10
|
var dom = createScope({
|
|
@@ -20,21 +20,15 @@ var dom = createScope({
|
|
|
20
20
|
getMarkerId: (ctx, value) => `slider:${ctx.id}:marker:${value}`,
|
|
21
21
|
getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
|
|
22
22
|
getThumbEl: (ctx) => dom.getById(ctx, dom.getThumbId(ctx)),
|
|
23
|
-
getControlEl: (ctx) => dom.
|
|
23
|
+
getControlEl: (ctx) => dom.queryById(ctx, dom.getControlId(ctx)),
|
|
24
24
|
getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
|
|
25
25
|
getValueFromPoint(ctx, point) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let percent;
|
|
33
|
-
if (ctx.isHorizontal) {
|
|
34
|
-
percent = ctx.isRtl ? 1 - percentX : percentX;
|
|
35
|
-
} else {
|
|
36
|
-
percent = 1 - percentY;
|
|
37
|
-
}
|
|
26
|
+
const relativePoint = getRelativePoint(point, dom.getControlEl(ctx));
|
|
27
|
+
const percent = relativePoint.getPercentValue({
|
|
28
|
+
orientation: ctx.orientation,
|
|
29
|
+
dir: ctx.dir,
|
|
30
|
+
inverted: { y: true }
|
|
31
|
+
});
|
|
38
32
|
return getPercentValue(percent, ctx.min, ctx.max, ctx.step);
|
|
39
33
|
},
|
|
40
34
|
dispatchChangeEvent(ctx) {
|
package/dist/index.js
CHANGED
|
@@ -49,8 +49,8 @@ var import_dom_query2 = require("@zag-js/dom-query");
|
|
|
49
49
|
var import_numeric_range3 = require("@zag-js/numeric-range");
|
|
50
50
|
|
|
51
51
|
// src/slider.dom.ts
|
|
52
|
-
var import_dom_query = require("@zag-js/dom-query");
|
|
53
52
|
var import_dom_event = require("@zag-js/dom-event");
|
|
53
|
+
var import_dom_query = require("@zag-js/dom-query");
|
|
54
54
|
var import_form_utils = require("@zag-js/form-utils");
|
|
55
55
|
var import_numeric_range2 = require("@zag-js/numeric-range");
|
|
56
56
|
|
|
@@ -180,21 +180,15 @@ var dom = (0, import_dom_query.createScope)({
|
|
|
180
180
|
getMarkerId: (ctx, value) => `slider:${ctx.id}:marker:${value}`,
|
|
181
181
|
getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
|
|
182
182
|
getThumbEl: (ctx) => dom.getById(ctx, dom.getThumbId(ctx)),
|
|
183
|
-
getControlEl: (ctx) => dom.
|
|
183
|
+
getControlEl: (ctx) => dom.queryById(ctx, dom.getControlId(ctx)),
|
|
184
184
|
getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
|
|
185
185
|
getValueFromPoint(ctx, point) {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let percent;
|
|
193
|
-
if (ctx.isHorizontal) {
|
|
194
|
-
percent = ctx.isRtl ? 1 - percentX : percentX;
|
|
195
|
-
} else {
|
|
196
|
-
percent = 1 - percentY;
|
|
197
|
-
}
|
|
186
|
+
const relativePoint = (0, import_dom_event.getRelativePoint)(point, dom.getControlEl(ctx));
|
|
187
|
+
const percent = relativePoint.getPercentValue({
|
|
188
|
+
orientation: ctx.orientation,
|
|
189
|
+
dir: ctx.dir,
|
|
190
|
+
inverted: { y: true }
|
|
191
|
+
});
|
|
198
192
|
return (0, import_numeric_range2.getPercentValue)(percent, ctx.min, ctx.max, ctx.step);
|
|
199
193
|
},
|
|
200
194
|
dispatchChangeEvent(ctx) {
|
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
connect
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-T6GXNUP7.mjs";
|
|
4
4
|
import {
|
|
5
5
|
anatomy
|
|
6
6
|
} from "./chunk-3Y7IIPR5.mjs";
|
|
7
7
|
import {
|
|
8
8
|
machine
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-OCT2YBMN.mjs";
|
|
10
10
|
import {
|
|
11
11
|
dom
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-NYN3VIY4.mjs";
|
|
13
13
|
import "./chunk-IJAAAKZQ.mjs";
|
|
14
14
|
import "./chunk-YGFSMEUO.mjs";
|
|
15
15
|
export {
|
package/dist/slider.connect.js
CHANGED
|
@@ -44,8 +44,8 @@ var anatomy = (0, import_anatomy.createAnatomy)("slider").parts(
|
|
|
44
44
|
var parts = anatomy.build();
|
|
45
45
|
|
|
46
46
|
// src/slider.dom.ts
|
|
47
|
-
var import_dom_query = require("@zag-js/dom-query");
|
|
48
47
|
var import_dom_event = require("@zag-js/dom-event");
|
|
48
|
+
var import_dom_query = require("@zag-js/dom-query");
|
|
49
49
|
var import_form_utils = require("@zag-js/form-utils");
|
|
50
50
|
var import_numeric_range2 = require("@zag-js/numeric-range");
|
|
51
51
|
|
|
@@ -175,21 +175,15 @@ var dom = (0, import_dom_query.createScope)({
|
|
|
175
175
|
getMarkerId: (ctx, value) => `slider:${ctx.id}:marker:${value}`,
|
|
176
176
|
getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
|
|
177
177
|
getThumbEl: (ctx) => dom.getById(ctx, dom.getThumbId(ctx)),
|
|
178
|
-
getControlEl: (ctx) => dom.
|
|
178
|
+
getControlEl: (ctx) => dom.queryById(ctx, dom.getControlId(ctx)),
|
|
179
179
|
getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
|
|
180
180
|
getValueFromPoint(ctx, point) {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
let percent;
|
|
188
|
-
if (ctx.isHorizontal) {
|
|
189
|
-
percent = ctx.isRtl ? 1 - percentX : percentX;
|
|
190
|
-
} else {
|
|
191
|
-
percent = 1 - percentY;
|
|
192
|
-
}
|
|
181
|
+
const relativePoint = (0, import_dom_event.getRelativePoint)(point, dom.getControlEl(ctx));
|
|
182
|
+
const percent = relativePoint.getPercentValue({
|
|
183
|
+
orientation: ctx.orientation,
|
|
184
|
+
dir: ctx.dir,
|
|
185
|
+
inverted: { y: true }
|
|
186
|
+
});
|
|
193
187
|
return (0, import_numeric_range2.getPercentValue)(percent, ctx.min, ctx.max, ctx.step);
|
|
194
188
|
},
|
|
195
189
|
dispatchChangeEvent(ctx) {
|
package/dist/slider.connect.mjs
CHANGED
package/dist/slider.dom.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ declare const dom: {
|
|
|
33
33
|
getMarkerId: (ctx: MachineContext, value: number) => string;
|
|
34
34
|
getRootEl: (ctx: MachineContext) => HTMLElement | null;
|
|
35
35
|
getThumbEl: (ctx: MachineContext) => HTMLElement | null;
|
|
36
|
-
getControlEl: (ctx: MachineContext) => HTMLElement
|
|
36
|
+
getControlEl: (ctx: MachineContext) => HTMLElement;
|
|
37
37
|
getHiddenInputEl: (ctx: MachineContext) => HTMLInputElement | null;
|
|
38
38
|
getValueFromPoint(ctx: MachineContext, point: Point): number | undefined;
|
|
39
39
|
dispatchChangeEvent(ctx: MachineContext): void;
|
package/dist/slider.dom.js
CHANGED
|
@@ -23,8 +23,8 @@ __export(slider_dom_exports, {
|
|
|
23
23
|
dom: () => dom
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(slider_dom_exports);
|
|
26
|
-
var import_dom_query = require("@zag-js/dom-query");
|
|
27
26
|
var import_dom_event = require("@zag-js/dom-event");
|
|
27
|
+
var import_dom_query = require("@zag-js/dom-query");
|
|
28
28
|
var import_form_utils = require("@zag-js/form-utils");
|
|
29
29
|
var import_numeric_range2 = require("@zag-js/numeric-range");
|
|
30
30
|
|
|
@@ -154,21 +154,15 @@ var dom = (0, import_dom_query.createScope)({
|
|
|
154
154
|
getMarkerId: (ctx, value) => `slider:${ctx.id}:marker:${value}`,
|
|
155
155
|
getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
|
|
156
156
|
getThumbEl: (ctx) => dom.getById(ctx, dom.getThumbId(ctx)),
|
|
157
|
-
getControlEl: (ctx) => dom.
|
|
157
|
+
getControlEl: (ctx) => dom.queryById(ctx, dom.getControlId(ctx)),
|
|
158
158
|
getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
|
|
159
159
|
getValueFromPoint(ctx, point) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
let percent;
|
|
167
|
-
if (ctx.isHorizontal) {
|
|
168
|
-
percent = ctx.isRtl ? 1 - percentX : percentX;
|
|
169
|
-
} else {
|
|
170
|
-
percent = 1 - percentY;
|
|
171
|
-
}
|
|
160
|
+
const relativePoint = (0, import_dom_event.getRelativePoint)(point, dom.getControlEl(ctx));
|
|
161
|
+
const percent = relativePoint.getPercentValue({
|
|
162
|
+
orientation: ctx.orientation,
|
|
163
|
+
dir: ctx.dir,
|
|
164
|
+
inverted: { y: true }
|
|
165
|
+
});
|
|
172
166
|
return (0, import_numeric_range2.getPercentValue)(percent, ctx.min, ctx.max, ctx.step);
|
|
173
167
|
},
|
|
174
168
|
dispatchChangeEvent(ctx) {
|
package/dist/slider.dom.mjs
CHANGED
package/dist/slider.machine.js
CHANGED
|
@@ -32,8 +32,8 @@ var import_numeric_range4 = require("@zag-js/numeric-range");
|
|
|
32
32
|
var import_utils = require("@zag-js/utils");
|
|
33
33
|
|
|
34
34
|
// src/slider.dom.ts
|
|
35
|
-
var import_dom_query = require("@zag-js/dom-query");
|
|
36
35
|
var import_dom_event = require("@zag-js/dom-event");
|
|
36
|
+
var import_dom_query = require("@zag-js/dom-query");
|
|
37
37
|
var import_form_utils = require("@zag-js/form-utils");
|
|
38
38
|
var import_numeric_range2 = require("@zag-js/numeric-range");
|
|
39
39
|
|
|
@@ -163,21 +163,15 @@ var dom = (0, import_dom_query.createScope)({
|
|
|
163
163
|
getMarkerId: (ctx, value) => `slider:${ctx.id}:marker:${value}`,
|
|
164
164
|
getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
|
|
165
165
|
getThumbEl: (ctx) => dom.getById(ctx, dom.getThumbId(ctx)),
|
|
166
|
-
getControlEl: (ctx) => dom.
|
|
166
|
+
getControlEl: (ctx) => dom.queryById(ctx, dom.getControlId(ctx)),
|
|
167
167
|
getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
|
|
168
168
|
getValueFromPoint(ctx, point) {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
let percent;
|
|
176
|
-
if (ctx.isHorizontal) {
|
|
177
|
-
percent = ctx.isRtl ? 1 - percentX : percentX;
|
|
178
|
-
} else {
|
|
179
|
-
percent = 1 - percentY;
|
|
180
|
-
}
|
|
169
|
+
const relativePoint = (0, import_dom_event.getRelativePoint)(point, dom.getControlEl(ctx));
|
|
170
|
+
const percent = relativePoint.getPercentValue({
|
|
171
|
+
orientation: ctx.orientation,
|
|
172
|
+
dir: ctx.dir,
|
|
173
|
+
inverted: { y: true }
|
|
174
|
+
});
|
|
181
175
|
return (0, import_numeric_range2.getPercentValue)(percent, ctx.min, ctx.max, ctx.step);
|
|
182
176
|
},
|
|
183
177
|
dispatchChangeEvent(ctx) {
|
package/dist/slider.machine.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zag-js/slider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Core logic for the slider widget implemented as a state machine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"repository": "https://github.com/chakra-ui/zag/tree/main/packages/slider",
|
|
18
18
|
"sideEffects": false,
|
|
19
19
|
"files": [
|
|
20
|
-
"dist
|
|
20
|
+
"dist",
|
|
21
|
+
"src"
|
|
21
22
|
],
|
|
22
23
|
"publishConfig": {
|
|
23
24
|
"access": "public"
|
|
@@ -26,15 +27,15 @@
|
|
|
26
27
|
"url": "https://github.com/chakra-ui/zag/issues"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@zag-js/anatomy": "0.
|
|
30
|
-
"@zag-js/core": "0.
|
|
31
|
-
"@zag-js/dom-query": "0.
|
|
32
|
-
"@zag-js/dom-event": "0.
|
|
33
|
-
"@zag-js/form-utils": "0.
|
|
34
|
-
"@zag-js/utils": "0.
|
|
35
|
-
"@zag-js/element-size": "0.
|
|
36
|
-
"@zag-js/numeric-range": "0.
|
|
37
|
-
"@zag-js/types": "0.
|
|
30
|
+
"@zag-js/anatomy": "0.10.0",
|
|
31
|
+
"@zag-js/core": "0.10.0",
|
|
32
|
+
"@zag-js/dom-query": "0.10.0",
|
|
33
|
+
"@zag-js/dom-event": "0.10.0",
|
|
34
|
+
"@zag-js/form-utils": "0.10.0",
|
|
35
|
+
"@zag-js/utils": "0.10.0",
|
|
36
|
+
"@zag-js/element-size": "0.10.0",
|
|
37
|
+
"@zag-js/numeric-range": "0.10.0",
|
|
38
|
+
"@zag-js/types": "0.10.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"clean-package": "2.2.0"
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createAnatomy } from "@zag-js/anatomy"
|
|
2
|
+
|
|
3
|
+
export const anatomy = createAnatomy("slider").parts(
|
|
4
|
+
"root",
|
|
5
|
+
"label",
|
|
6
|
+
"thumb",
|
|
7
|
+
"hiddenInput",
|
|
8
|
+
"output",
|
|
9
|
+
"track",
|
|
10
|
+
"range",
|
|
11
|
+
"control",
|
|
12
|
+
"markerGroup",
|
|
13
|
+
"marker",
|
|
14
|
+
)
|
|
15
|
+
export const parts = anatomy.build()
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventKeyMap,
|
|
3
|
+
getEventKey,
|
|
4
|
+
getEventPoint,
|
|
5
|
+
getEventStep,
|
|
6
|
+
getNativeEvent,
|
|
7
|
+
isLeftClick,
|
|
8
|
+
isModifiedEvent,
|
|
9
|
+
} from "@zag-js/dom-event"
|
|
10
|
+
import { ariaAttr, dataAttr } from "@zag-js/dom-query"
|
|
11
|
+
import { getPercentValue, getValuePercent } from "@zag-js/numeric-range"
|
|
12
|
+
import type { NormalizeProps, PropTypes } from "@zag-js/types"
|
|
13
|
+
import { parts } from "./slider.anatomy"
|
|
14
|
+
import { dom } from "./slider.dom"
|
|
15
|
+
import type { Send, State } from "./slider.types"
|
|
16
|
+
|
|
17
|
+
export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>) {
|
|
18
|
+
const ariaLabel = state.context["aria-label"]
|
|
19
|
+
const ariaLabelledBy = state.context["aria-labelledby"]
|
|
20
|
+
const ariaValueText = state.context.getAriaValueText?.(state.context.value)
|
|
21
|
+
|
|
22
|
+
const isFocused = state.matches("focus")
|
|
23
|
+
const isDragging = state.matches("dragging")
|
|
24
|
+
const isDisabled = state.context.disabled
|
|
25
|
+
const isInteractive = state.context.isInteractive
|
|
26
|
+
const isInvalid = state.context.invalid
|
|
27
|
+
|
|
28
|
+
function getPercentValueFn(percent: number) {
|
|
29
|
+
return getPercentValue(percent, state.context.min, state.context.max, state.context.step)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getValuePercentFn(value: number) {
|
|
33
|
+
return getValuePercent(value, state.context.min, state.context.max)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// TODO - getMarkerState
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
/**
|
|
40
|
+
* Whether the slider is focused.
|
|
41
|
+
*/
|
|
42
|
+
isFocused,
|
|
43
|
+
/**
|
|
44
|
+
* Whether the slider is being dragged.
|
|
45
|
+
*/
|
|
46
|
+
isDragging,
|
|
47
|
+
/**
|
|
48
|
+
* The value of the slider.
|
|
49
|
+
*/
|
|
50
|
+
value: state.context.value,
|
|
51
|
+
/**
|
|
52
|
+
* The value of the slider as a percent.
|
|
53
|
+
*/
|
|
54
|
+
percent: getValuePercent(state.context.value, state.context.min, state.context.max),
|
|
55
|
+
/**
|
|
56
|
+
* Function to set the value of the slider.
|
|
57
|
+
*/
|
|
58
|
+
setValue(value: number) {
|
|
59
|
+
send({ type: "SET_VALUE", value })
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* Returns the value of the slider at the given percent.
|
|
63
|
+
*/
|
|
64
|
+
getPercentValue: getPercentValueFn,
|
|
65
|
+
/**
|
|
66
|
+
* Returns the percent of the slider at the given value.
|
|
67
|
+
*/
|
|
68
|
+
getValuePercent: getValuePercentFn,
|
|
69
|
+
/**
|
|
70
|
+
* Function to focus the slider.
|
|
71
|
+
*/
|
|
72
|
+
focus() {
|
|
73
|
+
dom.getThumbEl(state.context)?.focus()
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* Function to increment the value of the slider by the step.
|
|
77
|
+
*/
|
|
78
|
+
increment() {
|
|
79
|
+
send("INCREMENT")
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* Function to decrement the value of the slider by the step.
|
|
83
|
+
*/
|
|
84
|
+
decrement() {
|
|
85
|
+
send("DECREMENT")
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
rootProps: normalize.element({
|
|
89
|
+
...parts.root.attrs,
|
|
90
|
+
"data-disabled": dataAttr(isDisabled),
|
|
91
|
+
"data-focus": dataAttr(isFocused),
|
|
92
|
+
"data-orientation": state.context.orientation,
|
|
93
|
+
"data-invalid": dataAttr(isInvalid),
|
|
94
|
+
id: dom.getRootId(state.context),
|
|
95
|
+
dir: state.context.dir,
|
|
96
|
+
style: dom.getRootStyle(state.context),
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
labelProps: normalize.label({
|
|
100
|
+
...parts.label.attrs,
|
|
101
|
+
"data-disabled": dataAttr(isDisabled),
|
|
102
|
+
"data-invalid": dataAttr(isInvalid),
|
|
103
|
+
"data-focus": dataAttr(isFocused),
|
|
104
|
+
id: dom.getLabelId(state.context),
|
|
105
|
+
htmlFor: dom.getHiddenInputId(state.context),
|
|
106
|
+
onClick(event) {
|
|
107
|
+
if (!isInteractive) return
|
|
108
|
+
event.preventDefault()
|
|
109
|
+
dom.getThumbEl(state.context)?.focus()
|
|
110
|
+
},
|
|
111
|
+
style: dom.getLabelStyle(),
|
|
112
|
+
}),
|
|
113
|
+
|
|
114
|
+
thumbProps: normalize.element({
|
|
115
|
+
...parts.thumb.attrs,
|
|
116
|
+
id: dom.getThumbId(state.context),
|
|
117
|
+
"data-disabled": dataAttr(isDisabled),
|
|
118
|
+
"data-orientation": state.context.orientation,
|
|
119
|
+
"data-focus": dataAttr(isFocused),
|
|
120
|
+
draggable: false,
|
|
121
|
+
"aria-invalid": ariaAttr(isInvalid),
|
|
122
|
+
"data-invalid": dataAttr(isInvalid),
|
|
123
|
+
"aria-disabled": ariaAttr(isDisabled),
|
|
124
|
+
"aria-label": ariaLabel,
|
|
125
|
+
"aria-labelledby": ariaLabel ? undefined : ariaLabelledBy ?? dom.getLabelId(state.context),
|
|
126
|
+
"aria-orientation": state.context.orientation,
|
|
127
|
+
"aria-valuemax": state.context.max,
|
|
128
|
+
"aria-valuemin": state.context.min,
|
|
129
|
+
"aria-valuenow": state.context.value,
|
|
130
|
+
"aria-valuetext": ariaValueText,
|
|
131
|
+
role: "slider",
|
|
132
|
+
tabIndex: isDisabled ? undefined : 0,
|
|
133
|
+
onBlur() {
|
|
134
|
+
if (!isInteractive) return
|
|
135
|
+
send("BLUR")
|
|
136
|
+
},
|
|
137
|
+
onFocus() {
|
|
138
|
+
if (!isInteractive) return
|
|
139
|
+
send("FOCUS")
|
|
140
|
+
},
|
|
141
|
+
onKeyDown(event) {
|
|
142
|
+
if (!isInteractive) return
|
|
143
|
+
const step = getEventStep(event) * state.context.step
|
|
144
|
+
let prevent = true
|
|
145
|
+
const keyMap: EventKeyMap = {
|
|
146
|
+
ArrowUp() {
|
|
147
|
+
send({ type: "ARROW_UP", step })
|
|
148
|
+
prevent = state.context.isVertical
|
|
149
|
+
},
|
|
150
|
+
ArrowDown() {
|
|
151
|
+
send({ type: "ARROW_DOWN", step })
|
|
152
|
+
prevent = state.context.isVertical
|
|
153
|
+
},
|
|
154
|
+
ArrowLeft() {
|
|
155
|
+
send({ type: "ARROW_LEFT", step })
|
|
156
|
+
prevent = state.context.isHorizontal
|
|
157
|
+
},
|
|
158
|
+
ArrowRight() {
|
|
159
|
+
send({ type: "ARROW_RIGHT", step })
|
|
160
|
+
prevent = state.context.isHorizontal
|
|
161
|
+
},
|
|
162
|
+
PageUp() {
|
|
163
|
+
send({ type: "PAGE_UP", step })
|
|
164
|
+
},
|
|
165
|
+
PageDown() {
|
|
166
|
+
send({ type: "PAGE_DOWN", step })
|
|
167
|
+
},
|
|
168
|
+
Home() {
|
|
169
|
+
send("HOME")
|
|
170
|
+
},
|
|
171
|
+
End() {
|
|
172
|
+
send("END")
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const key = getEventKey(event, state.context)
|
|
177
|
+
const exec = keyMap[key]
|
|
178
|
+
|
|
179
|
+
if (!exec) return
|
|
180
|
+
exec(event)
|
|
181
|
+
|
|
182
|
+
if (prevent) {
|
|
183
|
+
event.preventDefault()
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
style: dom.getThumbStyle(state.context),
|
|
187
|
+
}),
|
|
188
|
+
|
|
189
|
+
hiddenInputProps: normalize.input({
|
|
190
|
+
...parts.hiddenInput.attrs,
|
|
191
|
+
type: "text",
|
|
192
|
+
defaultValue: state.context.value,
|
|
193
|
+
name: state.context.name,
|
|
194
|
+
form: state.context.form,
|
|
195
|
+
id: dom.getHiddenInputId(state.context),
|
|
196
|
+
hidden: true,
|
|
197
|
+
}),
|
|
198
|
+
|
|
199
|
+
outputProps: normalize.output({
|
|
200
|
+
...parts.output.attrs,
|
|
201
|
+
"data-disabled": dataAttr(isDisabled),
|
|
202
|
+
"data-invalid": dataAttr(isInvalid),
|
|
203
|
+
id: dom.getOutputId(state.context),
|
|
204
|
+
htmlFor: dom.getHiddenInputId(state.context),
|
|
205
|
+
"aria-live": "off",
|
|
206
|
+
}),
|
|
207
|
+
|
|
208
|
+
trackProps: normalize.element({
|
|
209
|
+
...parts.track.attrs,
|
|
210
|
+
id: dom.getTrackId(state.context),
|
|
211
|
+
"data-disabled": dataAttr(isDisabled),
|
|
212
|
+
"data-focus": dataAttr(isFocused),
|
|
213
|
+
"data-invalid": dataAttr(isInvalid),
|
|
214
|
+
"data-orientation": state.context.orientation,
|
|
215
|
+
style: dom.getTrackStyle(),
|
|
216
|
+
}),
|
|
217
|
+
|
|
218
|
+
rangeProps: normalize.element({
|
|
219
|
+
...parts.range.attrs,
|
|
220
|
+
id: dom.getRangeId(state.context),
|
|
221
|
+
"data-focus": dataAttr(isFocused),
|
|
222
|
+
"data-invalid": dataAttr(isInvalid),
|
|
223
|
+
"data-disabled": dataAttr(isDisabled),
|
|
224
|
+
"data-orientation": state.context.orientation,
|
|
225
|
+
style: dom.getRangeStyle(state.context),
|
|
226
|
+
}),
|
|
227
|
+
|
|
228
|
+
controlProps: normalize.element({
|
|
229
|
+
...parts.control.attrs,
|
|
230
|
+
id: dom.getControlId(state.context),
|
|
231
|
+
"data-disabled": dataAttr(isDisabled),
|
|
232
|
+
"data-invalid": dataAttr(isInvalid),
|
|
233
|
+
"data-orientation": state.context.orientation,
|
|
234
|
+
"data-focus": dataAttr(isFocused),
|
|
235
|
+
onPointerDown(event) {
|
|
236
|
+
if (!isInteractive) return
|
|
237
|
+
|
|
238
|
+
const evt = getNativeEvent(event)
|
|
239
|
+
if (!isLeftClick(evt) || isModifiedEvent(evt)) return
|
|
240
|
+
|
|
241
|
+
const point = getEventPoint(evt)
|
|
242
|
+
send({ type: "POINTER_DOWN", point })
|
|
243
|
+
|
|
244
|
+
event.preventDefault()
|
|
245
|
+
event.stopPropagation()
|
|
246
|
+
},
|
|
247
|
+
style: dom.getControlStyle(),
|
|
248
|
+
}),
|
|
249
|
+
|
|
250
|
+
markerGroupProps: normalize.element({
|
|
251
|
+
...parts.markerGroup.attrs,
|
|
252
|
+
role: "presentation",
|
|
253
|
+
"aria-hidden": true,
|
|
254
|
+
"data-orientation": state.context.orientation,
|
|
255
|
+
style: dom.getMarkerGroupStyle(),
|
|
256
|
+
}),
|
|
257
|
+
|
|
258
|
+
getMarkerProps({ value }: { value: number }) {
|
|
259
|
+
const percent = getValuePercentFn(value)
|
|
260
|
+
const style = dom.getMarkerStyle(state.context, percent)
|
|
261
|
+
const markerState =
|
|
262
|
+
value > state.context.value ? "over-value" : value < state.context.value ? "under-value" : "at-value"
|
|
263
|
+
|
|
264
|
+
return normalize.element({
|
|
265
|
+
...parts.marker.attrs,
|
|
266
|
+
id: dom.getMarkerId(state.context, value),
|
|
267
|
+
role: "presentation",
|
|
268
|
+
"data-orientation": state.context.orientation,
|
|
269
|
+
"data-value": value,
|
|
270
|
+
"data-disabled": dataAttr(isDisabled),
|
|
271
|
+
"data-state": markerState,
|
|
272
|
+
style,
|
|
273
|
+
})
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getRelativePoint } from "@zag-js/dom-event"
|
|
2
|
+
import { createScope } from "@zag-js/dom-query"
|
|
3
|
+
import { dispatchInputValueEvent } from "@zag-js/form-utils"
|
|
4
|
+
import { getPercentValue } from "@zag-js/numeric-range"
|
|
5
|
+
import { styles } from "./slider.style"
|
|
6
|
+
import type { MachineContext as Ctx, Point } from "./slider.types"
|
|
7
|
+
|
|
8
|
+
export const dom = createScope({
|
|
9
|
+
...styles,
|
|
10
|
+
|
|
11
|
+
getRootId: (ctx: Ctx) => ctx.ids?.root ?? `slider:${ctx.id}`,
|
|
12
|
+
getThumbId: (ctx: Ctx) => ctx.ids?.thumb ?? `slider:${ctx.id}:thumb`,
|
|
13
|
+
getControlId: (ctx: Ctx) => ctx.ids?.control ?? `slider:${ctx.id}:control`,
|
|
14
|
+
getHiddenInputId: (ctx: Ctx) => ctx.ids?.hiddenInput ?? `slider:${ctx.id}:input`,
|
|
15
|
+
getOutputId: (ctx: Ctx) => ctx.ids?.output ?? `slider:${ctx.id}:output`,
|
|
16
|
+
getTrackId: (ctx: Ctx) => ctx.ids?.track ?? `slider:${ctx.id}track`,
|
|
17
|
+
getRangeId: (ctx: Ctx) => ctx.ids?.track ?? `slider:${ctx.id}:range`,
|
|
18
|
+
getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `slider:${ctx.id}:label`,
|
|
19
|
+
getMarkerId: (ctx: Ctx, value: number) => `slider:${ctx.id}:marker:${value}`,
|
|
20
|
+
|
|
21
|
+
getRootEl: (ctx: Ctx) => dom.getById(ctx, dom.getRootId(ctx)),
|
|
22
|
+
getThumbEl: (ctx: Ctx) => dom.getById(ctx, dom.getThumbId(ctx)),
|
|
23
|
+
getControlEl: (ctx: Ctx) => dom.queryById(ctx, dom.getControlId(ctx)),
|
|
24
|
+
getHiddenInputEl: (ctx: Ctx) => dom.getById<HTMLInputElement>(ctx, dom.getHiddenInputId(ctx)),
|
|
25
|
+
|
|
26
|
+
getValueFromPoint(ctx: Ctx, point: Point): number | undefined {
|
|
27
|
+
const relativePoint = getRelativePoint(point, dom.getControlEl(ctx))
|
|
28
|
+
const percent = relativePoint.getPercentValue({
|
|
29
|
+
orientation: ctx.orientation,
|
|
30
|
+
dir: ctx.dir,
|
|
31
|
+
inverted: { y: true },
|
|
32
|
+
})
|
|
33
|
+
return getPercentValue(percent, ctx.min, ctx.max, ctx.step)
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
dispatchChangeEvent(ctx: Ctx) {
|
|
37
|
+
const input = dom.getHiddenInputEl(ctx)
|
|
38
|
+
if (!input) return
|
|
39
|
+
dispatchInputValueEvent(input, { value: ctx.value })
|
|
40
|
+
},
|
|
41
|
+
})
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { createMachine } from "@zag-js/core"
|
|
2
|
+
import { trackPointerMove } from "@zag-js/dom-event"
|
|
3
|
+
import { raf } from "@zag-js/dom-query"
|
|
4
|
+
import { trackElementSize } from "@zag-js/element-size"
|
|
5
|
+
import { trackFormControl } from "@zag-js/form-utils"
|
|
6
|
+
import { clampValue, getValuePercent } from "@zag-js/numeric-range"
|
|
7
|
+
import { compact } from "@zag-js/utils"
|
|
8
|
+
import { dom } from "./slider.dom"
|
|
9
|
+
import type { MachineContext, MachineState, UserDefinedContext } from "./slider.types"
|
|
10
|
+
import { constrainValue, decrement, increment } from "./slider.utils"
|
|
11
|
+
|
|
12
|
+
export function machine(userContext: UserDefinedContext) {
|
|
13
|
+
const ctx = compact(userContext)
|
|
14
|
+
return createMachine<MachineContext, MachineState>(
|
|
15
|
+
{
|
|
16
|
+
id: "slider",
|
|
17
|
+
initial: "idle",
|
|
18
|
+
context: {
|
|
19
|
+
thumbSize: null,
|
|
20
|
+
thumbAlignment: "contain",
|
|
21
|
+
disabled: false,
|
|
22
|
+
threshold: 5,
|
|
23
|
+
dir: "ltr",
|
|
24
|
+
origin: "start",
|
|
25
|
+
orientation: "horizontal",
|
|
26
|
+
initialValue: null,
|
|
27
|
+
value: 0,
|
|
28
|
+
step: 1,
|
|
29
|
+
min: 0,
|
|
30
|
+
max: 100,
|
|
31
|
+
...ctx,
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
computed: {
|
|
35
|
+
isHorizontal: (ctx) => ctx.orientation === "horizontal",
|
|
36
|
+
isVertical: (ctx) => ctx.orientation === "vertical",
|
|
37
|
+
isRtl: (ctx) => ctx.orientation === "horizontal" && ctx.dir === "rtl",
|
|
38
|
+
isInteractive: (ctx) => !(ctx.disabled || ctx.readOnly),
|
|
39
|
+
hasMeasuredThumbSize: (ctx) => ctx.thumbSize !== null,
|
|
40
|
+
valuePercent: (ctx) => 100 * getValuePercent(ctx.value, ctx.min, ctx.max),
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
watch: {
|
|
44
|
+
value: ["invokeOnChange", "dispatchChangeEvent"],
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
activities: ["trackFormControlState", "trackThumbSize"],
|
|
48
|
+
|
|
49
|
+
on: {
|
|
50
|
+
SET_VALUE: {
|
|
51
|
+
actions: "setValue",
|
|
52
|
+
},
|
|
53
|
+
INCREMENT: {
|
|
54
|
+
actions: "increment",
|
|
55
|
+
},
|
|
56
|
+
DECREMENT: {
|
|
57
|
+
actions: "decrement",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
entry: ["checkValue"],
|
|
62
|
+
|
|
63
|
+
states: {
|
|
64
|
+
idle: {
|
|
65
|
+
on: {
|
|
66
|
+
POINTER_DOWN: {
|
|
67
|
+
target: "dragging",
|
|
68
|
+
actions: ["setPointerValue", "invokeOnChangeStart", "focusThumb"],
|
|
69
|
+
},
|
|
70
|
+
FOCUS: "focus",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
focus: {
|
|
75
|
+
entry: "focusThumb",
|
|
76
|
+
on: {
|
|
77
|
+
POINTER_DOWN: {
|
|
78
|
+
target: "dragging",
|
|
79
|
+
actions: ["setPointerValue", "invokeOnChangeStart", "focusThumb"],
|
|
80
|
+
},
|
|
81
|
+
ARROW_LEFT: {
|
|
82
|
+
guard: "isHorizontal",
|
|
83
|
+
actions: "decrement",
|
|
84
|
+
},
|
|
85
|
+
ARROW_RIGHT: {
|
|
86
|
+
guard: "isHorizontal",
|
|
87
|
+
actions: "increment",
|
|
88
|
+
},
|
|
89
|
+
ARROW_UP: {
|
|
90
|
+
guard: "isVertical",
|
|
91
|
+
actions: "increment",
|
|
92
|
+
},
|
|
93
|
+
ARROW_DOWN: {
|
|
94
|
+
guard: "isVertical",
|
|
95
|
+
actions: "decrement",
|
|
96
|
+
},
|
|
97
|
+
PAGE_UP: {
|
|
98
|
+
actions: "increment",
|
|
99
|
+
},
|
|
100
|
+
PAGE_DOWN: {
|
|
101
|
+
actions: "decrement",
|
|
102
|
+
},
|
|
103
|
+
HOME: {
|
|
104
|
+
actions: "setToMin",
|
|
105
|
+
},
|
|
106
|
+
END: {
|
|
107
|
+
actions: "setToMax",
|
|
108
|
+
},
|
|
109
|
+
BLUR: "idle",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
dragging: {
|
|
114
|
+
entry: "focusThumb",
|
|
115
|
+
activities: "trackPointerMove",
|
|
116
|
+
on: {
|
|
117
|
+
POINTER_UP: {
|
|
118
|
+
target: "focus",
|
|
119
|
+
actions: "invokeOnChangeEnd",
|
|
120
|
+
},
|
|
121
|
+
POINTER_MOVE: {
|
|
122
|
+
actions: "setPointerValue",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
guards: {
|
|
130
|
+
isHorizontal: (ctx) => ctx.isHorizontal,
|
|
131
|
+
isVertical: (ctx) => ctx.isVertical,
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
activities: {
|
|
135
|
+
trackFormControlState(ctx) {
|
|
136
|
+
return trackFormControl(dom.getHiddenInputEl(ctx), {
|
|
137
|
+
onFieldsetDisabled() {
|
|
138
|
+
ctx.disabled = true
|
|
139
|
+
},
|
|
140
|
+
onFormReset() {
|
|
141
|
+
if (ctx.initialValue != null) {
|
|
142
|
+
ctx.value = ctx.initialValue
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
trackPointerMove(ctx, _evt, { send }) {
|
|
149
|
+
return trackPointerMove(dom.getDoc(ctx), {
|
|
150
|
+
onPointerMove(info) {
|
|
151
|
+
send({ type: "POINTER_MOVE", point: info.point })
|
|
152
|
+
},
|
|
153
|
+
onPointerUp() {
|
|
154
|
+
send("POINTER_UP")
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
},
|
|
158
|
+
trackThumbSize(ctx, _evt) {
|
|
159
|
+
if (ctx.thumbAlignment !== "contain") return
|
|
160
|
+
return trackElementSize(dom.getThumbEl(ctx), (size) => {
|
|
161
|
+
if (size) ctx.thumbSize = size
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
actions: {
|
|
167
|
+
checkValue(ctx) {
|
|
168
|
+
const value = constrainValue(ctx, ctx.value)
|
|
169
|
+
ctx.value = value
|
|
170
|
+
ctx.initialValue = value
|
|
171
|
+
},
|
|
172
|
+
invokeOnChangeStart(ctx) {
|
|
173
|
+
ctx.onChangeStart?.({ value: ctx.value })
|
|
174
|
+
},
|
|
175
|
+
invokeOnChangeEnd(ctx) {
|
|
176
|
+
ctx.onChangeEnd?.({ value: ctx.value })
|
|
177
|
+
},
|
|
178
|
+
invokeOnChange(ctx) {
|
|
179
|
+
ctx.onChange?.({ value: ctx.value })
|
|
180
|
+
},
|
|
181
|
+
dispatchChangeEvent(ctx) {
|
|
182
|
+
dom.dispatchChangeEvent(ctx)
|
|
183
|
+
},
|
|
184
|
+
setPointerValue(ctx, evt) {
|
|
185
|
+
const value = dom.getValueFromPoint(ctx, evt.point)
|
|
186
|
+
if (value == null) return
|
|
187
|
+
ctx.value = clampValue(value, ctx.min, ctx.max)
|
|
188
|
+
},
|
|
189
|
+
focusThumb(ctx) {
|
|
190
|
+
raf(() => dom.getThumbEl(ctx)?.focus())
|
|
191
|
+
},
|
|
192
|
+
decrement(ctx, evt) {
|
|
193
|
+
ctx.value = decrement(ctx, evt.step)
|
|
194
|
+
},
|
|
195
|
+
increment(ctx, evt) {
|
|
196
|
+
ctx.value = increment(ctx, evt.step)
|
|
197
|
+
},
|
|
198
|
+
setToMin(ctx) {
|
|
199
|
+
ctx.value = ctx.min
|
|
200
|
+
},
|
|
201
|
+
setToMax(ctx) {
|
|
202
|
+
ctx.value = ctx.max
|
|
203
|
+
},
|
|
204
|
+
setValue(ctx, evt) {
|
|
205
|
+
ctx.value = constrainValue(ctx, evt.value)
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { getValuePercent, getValueTransformer } from "@zag-js/numeric-range"
|
|
2
|
+
import type { Style } from "@zag-js/types"
|
|
3
|
+
import type { MachineContext as Ctx, SharedContext } from "./slider.types"
|
|
4
|
+
|
|
5
|
+
/* -----------------------------------------------------------------------------
|
|
6
|
+
* Thumb style calculations
|
|
7
|
+
* -----------------------------------------------------------------------------*/
|
|
8
|
+
|
|
9
|
+
function getVerticalThumbOffset(ctx: SharedContext) {
|
|
10
|
+
const { height = 0 } = ctx.thumbSize ?? {}
|
|
11
|
+
const getValue = getValueTransformer([ctx.min, ctx.max], [-height / 2, height / 2])
|
|
12
|
+
return parseFloat(getValue(ctx.value).toFixed(2))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getHorizontalThumbOffset(ctx: SharedContext) {
|
|
16
|
+
const { width = 0 } = ctx.thumbSize ?? {}
|
|
17
|
+
|
|
18
|
+
if (ctx.isRtl) {
|
|
19
|
+
const getValue = getValueTransformer([ctx.max, ctx.min], [-width * 1.5, -width / 2])
|
|
20
|
+
return -1 * parseFloat(getValue(ctx.value).toFixed(2))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const getValue = getValueTransformer([ctx.min, ctx.max], [-width / 2, width / 2])
|
|
24
|
+
return parseFloat(getValue(ctx.value).toFixed(2))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getThumbOffset(ctx: SharedContext) {
|
|
28
|
+
const percent = getValuePercent(ctx.value, ctx.min, ctx.max) * 100
|
|
29
|
+
|
|
30
|
+
if (ctx.thumbAlignment === "center") {
|
|
31
|
+
return `${percent}%`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const offset = ctx.isVertical ? getVerticalThumbOffset(ctx) : getHorizontalThumbOffset(ctx)
|
|
35
|
+
return `calc(${percent}% - ${offset}px)`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getVisibility(ctx: SharedContext) {
|
|
39
|
+
let visibility: "visible" | "hidden" = "visible"
|
|
40
|
+
if (ctx.thumbAlignment === "contain" && !ctx.hasMeasuredThumbSize) {
|
|
41
|
+
visibility = "hidden"
|
|
42
|
+
}
|
|
43
|
+
return visibility
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getThumbStyle(ctx: SharedContext): Style {
|
|
47
|
+
const placementProp = ctx.isVertical ? "bottom" : ctx.isRtl ? "right" : "left"
|
|
48
|
+
return {
|
|
49
|
+
visibility: getVisibility(ctx),
|
|
50
|
+
position: "absolute",
|
|
51
|
+
transform: "var(--slider-thumb-transform)",
|
|
52
|
+
[placementProp]: "var(--slider-thumb-offset)",
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* -----------------------------------------------------------------------------
|
|
57
|
+
* Range style calculations
|
|
58
|
+
* -----------------------------------------------------------------------------*/
|
|
59
|
+
|
|
60
|
+
function getRangeOffsets(ctx: Ctx) {
|
|
61
|
+
let start = "0%"
|
|
62
|
+
let end = `${100 - ctx.valuePercent}%`
|
|
63
|
+
|
|
64
|
+
if (ctx.origin === "center") {
|
|
65
|
+
const isNegative = ctx.valuePercent < 50
|
|
66
|
+
start = isNegative ? `${ctx.valuePercent}%` : "50%"
|
|
67
|
+
end = isNegative ? "50%" : end
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { start, end }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getRangeStyle(ctx: Pick<SharedContext, "isVertical" | "isRtl">): Style {
|
|
74
|
+
if (ctx.isVertical) {
|
|
75
|
+
return {
|
|
76
|
+
position: "absolute",
|
|
77
|
+
bottom: "var(--slider-range-start)",
|
|
78
|
+
top: "var(--slider-range-end)",
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
position: "absolute",
|
|
84
|
+
[ctx.isRtl ? "right" : "left"]: "var(--slider-range-start)",
|
|
85
|
+
[ctx.isRtl ? "left" : "right"]: "var(--slider-range-end)",
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* -----------------------------------------------------------------------------
|
|
90
|
+
* Control style calculations
|
|
91
|
+
* -----------------------------------------------------------------------------*/
|
|
92
|
+
|
|
93
|
+
function getControlStyle(): Style {
|
|
94
|
+
return {
|
|
95
|
+
touchAction: "none",
|
|
96
|
+
userSelect: "none",
|
|
97
|
+
position: "relative",
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* -----------------------------------------------------------------------------
|
|
102
|
+
* Root style calculations
|
|
103
|
+
* -----------------------------------------------------------------------------*/
|
|
104
|
+
|
|
105
|
+
function getRootStyle(ctx: Ctx): Style {
|
|
106
|
+
const range = getRangeOffsets(ctx)
|
|
107
|
+
return {
|
|
108
|
+
"--slider-thumb-transform": ctx.isVertical ? "translateY(50%)" : "translateX(-50%)",
|
|
109
|
+
"--slider-thumb-offset": getThumbOffset(ctx),
|
|
110
|
+
"--slider-range-start": range.start,
|
|
111
|
+
"--slider-range-end": range.end,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* -----------------------------------------------------------------------------
|
|
116
|
+
* Marker style calculations
|
|
117
|
+
* -----------------------------------------------------------------------------*/
|
|
118
|
+
|
|
119
|
+
function getMarkerStyle(ctx: Pick<SharedContext, "isHorizontal" | "isRtl">, percent: number): Style {
|
|
120
|
+
return {
|
|
121
|
+
position: "absolute",
|
|
122
|
+
pointerEvents: "none",
|
|
123
|
+
[ctx.isHorizontal ? "left" : "bottom"]: `${(ctx.isRtl ? 1 - percent : percent) * 100}%`,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* -----------------------------------------------------------------------------
|
|
128
|
+
* Label style calculations
|
|
129
|
+
* -----------------------------------------------------------------------------*/
|
|
130
|
+
|
|
131
|
+
function getLabelStyle(): Style {
|
|
132
|
+
return { userSelect: "none" }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* -----------------------------------------------------------------------------
|
|
136
|
+
* Label style calculations
|
|
137
|
+
* -----------------------------------------------------------------------------*/
|
|
138
|
+
|
|
139
|
+
function getTrackStyle(): Style {
|
|
140
|
+
return { position: "relative" }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* -----------------------------------------------------------------------------
|
|
144
|
+
* Label style calculations
|
|
145
|
+
* -----------------------------------------------------------------------------*/
|
|
146
|
+
|
|
147
|
+
function getMarkerGroupStyle(): Style {
|
|
148
|
+
return {
|
|
149
|
+
userSelect: "none",
|
|
150
|
+
pointerEvents: "none",
|
|
151
|
+
position: "relative",
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const styles = {
|
|
156
|
+
getThumbOffset,
|
|
157
|
+
getControlStyle,
|
|
158
|
+
getThumbStyle,
|
|
159
|
+
getRangeStyle,
|
|
160
|
+
getRootStyle,
|
|
161
|
+
getMarkerStyle,
|
|
162
|
+
getLabelStyle,
|
|
163
|
+
getTrackStyle,
|
|
164
|
+
getMarkerGroupStyle,
|
|
165
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { StateMachine as S } from "@zag-js/core"
|
|
2
|
+
import type { CommonProperties, Context, DirectionProperty, RequiredBy } from "@zag-js/types"
|
|
3
|
+
|
|
4
|
+
type ElementIds = Partial<{
|
|
5
|
+
root: string
|
|
6
|
+
thumb: string
|
|
7
|
+
control: string
|
|
8
|
+
track: string
|
|
9
|
+
range: string
|
|
10
|
+
label: string
|
|
11
|
+
output: string
|
|
12
|
+
hiddenInput: string
|
|
13
|
+
}>
|
|
14
|
+
|
|
15
|
+
type PublicContext = DirectionProperty &
|
|
16
|
+
CommonProperties & {
|
|
17
|
+
/**
|
|
18
|
+
* The ids of the elements in the slider. Useful for composition.
|
|
19
|
+
*/
|
|
20
|
+
ids?: ElementIds
|
|
21
|
+
/**
|
|
22
|
+
* The value of the slider
|
|
23
|
+
*/
|
|
24
|
+
value: number
|
|
25
|
+
/**
|
|
26
|
+
* The name associated with the slider (when used in a form)
|
|
27
|
+
*/
|
|
28
|
+
name?: string
|
|
29
|
+
/**
|
|
30
|
+
* The associate form of the underlying input element.
|
|
31
|
+
*/
|
|
32
|
+
form?: string
|
|
33
|
+
/**
|
|
34
|
+
* Whether the slider is disabled
|
|
35
|
+
*/
|
|
36
|
+
disabled?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* Whether the slider is read-only
|
|
39
|
+
*/
|
|
40
|
+
readOnly?: boolean
|
|
41
|
+
/**
|
|
42
|
+
* Whether the slider value is invalid
|
|
43
|
+
*/
|
|
44
|
+
invalid?: boolean
|
|
45
|
+
/**
|
|
46
|
+
* The minimum value of the slider
|
|
47
|
+
*/
|
|
48
|
+
min: number
|
|
49
|
+
/**
|
|
50
|
+
* The maximum value of the slider
|
|
51
|
+
*/
|
|
52
|
+
max: number
|
|
53
|
+
/**
|
|
54
|
+
* The step value of the slider
|
|
55
|
+
*/
|
|
56
|
+
step: number
|
|
57
|
+
/**
|
|
58
|
+
* The orientation of the slider
|
|
59
|
+
*/
|
|
60
|
+
orientation?: "vertical" | "horizontal"
|
|
61
|
+
/**
|
|
62
|
+
* - "start": Useful when the value represents an absolute value
|
|
63
|
+
* - "center": Useful when the value represents an offset (relative)
|
|
64
|
+
*/
|
|
65
|
+
origin?: "start" | "center"
|
|
66
|
+
/**
|
|
67
|
+
* The aria-label of the slider. Useful for providing an accessible name to the slider
|
|
68
|
+
*/
|
|
69
|
+
"aria-label"?: string
|
|
70
|
+
/**
|
|
71
|
+
* The `id` of the element that labels the slider. Useful for providing an accessible name to the slider
|
|
72
|
+
*/
|
|
73
|
+
"aria-labelledby"?: string
|
|
74
|
+
/**
|
|
75
|
+
* Whether to focus the slider thumb after interaction (scrub and keyboard)
|
|
76
|
+
*/
|
|
77
|
+
focusThumbOnChange?: boolean
|
|
78
|
+
/**
|
|
79
|
+
* Function that returns a human readable value for the slider
|
|
80
|
+
*/
|
|
81
|
+
getAriaValueText?(value: number): string
|
|
82
|
+
/**
|
|
83
|
+
* Function invoked when the value of the slider changes
|
|
84
|
+
*/
|
|
85
|
+
onChange?(details: { value: number }): void
|
|
86
|
+
/**
|
|
87
|
+
* Function invoked when the slider value change is done
|
|
88
|
+
*/
|
|
89
|
+
onChangeEnd?(details: { value: number }): void
|
|
90
|
+
/**
|
|
91
|
+
* Function invoked when the slider value change is started
|
|
92
|
+
*/
|
|
93
|
+
onChangeStart?(details: { value: number }): void
|
|
94
|
+
/**
|
|
95
|
+
* The alignment of the slider thumb relative to the track
|
|
96
|
+
* - `center`: the thumb will extend beyond the bounds of the slider track.
|
|
97
|
+
* - `contain`: the thumb will be contained within the bounds of the track.
|
|
98
|
+
*/
|
|
99
|
+
thumbAlignment?: "contain" | "center"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type UserDefinedContext = RequiredBy<PublicContext, "id">
|
|
103
|
+
|
|
104
|
+
type ComputedContext = Readonly<{
|
|
105
|
+
/**
|
|
106
|
+
* @computed
|
|
107
|
+
* Whether the slider is interactive
|
|
108
|
+
*/
|
|
109
|
+
readonly isInteractive: boolean
|
|
110
|
+
/**
|
|
111
|
+
* @computed
|
|
112
|
+
* Whether the thumb size has been measured
|
|
113
|
+
*/
|
|
114
|
+
readonly hasMeasuredThumbSize: boolean
|
|
115
|
+
/**
|
|
116
|
+
* @computed
|
|
117
|
+
* Whether the slider is horizontal
|
|
118
|
+
*/
|
|
119
|
+
readonly isHorizontal: boolean
|
|
120
|
+
/**
|
|
121
|
+
* @computed
|
|
122
|
+
* Whether the slider is vertical
|
|
123
|
+
*/
|
|
124
|
+
readonly isVertical: boolean
|
|
125
|
+
/**
|
|
126
|
+
* @computed
|
|
127
|
+
* Whether the slider is in RTL mode
|
|
128
|
+
*/
|
|
129
|
+
readonly isRtl: boolean
|
|
130
|
+
/**
|
|
131
|
+
* @computed
|
|
132
|
+
* The value of the slider as a percentage
|
|
133
|
+
*/
|
|
134
|
+
readonly valuePercent: number
|
|
135
|
+
}>
|
|
136
|
+
|
|
137
|
+
type PrivateContext = Context<{
|
|
138
|
+
/**
|
|
139
|
+
* @internal The move threshold of the slider thumb before it is considered to be moved
|
|
140
|
+
*/
|
|
141
|
+
threshold: number
|
|
142
|
+
/**
|
|
143
|
+
* @internal The slider thumb dimensions
|
|
144
|
+
*/
|
|
145
|
+
thumbSize: { width: number; height: number } | null
|
|
146
|
+
/**
|
|
147
|
+
* @internal
|
|
148
|
+
* The value of the slider when it was initially rendered.
|
|
149
|
+
* Used when the `form.reset(...)` is called.
|
|
150
|
+
*/
|
|
151
|
+
initialValue: number | null
|
|
152
|
+
}>
|
|
153
|
+
|
|
154
|
+
export type MachineContext = PublicContext & ComputedContext & PrivateContext
|
|
155
|
+
|
|
156
|
+
export type MachineState = {
|
|
157
|
+
value: "idle" | "dragging" | "focus"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type State = S.State<MachineContext, MachineState>
|
|
161
|
+
|
|
162
|
+
export type Send = S.Send<S.AnyEventObject>
|
|
163
|
+
|
|
164
|
+
export type SharedContext = {
|
|
165
|
+
min: number
|
|
166
|
+
max: number
|
|
167
|
+
step: number
|
|
168
|
+
dir?: "ltr" | "rtl"
|
|
169
|
+
isRtl: boolean
|
|
170
|
+
isVertical: boolean
|
|
171
|
+
isHorizontal: boolean
|
|
172
|
+
value: number
|
|
173
|
+
thumbSize: { width: number; height: number } | null
|
|
174
|
+
thumbAlignment?: "contain" | "center"
|
|
175
|
+
orientation?: "horizontal" | "vertical"
|
|
176
|
+
readonly hasMeasuredThumbSize: boolean
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export type Point = {
|
|
180
|
+
x: number
|
|
181
|
+
y: number
|
|
182
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { clampValue, getNextStepValue, getPreviousStepValue, snapValueToStep } from "@zag-js/numeric-range"
|
|
2
|
+
import type { MachineContext as Ctx } from "./slider.types"
|
|
3
|
+
|
|
4
|
+
export function clampPercent(percent: number) {
|
|
5
|
+
return clampValue(percent, 0, 1)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function constrainValue(ctx: Ctx, value: number) {
|
|
9
|
+
const snapValue = snapValueToStep(value, ctx.min, ctx.max, ctx.step)
|
|
10
|
+
return clampValue(snapValue, ctx.min, ctx.max)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function decrement(ctx: Ctx, step?: number) {
|
|
14
|
+
const index = 0
|
|
15
|
+
const values = getPreviousStepValue(index, {
|
|
16
|
+
...ctx,
|
|
17
|
+
step: step ?? ctx.step,
|
|
18
|
+
values: [ctx.value],
|
|
19
|
+
})
|
|
20
|
+
return values[index]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function increment(ctx: Ctx, step?: number) {
|
|
24
|
+
const index = 0
|
|
25
|
+
const values = getNextStepValue(index, {
|
|
26
|
+
...ctx,
|
|
27
|
+
step: step ?? ctx.step,
|
|
28
|
+
values: [ctx.value],
|
|
29
|
+
})
|
|
30
|
+
return values[index]
|
|
31
|
+
}
|