@zag-js/number-input 1.41.0 → 2.0.0-next.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/number-input.anatomy.d.mts +2 -2
- package/dist/number-input.anatomy.d.ts +2 -2
- package/dist/number-input.anatomy.js +2 -1
- package/dist/number-input.anatomy.mjs +2 -1
- package/dist/number-input.connect.js +40 -19
- package/dist/number-input.connect.mjs +42 -20
- package/dist/number-input.dom.d.mts +10 -8
- package/dist/number-input.dom.d.ts +10 -8
- package/dist/number-input.dom.js +60 -64
- package/dist/number-input.dom.mjs +57 -59
- package/dist/number-input.machine.js +72 -28
- package/dist/number-input.machine.mjs +75 -29
- package/dist/number-input.props.js +6 -0
- package/dist/number-input.props.mjs +6 -0
- package/dist/number-input.types.d.mts +50 -1
- package/dist/number-input.types.d.ts +50 -1
- package/package.json +6 -6
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// src/number-input.dom.ts
|
|
2
|
-
import { isSafari, MAX_Z_INDEX } from "@zag-js/dom-query";
|
|
3
2
|
import { roundToDpr, wrap } from "@zag-js/utils";
|
|
4
|
-
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var
|
|
3
|
+
import { parts } from "./number-input.anatomy.mjs";
|
|
4
|
+
var getRootId = (ctx) => ctx.ids?.root ?? `${ctx.id}`;
|
|
5
|
+
var getInputId = (ctx) => ctx.ids?.input ?? `${ctx.id}:input`;
|
|
6
|
+
var getIncrementTriggerId = (ctx) => ctx.ids?.incrementTrigger ?? `${ctx.id}:inc`;
|
|
7
|
+
var getDecrementTriggerId = (ctx) => ctx.ids?.decrementTrigger ?? `${ctx.id}:dec`;
|
|
8
|
+
var getScrubberId = (ctx) => ctx.ids?.scrubber ?? `${ctx.id}:scrubber`;
|
|
9
|
+
var getScrubberCursorId = (ctx) => ctx.ids?.scrubberCursor ?? `${ctx.id}:scrubber-cursor`;
|
|
10
|
+
var getLabelId = (ctx) => ctx.ids?.label ?? `${ctx.id}:label`;
|
|
11
|
+
var getInputEl = (ctx) => ctx.query(ctx.selector(parts.input));
|
|
12
|
+
var getIncrementTriggerEl = (ctx) => ctx.query(ctx.selector(parts.incrementTrigger));
|
|
13
|
+
var getDecrementTriggerEl = (ctx) => ctx.query(ctx.selector(parts.decrementTrigger));
|
|
14
|
+
var getScrubberEl = (ctx) => ctx.query(ctx.selector(parts.scrubber));
|
|
15
|
+
var getScrubberCursorEl = (ctx) => ctx.query(ctx.selector(parts.scrubberCursor));
|
|
16
16
|
var getPressedTriggerEl = (ctx, hint) => {
|
|
17
17
|
let btnEl = null;
|
|
18
18
|
if (hint === "increment") {
|
|
@@ -23,20 +23,13 @@ var getPressedTriggerEl = (ctx, hint) => {
|
|
|
23
23
|
}
|
|
24
24
|
return btnEl;
|
|
25
25
|
};
|
|
26
|
-
var
|
|
27
|
-
if (isSafari()) return;
|
|
28
|
-
createVirtualCursor(ctx, point);
|
|
29
|
-
return () => {
|
|
30
|
-
getCursorEl(ctx)?.remove();
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
var preventTextSelection = (ctx) => {
|
|
26
|
+
var preventTextSelection = (ctx, direction) => {
|
|
34
27
|
const doc = ctx.getDoc();
|
|
35
28
|
const html = doc.documentElement;
|
|
36
29
|
const body = doc.body;
|
|
37
30
|
body.style.pointerEvents = "none";
|
|
38
31
|
html.style.userSelect = "none";
|
|
39
|
-
html.style.cursor = "ew-resize";
|
|
32
|
+
html.style.cursor = direction === "vertical" ? "ns-resize" : "ew-resize";
|
|
40
33
|
return () => {
|
|
41
34
|
body.style.pointerEvents = "";
|
|
42
35
|
html.style.userSelect = "";
|
|
@@ -50,48 +43,52 @@ var preventTextSelection = (ctx) => {
|
|
|
50
43
|
};
|
|
51
44
|
};
|
|
52
45
|
var getMousemoveValue = (ctx, opts) => {
|
|
53
|
-
const { point, isRtl, event } = opts;
|
|
46
|
+
const { point, isRtl, event, direction, teleportDistance } = opts;
|
|
54
47
|
const win = ctx.getWin();
|
|
55
48
|
const x = roundToDpr(event.movementX, win.devicePixelRatio);
|
|
56
49
|
const y = roundToDpr(event.movementY, win.devicePixelRatio);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
const isVertical = direction === "vertical";
|
|
51
|
+
const delta = isVertical ? y : x;
|
|
52
|
+
let hint;
|
|
53
|
+
if (isVertical) {
|
|
54
|
+
hint = delta < 0 ? "increment" : delta > 0 ? "decrement" : null;
|
|
55
|
+
} else {
|
|
56
|
+
hint = delta > 0 ? "increment" : delta < 0 ? "decrement" : null;
|
|
57
|
+
}
|
|
58
|
+
if (!isVertical && isRtl) {
|
|
59
|
+
if (hint === "increment") hint = "decrement";
|
|
60
|
+
else if (hint === "decrement") hint = "increment";
|
|
61
|
+
}
|
|
60
62
|
const newPoint = { x: point.x + x, y: point.y + y };
|
|
61
|
-
const width = win.innerWidth;
|
|
62
63
|
const half = roundToDpr(7.5, win.devicePixelRatio);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
doc.body.appendChild(el);
|
|
64
|
+
if (teleportDistance != null) {
|
|
65
|
+
const scrubberEl = getScrubberEl(ctx);
|
|
66
|
+
if (scrubberEl) {
|
|
67
|
+
const rect = scrubberEl.getBoundingClientRect();
|
|
68
|
+
if (isVertical) {
|
|
69
|
+
const center = rect.top + rect.height / 2;
|
|
70
|
+
const min = center - teleportDistance / 2;
|
|
71
|
+
const max = center + teleportDistance / 2;
|
|
72
|
+
newPoint.y = wrap(newPoint.y + half - min, max - min) + min - half;
|
|
73
|
+
} else {
|
|
74
|
+
const center = rect.left + rect.width / 2;
|
|
75
|
+
const min = center - teleportDistance / 2;
|
|
76
|
+
const max = center + teleportDistance / 2;
|
|
77
|
+
newPoint.x = wrap(newPoint.x + half - min, max - min) + min - half;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
if (isVertical) {
|
|
82
|
+
const height = win.innerHeight;
|
|
83
|
+
newPoint.y = wrap(newPoint.y + half, height) - half;
|
|
84
|
+
} else {
|
|
85
|
+
const width = win.innerWidth;
|
|
86
|
+
newPoint.x = wrap(newPoint.x + half, width) - half;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { hint, point: newPoint, delta };
|
|
90
90
|
};
|
|
91
91
|
export {
|
|
92
|
-
createVirtualCursor,
|
|
93
|
-
getCursorEl,
|
|
94
|
-
getCursorId,
|
|
95
92
|
getDecrementTriggerEl,
|
|
96
93
|
getDecrementTriggerId,
|
|
97
94
|
getIncrementTriggerEl,
|
|
@@ -102,8 +99,9 @@ export {
|
|
|
102
99
|
getMousemoveValue,
|
|
103
100
|
getPressedTriggerEl,
|
|
104
101
|
getRootId,
|
|
102
|
+
getScrubberCursorEl,
|
|
103
|
+
getScrubberCursorId,
|
|
105
104
|
getScrubberEl,
|
|
106
105
|
getScrubberId,
|
|
107
|
-
preventTextSelection
|
|
108
|
-
setupVirtualCursor
|
|
106
|
+
preventTextSelection
|
|
109
107
|
};
|
|
@@ -54,9 +54,14 @@ var machine = createMachine({
|
|
|
54
54
|
pattern: "-?[0-9]*(.[0-9]+)?",
|
|
55
55
|
defaultValue: "",
|
|
56
56
|
step,
|
|
57
|
+
largeStep: props.largeStep ?? step * 10,
|
|
58
|
+
smallStep: props.smallStep ?? step * 0.1,
|
|
57
59
|
min: Number.MIN_SAFE_INTEGER,
|
|
58
60
|
max: Number.MAX_SAFE_INTEGER,
|
|
59
61
|
spinOnPress: true,
|
|
62
|
+
scrubberPixelSensitivity: 2,
|
|
63
|
+
scrubberDirection: "horizontal",
|
|
64
|
+
snapOnStep: false,
|
|
60
65
|
...props,
|
|
61
66
|
translations: {
|
|
62
67
|
incrementLabel: "increment value",
|
|
@@ -86,7 +91,10 @@ var machine = createMachine({
|
|
|
86
91
|
return value ? `x:${value.x}, y:${value.y}` : "";
|
|
87
92
|
}
|
|
88
93
|
})),
|
|
89
|
-
fieldsetDisabled: bindable(() => ({ defaultValue: false }))
|
|
94
|
+
fieldsetDisabled: bindable(() => ({ defaultValue: false })),
|
|
95
|
+
cumulativeDelta: bindable(() => ({ defaultValue: 0 })),
|
|
96
|
+
isPointerLockSupported: bindable(() => ({ defaultValue: false })),
|
|
97
|
+
visualScale: bindable(() => ({ defaultValue: 1 }))
|
|
90
98
|
};
|
|
91
99
|
},
|
|
92
100
|
computed: {
|
|
@@ -121,7 +129,7 @@ var machine = createMachine({
|
|
|
121
129
|
action(["setVirtualCursorPosition"]);
|
|
122
130
|
});
|
|
123
131
|
},
|
|
124
|
-
effects: ["trackFormControl"],
|
|
132
|
+
effects: ["trackFormControl", "detectPointerLock"],
|
|
125
133
|
on: {
|
|
126
134
|
"VALUE.SET": {
|
|
127
135
|
actions: ["setRawValue"]
|
|
@@ -244,22 +252,16 @@ var machine = createMachine({
|
|
|
244
252
|
},
|
|
245
253
|
scrubbing: {
|
|
246
254
|
tags: ["focus"],
|
|
247
|
-
effects: ["activatePointerLock", "trackMousemove", "
|
|
255
|
+
effects: ["activatePointerLock", "trackMousemove", "preventTextSelection", "trackVisualViewport"],
|
|
256
|
+
entry: ["clearCumulativeDelta"],
|
|
248
257
|
on: {
|
|
249
258
|
"SCRUBBER.POINTER_UP": {
|
|
250
259
|
target: "focused",
|
|
251
|
-
actions: ["focusInput", "clearCursorPoint"]
|
|
260
|
+
actions: ["focusInput", "clearCursorPoint", "clearCumulativeDelta"]
|
|
252
261
|
},
|
|
253
|
-
"SCRUBBER.POINTER_MOVE":
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
actions: ["increment", "setCursorPoint"]
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
guard: "isDecrementHint",
|
|
260
|
-
actions: ["decrement", "setCursorPoint"]
|
|
261
|
-
}
|
|
262
|
-
]
|
|
262
|
+
"SCRUBBER.POINTER_MOVE": {
|
|
263
|
+
actions: ["accumulateDelta", "setCursorPoint"]
|
|
264
|
+
}
|
|
263
265
|
}
|
|
264
266
|
}
|
|
265
267
|
},
|
|
@@ -286,6 +288,9 @@ var machine = createMachine({
|
|
|
286
288
|
}, 50);
|
|
287
289
|
return () => clearInterval(id);
|
|
288
290
|
},
|
|
291
|
+
detectPointerLock({ context }) {
|
|
292
|
+
context.set("isPointerLockSupported", !(0, import_dom_query.isSafari)());
|
|
293
|
+
},
|
|
289
294
|
trackFormControl({ context, scope }) {
|
|
290
295
|
const inputEl = dom.getInputEl(scope);
|
|
291
296
|
return (0, import_dom_query.trackFormControl)(inputEl, {
|
|
@@ -297,12 +302,8 @@ var machine = createMachine({
|
|
|
297
302
|
}
|
|
298
303
|
});
|
|
299
304
|
},
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return dom.setupVirtualCursor(scope, point);
|
|
303
|
-
},
|
|
304
|
-
preventTextSelection({ scope }) {
|
|
305
|
-
return dom.preventTextSelection(scope);
|
|
305
|
+
preventTextSelection({ scope, prop }) {
|
|
306
|
+
return dom.preventTextSelection(scope, prop("scrubberDirection"));
|
|
306
307
|
},
|
|
307
308
|
trackButtonDisabled({ context, scope, send }) {
|
|
308
309
|
const hint = context.get("hint");
|
|
@@ -328,21 +329,39 @@ var machine = createMachine({
|
|
|
328
329
|
}
|
|
329
330
|
return (0, import_dom_query.addDomEvent)(inputEl, "wheel", onWheel, { passive: false });
|
|
330
331
|
},
|
|
332
|
+
trackVisualViewport({ scope, context }) {
|
|
333
|
+
const vV = scope.getWin().visualViewport;
|
|
334
|
+
if (!vV) return;
|
|
335
|
+
function onResize() {
|
|
336
|
+
context.set("visualScale", vV.scale);
|
|
337
|
+
}
|
|
338
|
+
onResize();
|
|
339
|
+
return (0, import_dom_query.addDomEvent)(vV, "resize", onResize);
|
|
340
|
+
},
|
|
331
341
|
activatePointerLock({ scope }) {
|
|
332
342
|
if ((0, import_dom_query.isSafari)()) return;
|
|
333
|
-
|
|
343
|
+
const cleanup = (0, import_dom_query.requestPointerLock)(scope.getDoc());
|
|
344
|
+
return () => {
|
|
345
|
+
if ((0, import_dom_query.isFirefox)()) {
|
|
346
|
+
setTimeout(() => cleanup?.(), 20);
|
|
347
|
+
} else {
|
|
348
|
+
cleanup?.();
|
|
349
|
+
}
|
|
350
|
+
};
|
|
334
351
|
},
|
|
335
|
-
trackMousemove({ scope, send, context, computed }) {
|
|
352
|
+
trackMousemove({ scope, send, context, computed, prop }) {
|
|
336
353
|
const doc = scope.getDoc();
|
|
337
354
|
function onMousemove(event) {
|
|
338
355
|
const point = context.get("scrubberCursorPoint");
|
|
339
356
|
const isRtl = computed("isRtl");
|
|
340
|
-
const
|
|
341
|
-
|
|
357
|
+
const direction = prop("scrubberDirection");
|
|
358
|
+
const teleportDistance = prop("scrubberTeleportDistance");
|
|
359
|
+
const value = dom.getMousemoveValue(scope, { point, isRtl, event, direction, teleportDistance });
|
|
342
360
|
send({
|
|
343
361
|
type: "SCRUBBER.POINTER_MOVE",
|
|
344
362
|
hint: value.hint,
|
|
345
|
-
point: value.point
|
|
363
|
+
point: value.point,
|
|
364
|
+
delta: value.delta
|
|
346
365
|
});
|
|
347
366
|
}
|
|
348
367
|
function onMouseup() {
|
|
@@ -361,11 +380,15 @@ var machine = createMachine({
|
|
|
361
380
|
increment({ context, event, prop, computed }) {
|
|
362
381
|
let nextValue = (0, import_utils.incrementValue)(computed("valueAsNumber"), event.step ?? prop("step"));
|
|
363
382
|
if (!prop("allowOverflow")) nextValue = (0, import_utils.clampValue)(nextValue, prop("min"), prop("max"));
|
|
383
|
+
if (prop("snapOnStep"))
|
|
384
|
+
nextValue = (0, import_utils.snapValueToStep)(nextValue, prop("min"), prop("max"), event.step ?? prop("step"));
|
|
364
385
|
context.set("value", (0, import_number_input.formatValue)(nextValue, { computed, prop }));
|
|
365
386
|
},
|
|
366
387
|
decrement({ context, event, prop, computed }) {
|
|
367
388
|
let nextValue = (0, import_utils.decrementValue)(computed("valueAsNumber"), event.step ?? prop("step"));
|
|
368
389
|
if (!prop("allowOverflow")) nextValue = (0, import_utils.clampValue)(nextValue, prop("min"), prop("max"));
|
|
390
|
+
if (prop("snapOnStep"))
|
|
391
|
+
nextValue = (0, import_utils.snapValueToStep)(nextValue, prop("min"), prop("max"), event.step ?? prop("step"));
|
|
369
392
|
context.set("value", (0, import_number_input.formatValue)(nextValue, { computed, prop }));
|
|
370
393
|
},
|
|
371
394
|
setClampedValue({ context, prop, computed }) {
|
|
@@ -447,10 +470,31 @@ var machine = createMachine({
|
|
|
447
470
|
context.set("scrubberCursorPoint", null);
|
|
448
471
|
},
|
|
449
472
|
setVirtualCursorPosition({ context, scope }) {
|
|
450
|
-
const
|
|
473
|
+
const scrubberEl = dom.getScrubberEl(scope);
|
|
451
474
|
const point = context.get("scrubberCursorPoint");
|
|
452
|
-
if (!
|
|
453
|
-
|
|
475
|
+
if (!scrubberEl || !point) return;
|
|
476
|
+
scrubberEl.style.setProperty("--scrubber-x", `${point.x}px`);
|
|
477
|
+
scrubberEl.style.setProperty("--scrubber-y", `${point.y}px`);
|
|
478
|
+
},
|
|
479
|
+
accumulateDelta({ context, event, prop, send }) {
|
|
480
|
+
const delta = event.delta ?? 0;
|
|
481
|
+
const newDelta = context.get("cumulativeDelta") + delta;
|
|
482
|
+
const sensitivity = prop("scrubberPixelSensitivity");
|
|
483
|
+
if (Math.abs(newDelta) >= sensitivity) {
|
|
484
|
+
const step = prop("step");
|
|
485
|
+
const hint = event.hint;
|
|
486
|
+
if (hint === "increment") {
|
|
487
|
+
send({ type: "VALUE.INCREMENT", step });
|
|
488
|
+
} else if (hint === "decrement") {
|
|
489
|
+
send({ type: "VALUE.DECREMENT", step });
|
|
490
|
+
}
|
|
491
|
+
context.set("cumulativeDelta", newDelta % sensitivity);
|
|
492
|
+
} else {
|
|
493
|
+
context.set("cumulativeDelta", newDelta);
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
clearCumulativeDelta({ context }) {
|
|
497
|
+
context.set("cumulativeDelta", 0);
|
|
454
498
|
}
|
|
455
499
|
}
|
|
456
500
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { memo, setup } from "@zag-js/core";
|
|
3
3
|
import {
|
|
4
4
|
addDomEvent,
|
|
5
|
+
isFirefox,
|
|
5
6
|
isSafari,
|
|
6
7
|
observeAttributes,
|
|
7
8
|
raf,
|
|
@@ -16,7 +17,8 @@ import {
|
|
|
16
17
|
incrementValue,
|
|
17
18
|
isValueAtMax,
|
|
18
19
|
isValueAtMin,
|
|
19
|
-
isValueWithinRange
|
|
20
|
+
isValueWithinRange,
|
|
21
|
+
snapValueToStep
|
|
20
22
|
} from "@zag-js/utils";
|
|
21
23
|
import { recordCursor, restoreCursor } from "./cursor.mjs";
|
|
22
24
|
import * as dom from "./number-input.dom.mjs";
|
|
@@ -36,9 +38,14 @@ var machine = createMachine({
|
|
|
36
38
|
pattern: "-?[0-9]*(.[0-9]+)?",
|
|
37
39
|
defaultValue: "",
|
|
38
40
|
step,
|
|
41
|
+
largeStep: props.largeStep ?? step * 10,
|
|
42
|
+
smallStep: props.smallStep ?? step * 0.1,
|
|
39
43
|
min: Number.MIN_SAFE_INTEGER,
|
|
40
44
|
max: Number.MAX_SAFE_INTEGER,
|
|
41
45
|
spinOnPress: true,
|
|
46
|
+
scrubberPixelSensitivity: 2,
|
|
47
|
+
scrubberDirection: "horizontal",
|
|
48
|
+
snapOnStep: false,
|
|
42
49
|
...props,
|
|
43
50
|
translations: {
|
|
44
51
|
incrementLabel: "increment value",
|
|
@@ -68,7 +75,10 @@ var machine = createMachine({
|
|
|
68
75
|
return value ? `x:${value.x}, y:${value.y}` : "";
|
|
69
76
|
}
|
|
70
77
|
})),
|
|
71
|
-
fieldsetDisabled: bindable(() => ({ defaultValue: false }))
|
|
78
|
+
fieldsetDisabled: bindable(() => ({ defaultValue: false })),
|
|
79
|
+
cumulativeDelta: bindable(() => ({ defaultValue: 0 })),
|
|
80
|
+
isPointerLockSupported: bindable(() => ({ defaultValue: false })),
|
|
81
|
+
visualScale: bindable(() => ({ defaultValue: 1 }))
|
|
72
82
|
};
|
|
73
83
|
},
|
|
74
84
|
computed: {
|
|
@@ -103,7 +113,7 @@ var machine = createMachine({
|
|
|
103
113
|
action(["setVirtualCursorPosition"]);
|
|
104
114
|
});
|
|
105
115
|
},
|
|
106
|
-
effects: ["trackFormControl"],
|
|
116
|
+
effects: ["trackFormControl", "detectPointerLock"],
|
|
107
117
|
on: {
|
|
108
118
|
"VALUE.SET": {
|
|
109
119
|
actions: ["setRawValue"]
|
|
@@ -226,22 +236,16 @@ var machine = createMachine({
|
|
|
226
236
|
},
|
|
227
237
|
scrubbing: {
|
|
228
238
|
tags: ["focus"],
|
|
229
|
-
effects: ["activatePointerLock", "trackMousemove", "
|
|
239
|
+
effects: ["activatePointerLock", "trackMousemove", "preventTextSelection", "trackVisualViewport"],
|
|
240
|
+
entry: ["clearCumulativeDelta"],
|
|
230
241
|
on: {
|
|
231
242
|
"SCRUBBER.POINTER_UP": {
|
|
232
243
|
target: "focused",
|
|
233
|
-
actions: ["focusInput", "clearCursorPoint"]
|
|
244
|
+
actions: ["focusInput", "clearCursorPoint", "clearCumulativeDelta"]
|
|
234
245
|
},
|
|
235
|
-
"SCRUBBER.POINTER_MOVE":
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
actions: ["increment", "setCursorPoint"]
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
guard: "isDecrementHint",
|
|
242
|
-
actions: ["decrement", "setCursorPoint"]
|
|
243
|
-
}
|
|
244
|
-
]
|
|
246
|
+
"SCRUBBER.POINTER_MOVE": {
|
|
247
|
+
actions: ["accumulateDelta", "setCursorPoint"]
|
|
248
|
+
}
|
|
245
249
|
}
|
|
246
250
|
}
|
|
247
251
|
},
|
|
@@ -268,6 +272,9 @@ var machine = createMachine({
|
|
|
268
272
|
}, 50);
|
|
269
273
|
return () => clearInterval(id);
|
|
270
274
|
},
|
|
275
|
+
detectPointerLock({ context }) {
|
|
276
|
+
context.set("isPointerLockSupported", !isSafari());
|
|
277
|
+
},
|
|
271
278
|
trackFormControl({ context, scope }) {
|
|
272
279
|
const inputEl = dom.getInputEl(scope);
|
|
273
280
|
return trackFormControl(inputEl, {
|
|
@@ -279,12 +286,8 @@ var machine = createMachine({
|
|
|
279
286
|
}
|
|
280
287
|
});
|
|
281
288
|
},
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return dom.setupVirtualCursor(scope, point);
|
|
285
|
-
},
|
|
286
|
-
preventTextSelection({ scope }) {
|
|
287
|
-
return dom.preventTextSelection(scope);
|
|
289
|
+
preventTextSelection({ scope, prop }) {
|
|
290
|
+
return dom.preventTextSelection(scope, prop("scrubberDirection"));
|
|
288
291
|
},
|
|
289
292
|
trackButtonDisabled({ context, scope, send }) {
|
|
290
293
|
const hint = context.get("hint");
|
|
@@ -310,21 +313,39 @@ var machine = createMachine({
|
|
|
310
313
|
}
|
|
311
314
|
return addDomEvent(inputEl, "wheel", onWheel, { passive: false });
|
|
312
315
|
},
|
|
316
|
+
trackVisualViewport({ scope, context }) {
|
|
317
|
+
const vV = scope.getWin().visualViewport;
|
|
318
|
+
if (!vV) return;
|
|
319
|
+
function onResize() {
|
|
320
|
+
context.set("visualScale", vV.scale);
|
|
321
|
+
}
|
|
322
|
+
onResize();
|
|
323
|
+
return addDomEvent(vV, "resize", onResize);
|
|
324
|
+
},
|
|
313
325
|
activatePointerLock({ scope }) {
|
|
314
326
|
if (isSafari()) return;
|
|
315
|
-
|
|
327
|
+
const cleanup = requestPointerLock(scope.getDoc());
|
|
328
|
+
return () => {
|
|
329
|
+
if (isFirefox()) {
|
|
330
|
+
setTimeout(() => cleanup?.(), 20);
|
|
331
|
+
} else {
|
|
332
|
+
cleanup?.();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
316
335
|
},
|
|
317
|
-
trackMousemove({ scope, send, context, computed }) {
|
|
336
|
+
trackMousemove({ scope, send, context, computed, prop }) {
|
|
318
337
|
const doc = scope.getDoc();
|
|
319
338
|
function onMousemove(event) {
|
|
320
339
|
const point = context.get("scrubberCursorPoint");
|
|
321
340
|
const isRtl = computed("isRtl");
|
|
322
|
-
const
|
|
323
|
-
|
|
341
|
+
const direction = prop("scrubberDirection");
|
|
342
|
+
const teleportDistance = prop("scrubberTeleportDistance");
|
|
343
|
+
const value = dom.getMousemoveValue(scope, { point, isRtl, event, direction, teleportDistance });
|
|
324
344
|
send({
|
|
325
345
|
type: "SCRUBBER.POINTER_MOVE",
|
|
326
346
|
hint: value.hint,
|
|
327
|
-
point: value.point
|
|
347
|
+
point: value.point,
|
|
348
|
+
delta: value.delta
|
|
328
349
|
});
|
|
329
350
|
}
|
|
330
351
|
function onMouseup() {
|
|
@@ -343,11 +364,15 @@ var machine = createMachine({
|
|
|
343
364
|
increment({ context, event, prop, computed }) {
|
|
344
365
|
let nextValue = incrementValue(computed("valueAsNumber"), event.step ?? prop("step"));
|
|
345
366
|
if (!prop("allowOverflow")) nextValue = clampValue(nextValue, prop("min"), prop("max"));
|
|
367
|
+
if (prop("snapOnStep"))
|
|
368
|
+
nextValue = snapValueToStep(nextValue, prop("min"), prop("max"), event.step ?? prop("step"));
|
|
346
369
|
context.set("value", formatValue(nextValue, { computed, prop }));
|
|
347
370
|
},
|
|
348
371
|
decrement({ context, event, prop, computed }) {
|
|
349
372
|
let nextValue = decrementValue(computed("valueAsNumber"), event.step ?? prop("step"));
|
|
350
373
|
if (!prop("allowOverflow")) nextValue = clampValue(nextValue, prop("min"), prop("max"));
|
|
374
|
+
if (prop("snapOnStep"))
|
|
375
|
+
nextValue = snapValueToStep(nextValue, prop("min"), prop("max"), event.step ?? prop("step"));
|
|
351
376
|
context.set("value", formatValue(nextValue, { computed, prop }));
|
|
352
377
|
},
|
|
353
378
|
setClampedValue({ context, prop, computed }) {
|
|
@@ -429,10 +454,31 @@ var machine = createMachine({
|
|
|
429
454
|
context.set("scrubberCursorPoint", null);
|
|
430
455
|
},
|
|
431
456
|
setVirtualCursorPosition({ context, scope }) {
|
|
432
|
-
const
|
|
457
|
+
const scrubberEl = dom.getScrubberEl(scope);
|
|
433
458
|
const point = context.get("scrubberCursorPoint");
|
|
434
|
-
if (!
|
|
435
|
-
|
|
459
|
+
if (!scrubberEl || !point) return;
|
|
460
|
+
scrubberEl.style.setProperty("--scrubber-x", `${point.x}px`);
|
|
461
|
+
scrubberEl.style.setProperty("--scrubber-y", `${point.y}px`);
|
|
462
|
+
},
|
|
463
|
+
accumulateDelta({ context, event, prop, send }) {
|
|
464
|
+
const delta = event.delta ?? 0;
|
|
465
|
+
const newDelta = context.get("cumulativeDelta") + delta;
|
|
466
|
+
const sensitivity = prop("scrubberPixelSensitivity");
|
|
467
|
+
if (Math.abs(newDelta) >= sensitivity) {
|
|
468
|
+
const step = prop("step");
|
|
469
|
+
const hint = event.hint;
|
|
470
|
+
if (hint === "increment") {
|
|
471
|
+
send({ type: "VALUE.INCREMENT", step });
|
|
472
|
+
} else if (hint === "decrement") {
|
|
473
|
+
send({ type: "VALUE.DECREMENT", step });
|
|
474
|
+
}
|
|
475
|
+
context.set("cumulativeDelta", newDelta % sensitivity);
|
|
476
|
+
} else {
|
|
477
|
+
context.set("cumulativeDelta", newDelta);
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
clearCumulativeDelta({ context }) {
|
|
481
|
+
context.set("cumulativeDelta", 0);
|
|
436
482
|
}
|
|
437
483
|
}
|
|
438
484
|
}
|
|
@@ -40,6 +40,7 @@ var props = (0, import_types.createProps)()([
|
|
|
40
40
|
"ids",
|
|
41
41
|
"inputMode",
|
|
42
42
|
"invalid",
|
|
43
|
+
"largeStep",
|
|
43
44
|
"locale",
|
|
44
45
|
"max",
|
|
45
46
|
"min",
|
|
@@ -51,6 +52,11 @@ var props = (0, import_types.createProps)()([
|
|
|
51
52
|
"pattern",
|
|
52
53
|
"required",
|
|
53
54
|
"readOnly",
|
|
55
|
+
"scrubberDirection",
|
|
56
|
+
"scrubberPixelSensitivity",
|
|
57
|
+
"scrubberTeleportDistance",
|
|
58
|
+
"smallStep",
|
|
59
|
+
"snapOnStep",
|
|
54
60
|
"spinOnPress",
|
|
55
61
|
"step",
|
|
56
62
|
"translations",
|
|
@@ -15,6 +15,7 @@ var props = createProps()([
|
|
|
15
15
|
"ids",
|
|
16
16
|
"inputMode",
|
|
17
17
|
"invalid",
|
|
18
|
+
"largeStep",
|
|
18
19
|
"locale",
|
|
19
20
|
"max",
|
|
20
21
|
"min",
|
|
@@ -26,6 +27,11 @@ var props = createProps()([
|
|
|
26
27
|
"pattern",
|
|
27
28
|
"required",
|
|
28
29
|
"readOnly",
|
|
30
|
+
"scrubberDirection",
|
|
31
|
+
"scrubberPixelSensitivity",
|
|
32
|
+
"scrubberTeleportDistance",
|
|
33
|
+
"smallStep",
|
|
34
|
+
"snapOnStep",
|
|
29
35
|
"spinOnPress",
|
|
30
36
|
"step",
|
|
31
37
|
"translations",
|
|
@@ -21,6 +21,7 @@ type ElementIds = Partial<{
|
|
|
21
21
|
incrementTrigger: string;
|
|
22
22
|
decrementTrigger: string;
|
|
23
23
|
scrubber: string;
|
|
24
|
+
scrubberCursor: string;
|
|
24
25
|
}>;
|
|
25
26
|
interface IntlTranslations {
|
|
26
27
|
/**
|
|
@@ -150,8 +151,39 @@ interface NumberInputProps extends LocaleProperties, CommonProperties {
|
|
|
150
151
|
* @default true
|
|
151
152
|
*/
|
|
152
153
|
spinOnPress?: boolean | undefined;
|
|
154
|
+
/**
|
|
155
|
+
* Step value when Shift key is held (large increments).
|
|
156
|
+
* @default step * 10
|
|
157
|
+
*/
|
|
158
|
+
largeStep?: number | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* Step value when Alt/Option key is held (fine control).
|
|
161
|
+
* @default step * 0.1
|
|
162
|
+
*/
|
|
163
|
+
smallStep?: number | undefined;
|
|
164
|
+
/**
|
|
165
|
+
* How many pixels the pointer must move to change the value by one step.
|
|
166
|
+
* Higher values mean more precise control. Lower values mean more sensitivity.
|
|
167
|
+
* @default 2
|
|
168
|
+
*/
|
|
169
|
+
scrubberPixelSensitivity?: number | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Distance in pixels before the virtual cursor wraps around.
|
|
172
|
+
* When not set, the viewport bounds are used (default behavior).
|
|
173
|
+
*/
|
|
174
|
+
scrubberTeleportDistance?: number | undefined;
|
|
175
|
+
/**
|
|
176
|
+
* The direction of scrubbing.
|
|
177
|
+
* @default "horizontal"
|
|
178
|
+
*/
|
|
179
|
+
scrubberDirection?: "horizontal" | "vertical" | undefined;
|
|
180
|
+
/**
|
|
181
|
+
* Whether values snap to step multiples when scrubbing.
|
|
182
|
+
* @default false
|
|
183
|
+
*/
|
|
184
|
+
snapOnStep?: boolean | undefined;
|
|
153
185
|
}
|
|
154
|
-
type PropsWithDefault = "dir" | "locale" | "focusInputOnChange" | "clampValueOnBlur" | "allowOverflow" | "inputMode" | "pattern" | "translations" | "step" | "spinOnPress" | "min" | "max" | "
|
|
186
|
+
type PropsWithDefault = "dir" | "locale" | "focusInputOnChange" | "clampValueOnBlur" | "allowOverflow" | "inputMode" | "pattern" | "translations" | "step" | "spinOnPress" | "min" | "max" | "largeStep" | "smallStep" | "scrubberPixelSensitivity" | "scrubberDirection" | "snapOnStep";
|
|
155
187
|
type ComputedContext = Readonly<{
|
|
156
188
|
/**
|
|
157
189
|
* The value of the input as a number
|
|
@@ -227,6 +259,18 @@ interface PrivateContext {
|
|
|
227
259
|
* The value of the input
|
|
228
260
|
*/
|
|
229
261
|
value: string;
|
|
262
|
+
/**
|
|
263
|
+
* The accumulated delta for pixel sensitivity tracking
|
|
264
|
+
*/
|
|
265
|
+
cumulativeDelta: number;
|
|
266
|
+
/**
|
|
267
|
+
* Whether the browser supports pointer lock (false on Safari, false during SSR)
|
|
268
|
+
*/
|
|
269
|
+
isPointerLockSupported: boolean;
|
|
270
|
+
/**
|
|
271
|
+
* The visual viewport scale (for pinch-zoom compensation on the cursor)
|
|
272
|
+
*/
|
|
273
|
+
visualScale: number;
|
|
230
274
|
}
|
|
231
275
|
interface NumberInputSchema {
|
|
232
276
|
state: "idle" | "focused" | "spinning" | "before:spin" | "scrubbing";
|
|
@@ -246,6 +290,10 @@ interface NumberInputApi<T extends PropTypes = PropTypes> {
|
|
|
246
290
|
* Whether the input is focused.
|
|
247
291
|
*/
|
|
248
292
|
focused: boolean;
|
|
293
|
+
/**
|
|
294
|
+
* Whether the input is being scrubbed.
|
|
295
|
+
*/
|
|
296
|
+
scrubbing: boolean;
|
|
249
297
|
/**
|
|
250
298
|
* Whether the input is invalid.
|
|
251
299
|
*/
|
|
@@ -298,6 +346,7 @@ interface NumberInputApi<T extends PropTypes = PropTypes> {
|
|
|
298
346
|
getDecrementTriggerProps: () => T["button"];
|
|
299
347
|
getIncrementTriggerProps: () => T["button"];
|
|
300
348
|
getScrubberProps: () => T["element"];
|
|
349
|
+
getScrubberCursorProps: () => T["element"];
|
|
301
350
|
}
|
|
302
351
|
|
|
303
352
|
export type { ElementIds, FocusChangeDetails, HintValue, InputMode, IntlTranslations, NumberInputApi, NumberInputMachine, NumberInputProps, NumberInputSchema, NumberInputService, ValidityState, ValueChangeDetails, ValueInvalidDetails };
|