@zag-js/slider 0.70.0 → 0.72.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/src/slider.dom.ts DELETED
@@ -1,47 +0,0 @@
1
- import { getRelativePoint, type Point } from "@zag-js/dom-event"
2
- import { createScope, queryAll } from "@zag-js/dom-query"
3
- import { dispatchInputValueEvent } from "@zag-js/form-utils"
4
- import { getPercentValue } from "@zag-js/numeric-range"
5
- import { styleGetterFns } from "./slider.style"
6
- import type { MachineContext as Ctx } from "./slider.types"
7
-
8
- export const dom = createScope({
9
- ...styleGetterFns,
10
- getRootId: (ctx: Ctx) => ctx.ids?.root ?? `slider:${ctx.id}`,
11
- getThumbId: (ctx: Ctx, index: number) => ctx.ids?.thumb?.(index) ?? `slider:${ctx.id}:thumb:${index}`,
12
- getHiddenInputId: (ctx: Ctx, index: number) => ctx.ids?.hiddenInput?.(index) ?? `slider:${ctx.id}:input:${index}`,
13
- getControlId: (ctx: Ctx) => ctx.ids?.control ?? `slider:${ctx.id}:control`,
14
- getTrackId: (ctx: Ctx) => ctx.ids?.track ?? `slider:${ctx.id}:track`,
15
- getRangeId: (ctx: Ctx) => ctx.ids?.range ?? `slider:${ctx.id}:range`,
16
- getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `slider:${ctx.id}:label`,
17
- getValueTextId: (ctx: Ctx) => ctx.ids?.valueText ?? `slider:${ctx.id}:value-text`,
18
- getMarkerId: (ctx: Ctx, value: number) => ctx.ids?.marker?.(value) ?? `slider:${ctx.id}:marker:${value}`,
19
-
20
- getRootEl: (ctx: Ctx) => dom.getById(ctx, dom.getRootId(ctx)),
21
- getThumbEl: (ctx: Ctx, index: number) => dom.getById(ctx, dom.getThumbId(ctx, index)),
22
- getHiddenInputEl: (ctx: Ctx, index: number) => dom.getById<HTMLInputElement>(ctx, dom.getHiddenInputId(ctx, index)),
23
- getControlEl: (ctx: Ctx) => dom.getById(ctx, dom.getControlId(ctx)),
24
- getElements: (ctx: Ctx) => queryAll(dom.getControlEl(ctx), "[role=slider]"),
25
- getFirstEl: (ctx: Ctx) => dom.getElements(ctx)[0],
26
- getRangeEl: (ctx: Ctx) => dom.getById(ctx, dom.getRangeId(ctx)),
27
-
28
- getValueFromPoint(ctx: Ctx, point: Point) {
29
- const controlEl = dom.getControlEl(ctx)
30
- if (!controlEl) return
31
- const relativePoint = getRelativePoint(point, controlEl)
32
- const percent = relativePoint.getPercentValue({
33
- orientation: ctx.orientation,
34
- dir: ctx.dir,
35
- inverted: { y: true },
36
- })
37
- return getPercentValue(percent, ctx.min, ctx.max, ctx.step)
38
- },
39
- dispatchChangeEvent(ctx: Ctx) {
40
- const valueArray = Array.from(ctx.value)
41
- valueArray.forEach((value, index) => {
42
- const inputEl = dom.getHiddenInputEl(ctx, index)
43
- if (!inputEl) return
44
- dispatchInputValueEvent(inputEl, { value })
45
- })
46
- },
47
- })
@@ -1,290 +0,0 @@
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 { trackElementsSize, type ElementSize } from "@zag-js/element-size"
5
- import { trackFormControl } from "@zag-js/form-utils"
6
- import { getValuePercent } from "@zag-js/numeric-range"
7
- import { compact, isEqual } from "@zag-js/utils"
8
- import { dom } from "./slider.dom"
9
- import type { MachineContext, MachineState, UserDefinedContext } from "./slider.types"
10
- import {
11
- assignArray,
12
- constrainValue,
13
- decrement,
14
- getClosestIndex,
15
- getRangeAtIndex,
16
- increment,
17
- normalizeValues,
18
- } from "./slider.utils"
19
-
20
- const isEqualSize = (a: ElementSize | null, b: ElementSize | null) => {
21
- return a?.width === b?.width && a?.height === b?.height
22
- }
23
-
24
- export function machine(userContext: UserDefinedContext) {
25
- const ctx = compact(userContext)
26
- return createMachine<MachineContext, MachineState>(
27
- {
28
- id: "slider",
29
- initial: "idle",
30
-
31
- context: {
32
- thumbSize: null,
33
- thumbAlignment: "contain",
34
- min: 0,
35
- max: 100,
36
- step: 1,
37
- value: [0],
38
- origin: "start",
39
- orientation: "horizontal",
40
- dir: "ltr",
41
- minStepsBetweenThumbs: 0,
42
- disabled: false,
43
- readOnly: false,
44
- ...ctx,
45
- focusedIndex: -1,
46
- fieldsetDisabled: false,
47
- },
48
-
49
- computed: {
50
- isHorizontal: (ctx) => ctx.orientation === "horizontal",
51
- isVertical: (ctx) => ctx.orientation === "vertical",
52
- isRtl: (ctx) => ctx.orientation === "horizontal" && ctx.dir === "rtl",
53
- isDisabled: (ctx) => !!ctx.disabled || ctx.fieldsetDisabled,
54
- isInteractive: (ctx) => !(ctx.readOnly || ctx.isDisabled),
55
- hasMeasuredThumbSize: (ctx) => ctx.thumbSize != null,
56
- valuePercent(ctx) {
57
- return ctx.value.map((value) => 100 * getValuePercent(value, ctx.min, ctx.max))
58
- },
59
- },
60
-
61
- watch: {
62
- value: ["syncInputElements"],
63
- },
64
-
65
- entry: ["coarseValue"],
66
-
67
- activities: ["trackFormControlState", "trackThumbsSize"],
68
-
69
- on: {
70
- SET_VALUE: [
71
- {
72
- guard: "hasIndex",
73
- actions: "setValueAtIndex",
74
- },
75
- { actions: "setValue" },
76
- ],
77
- INCREMENT: {
78
- actions: "incrementThumbAtIndex",
79
- },
80
- DECREMENT: {
81
- actions: "decrementThumbAtIndex",
82
- },
83
- },
84
-
85
- states: {
86
- idle: {
87
- on: {
88
- POINTER_DOWN: {
89
- target: "dragging",
90
- actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"],
91
- },
92
- FOCUS: {
93
- target: "focus",
94
- actions: "setFocusedIndex",
95
- },
96
- THUMB_POINTER_DOWN: {
97
- target: "dragging",
98
- actions: ["setFocusedIndex", "focusActiveThumb"],
99
- },
100
- },
101
- },
102
- focus: {
103
- entry: "focusActiveThumb",
104
- on: {
105
- POINTER_DOWN: {
106
- target: "dragging",
107
- actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"],
108
- },
109
- THUMB_POINTER_DOWN: {
110
- target: "dragging",
111
- actions: ["setFocusedIndex", "focusActiveThumb"],
112
- },
113
- ARROW_DEC: {
114
- actions: ["decrementThumbAtIndex", "invokeOnChangeEnd"],
115
- },
116
- ARROW_INC: {
117
- actions: ["incrementThumbAtIndex", "invokeOnChangeEnd"],
118
- },
119
- HOME: {
120
- actions: ["setFocusedThumbToMin", "invokeOnChangeEnd"],
121
- },
122
- END: {
123
- actions: ["setFocusedThumbToMax", "invokeOnChangeEnd"],
124
- },
125
- BLUR: {
126
- target: "idle",
127
- actions: "clearFocusedIndex",
128
- },
129
- },
130
- },
131
- dragging: {
132
- entry: "focusActiveThumb",
133
- activities: "trackPointerMove",
134
- on: {
135
- POINTER_UP: {
136
- target: "focus",
137
- actions: "invokeOnChangeEnd",
138
- },
139
- POINTER_MOVE: {
140
- actions: "setPointerValue",
141
- },
142
- },
143
- },
144
- },
145
- },
146
- {
147
- guards: {
148
- hasIndex: (_ctx, evt) => evt.index != null,
149
- },
150
- activities: {
151
- trackFormControlState(ctx, _evt, { initialContext }) {
152
- return trackFormControl(dom.getRootEl(ctx), {
153
- onFieldsetDisabledChange(disabled) {
154
- ctx.fieldsetDisabled = disabled
155
- },
156
- onFormReset() {
157
- set.value(ctx, initialContext.value)
158
- },
159
- })
160
- },
161
-
162
- trackPointerMove(ctx, _evt, { send }) {
163
- return trackPointerMove(dom.getDoc(ctx), {
164
- onPointerMove(info) {
165
- send({ type: "POINTER_MOVE", point: info.point })
166
- },
167
- onPointerUp() {
168
- send("POINTER_UP")
169
- },
170
- })
171
- },
172
- trackThumbsSize(ctx) {
173
- if (ctx.thumbAlignment !== "contain" || ctx.thumbSize) return
174
-
175
- return trackElementsSize({
176
- getNodes: () => dom.getElements(ctx),
177
- observeMutation: true,
178
- callback(size) {
179
- if (!size || isEqualSize(ctx.thumbSize, size)) return
180
- ctx.thumbSize = size
181
- },
182
- })
183
- },
184
- },
185
- actions: {
186
- syncInputElements(ctx) {
187
- ctx.value.forEach((value, index) => {
188
- const inputEl = dom.getHiddenInputEl(ctx, index)
189
- dom.setValue(inputEl, value)
190
- })
191
- },
192
- invokeOnChangeEnd(ctx) {
193
- invoke.valueChangeEnd(ctx)
194
- },
195
- setClosestThumbIndex(ctx, evt) {
196
- const pointValue = dom.getValueFromPoint(ctx, evt.point)
197
- if (pointValue == null) return
198
-
199
- const focusedIndex = getClosestIndex(ctx, pointValue)
200
- set.focusedIndex(ctx, focusedIndex)
201
- },
202
- setFocusedIndex(ctx, evt) {
203
- set.focusedIndex(ctx, evt.index)
204
- },
205
- clearFocusedIndex(ctx) {
206
- set.focusedIndex(ctx, -1)
207
- },
208
- setPointerValue(ctx, evt) {
209
- const pointerValue = dom.getValueFromPoint(ctx, evt.point)
210
- if (pointerValue == null) return
211
-
212
- const value = constrainValue(ctx, pointerValue, ctx.focusedIndex)
213
- set.valueAtIndex(ctx, ctx.focusedIndex, value)
214
- },
215
- focusActiveThumb(ctx) {
216
- raf(() => {
217
- const thumbEl = dom.getThumbEl(ctx, ctx.focusedIndex)
218
- thumbEl?.focus({ preventScroll: true })
219
- })
220
- },
221
- decrementThumbAtIndex(ctx, evt) {
222
- const value = decrement(ctx, evt.index, evt.step)
223
- set.value(ctx, value)
224
- },
225
- incrementThumbAtIndex(ctx, evt) {
226
- const value = increment(ctx, evt.index, evt.step)
227
- set.value(ctx, value)
228
- },
229
- setFocusedThumbToMin(ctx) {
230
- const { min } = getRangeAtIndex(ctx, ctx.focusedIndex)
231
- set.valueAtIndex(ctx, ctx.focusedIndex, min)
232
- },
233
- setFocusedThumbToMax(ctx) {
234
- const { max } = getRangeAtIndex(ctx, ctx.focusedIndex)
235
- set.valueAtIndex(ctx, ctx.focusedIndex, max)
236
- },
237
- coarseValue(ctx) {
238
- const value = normalizeValues(ctx, ctx.value)
239
- set.value(ctx, value)
240
- },
241
- setValueAtIndex(ctx, evt) {
242
- const value = constrainValue(ctx, evt.value, evt.index)
243
- set.valueAtIndex(ctx, evt.index, value)
244
- },
245
- setValue(ctx, evt) {
246
- const value = normalizeValues(ctx, evt.value)
247
- set.value(ctx, value)
248
- },
249
- },
250
- },
251
- )
252
- }
253
-
254
- const invoke = {
255
- valueChange(ctx: MachineContext) {
256
- ctx.onValueChange?.({
257
- value: Array.from(ctx.value),
258
- })
259
- dom.dispatchChangeEvent(ctx)
260
- },
261
- valueChangeEnd(ctx: MachineContext) {
262
- ctx.onValueChangeEnd?.({
263
- value: Array.from(ctx.value),
264
- })
265
- },
266
- focusChange(ctx: MachineContext) {
267
- ctx.onFocusChange?.({
268
- value: Array.from(ctx.value),
269
- focusedIndex: ctx.focusedIndex,
270
- })
271
- },
272
- }
273
-
274
- const set = {
275
- valueAtIndex: (ctx: MachineContext, index: number, value: number) => {
276
- if (isEqual(ctx.value[index], value)) return
277
- ctx.value[index] = value
278
- invoke.valueChange(ctx)
279
- },
280
- value: (ctx: MachineContext, value: number[]) => {
281
- if (isEqual(ctx.value, value)) return
282
- assignArray(ctx.value, value)
283
- invoke.valueChange(ctx)
284
- },
285
- focusedIndex: (ctx: MachineContext, index: number) => {
286
- if (isEqual(ctx.focusedIndex, index)) return
287
- ctx.focusedIndex = index
288
- invoke.focusChange(ctx)
289
- },
290
- }
@@ -1,36 +0,0 @@
1
- import { createProps } from "@zag-js/types"
2
- import { createSplitProps } from "@zag-js/utils"
3
- import type { ThumbProps, UserDefinedContext } from "./slider.types"
4
-
5
- export const props = createProps<UserDefinedContext>()([
6
- "aria-label",
7
- "aria-labelledby",
8
- "dir",
9
- "disabled",
10
- "form",
11
- "getAriaValueText",
12
- "getRootNode",
13
- "id",
14
- "ids",
15
- "invalid",
16
- "max",
17
- "min",
18
- "minStepsBetweenThumbs",
19
- "name",
20
- "onFocusChange",
21
- "onValueChange",
22
- "onValueChangeEnd",
23
- "orientation",
24
- "origin",
25
- "readOnly",
26
- "step",
27
- "thumbAlignment",
28
- "thumbAlignment",
29
- "thumbSize",
30
- "value",
31
- ])
32
-
33
- export const splitProps = createSplitProps<Partial<UserDefinedContext>>(props)
34
-
35
- export const thumbProps = createProps<ThumbProps>()(["index", "name"])
36
- export const splitThumbProps = createSplitProps<ThumbProps>(thumbProps)
@@ -1,174 +0,0 @@
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
- * Range style calculations
7
- * -----------------------------------------------------------------------------*/
8
-
9
- function getBounds<T>(value: T[]): [T, T] {
10
- const firstValue = value[0]
11
- const lastThumb = value[value.length - 1]
12
- return [firstValue, lastThumb]
13
- }
14
-
15
- export function getRangeOffsets(ctx: Ctx) {
16
- const [firstPercent, lastPercent] = getBounds(ctx.valuePercent)
17
-
18
- if (ctx.valuePercent.length === 1) {
19
- if (ctx.origin === "center") {
20
- const isNegative = ctx.valuePercent[0] < 50
21
- const start = isNegative ? `${ctx.valuePercent[0]}%` : "50%"
22
- const end = isNegative ? "50%" : `${100 - ctx.valuePercent[0]}%`
23
-
24
- return { start, end }
25
- }
26
-
27
- return { start: "0%", end: `${100 - lastPercent}%` }
28
- }
29
-
30
- return { start: `${firstPercent}%`, end: `${100 - lastPercent}%` }
31
- }
32
-
33
- function getRangeStyle(ctx: Pick<SharedContext, "isVertical" | "isRtl">): Style {
34
- if (ctx.isVertical) {
35
- return {
36
- position: "absolute",
37
- bottom: "var(--slider-range-start)",
38
- top: "var(--slider-range-end)",
39
- }
40
- }
41
-
42
- return {
43
- position: "absolute",
44
- [ctx.isRtl ? "right" : "left"]: "var(--slider-range-start)",
45
- [ctx.isRtl ? "left" : "right"]: "var(--slider-range-end)",
46
- }
47
- }
48
-
49
- /* -----------------------------------------------------------------------------
50
- * Thumb style calculations
51
- * -----------------------------------------------------------------------------*/
52
-
53
- function getVerticalThumbOffset(ctx: SharedContext) {
54
- const { height = 0 } = ctx.thumbSize ?? {}
55
- const getValue = getValueTransformer([ctx.min, ctx.max], [-height / 2, height / 2])
56
- return parseFloat(getValue(ctx.value).toFixed(2))
57
- }
58
-
59
- function getHorizontalThumbOffset(ctx: SharedContext) {
60
- const { width = 0 } = ctx.thumbSize ?? {}
61
-
62
- if (ctx.isRtl) {
63
- const getValue = getValueTransformer([ctx.max, ctx.min], [-width / 2, width / 2])
64
- return -1 * parseFloat(getValue(ctx.value).toFixed(2))
65
- }
66
-
67
- const getValue = getValueTransformer([ctx.min, ctx.max], [-width / 2, width / 2])
68
- return parseFloat(getValue(ctx.value).toFixed(2))
69
- }
70
-
71
- function getOffset(ctx: SharedContext, percent: number) {
72
- if (ctx.thumbAlignment === "center") return `${percent}%`
73
- const offset = ctx.isVertical ? getVerticalThumbOffset(ctx) : getHorizontalThumbOffset(ctx)
74
- return `calc(${percent}% - ${offset}px)`
75
- }
76
-
77
- function getThumbOffset(ctx: SharedContext) {
78
- let percent = getValuePercent(ctx.value, ctx.min, ctx.max) * 100
79
- return getOffset(ctx, percent)
80
- }
81
-
82
- function getVisibility(ctx: Ctx) {
83
- let visibility: "visible" | "hidden" = "visible"
84
- if (ctx.thumbAlignment === "contain" && !ctx.hasMeasuredThumbSize) {
85
- visibility = "hidden"
86
- }
87
- return visibility
88
- }
89
-
90
- function getThumbStyle(ctx: Ctx, index: number): Style {
91
- const placementProp = ctx.isVertical ? "bottom" : "insetInlineStart"
92
- return {
93
- visibility: getVisibility(ctx),
94
- position: "absolute",
95
- transform: "var(--slider-thumb-transform)",
96
- [placementProp]: `var(--slider-thumb-offset-${index})`,
97
- }
98
- }
99
-
100
- /* -----------------------------------------------------------------------------
101
- * Control style calculations
102
- * -----------------------------------------------------------------------------*/
103
-
104
- function getControlStyle(): Style {
105
- return {
106
- touchAction: "none",
107
- userSelect: "none",
108
- WebkitUserSelect: "none",
109
- position: "relative",
110
- }
111
- }
112
-
113
- /* -----------------------------------------------------------------------------
114
- * Root style calculations
115
- * -----------------------------------------------------------------------------*/
116
-
117
- function getRootStyle(ctx: Ctx): Style {
118
- const range = getRangeOffsets(ctx)
119
-
120
- const offsetStyles = ctx.value.reduce<Style>((styles, value, index) => {
121
- const offset = getThumbOffset({ ...ctx, value })
122
- return { ...styles, [`--slider-thumb-offset-${index}`]: offset }
123
- }, {})
124
-
125
- return {
126
- ...offsetStyles,
127
- "--slider-thumb-transform": ctx.isVertical ? "translateY(50%)" : ctx.isRtl ? "translateX(50%)" : "translateX(-50%)",
128
- "--slider-range-start": range.start,
129
- "--slider-range-end": range.end,
130
- }
131
- }
132
-
133
- /* -----------------------------------------------------------------------------
134
- * Marker style calculations
135
- * -----------------------------------------------------------------------------*/
136
-
137
- function getMarkerStyle(
138
- ctx: Pick<SharedContext, "isHorizontal" | "isRtl" | "thumbAlignment" | "hasMeasuredThumbSize">,
139
- value: number,
140
- ): Style {
141
- return {
142
- // @ts-expect-error
143
- visibility: getVisibility(ctx),
144
- position: "absolute",
145
- pointerEvents: "none",
146
- // @ts-expect-error
147
- [ctx.isHorizontal ? "insetInlineStart" : "bottom"]: getThumbOffset({ ...ctx, value }),
148
- translate: "var(--tx) var(--ty)",
149
- "--tx": ctx.isHorizontal ? (ctx.isRtl ? "50%" : "-50%") : "0%",
150
- "--ty": !ctx.isHorizontal ? "50%" : "0%",
151
- }
152
- }
153
-
154
- /* -----------------------------------------------------------------------------
155
- * Label style calculations
156
- * -----------------------------------------------------------------------------*/
157
-
158
- function getMarkerGroupStyle(): Style {
159
- return {
160
- userSelect: "none",
161
- WebkitUserSelect: "none",
162
- pointerEvents: "none",
163
- position: "relative",
164
- }
165
- }
166
-
167
- export const styleGetterFns = {
168
- getRootStyle,
169
- getControlStyle,
170
- getThumbStyle,
171
- getRangeStyle,
172
- getMarkerStyle,
173
- getMarkerGroupStyle,
174
- }