@zag-js/slider 0.23.0 → 0.25.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/index.mjs CHANGED
@@ -27,12 +27,44 @@ import { getPercentValue as getPercentValue2, getValuePercent as getValuePercent
27
27
 
28
28
  // src/slider.dom.ts
29
29
  import { getRelativePoint } from "@zag-js/dom-event";
30
- import { createScope } from "@zag-js/dom-query";
30
+ import { createScope, queryAll } from "@zag-js/dom-query";
31
31
  import { dispatchInputValueEvent } from "@zag-js/form-utils";
32
32
  import { getPercentValue } from "@zag-js/numeric-range";
33
33
 
34
34
  // src/slider.style.ts
35
35
  import { getValuePercent, getValueTransformer } from "@zag-js/numeric-range";
36
+ function getBounds(value) {
37
+ const firstValue = value[0];
38
+ const lastThumb = value[value.length - 1];
39
+ return [firstValue, lastThumb];
40
+ }
41
+ function getRangeOffsets(ctx) {
42
+ const [firstPercent, lastPercent] = getBounds(ctx.valuePercent);
43
+ if (ctx.valuePercent.length === 1) {
44
+ if (ctx.origin === "center") {
45
+ const isNegative = ctx.valuePercent[0] < 50;
46
+ const start = isNegative ? `${ctx.valuePercent[0]}%` : "50%";
47
+ const end = isNegative ? "50%" : `${100 - ctx.valuePercent[0]}%`;
48
+ return { start, end };
49
+ }
50
+ return { start: "0%", end: `${100 - lastPercent}%` };
51
+ }
52
+ return { start: `${firstPercent}%`, end: `${100 - lastPercent}%` };
53
+ }
54
+ function getRangeStyle(ctx) {
55
+ if (ctx.isVertical) {
56
+ return {
57
+ position: "absolute",
58
+ bottom: "var(--slider-range-start)",
59
+ top: "var(--slider-range-end)"
60
+ };
61
+ }
62
+ return {
63
+ position: "absolute",
64
+ [ctx.isRtl ? "right" : "left"]: "var(--slider-range-start)",
65
+ [ctx.isRtl ? "left" : "right"]: "var(--slider-range-end)"
66
+ };
67
+ }
36
68
  function getVerticalThumbOffset(ctx) {
37
69
  const { height = 0 } = ctx.thumbSize ?? {};
38
70
  const getValue = getValueTransformer([ctx.min, ctx.max], [-height / 2, height / 2]);
@@ -64,37 +96,13 @@ function getVisibility(ctx) {
64
96
  }
65
97
  return visibility;
66
98
  }
67
- function getThumbStyle(ctx) {
99
+ function getThumbStyle(ctx, index) {
68
100
  const placementProp = ctx.isVertical ? "bottom" : "insetInlineStart";
69
101
  return {
70
102
  visibility: getVisibility(ctx),
71
103
  position: "absolute",
72
104
  transform: "var(--slider-thumb-transform)",
73
- [placementProp]: "var(--slider-thumb-offset)"
74
- };
75
- }
76
- function getRangeOffsets(ctx) {
77
- let start = "0%";
78
- let end = `${100 - ctx.valuePercent}%`;
79
- if (ctx.origin === "center") {
80
- const isNegative = ctx.valuePercent < 50;
81
- start = isNegative ? `${ctx.valuePercent}%` : "50%";
82
- end = isNegative ? "50%" : end;
83
- }
84
- return { start, end };
85
- }
86
- function getRangeStyle(ctx) {
87
- if (ctx.isVertical) {
88
- return {
89
- position: "absolute",
90
- bottom: "var(--slider-range-start)",
91
- top: "var(--slider-range-end)"
92
- };
93
- }
94
- return {
95
- position: "absolute",
96
- [ctx.isRtl ? "right" : "left"]: "var(--slider-range-start)",
97
- [ctx.isRtl ? "left" : "right"]: "var(--slider-range-end)"
105
+ [placementProp]: `var(--slider-thumb-offset-${index})`
98
106
  };
99
107
  }
100
108
  function getControlStyle() {
@@ -106,15 +114,20 @@ function getControlStyle() {
106
114
  }
107
115
  function getRootStyle(ctx) {
108
116
  const range = getRangeOffsets(ctx);
117
+ const offsetStyles = ctx.value.reduce((styles, value, index) => {
118
+ const offset = getThumbOffset({ ...ctx, value });
119
+ return { ...styles, [`--slider-thumb-offset-${index}`]: offset };
120
+ }, {});
109
121
  return {
122
+ ...offsetStyles,
110
123
  "--slider-thumb-transform": ctx.isVertical ? "translateY(50%)" : ctx.isRtl ? "translateX(50%)" : "translateX(-50%)",
111
- "--slider-thumb-offset": getThumbOffset(ctx),
112
124
  "--slider-range-start": range.start,
113
125
  "--slider-range-end": range.end
114
126
  };
115
127
  }
116
128
  function getMarkerStyle(ctx, value) {
117
129
  return {
130
+ // @ts-expect-error
118
131
  visibility: getVisibility(ctx),
119
132
  position: "absolute",
120
133
  pointerEvents: "none",
@@ -125,12 +138,6 @@ function getMarkerStyle(ctx, value) {
125
138
  "--ty": !ctx.isHorizontal ? "50%" : "0%"
126
139
  };
127
140
  }
128
- function getLabelStyle() {
129
- return { userSelect: "none" };
130
- }
131
- function getTrackStyle() {
132
- return { position: "relative" };
133
- }
134
141
  function getMarkerGroupStyle() {
135
142
  return {
136
143
  userSelect: "none",
@@ -138,34 +145,34 @@ function getMarkerGroupStyle() {
138
145
  position: "relative"
139
146
  };
140
147
  }
141
- var styles = {
142
- getThumbOffset,
148
+ var styleGetterFns = {
149
+ getRootStyle,
143
150
  getControlStyle,
144
151
  getThumbStyle,
145
152
  getRangeStyle,
146
- getRootStyle,
147
153
  getMarkerStyle,
148
- getLabelStyle,
149
- getTrackStyle,
150
154
  getMarkerGroupStyle
151
155
  };
152
156
 
153
157
  // src/slider.dom.ts
154
158
  var dom = createScope({
155
- ...styles,
159
+ ...styleGetterFns,
156
160
  getRootId: (ctx) => ctx.ids?.root ?? `slider:${ctx.id}`,
157
- getThumbId: (ctx) => ctx.ids?.thumb ?? `slider:${ctx.id}:thumb`,
161
+ getThumbId: (ctx, index) => ctx.ids?.thumb?.(index) ?? `slider:${ctx.id}:thumb:${index}`,
162
+ getHiddenInputId: (ctx, index) => `slider:${ctx.id}:input:${index}`,
158
163
  getControlId: (ctx) => ctx.ids?.control ?? `slider:${ctx.id}:control`,
159
- getHiddenInputId: (ctx) => ctx.ids?.hiddenInput ?? `slider:${ctx.id}:input`,
160
- getOutputId: (ctx) => ctx.ids?.output ?? `slider:${ctx.id}:output`,
161
- getTrackId: (ctx) => ctx.ids?.track ?? `slider:${ctx.id}track`,
162
- getRangeId: (ctx) => ctx.ids?.track ?? `slider:${ctx.id}:range`,
164
+ getTrackId: (ctx) => ctx.ids?.track ?? `slider:${ctx.id}:track`,
165
+ getRangeId: (ctx) => ctx.ids?.range ?? `slider:${ctx.id}:range`,
163
166
  getLabelId: (ctx) => ctx.ids?.label ?? `slider:${ctx.id}:label`,
164
- getMarkerId: (ctx, value) => `slider:${ctx.id}:marker:${value}`,
167
+ getOutputId: (ctx) => ctx.ids?.output ?? `slider:${ctx.id}:output`,
168
+ getMarkerId: (ctx, value) => ctx.ids?.marker?.(value) ?? `slider:${ctx.id}:marker:${value}`,
165
169
  getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
166
- getThumbEl: (ctx) => dom.getById(ctx, dom.getThumbId(ctx)),
170
+ getThumbEl: (ctx, index) => dom.getById(ctx, dom.getThumbId(ctx, index)),
171
+ getHiddenInputEl: (ctx, index) => dom.getById(ctx, dom.getHiddenInputId(ctx, index)),
167
172
  getControlEl: (ctx) => dom.getById(ctx, dom.getControlId(ctx)),
168
- getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
173
+ getElements: (ctx) => queryAll(dom.getControlEl(ctx), "[role=slider]"),
174
+ getFirstEl: (ctx) => dom.getElements(ctx)[0],
175
+ getRangeEl: (ctx) => dom.getById(ctx, dom.getRangeId(ctx)),
169
176
  getValueFromPoint(ctx, point) {
170
177
  const controlEl = dom.getControlEl(ctx);
171
178
  if (!controlEl)
@@ -179,174 +186,163 @@ var dom = createScope({
179
186
  return getPercentValue(percent, ctx.min, ctx.max, ctx.step);
180
187
  },
181
188
  dispatchChangeEvent(ctx) {
182
- const input = dom.getHiddenInputEl(ctx);
183
- if (!input)
184
- return;
185
- dispatchInputValueEvent(input, { value: ctx.value });
189
+ const valueArray = Array.from(ctx.value);
190
+ valueArray.forEach((value, index) => {
191
+ const inputEl = dom.getHiddenInputEl(ctx, index);
192
+ if (!inputEl)
193
+ return;
194
+ dispatchInputValueEvent(inputEl, { value });
195
+ });
186
196
  }
187
197
  });
188
198
 
199
+ // src/slider.utils.ts
200
+ import {
201
+ clampValue,
202
+ getClosestValueIndex,
203
+ getNextStepValue,
204
+ getPreviousStepValue,
205
+ getValueRanges,
206
+ snapValueToStep
207
+ } from "@zag-js/numeric-range";
208
+ function normalizeValues(ctx, nextValues) {
209
+ return nextValues.map((value, index, values) => {
210
+ return constrainValue({ ...ctx, value: values }, value, index);
211
+ });
212
+ }
213
+ function getRangeAtIndex(ctx, index) {
214
+ return getValueRanges(ctx.value, ctx.min, ctx.max, ctx.minStepsBetweenThumbs)[index];
215
+ }
216
+ function constrainValue(ctx, value, index) {
217
+ const range = getRangeAtIndex(ctx, index);
218
+ const snapValue = snapValueToStep(value, ctx.min, ctx.max, ctx.step);
219
+ return clampValue(snapValue, range.min, range.max);
220
+ }
221
+ function decrement(ctx, index, step) {
222
+ const idx = index ?? ctx.focusedIndex;
223
+ const range = getRangeAtIndex(ctx, idx);
224
+ const nextValues = getPreviousStepValue(idx, {
225
+ ...range,
226
+ step: step ?? ctx.step,
227
+ values: ctx.value
228
+ });
229
+ nextValues[idx] = clampValue(nextValues[idx], range.min, range.max);
230
+ return nextValues;
231
+ }
232
+ function increment(ctx, index, step) {
233
+ const idx = index ?? ctx.focusedIndex;
234
+ const range = getRangeAtIndex(ctx, idx);
235
+ const nextValues = getNextStepValue(idx, {
236
+ ...range,
237
+ step: step ?? ctx.step,
238
+ values: ctx.value
239
+ });
240
+ nextValues[idx] = clampValue(nextValues[idx], range.min, range.max);
241
+ return nextValues;
242
+ }
243
+ function getClosestIndex(ctx, pointValue) {
244
+ return getClosestValueIndex(ctx.value, pointValue);
245
+ }
246
+ function assignArray(current, next) {
247
+ for (let i = 0; i < next.length; i++) {
248
+ const value = next[i];
249
+ current[i] = value;
250
+ }
251
+ }
252
+
189
253
  // src/slider.connect.ts
190
254
  function connect(state, send, normalize) {
191
255
  const ariaLabel = state.context["aria-label"];
192
256
  const ariaLabelledBy = state.context["aria-labelledby"];
193
- const ariaValueText = state.context.getAriaValueText?.(state.context.value);
257
+ const sliderValue = state.context.value;
194
258
  const isFocused = state.matches("focus");
195
259
  const isDragging = state.matches("dragging");
196
260
  const isDisabled = state.context.isDisabled;
197
- const isInteractive = state.context.isInteractive;
198
261
  const isInvalid = state.context.invalid;
199
- function getPercentValueFn(percent) {
200
- return getPercentValue2(percent, state.context.min, state.context.max, state.context.step);
201
- }
262
+ const isInteractive = state.context.isInteractive;
202
263
  function getValuePercentFn(value) {
203
264
  return getValuePercent2(value, state.context.min, state.context.max);
204
265
  }
266
+ function getPercentValueFn(percent) {
267
+ return getPercentValue2(percent, state.context.min, state.context.max, state.context.step);
268
+ }
205
269
  return {
206
- isFocused,
207
- isDragging,
208
270
  value: state.context.value,
209
- percent: getValuePercent2(state.context.value, state.context.min, state.context.max),
271
+ isDragging,
272
+ isFocused,
210
273
  setValue(value) {
211
274
  send({ type: "SET_VALUE", value });
212
275
  },
213
- getPercentValue: getPercentValueFn,
276
+ getThumbValue(index) {
277
+ return sliderValue[index];
278
+ },
279
+ setThumbValue(index, value) {
280
+ send({ type: "SET_VALUE", index, value });
281
+ },
214
282
  getValuePercent: getValuePercentFn,
215
- focus() {
216
- dom.getThumbEl(state.context)?.focus();
283
+ getPercentValue: getPercentValueFn,
284
+ getThumbPercent(index) {
285
+ return getValuePercentFn(sliderValue[index]);
217
286
  },
218
- /**
219
- * Function to increment the value of the slider by the step.
220
- */
221
- increment() {
222
- send("INCREMENT");
287
+ setThumbPercent(index, percent) {
288
+ const value = getPercentValueFn(percent);
289
+ send({ type: "SET_VALUE", index, value });
223
290
  },
224
- decrement() {
225
- send("DECREMENT");
291
+ getThumbMin(index) {
292
+ return getRangeAtIndex(state.context, index).min;
293
+ },
294
+ getThumbMax(index) {
295
+ return getRangeAtIndex(state.context, index).max;
296
+ },
297
+ increment(index) {
298
+ send({ type: "INCREMENT", index });
299
+ },
300
+ decrement(index) {
301
+ send({ type: "DECREMENT", index });
302
+ },
303
+ focus() {
304
+ if (!isInteractive)
305
+ return;
306
+ send({ type: "FOCUS", index: 0 });
226
307
  },
227
- rootProps: normalize.element({
228
- ...parts.root.attrs,
229
- "data-disabled": dataAttr(isDisabled),
230
- "data-focus": dataAttr(isFocused),
231
- "data-orientation": state.context.orientation,
232
- "data-invalid": dataAttr(isInvalid),
233
- id: dom.getRootId(state.context),
234
- dir: state.context.dir,
235
- style: dom.getRootStyle(state.context)
236
- }),
237
308
  labelProps: normalize.label({
238
309
  ...parts.label.attrs,
310
+ dir: state.context.dir,
239
311
  "data-disabled": dataAttr(isDisabled),
312
+ "data-orientation": state.context.orientation,
240
313
  "data-invalid": dataAttr(isInvalid),
241
314
  "data-focus": dataAttr(isFocused),
242
- dir: state.context.dir,
243
315
  id: dom.getLabelId(state.context),
244
- htmlFor: dom.getHiddenInputId(state.context),
316
+ htmlFor: dom.getHiddenInputId(state.context, 0),
245
317
  onClick(event) {
246
318
  if (!isInteractive)
247
319
  return;
248
320
  event.preventDefault();
249
- dom.getThumbEl(state.context)?.focus();
321
+ dom.getFirstEl(state.context)?.focus();
250
322
  },
251
- style: dom.getLabelStyle()
323
+ style: {
324
+ userSelect: "none"
325
+ }
252
326
  }),
253
- thumbProps: normalize.element({
254
- ...parts.thumb.attrs,
255
- dir: state.context.dir,
256
- id: dom.getThumbId(state.context),
327
+ rootProps: normalize.element({
328
+ ...parts.root.attrs,
257
329
  "data-disabled": dataAttr(isDisabled),
258
330
  "data-orientation": state.context.orientation,
259
- "data-focus": dataAttr(isFocused),
260
- draggable: false,
261
- "aria-invalid": ariaAttr(isInvalid),
262
331
  "data-invalid": dataAttr(isInvalid),
263
- "aria-disabled": ariaAttr(isDisabled),
264
- "aria-label": ariaLabel,
265
- "aria-labelledby": ariaLabel ? void 0 : ariaLabelledBy ?? dom.getLabelId(state.context),
266
- "aria-orientation": state.context.orientation,
267
- "aria-valuemax": state.context.max,
268
- "aria-valuemin": state.context.min,
269
- "aria-valuenow": state.context.value,
270
- "aria-valuetext": ariaValueText,
271
- role: "slider",
272
- tabIndex: isDisabled ? void 0 : 0,
273
- onPointerDown(event) {
274
- if (!isInteractive)
275
- return;
276
- send({ type: "THUMB_POINTER_DOWN" });
277
- event.stopPropagation();
278
- },
279
- onBlur() {
280
- if (!isInteractive)
281
- return;
282
- send("BLUR");
283
- },
284
- onFocus() {
285
- if (!isInteractive)
286
- return;
287
- send("FOCUS");
288
- },
289
- onKeyDown(event) {
290
- if (!isInteractive)
291
- return;
292
- const step = getEventStep(event) * state.context.step;
293
- let prevent = true;
294
- const keyMap = {
295
- ArrowUp() {
296
- send({ type: "ARROW_UP", step });
297
- prevent = state.context.isVertical;
298
- },
299
- ArrowDown() {
300
- send({ type: "ARROW_DOWN", step });
301
- prevent = state.context.isVertical;
302
- },
303
- ArrowLeft() {
304
- send({ type: "ARROW_LEFT", step });
305
- prevent = state.context.isHorizontal;
306
- },
307
- ArrowRight() {
308
- send({ type: "ARROW_RIGHT", step });
309
- prevent = state.context.isHorizontal;
310
- },
311
- PageUp() {
312
- send({ type: "PAGE_UP", step });
313
- },
314
- PageDown() {
315
- send({ type: "PAGE_DOWN", step });
316
- },
317
- Home() {
318
- send("HOME");
319
- },
320
- End() {
321
- send("END");
322
- }
323
- };
324
- const key = getEventKey(event, state.context);
325
- const exec = keyMap[key];
326
- if (!exec)
327
- return;
328
- exec(event);
329
- if (prevent) {
330
- event.preventDefault();
331
- }
332
- },
333
- style: dom.getThumbStyle(state.context)
334
- }),
335
- hiddenInputProps: normalize.input({
336
- defaultValue: state.context.value,
337
- name: state.context.name,
338
- form: state.context.form,
339
- id: dom.getHiddenInputId(state.context),
340
- hidden: true
332
+ "data-focus": dataAttr(isFocused),
333
+ id: dom.getRootId(state.context),
334
+ dir: state.context.dir,
335
+ style: dom.getRootStyle(state.context)
341
336
  }),
342
337
  outputProps: normalize.output({
343
338
  ...parts.output.attrs,
339
+ dir: state.context.dir,
344
340
  "data-disabled": dataAttr(isDisabled),
345
- "data-invalid": dataAttr(isInvalid),
346
341
  "data-orientation": state.context.orientation,
342
+ "data-invalid": dataAttr(isInvalid),
343
+ "data-focus": dataAttr(isFocused),
347
344
  id: dom.getOutputId(state.context),
348
- dir: state.context.dir,
349
- htmlFor: dom.getHiddenInputId(state.context),
345
+ htmlFor: sliderValue.map((_v, i) => dom.getHiddenInputId(state.context, i)).join(" "),
350
346
  "aria-live": "off"
351
347
  }),
352
348
  trackProps: normalize.element({
@@ -354,15 +350,116 @@ function connect(state, send, normalize) {
354
350
  dir: state.context.dir,
355
351
  id: dom.getTrackId(state.context),
356
352
  "data-disabled": dataAttr(isDisabled),
357
- "data-focus": dataAttr(isFocused),
358
353
  "data-invalid": dataAttr(isInvalid),
359
354
  "data-orientation": state.context.orientation,
360
- style: dom.getTrackStyle()
355
+ "data-focus": dataAttr(isFocused),
356
+ style: { position: "relative" }
361
357
  }),
358
+ getThumbProps(props) {
359
+ const { index } = props;
360
+ const value = sliderValue[index];
361
+ const range = getRangeAtIndex(state.context, index);
362
+ const ariaValueText = state.context.getAriaValueText?.(value, index);
363
+ const _ariaLabel = Array.isArray(ariaLabel) ? ariaLabel[index] : ariaLabel;
364
+ const _ariaLabelledBy = Array.isArray(ariaLabelledBy) ? ariaLabelledBy[index] : ariaLabelledBy;
365
+ return normalize.element({
366
+ ...parts.thumb.attrs,
367
+ dir: state.context.dir,
368
+ "data-index": index,
369
+ id: dom.getThumbId(state.context, index),
370
+ "data-disabled": dataAttr(isDisabled),
371
+ "data-orientation": state.context.orientation,
372
+ "data-focus": dataAttr(isFocused && state.context.focusedIndex === index),
373
+ draggable: false,
374
+ "aria-disabled": ariaAttr(isDisabled),
375
+ "aria-label": _ariaLabel,
376
+ "aria-labelledby": _ariaLabelledBy ?? dom.getLabelId(state.context),
377
+ "aria-orientation": state.context.orientation,
378
+ "aria-valuemax": range.max,
379
+ "aria-valuemin": range.min,
380
+ "aria-valuenow": sliderValue[index],
381
+ "aria-valuetext": ariaValueText,
382
+ role: "slider",
383
+ tabIndex: isDisabled ? void 0 : 0,
384
+ style: dom.getThumbStyle(state.context, index),
385
+ onPointerDown(event) {
386
+ if (!isInteractive)
387
+ return;
388
+ send({ type: "THUMB_POINTER_DOWN", index });
389
+ event.stopPropagation();
390
+ },
391
+ onBlur() {
392
+ if (!isInteractive)
393
+ return;
394
+ send("BLUR");
395
+ },
396
+ onFocus() {
397
+ if (!isInteractive)
398
+ return;
399
+ send({ type: "FOCUS", index });
400
+ },
401
+ onKeyDown(event) {
402
+ if (!isInteractive)
403
+ return;
404
+ const step = getEventStep(event) * state.context.step;
405
+ let prevent = true;
406
+ const keyMap = {
407
+ ArrowUp() {
408
+ send({ type: "ARROW_UP", step });
409
+ prevent = state.context.isVertical;
410
+ },
411
+ ArrowDown() {
412
+ send({ type: "ARROW_DOWN", step });
413
+ prevent = state.context.isVertical;
414
+ },
415
+ ArrowLeft() {
416
+ send({ type: "ARROW_LEFT", step });
417
+ prevent = state.context.isHorizontal;
418
+ },
419
+ ArrowRight() {
420
+ send({ type: "ARROW_RIGHT", step });
421
+ prevent = state.context.isHorizontal;
422
+ },
423
+ PageUp() {
424
+ send({ type: "PAGE_UP", step });
425
+ },
426
+ PageDown() {
427
+ send({ type: "PAGE_DOWN", step });
428
+ },
429
+ Home() {
430
+ send("HOME");
431
+ },
432
+ End() {
433
+ send("END");
434
+ }
435
+ };
436
+ const key = getEventKey(event, state.context);
437
+ const exec = keyMap[key];
438
+ if (!exec)
439
+ return;
440
+ exec(event);
441
+ if (prevent) {
442
+ event.preventDefault();
443
+ event.stopPropagation();
444
+ }
445
+ }
446
+ });
447
+ },
448
+ getHiddenInputProps(props) {
449
+ const { index } = props;
450
+ return normalize.input({
451
+ name: `${state.context.name}[${index}]`,
452
+ form: state.context.form,
453
+ type: "text",
454
+ hidden: true,
455
+ defaultValue: state.context.value[index],
456
+ id: dom.getHiddenInputId(state.context, index)
457
+ });
458
+ },
362
459
  rangeProps: normalize.element({
460
+ id: dom.getRangeId(state.context),
363
461
  ...parts.range.attrs,
364
462
  dir: state.context.dir,
365
- id: dom.getRangeId(state.context),
366
463
  "data-focus": dataAttr(isFocused),
367
464
  "data-invalid": dataAttr(isInvalid),
368
465
  "data-disabled": dataAttr(isDisabled),
@@ -374,9 +471,10 @@ function connect(state, send, normalize) {
374
471
  dir: state.context.dir,
375
472
  id: dom.getControlId(state.context),
376
473
  "data-disabled": dataAttr(isDisabled),
377
- "data-invalid": dataAttr(isInvalid),
378
474
  "data-orientation": state.context.orientation,
475
+ "data-invalid": dataAttr(isInvalid),
379
476
  "data-focus": dataAttr(isFocused),
477
+ style: dom.getControlStyle(),
380
478
  onPointerDown(event) {
381
479
  if (!isInteractive)
382
480
  return;
@@ -387,27 +485,35 @@ function connect(state, send, normalize) {
387
485
  send({ type: "POINTER_DOWN", point });
388
486
  event.preventDefault();
389
487
  event.stopPropagation();
390
- },
391
- style: dom.getControlStyle()
488
+ }
392
489
  }),
393
490
  markerGroupProps: normalize.element({
394
491
  ...parts.markerGroup.attrs,
395
- dir: state.context.dir,
396
492
  role: "presentation",
493
+ dir: state.context.dir,
397
494
  "aria-hidden": true,
398
495
  "data-orientation": state.context.orientation,
399
496
  style: dom.getMarkerGroupStyle()
400
497
  }),
401
- getMarkerProps({ value }) {
402
- const style = dom.getMarkerStyle(state.context, value);
403
- const markerState = value > state.context.value ? "over-value" : value === state.context.value ? "at-value" : "under-value";
498
+ getMarkerProps(props) {
499
+ const style = dom.getMarkerStyle(state.context, props.value);
500
+ let markerState;
501
+ const first = state.context.value[0];
502
+ const last = state.context.value[state.context.value.length - 1];
503
+ if (props.value < first) {
504
+ markerState = "under-value";
505
+ } else if (props.value > last) {
506
+ markerState = "over-value";
507
+ } else {
508
+ markerState = "at-value";
509
+ }
404
510
  return normalize.element({
405
511
  ...parts.marker.attrs,
406
- dir: state.context.dir,
407
- id: dom.getMarkerId(state.context, value),
512
+ id: dom.getMarkerId(state.context, props.value),
408
513
  role: "presentation",
514
+ dir: state.context.dir,
409
515
  "data-orientation": state.context.orientation,
410
- "data-value": value,
516
+ "data-value": props.value,
411
517
  "data-disabled": dataAttr(isDisabled),
412
518
  "data-state": markerState,
413
519
  style
@@ -420,37 +526,13 @@ function connect(state, send, normalize) {
420
526
  import { createMachine } from "@zag-js/core";
421
527
  import { trackPointerMove } from "@zag-js/dom-event";
422
528
  import { raf } from "@zag-js/dom-query";
423
- import { trackElementSize } from "@zag-js/element-size";
529
+ import { trackElementsSize } from "@zag-js/element-size";
424
530
  import { trackFormControl } from "@zag-js/form-utils";
425
- import { clampValue as clampValue2, getValuePercent as getValuePercent3 } from "@zag-js/numeric-range";
531
+ import { getValuePercent as getValuePercent3 } from "@zag-js/numeric-range";
426
532
  import { compact, isEqual } from "@zag-js/utils";
427
-
428
- // src/slider.utils.ts
429
- import { clampValue, getNextStepValue, getPreviousStepValue, snapValueToStep } from "@zag-js/numeric-range";
430
- function constrainValue(ctx, value) {
431
- const snapValue = snapValueToStep(value, ctx.min, ctx.max, ctx.step);
432
- return clampValue(snapValue, ctx.min, ctx.max);
433
- }
434
- function decrement(ctx, step) {
435
- const index = 0;
436
- const values = getPreviousStepValue(index, {
437
- ...ctx,
438
- step: step ?? ctx.step,
439
- values: [ctx.value]
440
- });
441
- return values[index];
442
- }
443
- function increment(ctx, step) {
444
- const index = 0;
445
- const values = getNextStepValue(index, {
446
- ...ctx,
447
- step: step ?? ctx.step,
448
- values: [ctx.value]
449
- });
450
- return values[index];
451
- }
452
-
453
- // src/slider.machine.ts
533
+ var isEqualSize = (a, b) => {
534
+ return a?.width === b?.width && a?.height === b?.height;
535
+ };
454
536
  function machine(userContext) {
455
537
  const ctx = compact(userContext);
456
538
  return createMachine(
@@ -460,14 +542,15 @@ function machine(userContext) {
460
542
  context: {
461
543
  thumbSize: null,
462
544
  thumbAlignment: "contain",
463
- threshold: 5,
464
- dir: "ltr",
465
- origin: "start",
466
- orientation: "horizontal",
467
- value: 0,
468
- step: 1,
545
+ focusedIndex: -1,
469
546
  min: 0,
470
547
  max: 100,
548
+ step: 1,
549
+ value: [0],
550
+ origin: "start",
551
+ orientation: "horizontal",
552
+ dir: "ltr",
553
+ minStepsBetweenThumbs: 0,
471
554
  disabled: false,
472
555
  ...ctx,
473
556
  fieldsetDisabled: false
@@ -476,85 +559,98 @@ function machine(userContext) {
476
559
  isHorizontal: (ctx2) => ctx2.orientation === "horizontal",
477
560
  isVertical: (ctx2) => ctx2.orientation === "vertical",
478
561
  isRtl: (ctx2) => ctx2.orientation === "horizontal" && ctx2.dir === "rtl",
479
- isDisabled: (ctx2) => ctx2.disabled || ctx2.fieldsetDisabled,
480
- isInteractive: (ctx2) => !(ctx2.isDisabled || ctx2.readOnly),
481
- hasMeasuredThumbSize: (ctx2) => ctx2.thumbSize !== null,
482
- valuePercent: (ctx2) => 100 * getValuePercent3(ctx2.value, ctx2.min, ctx2.max)
562
+ isDisabled: (ctx2) => !!ctx2.disabled || ctx2.fieldsetDisabled,
563
+ isInteractive: (ctx2) => !(ctx2.readOnly || ctx2.isDisabled),
564
+ spacing: (ctx2) => ctx2.minStepsBetweenThumbs * ctx2.step,
565
+ hasMeasuredThumbSize: (ctx2) => ctx2.thumbSize != null,
566
+ valuePercent(ctx2) {
567
+ return ctx2.value.map((value) => 100 * getValuePercent3(value, ctx2.min, ctx2.max));
568
+ }
483
569
  },
484
570
  watch: {
485
- value: ["syncInputElement"]
571
+ value: ["syncInputElements"]
486
572
  },
487
- activities: ["trackFormControlState", "trackThumbSize"],
573
+ entry: ["coarseValue"],
574
+ activities: ["trackFormControlState", "trackThumbsSize"],
488
575
  on: {
489
- SET_VALUE: {
490
- actions: "setValue"
491
- },
576
+ SET_VALUE: [
577
+ {
578
+ guard: "hasIndex",
579
+ actions: "setValueAtIndex"
580
+ },
581
+ { actions: "setValue" }
582
+ ],
492
583
  INCREMENT: {
493
- actions: "increment"
584
+ actions: "incrementAtIndex"
494
585
  },
495
586
  DECREMENT: {
496
- actions: "decrement"
587
+ actions: "decrementAtIndex"
497
588
  }
498
589
  },
499
- entry: ["checkValue"],
500
590
  states: {
501
591
  idle: {
502
592
  on: {
503
593
  POINTER_DOWN: {
504
594
  target: "dragging",
505
- actions: ["setPointerValue", "invokeOnChangeStart", "focusThumb"]
595
+ actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"]
596
+ },
597
+ FOCUS: {
598
+ target: "focus",
599
+ actions: "setFocusedIndex"
506
600
  },
507
- FOCUS: "focus",
508
601
  THUMB_POINTER_DOWN: {
509
602
  target: "dragging",
510
- actions: ["invokeOnChangeStart", "focusThumb"]
603
+ actions: ["setFocusedIndex", "focusActiveThumb"]
511
604
  }
512
605
  }
513
606
  },
514
607
  focus: {
515
- entry: "focusThumb",
608
+ entry: "focusActiveThumb",
516
609
  on: {
517
610
  POINTER_DOWN: {
518
611
  target: "dragging",
519
- actions: ["setPointerValue", "invokeOnChangeStart", "focusThumb"]
612
+ actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"]
520
613
  },
521
614
  THUMB_POINTER_DOWN: {
522
615
  target: "dragging",
523
- actions: ["invokeOnChangeStart", "focusThumb"]
616
+ actions: ["setFocusedIndex", "focusActiveThumb"]
524
617
  },
525
618
  ARROW_LEFT: {
526
619
  guard: "isHorizontal",
527
- actions: "decrement"
620
+ actions: "decrementAtIndex"
528
621
  },
529
622
  ARROW_RIGHT: {
530
623
  guard: "isHorizontal",
531
- actions: "increment"
624
+ actions: "incrementAtIndex"
532
625
  },
533
626
  ARROW_UP: {
534
627
  guard: "isVertical",
535
- actions: "increment"
628
+ actions: "incrementAtIndex"
536
629
  },
537
630
  ARROW_DOWN: {
538
631
  guard: "isVertical",
539
- actions: "decrement"
632
+ actions: "decrementAtIndex"
540
633
  },
541
634
  PAGE_UP: {
542
- actions: "increment"
635
+ actions: "incrementAtIndex"
543
636
  },
544
637
  PAGE_DOWN: {
545
- actions: "decrement"
638
+ actions: "decrementAtIndex"
546
639
  },
547
640
  HOME: {
548
- actions: "setToMin"
641
+ actions: "setActiveThumbToMin"
549
642
  },
550
643
  END: {
551
- actions: "setToMax"
644
+ actions: "setActiveThumbToMax"
552
645
  },
553
- BLUR: "idle"
646
+ BLUR: {
647
+ target: "idle",
648
+ actions: "clearFocusedIndex"
649
+ }
554
650
  }
555
651
  },
556
652
  dragging: {
557
- entry: "focusThumb",
653
+ entry: "focusActiveThumb",
558
654
  activities: "trackPointerMove",
559
655
  on: {
560
656
  POINTER_UP: {
@@ -571,11 +667,12 @@ function machine(userContext) {
571
667
  {
572
668
  guards: {
573
669
  isHorizontal: (ctx2) => ctx2.isHorizontal,
574
- isVertical: (ctx2) => ctx2.isVertical
670
+ isVertical: (ctx2) => ctx2.isVertical,
671
+ hasIndex: (_ctx, evt) => evt.index != null
575
672
  },
576
673
  activities: {
577
674
  trackFormControlState(ctx2, _evt, { initialContext }) {
578
- return trackFormControl(dom.getHiddenInputEl(ctx2), {
675
+ return trackFormControl(dom.getRootEl(ctx2), {
579
676
  onFieldsetDisabledChange(disabled) {
580
677
  ctx2.fieldsetDisabled = disabled;
581
678
  },
@@ -594,55 +691,83 @@ function machine(userContext) {
594
691
  }
595
692
  });
596
693
  },
597
- trackThumbSize(ctx2, _evt) {
694
+ trackThumbsSize(ctx2) {
598
695
  if (ctx2.thumbAlignment !== "contain" || ctx2.thumbSize)
599
696
  return;
600
- return trackElementSize(dom.getThumbEl(ctx2), (size) => {
601
- if (size)
697
+ return trackElementsSize({
698
+ getNodes: () => dom.getElements(ctx2),
699
+ observeMutation: true,
700
+ callback(size) {
701
+ if (!size || isEqualSize(ctx2.thumbSize, size))
702
+ return;
602
703
  ctx2.thumbSize = size;
704
+ }
603
705
  });
604
706
  }
605
707
  },
606
708
  actions: {
607
- checkValue(ctx2) {
608
- ctx2.value = constrainValue(ctx2, ctx2.value);
609
- },
610
- invokeOnChangeStart(ctx2) {
611
- ctx2.onValueChangeStart?.({ value: ctx2.value });
709
+ syncInputElements(ctx2) {
710
+ ctx2.value.forEach((value, index) => {
711
+ const inputEl = dom.getHiddenInputEl(ctx2, index);
712
+ dom.setValue(inputEl, value);
713
+ });
612
714
  },
613
715
  invokeOnChangeEnd(ctx2) {
614
716
  ctx2.onValueChangeEnd?.({ value: ctx2.value });
615
717
  },
718
+ setClosestThumbIndex(ctx2, evt) {
719
+ const pointValue = dom.getValueFromPoint(ctx2, evt.point);
720
+ if (pointValue == null)
721
+ return;
722
+ const focusedIndex = getClosestIndex(ctx2, pointValue);
723
+ set.focusedIndex(ctx2, focusedIndex);
724
+ },
725
+ setFocusedIndex(ctx2, evt) {
726
+ set.focusedIndex(ctx2, evt.index);
727
+ },
728
+ clearFocusedIndex(ctx2) {
729
+ set.focusedIndex(ctx2, -1);
730
+ },
616
731
  setPointerValue(ctx2, evt) {
617
- const value = dom.getValueFromPoint(ctx2, evt.point);
618
- if (value == null)
732
+ const pointerValue = dom.getValueFromPoint(ctx2, evt.point);
733
+ if (pointerValue == null)
619
734
  return;
620
- set.value(ctx2, clampValue2(value, ctx2.min, ctx2.max));
735
+ const value = constrainValue(ctx2, pointerValue, ctx2.focusedIndex);
736
+ set.valueAtIndex(ctx2, ctx2.focusedIndex, value);
621
737
  },
622
- focusThumb(ctx2) {
623
- raf(() => dom.getThumbEl(ctx2)?.focus({ preventScroll: true }));
738
+ focusActiveThumb(ctx2) {
739
+ raf(() => {
740
+ const thumbEl = dom.getThumbEl(ctx2, ctx2.focusedIndex);
741
+ thumbEl?.focus({ preventScroll: true });
742
+ });
624
743
  },
625
- decrement(ctx2, evt) {
626
- const value = decrement(ctx2, evt.step);
744
+ decrementAtIndex(ctx2, evt) {
745
+ const value = decrement(ctx2, evt.index, evt.step);
627
746
  set.value(ctx2, value);
628
747
  },
629
- increment(ctx2, evt) {
630
- const value = increment(ctx2, evt.step);
748
+ incrementAtIndex(ctx2, evt) {
749
+ const value = increment(ctx2, evt.index, evt.step);
631
750
  set.value(ctx2, value);
632
751
  },
633
- setToMin(ctx2) {
634
- set.value(ctx2, ctx2.min);
752
+ setActiveThumbToMin(ctx2) {
753
+ const { min } = getRangeAtIndex(ctx2, ctx2.focusedIndex);
754
+ set.valueAtIndex(ctx2, ctx2.focusedIndex, min);
635
755
  },
636
- setToMax(ctx2) {
637
- set.value(ctx2, ctx2.max);
756
+ setActiveThumbToMax(ctx2) {
757
+ const { max } = getRangeAtIndex(ctx2, ctx2.focusedIndex);
758
+ set.valueAtIndex(ctx2, ctx2.focusedIndex, max);
638
759
  },
639
- setValue(ctx2, evt) {
640
- const value = constrainValue(ctx2, evt.value);
760
+ coarseValue(ctx2) {
761
+ const value = normalizeValues(ctx2, ctx2.value);
641
762
  set.value(ctx2, value);
642
763
  },
643
- syncInputElement(ctx2) {
644
- const inputEl = dom.getHiddenInputEl(ctx2);
645
- dom.setValue(inputEl, ctx2.value);
764
+ setValueAtIndex(ctx2, evt) {
765
+ const value = constrainValue(ctx2, evt.value, evt.index);
766
+ set.valueAtIndex(ctx2, evt.index, value);
767
+ },
768
+ setValue(ctx2, evt) {
769
+ const value = normalizeValues(ctx2, evt.value);
770
+ set.value(ctx2, value);
646
771
  }
647
772
  }
648
773
  }
@@ -650,22 +775,41 @@ function machine(userContext) {
650
775
  }
651
776
  var invoke = {
652
777
  change: (ctx) => {
653
- ctx.onValueChange?.({ value: ctx.value });
778
+ ctx.onValueChange?.({
779
+ value: Array.from(ctx.value)
780
+ });
654
781
  dom.dispatchChangeEvent(ctx);
782
+ },
783
+ focusChange: (ctx) => {
784
+ ctx.onFocusChange?.({
785
+ value: Array.from(ctx.value),
786
+ focusedIndex: ctx.focusedIndex
787
+ });
655
788
  }
656
789
  };
657
790
  var set = {
791
+ valueAtIndex: (ctx, index, value) => {
792
+ if (isEqual(ctx.value[index], value))
793
+ return;
794
+ ctx.value[index] = value;
795
+ invoke.change(ctx);
796
+ },
658
797
  value: (ctx, value) => {
659
798
  if (isEqual(ctx.value, value))
660
799
  return;
661
- ctx.value = value;
800
+ assignArray(ctx.value, value);
662
801
  invoke.change(ctx);
802
+ },
803
+ focusedIndex: (ctx, index) => {
804
+ if (isEqual(ctx.focusedIndex, index))
805
+ return;
806
+ ctx.focusedIndex = index;
807
+ invoke.focusChange(ctx);
663
808
  }
664
809
  };
665
810
  export {
666
811
  anatomy,
667
812
  connect,
668
- machine,
669
- dom as unstable__dom
813
+ machine
670
814
  };
671
815
  //# sourceMappingURL=index.mjs.map