@zag-js/slider 1.32.0 → 1.33.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.d.mts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +143 -13
- package/dist/index.mjs +145 -15
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -5,6 +5,8 @@ import { Service, EventObject, Machine } from '@zag-js/core';
|
|
|
5
5
|
|
|
6
6
|
declare const anatomy: _zag_js_anatomy.AnatomyInstance<"root" | "label" | "thumb" | "valueText" | "track" | "range" | "control" | "markerGroup" | "marker" | "draggingIndicator">;
|
|
7
7
|
|
|
8
|
+
type ThumbCollisionBehavior = "none" | "push" | "swap";
|
|
9
|
+
type ThumbAlignment = "contain" | "center";
|
|
8
10
|
interface ValueChangeDetails {
|
|
9
11
|
value: number[];
|
|
10
12
|
}
|
|
@@ -141,8 +143,17 @@ interface SliderProps extends DirectionProperty, CommonProperties {
|
|
|
141
143
|
width: number;
|
|
142
144
|
height: number;
|
|
143
145
|
} | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Controls how thumbs behave when they collide during pointer interactions.
|
|
148
|
+
* - `none` (default): Thumbs cannot move past each other; excess movement is ignored.
|
|
149
|
+
* - `push`: Thumbs push each other without restoring their previous positions when dragged back.
|
|
150
|
+
* - `swap`: Thumbs swap places when dragged past each other.
|
|
151
|
+
*
|
|
152
|
+
* @default "none"
|
|
153
|
+
*/
|
|
154
|
+
thumbCollisionBehavior?: "none" | "push" | "swap" | undefined;
|
|
144
155
|
}
|
|
145
|
-
type PropsWithDefault = "dir" | "min" | "max" | "step" | "orientation" | "defaultValue" | "origin" | "thumbAlignment" | "minStepsBetweenThumbs";
|
|
156
|
+
type PropsWithDefault = "dir" | "min" | "max" | "step" | "orientation" | "defaultValue" | "origin" | "thumbAlignment" | "minStepsBetweenThumbs" | "thumbCollisionBehavior";
|
|
146
157
|
type Computed = Readonly<{
|
|
147
158
|
/**
|
|
148
159
|
* @computed
|
|
@@ -208,6 +219,11 @@ interface SliderSchema {
|
|
|
208
219
|
x: number;
|
|
209
220
|
y: number;
|
|
210
221
|
} | null;
|
|
222
|
+
/**
|
|
223
|
+
* The values when a thumb drag starts.
|
|
224
|
+
* Used for swap collision behavior to determine swap direction.
|
|
225
|
+
*/
|
|
226
|
+
thumbDragStartValue: number[] | null;
|
|
211
227
|
};
|
|
212
228
|
computed: Computed;
|
|
213
229
|
event: EventObject;
|
|
@@ -316,4 +332,4 @@ declare const splitThumbProps: <Props extends ThumbProps>(props: Props) => [Thum
|
|
|
316
332
|
declare const markerProps: "value"[];
|
|
317
333
|
declare const splitMarkerProps: <Props extends MarkerProps>(props: Props) => [MarkerProps, Omit<Props, "value">];
|
|
318
334
|
|
|
319
|
-
export { type SliderApi as Api, type DraggingIndicatorProps, type ElementIds, type FocusChangeDetails, type SliderMachine as Machine, type MarkerProps, type SliderProps as Props, type SliderService as Service, type ThumbProps, type ValueChangeDetails, type ValueTextDetails, anatomy, connect, machine, markerProps, props, splitMarkerProps, splitProps, splitThumbProps, thumbProps };
|
|
335
|
+
export { type SliderApi as Api, type DraggingIndicatorProps, type ElementIds, type FocusChangeDetails, type SliderMachine as Machine, type MarkerProps, type SliderProps as Props, type SliderService as Service, type ThumbAlignment, type ThumbCollisionBehavior, type ThumbProps, type ValueChangeDetails, type ValueTextDetails, anatomy, connect, machine, markerProps, props, splitMarkerProps, splitProps, splitThumbProps, thumbProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { Service, EventObject, Machine } from '@zag-js/core';
|
|
|
5
5
|
|
|
6
6
|
declare const anatomy: _zag_js_anatomy.AnatomyInstance<"root" | "label" | "thumb" | "valueText" | "track" | "range" | "control" | "markerGroup" | "marker" | "draggingIndicator">;
|
|
7
7
|
|
|
8
|
+
type ThumbCollisionBehavior = "none" | "push" | "swap";
|
|
9
|
+
type ThumbAlignment = "contain" | "center";
|
|
8
10
|
interface ValueChangeDetails {
|
|
9
11
|
value: number[];
|
|
10
12
|
}
|
|
@@ -141,8 +143,17 @@ interface SliderProps extends DirectionProperty, CommonProperties {
|
|
|
141
143
|
width: number;
|
|
142
144
|
height: number;
|
|
143
145
|
} | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Controls how thumbs behave when they collide during pointer interactions.
|
|
148
|
+
* - `none` (default): Thumbs cannot move past each other; excess movement is ignored.
|
|
149
|
+
* - `push`: Thumbs push each other without restoring their previous positions when dragged back.
|
|
150
|
+
* - `swap`: Thumbs swap places when dragged past each other.
|
|
151
|
+
*
|
|
152
|
+
* @default "none"
|
|
153
|
+
*/
|
|
154
|
+
thumbCollisionBehavior?: "none" | "push" | "swap" | undefined;
|
|
144
155
|
}
|
|
145
|
-
type PropsWithDefault = "dir" | "min" | "max" | "step" | "orientation" | "defaultValue" | "origin" | "thumbAlignment" | "minStepsBetweenThumbs";
|
|
156
|
+
type PropsWithDefault = "dir" | "min" | "max" | "step" | "orientation" | "defaultValue" | "origin" | "thumbAlignment" | "minStepsBetweenThumbs" | "thumbCollisionBehavior";
|
|
146
157
|
type Computed = Readonly<{
|
|
147
158
|
/**
|
|
148
159
|
* @computed
|
|
@@ -208,6 +219,11 @@ interface SliderSchema {
|
|
|
208
219
|
x: number;
|
|
209
220
|
y: number;
|
|
210
221
|
} | null;
|
|
222
|
+
/**
|
|
223
|
+
* The values when a thumb drag starts.
|
|
224
|
+
* Used for swap collision behavior to determine swap direction.
|
|
225
|
+
*/
|
|
226
|
+
thumbDragStartValue: number[] | null;
|
|
211
227
|
};
|
|
212
228
|
computed: Computed;
|
|
213
229
|
event: EventObject;
|
|
@@ -316,4 +332,4 @@ declare const splitThumbProps: <Props extends ThumbProps>(props: Props) => [Thum
|
|
|
316
332
|
declare const markerProps: "value"[];
|
|
317
333
|
declare const splitMarkerProps: <Props extends MarkerProps>(props: Props) => [MarkerProps, Omit<Props, "value">];
|
|
318
334
|
|
|
319
|
-
export { type SliderApi as Api, type DraggingIndicatorProps, type ElementIds, type FocusChangeDetails, type SliderMachine as Machine, type MarkerProps, type SliderProps as Props, type SliderService as Service, type ThumbProps, type ValueChangeDetails, type ValueTextDetails, anatomy, connect, machine, markerProps, props, splitMarkerProps, splitProps, splitThumbProps, thumbProps };
|
|
335
|
+
export { type SliderApi as Api, type DraggingIndicatorProps, type ElementIds, type FocusChangeDetails, type SliderMachine as Machine, type MarkerProps, type SliderProps as Props, type SliderService as Service, type ThumbAlignment, type ThumbCollisionBehavior, type ThumbProps, type ValueChangeDetails, type ValueTextDetails, anatomy, connect, machine, markerProps, props, splitMarkerProps, splitProps, splitThumbProps, thumbProps };
|
package/dist/index.js
CHANGED
|
@@ -35,8 +35,13 @@ var getThumbEls = (ctx) => domQuery.queryAll(getControlEl(ctx), "[role=slider]")
|
|
|
35
35
|
var getFirstThumbEl = (ctx) => getThumbEls(ctx)[0];
|
|
36
36
|
var getHiddenInputEl = (ctx, index) => ctx.getById(getHiddenInputId(ctx, index));
|
|
37
37
|
var getControlEl = (ctx) => ctx.getById(getControlId(ctx));
|
|
38
|
+
var getThumbInset = (thumbSize, thumbAlignment, orientation) => {
|
|
39
|
+
const isContain = thumbAlignment === "contain";
|
|
40
|
+
const isVertical = orientation === "vertical";
|
|
41
|
+
return isContain ? (isVertical ? thumbSize?.height ?? 0 : thumbSize?.width ?? 0) / 2 : 0;
|
|
42
|
+
};
|
|
38
43
|
var getPointValue = (params, point) => {
|
|
39
|
-
const { prop, scope, refs } = params;
|
|
44
|
+
const { context, prop, scope, refs } = params;
|
|
40
45
|
const controlEl = getControlEl(scope);
|
|
41
46
|
if (!controlEl) return;
|
|
42
47
|
const offset = refs.get("thumbDragOffset");
|
|
@@ -44,7 +49,8 @@ var getPointValue = (params, point) => {
|
|
|
44
49
|
x: point.x - (offset?.x ?? 0),
|
|
45
50
|
y: point.y - (offset?.y ?? 0)
|
|
46
51
|
};
|
|
47
|
-
const
|
|
52
|
+
const thumbInset = getThumbInset(context.get("thumbSize"), prop("thumbAlignment"), prop("orientation"));
|
|
53
|
+
const relativePoint = getRelativePointWithInset(adjustedPoint, controlEl, thumbInset);
|
|
48
54
|
const percent = relativePoint.getPercentValue({
|
|
49
55
|
orientation: prop("orientation"),
|
|
50
56
|
dir: prop("dir"),
|
|
@@ -52,6 +58,31 @@ var getPointValue = (params, point) => {
|
|
|
52
58
|
});
|
|
53
59
|
return utils.getPercentValue(percent, prop("min"), prop("max"), prop("step"));
|
|
54
60
|
};
|
|
61
|
+
function getRelativePointWithInset(point, element, inset) {
|
|
62
|
+
const { left, top, width, height } = element.getBoundingClientRect();
|
|
63
|
+
const effectiveWidth = width - inset * 2;
|
|
64
|
+
const effectiveHeight = height - inset * 2;
|
|
65
|
+
const effectiveLeft = left + inset;
|
|
66
|
+
const effectiveTop = top + inset;
|
|
67
|
+
const offset = {
|
|
68
|
+
x: point.x - effectiveLeft,
|
|
69
|
+
y: point.y - effectiveTop
|
|
70
|
+
};
|
|
71
|
+
const percent = {
|
|
72
|
+
x: effectiveWidth > 0 ? utils.clampPercent(offset.x / effectiveWidth) : 0,
|
|
73
|
+
y: effectiveHeight > 0 ? utils.clampPercent(offset.y / effectiveHeight) : 0
|
|
74
|
+
};
|
|
75
|
+
function getPercentValue3(options = {}) {
|
|
76
|
+
const { dir = "ltr", orientation = "horizontal", inverted } = options;
|
|
77
|
+
const invertX = typeof inverted === "object" ? inverted.x : inverted;
|
|
78
|
+
const invertY = typeof inverted === "object" ? inverted.y : inverted;
|
|
79
|
+
if (orientation === "horizontal") {
|
|
80
|
+
return dir === "rtl" || invertX ? 1 - percent.x : percent.x;
|
|
81
|
+
}
|
|
82
|
+
return invertY ? 1 - percent.y : percent.y;
|
|
83
|
+
}
|
|
84
|
+
return { offset, percent, getPercentValue: getPercentValue3 };
|
|
85
|
+
}
|
|
55
86
|
var dispatchChangeEvent = (ctx, value) => {
|
|
56
87
|
value.forEach((value2, index) => {
|
|
57
88
|
const inputEl = getHiddenInputEl(ctx, index);
|
|
@@ -202,6 +233,83 @@ function getMarkerGroupStyle() {
|
|
|
202
233
|
position: "relative"
|
|
203
234
|
};
|
|
204
235
|
}
|
|
236
|
+
function getThumbBounds(ctx) {
|
|
237
|
+
const { index, values, min, max, gap } = ctx;
|
|
238
|
+
const prevThumb = values[index - 1];
|
|
239
|
+
const nextThumb = values[index + 1];
|
|
240
|
+
return {
|
|
241
|
+
min: prevThumb != null ? prevThumb + gap : min,
|
|
242
|
+
max: nextThumb != null ? nextThumb - gap : max
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function round(value) {
|
|
246
|
+
return Math.round(value * 1e10) / 1e10;
|
|
247
|
+
}
|
|
248
|
+
function handleNone(ctx) {
|
|
249
|
+
const { index, value, values } = ctx;
|
|
250
|
+
const bounds = getThumbBounds(ctx);
|
|
251
|
+
const nextValues = values.slice();
|
|
252
|
+
nextValues[index] = round(utils.clampValue(value, bounds.min, bounds.max));
|
|
253
|
+
return { values: nextValues, index, swapped: false };
|
|
254
|
+
}
|
|
255
|
+
function handlePush(ctx) {
|
|
256
|
+
const { index, value, values, min, max, gap } = ctx;
|
|
257
|
+
const nextValues = values.slice();
|
|
258
|
+
const absoluteMin = min + index * gap;
|
|
259
|
+
const absoluteMax = max - (values.length - 1 - index) * gap;
|
|
260
|
+
nextValues[index] = round(utils.clampValue(value, absoluteMin, absoluteMax));
|
|
261
|
+
for (let i = index + 1; i < values.length; i++) {
|
|
262
|
+
const minAllowed = nextValues[i - 1] + gap;
|
|
263
|
+
if (nextValues[i] < minAllowed) {
|
|
264
|
+
nextValues[i] = round(minAllowed);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
268
|
+
const maxAllowed = nextValues[i + 1] - gap;
|
|
269
|
+
if (nextValues[i] > maxAllowed) {
|
|
270
|
+
nextValues[i] = round(maxAllowed);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { values: nextValues, index, swapped: false };
|
|
274
|
+
}
|
|
275
|
+
function handleSwap(ctx, startValue) {
|
|
276
|
+
const { index, value, values, gap } = ctx;
|
|
277
|
+
const prevThumb = values[index - 1];
|
|
278
|
+
const nextThumb = values[index + 1];
|
|
279
|
+
const crossingNext = nextThumb != null && value >= nextThumb && value > startValue;
|
|
280
|
+
const crossingPrev = prevThumb != null && value <= prevThumb && value < startValue;
|
|
281
|
+
if (!crossingNext && !crossingPrev) {
|
|
282
|
+
return handleNone(ctx);
|
|
283
|
+
}
|
|
284
|
+
const swapIndex = crossingNext ? index + 1 : index - 1;
|
|
285
|
+
const nextValues = values.slice();
|
|
286
|
+
const newCtx = { ...ctx, index: swapIndex };
|
|
287
|
+
const bounds = getThumbBounds(newCtx);
|
|
288
|
+
nextValues[swapIndex] = round(utils.clampValue(value, bounds.min, bounds.max));
|
|
289
|
+
nextValues[index] = values[swapIndex];
|
|
290
|
+
if (crossingNext && nextValues[index] > nextValues[swapIndex] - gap) {
|
|
291
|
+
nextValues[index] = round(nextValues[swapIndex] - gap);
|
|
292
|
+
} else if (crossingPrev && nextValues[index] < nextValues[swapIndex] + gap) {
|
|
293
|
+
nextValues[index] = round(nextValues[swapIndex] + gap);
|
|
294
|
+
}
|
|
295
|
+
return { values: nextValues, index: swapIndex, swapped: true };
|
|
296
|
+
}
|
|
297
|
+
function resolveThumbCollision(behavior, index, value, values, min, max, step, minStepsBetweenThumbs, startValue) {
|
|
298
|
+
if (values.length === 1) {
|
|
299
|
+
return { values: [round(utils.clampValue(value, min, max))], index: 0, swapped: false };
|
|
300
|
+
}
|
|
301
|
+
const gap = step * minStepsBetweenThumbs;
|
|
302
|
+
const ctx = { behavior, index, value, values, min, max, gap };
|
|
303
|
+
switch (behavior) {
|
|
304
|
+
case "push":
|
|
305
|
+
return handlePush(ctx);
|
|
306
|
+
case "swap":
|
|
307
|
+
return handleSwap(ctx, startValue ?? values[index]);
|
|
308
|
+
case "none":
|
|
309
|
+
default:
|
|
310
|
+
return handleNone(ctx);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
205
313
|
function normalizeValues(params, nextValues) {
|
|
206
314
|
return nextValues.map((value, index) => {
|
|
207
315
|
return constrainValue(params, value, index);
|
|
@@ -609,6 +717,7 @@ var machine = core.createMachine({
|
|
|
609
717
|
thumbAlignment: "contain",
|
|
610
718
|
origin: "start",
|
|
611
719
|
orientation: "horizontal",
|
|
720
|
+
thumbCollisionBehavior: "none",
|
|
612
721
|
minStepsBetweenThumbs,
|
|
613
722
|
...props2,
|
|
614
723
|
defaultValue: normalize(defaultValue, min, max, step, minStepsBetweenThumbs),
|
|
@@ -651,7 +760,8 @@ var machine = core.createMachine({
|
|
|
651
760
|
},
|
|
652
761
|
refs() {
|
|
653
762
|
return {
|
|
654
|
-
thumbDragOffset: null
|
|
763
|
+
thumbDragOffset: null,
|
|
764
|
+
thumbDragStartValue: null
|
|
655
765
|
};
|
|
656
766
|
},
|
|
657
767
|
computed: {
|
|
@@ -699,7 +809,7 @@ var machine = core.createMachine({
|
|
|
699
809
|
on: {
|
|
700
810
|
POINTER_DOWN: {
|
|
701
811
|
target: "dragging",
|
|
702
|
-
actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"]
|
|
812
|
+
actions: ["setClosestThumbIndex", "setThumbDragStartValue", "setPointerValue", "focusActiveThumb"]
|
|
703
813
|
},
|
|
704
814
|
FOCUS: {
|
|
705
815
|
target: "focus",
|
|
@@ -707,7 +817,7 @@ var machine = core.createMachine({
|
|
|
707
817
|
},
|
|
708
818
|
THUMB_POINTER_DOWN: {
|
|
709
819
|
target: "dragging",
|
|
710
|
-
actions: ["setFocusedIndex", "setThumbDragOffset", "focusActiveThumb"]
|
|
820
|
+
actions: ["setFocusedIndex", "setThumbDragOffset", "setThumbDragStartValue", "focusActiveThumb"]
|
|
711
821
|
}
|
|
712
822
|
}
|
|
713
823
|
},
|
|
@@ -716,11 +826,11 @@ var machine = core.createMachine({
|
|
|
716
826
|
on: {
|
|
717
827
|
POINTER_DOWN: {
|
|
718
828
|
target: "dragging",
|
|
719
|
-
actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"]
|
|
829
|
+
actions: ["setClosestThumbIndex", "setThumbDragStartValue", "setPointerValue", "focusActiveThumb"]
|
|
720
830
|
},
|
|
721
831
|
THUMB_POINTER_DOWN: {
|
|
722
832
|
target: "dragging",
|
|
723
|
-
actions: ["setFocusedIndex", "setThumbDragOffset", "focusActiveThumb"]
|
|
833
|
+
actions: ["setFocusedIndex", "setThumbDragOffset", "setThumbDragStartValue", "focusActiveThumb"]
|
|
724
834
|
},
|
|
725
835
|
ARROW_DEC: {
|
|
726
836
|
actions: ["decrementThumbAtIndex", "invokeOnChangeEnd"]
|
|
@@ -746,14 +856,14 @@ var machine = core.createMachine({
|
|
|
746
856
|
on: {
|
|
747
857
|
POINTER_UP: {
|
|
748
858
|
target: "focus",
|
|
749
|
-
actions: ["invokeOnChangeEnd", "clearThumbDragOffset"]
|
|
859
|
+
actions: ["invokeOnChangeEnd", "clearThumbDragOffset", "clearThumbDragStartValue"]
|
|
750
860
|
},
|
|
751
861
|
POINTER_MOVE: {
|
|
752
862
|
actions: ["setPointerValue"]
|
|
753
863
|
},
|
|
754
864
|
POINTER_CANCEL: {
|
|
755
865
|
target: "idle",
|
|
756
|
-
actions: ["clearFocusedIndex", "clearThumbDragOffset"]
|
|
866
|
+
actions: ["clearFocusedIndex", "clearThumbDragOffset", "clearThumbDragStartValue"]
|
|
757
867
|
}
|
|
758
868
|
}
|
|
759
869
|
}
|
|
@@ -834,14 +944,34 @@ var machine = core.createMachine({
|
|
|
834
944
|
clearThumbDragOffset({ refs }) {
|
|
835
945
|
refs.set("thumbDragOffset", null);
|
|
836
946
|
},
|
|
947
|
+
setThumbDragStartValue({ refs, context }) {
|
|
948
|
+
refs.set("thumbDragStartValue", context.get("value").slice());
|
|
949
|
+
},
|
|
950
|
+
clearThumbDragStartValue({ refs }) {
|
|
951
|
+
refs.set("thumbDragStartValue", null);
|
|
952
|
+
},
|
|
837
953
|
setPointerValue(params) {
|
|
838
954
|
queueMicrotask(() => {
|
|
839
|
-
const { context, event } = params;
|
|
955
|
+
const { context, event, prop, refs } = params;
|
|
840
956
|
const pointValue = getPointValue(params, event.point);
|
|
841
957
|
if (pointValue == null) return;
|
|
842
958
|
const focusedIndex = context.get("focusedIndex");
|
|
843
|
-
const
|
|
844
|
-
|
|
959
|
+
const startValues = refs.get("thumbDragStartValue");
|
|
960
|
+
const result = resolveThumbCollision(
|
|
961
|
+
prop("thumbCollisionBehavior"),
|
|
962
|
+
focusedIndex,
|
|
963
|
+
pointValue,
|
|
964
|
+
context.get("value"),
|
|
965
|
+
prop("min"),
|
|
966
|
+
prop("max"),
|
|
967
|
+
prop("step"),
|
|
968
|
+
prop("minStepsBetweenThumbs"),
|
|
969
|
+
startValues?.[focusedIndex]
|
|
970
|
+
);
|
|
971
|
+
if (result.swapped) {
|
|
972
|
+
context.set("focusedIndex", result.index);
|
|
973
|
+
}
|
|
974
|
+
context.set("value", result.values);
|
|
845
975
|
});
|
|
846
976
|
},
|
|
847
977
|
focusActiveThumb({ scope, context }) {
|
|
@@ -908,7 +1038,7 @@ var props = types.createProps()([
|
|
|
908
1038
|
"readOnly",
|
|
909
1039
|
"step",
|
|
910
1040
|
"thumbAlignment",
|
|
911
|
-
"
|
|
1041
|
+
"thumbCollisionBehavior",
|
|
912
1042
|
"thumbSize",
|
|
913
1043
|
"value",
|
|
914
1044
|
"defaultValue"
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createAnatomy } from '@zag-js/anatomy';
|
|
2
|
-
import { raf, setElementValue, queryAll, resizeObserverBorderBox, trackPointerMove, trackFormControl,
|
|
3
|
-
import { setValueAtIndex, callAll, getValuePercent, isEqual, createSplitProps, snapValueToStep, clampValue, getValueRanges, getNextStepValue, getPreviousStepValue, getPercentValue, pick, isValueWithinRange, first, last, toPx, getValueTransformer } from '@zag-js/utils';
|
|
2
|
+
import { raf, setElementValue, queryAll, resizeObserverBorderBox, trackPointerMove, trackFormControl, dispatchInputValueEvent, dataAttr, isLeftClick, isModifierKey, getEventPoint, ariaAttr, getEventStep, getEventKey } from '@zag-js/dom-query';
|
|
3
|
+
import { setValueAtIndex, callAll, getValuePercent, isEqual, createSplitProps, snapValueToStep, clampValue, getValueRanges, getNextStepValue, getPreviousStepValue, getPercentValue, pick, isValueWithinRange, clampPercent, first, last, toPx, getValueTransformer } from '@zag-js/utils';
|
|
4
4
|
import { createMachine, memo } from '@zag-js/core';
|
|
5
5
|
import { createProps } from '@zag-js/types';
|
|
6
6
|
|
|
@@ -33,8 +33,13 @@ var getThumbEls = (ctx) => queryAll(getControlEl(ctx), "[role=slider]");
|
|
|
33
33
|
var getFirstThumbEl = (ctx) => getThumbEls(ctx)[0];
|
|
34
34
|
var getHiddenInputEl = (ctx, index) => ctx.getById(getHiddenInputId(ctx, index));
|
|
35
35
|
var getControlEl = (ctx) => ctx.getById(getControlId(ctx));
|
|
36
|
+
var getThumbInset = (thumbSize, thumbAlignment, orientation) => {
|
|
37
|
+
const isContain = thumbAlignment === "contain";
|
|
38
|
+
const isVertical = orientation === "vertical";
|
|
39
|
+
return isContain ? (isVertical ? thumbSize?.height ?? 0 : thumbSize?.width ?? 0) / 2 : 0;
|
|
40
|
+
};
|
|
36
41
|
var getPointValue = (params, point) => {
|
|
37
|
-
const { prop, scope, refs } = params;
|
|
42
|
+
const { context, prop, scope, refs } = params;
|
|
38
43
|
const controlEl = getControlEl(scope);
|
|
39
44
|
if (!controlEl) return;
|
|
40
45
|
const offset = refs.get("thumbDragOffset");
|
|
@@ -42,7 +47,8 @@ var getPointValue = (params, point) => {
|
|
|
42
47
|
x: point.x - (offset?.x ?? 0),
|
|
43
48
|
y: point.y - (offset?.y ?? 0)
|
|
44
49
|
};
|
|
45
|
-
const
|
|
50
|
+
const thumbInset = getThumbInset(context.get("thumbSize"), prop("thumbAlignment"), prop("orientation"));
|
|
51
|
+
const relativePoint = getRelativePointWithInset(adjustedPoint, controlEl, thumbInset);
|
|
46
52
|
const percent = relativePoint.getPercentValue({
|
|
47
53
|
orientation: prop("orientation"),
|
|
48
54
|
dir: prop("dir"),
|
|
@@ -50,6 +56,31 @@ var getPointValue = (params, point) => {
|
|
|
50
56
|
});
|
|
51
57
|
return getPercentValue(percent, prop("min"), prop("max"), prop("step"));
|
|
52
58
|
};
|
|
59
|
+
function getRelativePointWithInset(point, element, inset) {
|
|
60
|
+
const { left, top, width, height } = element.getBoundingClientRect();
|
|
61
|
+
const effectiveWidth = width - inset * 2;
|
|
62
|
+
const effectiveHeight = height - inset * 2;
|
|
63
|
+
const effectiveLeft = left + inset;
|
|
64
|
+
const effectiveTop = top + inset;
|
|
65
|
+
const offset = {
|
|
66
|
+
x: point.x - effectiveLeft,
|
|
67
|
+
y: point.y - effectiveTop
|
|
68
|
+
};
|
|
69
|
+
const percent = {
|
|
70
|
+
x: effectiveWidth > 0 ? clampPercent(offset.x / effectiveWidth) : 0,
|
|
71
|
+
y: effectiveHeight > 0 ? clampPercent(offset.y / effectiveHeight) : 0
|
|
72
|
+
};
|
|
73
|
+
function getPercentValue3(options = {}) {
|
|
74
|
+
const { dir = "ltr", orientation = "horizontal", inverted } = options;
|
|
75
|
+
const invertX = typeof inverted === "object" ? inverted.x : inverted;
|
|
76
|
+
const invertY = typeof inverted === "object" ? inverted.y : inverted;
|
|
77
|
+
if (orientation === "horizontal") {
|
|
78
|
+
return dir === "rtl" || invertX ? 1 - percent.x : percent.x;
|
|
79
|
+
}
|
|
80
|
+
return invertY ? 1 - percent.y : percent.y;
|
|
81
|
+
}
|
|
82
|
+
return { offset, percent, getPercentValue: getPercentValue3 };
|
|
83
|
+
}
|
|
53
84
|
var dispatchChangeEvent = (ctx, value) => {
|
|
54
85
|
value.forEach((value2, index) => {
|
|
55
86
|
const inputEl = getHiddenInputEl(ctx, index);
|
|
@@ -200,6 +231,83 @@ function getMarkerGroupStyle() {
|
|
|
200
231
|
position: "relative"
|
|
201
232
|
};
|
|
202
233
|
}
|
|
234
|
+
function getThumbBounds(ctx) {
|
|
235
|
+
const { index, values, min, max, gap } = ctx;
|
|
236
|
+
const prevThumb = values[index - 1];
|
|
237
|
+
const nextThumb = values[index + 1];
|
|
238
|
+
return {
|
|
239
|
+
min: prevThumb != null ? prevThumb + gap : min,
|
|
240
|
+
max: nextThumb != null ? nextThumb - gap : max
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function round(value) {
|
|
244
|
+
return Math.round(value * 1e10) / 1e10;
|
|
245
|
+
}
|
|
246
|
+
function handleNone(ctx) {
|
|
247
|
+
const { index, value, values } = ctx;
|
|
248
|
+
const bounds = getThumbBounds(ctx);
|
|
249
|
+
const nextValues = values.slice();
|
|
250
|
+
nextValues[index] = round(clampValue(value, bounds.min, bounds.max));
|
|
251
|
+
return { values: nextValues, index, swapped: false };
|
|
252
|
+
}
|
|
253
|
+
function handlePush(ctx) {
|
|
254
|
+
const { index, value, values, min, max, gap } = ctx;
|
|
255
|
+
const nextValues = values.slice();
|
|
256
|
+
const absoluteMin = min + index * gap;
|
|
257
|
+
const absoluteMax = max - (values.length - 1 - index) * gap;
|
|
258
|
+
nextValues[index] = round(clampValue(value, absoluteMin, absoluteMax));
|
|
259
|
+
for (let i = index + 1; i < values.length; i++) {
|
|
260
|
+
const minAllowed = nextValues[i - 1] + gap;
|
|
261
|
+
if (nextValues[i] < minAllowed) {
|
|
262
|
+
nextValues[i] = round(minAllowed);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
266
|
+
const maxAllowed = nextValues[i + 1] - gap;
|
|
267
|
+
if (nextValues[i] > maxAllowed) {
|
|
268
|
+
nextValues[i] = round(maxAllowed);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { values: nextValues, index, swapped: false };
|
|
272
|
+
}
|
|
273
|
+
function handleSwap(ctx, startValue) {
|
|
274
|
+
const { index, value, values, gap } = ctx;
|
|
275
|
+
const prevThumb = values[index - 1];
|
|
276
|
+
const nextThumb = values[index + 1];
|
|
277
|
+
const crossingNext = nextThumb != null && value >= nextThumb && value > startValue;
|
|
278
|
+
const crossingPrev = prevThumb != null && value <= prevThumb && value < startValue;
|
|
279
|
+
if (!crossingNext && !crossingPrev) {
|
|
280
|
+
return handleNone(ctx);
|
|
281
|
+
}
|
|
282
|
+
const swapIndex = crossingNext ? index + 1 : index - 1;
|
|
283
|
+
const nextValues = values.slice();
|
|
284
|
+
const newCtx = { ...ctx, index: swapIndex };
|
|
285
|
+
const bounds = getThumbBounds(newCtx);
|
|
286
|
+
nextValues[swapIndex] = round(clampValue(value, bounds.min, bounds.max));
|
|
287
|
+
nextValues[index] = values[swapIndex];
|
|
288
|
+
if (crossingNext && nextValues[index] > nextValues[swapIndex] - gap) {
|
|
289
|
+
nextValues[index] = round(nextValues[swapIndex] - gap);
|
|
290
|
+
} else if (crossingPrev && nextValues[index] < nextValues[swapIndex] + gap) {
|
|
291
|
+
nextValues[index] = round(nextValues[swapIndex] + gap);
|
|
292
|
+
}
|
|
293
|
+
return { values: nextValues, index: swapIndex, swapped: true };
|
|
294
|
+
}
|
|
295
|
+
function resolveThumbCollision(behavior, index, value, values, min, max, step, minStepsBetweenThumbs, startValue) {
|
|
296
|
+
if (values.length === 1) {
|
|
297
|
+
return { values: [round(clampValue(value, min, max))], index: 0, swapped: false };
|
|
298
|
+
}
|
|
299
|
+
const gap = step * minStepsBetweenThumbs;
|
|
300
|
+
const ctx = { behavior, index, value, values, min, max, gap };
|
|
301
|
+
switch (behavior) {
|
|
302
|
+
case "push":
|
|
303
|
+
return handlePush(ctx);
|
|
304
|
+
case "swap":
|
|
305
|
+
return handleSwap(ctx, startValue ?? values[index]);
|
|
306
|
+
case "none":
|
|
307
|
+
default:
|
|
308
|
+
return handleNone(ctx);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
203
311
|
function normalizeValues(params, nextValues) {
|
|
204
312
|
return nextValues.map((value, index) => {
|
|
205
313
|
return constrainValue(params, value, index);
|
|
@@ -607,6 +715,7 @@ var machine = createMachine({
|
|
|
607
715
|
thumbAlignment: "contain",
|
|
608
716
|
origin: "start",
|
|
609
717
|
orientation: "horizontal",
|
|
718
|
+
thumbCollisionBehavior: "none",
|
|
610
719
|
minStepsBetweenThumbs,
|
|
611
720
|
...props2,
|
|
612
721
|
defaultValue: normalize(defaultValue, min, max, step, minStepsBetweenThumbs),
|
|
@@ -649,7 +758,8 @@ var machine = createMachine({
|
|
|
649
758
|
},
|
|
650
759
|
refs() {
|
|
651
760
|
return {
|
|
652
|
-
thumbDragOffset: null
|
|
761
|
+
thumbDragOffset: null,
|
|
762
|
+
thumbDragStartValue: null
|
|
653
763
|
};
|
|
654
764
|
},
|
|
655
765
|
computed: {
|
|
@@ -697,7 +807,7 @@ var machine = createMachine({
|
|
|
697
807
|
on: {
|
|
698
808
|
POINTER_DOWN: {
|
|
699
809
|
target: "dragging",
|
|
700
|
-
actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"]
|
|
810
|
+
actions: ["setClosestThumbIndex", "setThumbDragStartValue", "setPointerValue", "focusActiveThumb"]
|
|
701
811
|
},
|
|
702
812
|
FOCUS: {
|
|
703
813
|
target: "focus",
|
|
@@ -705,7 +815,7 @@ var machine = createMachine({
|
|
|
705
815
|
},
|
|
706
816
|
THUMB_POINTER_DOWN: {
|
|
707
817
|
target: "dragging",
|
|
708
|
-
actions: ["setFocusedIndex", "setThumbDragOffset", "focusActiveThumb"]
|
|
818
|
+
actions: ["setFocusedIndex", "setThumbDragOffset", "setThumbDragStartValue", "focusActiveThumb"]
|
|
709
819
|
}
|
|
710
820
|
}
|
|
711
821
|
},
|
|
@@ -714,11 +824,11 @@ var machine = createMachine({
|
|
|
714
824
|
on: {
|
|
715
825
|
POINTER_DOWN: {
|
|
716
826
|
target: "dragging",
|
|
717
|
-
actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"]
|
|
827
|
+
actions: ["setClosestThumbIndex", "setThumbDragStartValue", "setPointerValue", "focusActiveThumb"]
|
|
718
828
|
},
|
|
719
829
|
THUMB_POINTER_DOWN: {
|
|
720
830
|
target: "dragging",
|
|
721
|
-
actions: ["setFocusedIndex", "setThumbDragOffset", "focusActiveThumb"]
|
|
831
|
+
actions: ["setFocusedIndex", "setThumbDragOffset", "setThumbDragStartValue", "focusActiveThumb"]
|
|
722
832
|
},
|
|
723
833
|
ARROW_DEC: {
|
|
724
834
|
actions: ["decrementThumbAtIndex", "invokeOnChangeEnd"]
|
|
@@ -744,14 +854,14 @@ var machine = createMachine({
|
|
|
744
854
|
on: {
|
|
745
855
|
POINTER_UP: {
|
|
746
856
|
target: "focus",
|
|
747
|
-
actions: ["invokeOnChangeEnd", "clearThumbDragOffset"]
|
|
857
|
+
actions: ["invokeOnChangeEnd", "clearThumbDragOffset", "clearThumbDragStartValue"]
|
|
748
858
|
},
|
|
749
859
|
POINTER_MOVE: {
|
|
750
860
|
actions: ["setPointerValue"]
|
|
751
861
|
},
|
|
752
862
|
POINTER_CANCEL: {
|
|
753
863
|
target: "idle",
|
|
754
|
-
actions: ["clearFocusedIndex", "clearThumbDragOffset"]
|
|
864
|
+
actions: ["clearFocusedIndex", "clearThumbDragOffset", "clearThumbDragStartValue"]
|
|
755
865
|
}
|
|
756
866
|
}
|
|
757
867
|
}
|
|
@@ -832,14 +942,34 @@ var machine = createMachine({
|
|
|
832
942
|
clearThumbDragOffset({ refs }) {
|
|
833
943
|
refs.set("thumbDragOffset", null);
|
|
834
944
|
},
|
|
945
|
+
setThumbDragStartValue({ refs, context }) {
|
|
946
|
+
refs.set("thumbDragStartValue", context.get("value").slice());
|
|
947
|
+
},
|
|
948
|
+
clearThumbDragStartValue({ refs }) {
|
|
949
|
+
refs.set("thumbDragStartValue", null);
|
|
950
|
+
},
|
|
835
951
|
setPointerValue(params) {
|
|
836
952
|
queueMicrotask(() => {
|
|
837
|
-
const { context, event } = params;
|
|
953
|
+
const { context, event, prop, refs } = params;
|
|
838
954
|
const pointValue = getPointValue(params, event.point);
|
|
839
955
|
if (pointValue == null) return;
|
|
840
956
|
const focusedIndex = context.get("focusedIndex");
|
|
841
|
-
const
|
|
842
|
-
|
|
957
|
+
const startValues = refs.get("thumbDragStartValue");
|
|
958
|
+
const result = resolveThumbCollision(
|
|
959
|
+
prop("thumbCollisionBehavior"),
|
|
960
|
+
focusedIndex,
|
|
961
|
+
pointValue,
|
|
962
|
+
context.get("value"),
|
|
963
|
+
prop("min"),
|
|
964
|
+
prop("max"),
|
|
965
|
+
prop("step"),
|
|
966
|
+
prop("minStepsBetweenThumbs"),
|
|
967
|
+
startValues?.[focusedIndex]
|
|
968
|
+
);
|
|
969
|
+
if (result.swapped) {
|
|
970
|
+
context.set("focusedIndex", result.index);
|
|
971
|
+
}
|
|
972
|
+
context.set("value", result.values);
|
|
843
973
|
});
|
|
844
974
|
},
|
|
845
975
|
focusActiveThumb({ scope, context }) {
|
|
@@ -906,7 +1036,7 @@ var props = createProps()([
|
|
|
906
1036
|
"readOnly",
|
|
907
1037
|
"step",
|
|
908
1038
|
"thumbAlignment",
|
|
909
|
-
"
|
|
1039
|
+
"thumbCollisionBehavior",
|
|
910
1040
|
"thumbSize",
|
|
911
1041
|
"value",
|
|
912
1042
|
"defaultValue"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zag-js/slider",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"description": "Core logic for the slider widget implemented as a state machine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"js",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"url": "https://github.com/chakra-ui/zag/issues"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@zag-js/anatomy": "1.
|
|
31
|
-
"@zag-js/core": "1.
|
|
32
|
-
"@zag-js/dom-query": "1.
|
|
33
|
-
"@zag-js/utils": "1.
|
|
34
|
-
"@zag-js/types": "1.
|
|
30
|
+
"@zag-js/anatomy": "1.33.0",
|
|
31
|
+
"@zag-js/core": "1.33.0",
|
|
32
|
+
"@zag-js/dom-query": "1.33.0",
|
|
33
|
+
"@zag-js/utils": "1.33.0",
|
|
34
|
+
"@zag-js/types": "1.33.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"clean-package": "2.2.0"
|