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