@zag-js/number-input 1.34.1 → 1.35.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.
Files changed (37) hide show
  1. package/dist/cursor.d.mts +12 -0
  2. package/dist/cursor.d.ts +12 -0
  3. package/dist/cursor.js +111 -0
  4. package/dist/cursor.mjs +84 -0
  5. package/dist/index.d.mts +9 -314
  6. package/dist/index.d.ts +9 -314
  7. package/dist/index.js +37 -931
  8. package/dist/index.mjs +9 -926
  9. package/dist/number-input.anatomy.d.mts +6 -0
  10. package/dist/number-input.anatomy.d.ts +6 -0
  11. package/dist/number-input.anatomy.js +43 -0
  12. package/dist/number-input.anatomy.mjs +17 -0
  13. package/dist/number-input.connect.d.mts +8 -0
  14. package/dist/number-input.connect.d.ts +8 -0
  15. package/dist/number-input.connect.js +308 -0
  16. package/dist/number-input.connect.mjs +284 -0
  17. package/dist/number-input.dom.d.mts +34 -0
  18. package/dist/number-input.dom.d.ts +34 -0
  19. package/dist/number-input.dom.js +150 -0
  20. package/dist/number-input.dom.mjs +109 -0
  21. package/dist/number-input.machine.d.mts +8 -0
  22. package/dist/number-input.machine.d.ts +8 -0
  23. package/dist/number-input.machine.js +460 -0
  24. package/dist/number-input.machine.mjs +441 -0
  25. package/dist/number-input.props.d.mts +9 -0
  26. package/dist/number-input.props.d.ts +9 -0
  27. package/dist/number-input.props.js +65 -0
  28. package/dist/number-input.props.mjs +39 -0
  29. package/dist/number-input.types.d.mts +303 -0
  30. package/dist/number-input.types.d.ts +303 -0
  31. package/dist/number-input.types.js +18 -0
  32. package/dist/number-input.types.mjs +0 -0
  33. package/dist/number-input.utils.d.mts +13 -0
  34. package/dist/number-input.utils.d.ts +13 -0
  35. package/dist/number-input.utils.js +63 -0
  36. package/dist/number-input.utils.mjs +34 -0
  37. package/package.json +17 -7
package/dist/index.mjs CHANGED
@@ -1,927 +1,10 @@
1
- import { createAnatomy } from '@zag-js/anatomy';
2
- import { raf, setElementValue, addDomEvent, isSafari, requestPointerLock, observeAttributes, trackFormControl, MAX_Z_INDEX, isLeftClick, getEventPoint, getWindow, setCaretToEnd, dataAttr, isComposingEvent, getEventStep, ariaAttr, isModifierKey } from '@zag-js/dom-query';
3
- import { clampValue, decrementValue, incrementValue, callAll, isValueWithinRange, isValueAtMax, isValueAtMin, createSplitProps, roundToDpr, wrap } from '@zag-js/utils';
4
- import { setup, memo } from '@zag-js/core';
5
- import { NumberParser } from '@internationalized/number';
6
- import { createProps } from '@zag-js/types';
7
-
8
- // src/number-input.anatomy.ts
9
- var anatomy = createAnatomy("numberInput").parts(
10
- "root",
11
- "label",
12
- "input",
13
- "control",
14
- "valueText",
15
- "incrementTrigger",
16
- "decrementTrigger",
17
- "scrubber"
18
- );
19
- var parts = anatomy.build();
20
-
21
- // src/cursor.ts
22
- function recordCursor(inputEl, scope) {
23
- if (!inputEl || !scope.isActiveElement(inputEl)) return;
24
- try {
25
- const { selectionStart: start, selectionEnd: end, value } = inputEl;
26
- if (start == null || end == null) return void 0;
27
- return { start, end, value };
28
- } catch {
29
- return void 0;
30
- }
31
- }
32
- function restoreCursor(inputEl, selection, scope) {
33
- if (!inputEl || !scope.isActiveElement(inputEl)) return;
34
- if (!selection) {
35
- const len = inputEl.value.length;
36
- inputEl.setSelectionRange(len, len);
37
- return;
38
- }
39
- try {
40
- const newValue = inputEl.value;
41
- const { start, end, value: oldValue } = selection;
42
- if (newValue === oldValue) {
43
- inputEl.setSelectionRange(start, end);
44
- return;
45
- }
46
- const newStart = getNextCursorPosition(oldValue, newValue, start);
47
- const newEnd = start === end ? newStart : getNextCursorPosition(oldValue, newValue, end);
48
- const clampedStart = Math.max(0, Math.min(newStart, newValue.length));
49
- const clampedEnd = Math.max(clampedStart, Math.min(newEnd, newValue.length));
50
- inputEl.setSelectionRange(clampedStart, clampedEnd);
51
- } catch {
52
- const len = inputEl.value.length;
53
- inputEl.setSelectionRange(len, len);
54
- }
55
- }
56
- function getNextCursorPosition(oldValue, newValue, oldPosition) {
57
- const beforeCursor = oldValue.slice(0, oldPosition);
58
- const afterCursor = oldValue.slice(oldPosition);
59
- let prefixLength = 0;
60
- const maxPrefixLength = Math.min(beforeCursor.length, newValue.length);
61
- for (let i = 0; i < maxPrefixLength; i++) {
62
- if (beforeCursor[i] === newValue[i]) {
63
- prefixLength = i + 1;
64
- } else {
65
- break;
66
- }
67
- }
68
- let suffixLength = 0;
69
- const maxSuffixLength = Math.min(afterCursor.length, newValue.length - prefixLength);
70
- for (let i = 0; i < maxSuffixLength; i++) {
71
- const oldIndex = afterCursor.length - 1 - i;
72
- const newIndex = newValue.length - 1 - i;
73
- if (afterCursor[oldIndex] === newValue[newIndex]) {
74
- suffixLength = i + 1;
75
- } else {
76
- break;
77
- }
78
- }
79
- if (beforeCursor.length > 0 && prefixLength >= beforeCursor.length) {
80
- return prefixLength;
81
- }
82
- if (suffixLength >= afterCursor.length) {
83
- return newValue.length - suffixLength;
84
- }
85
- if (prefixLength > 0) {
86
- return prefixLength;
87
- }
88
- if (suffixLength > 0) {
89
- return newValue.length - suffixLength;
90
- }
91
- if (oldPosition === 0 && prefixLength === 0 && suffixLength === 0) {
92
- return newValue.length;
93
- }
94
- if (oldValue.length > 0) {
95
- const ratio = oldPosition / oldValue.length;
96
- return Math.round(ratio * newValue.length);
97
- }
98
- return newValue.length;
99
- }
100
- var getRootId = (ctx) => ctx.ids?.root ?? `number-input:${ctx.id}`;
101
- var getInputId = (ctx) => ctx.ids?.input ?? `number-input:${ctx.id}:input`;
102
- var getIncrementTriggerId = (ctx) => ctx.ids?.incrementTrigger ?? `number-input:${ctx.id}:inc`;
103
- var getDecrementTriggerId = (ctx) => ctx.ids?.decrementTrigger ?? `number-input:${ctx.id}:dec`;
104
- var getScrubberId = (ctx) => ctx.ids?.scrubber ?? `number-input:${ctx.id}:scrubber`;
105
- var getCursorId = (ctx) => `number-input:${ctx.id}:cursor`;
106
- var getLabelId = (ctx) => ctx.ids?.label ?? `number-input:${ctx.id}:label`;
107
- var getInputEl = (ctx) => ctx.getById(getInputId(ctx));
108
- var getIncrementTriggerEl = (ctx) => ctx.getById(getIncrementTriggerId(ctx));
109
- var getDecrementTriggerEl = (ctx) => ctx.getById(getDecrementTriggerId(ctx));
110
- var getCursorEl = (ctx) => ctx.getDoc().getElementById(getCursorId(ctx));
111
- var getPressedTriggerEl = (ctx, hint) => {
112
- let btnEl = null;
113
- if (hint === "increment") {
114
- btnEl = getIncrementTriggerEl(ctx);
115
- }
116
- if (hint === "decrement") {
117
- btnEl = getDecrementTriggerEl(ctx);
118
- }
119
- return btnEl;
1
+ // src/index.ts
2
+ import { anatomy } from "./number-input.anatomy.mjs";
3
+ import { connect } from "./number-input.connect.mjs";
4
+ import { machine } from "./number-input.machine.mjs";
5
+ export * from "./number-input.props.mjs";
6
+ export {
7
+ anatomy,
8
+ connect,
9
+ machine
120
10
  };
121
- var setupVirtualCursor = (ctx, point) => {
122
- if (isSafari()) return;
123
- createVirtualCursor(ctx, point);
124
- return () => {
125
- getCursorEl(ctx)?.remove();
126
- };
127
- };
128
- var preventTextSelection = (ctx) => {
129
- const doc = ctx.getDoc();
130
- const html = doc.documentElement;
131
- const body = doc.body;
132
- body.style.pointerEvents = "none";
133
- html.style.userSelect = "none";
134
- html.style.cursor = "ew-resize";
135
- return () => {
136
- body.style.pointerEvents = "";
137
- html.style.userSelect = "";
138
- html.style.cursor = "";
139
- if (!html.style.length) {
140
- html.removeAttribute("style");
141
- }
142
- if (!body.style.length) {
143
- body.removeAttribute("style");
144
- }
145
- };
146
- };
147
- var getMousemoveValue = (ctx, opts) => {
148
- const { point, isRtl, event } = opts;
149
- const win = ctx.getWin();
150
- const x = roundToDpr(event.movementX, win.devicePixelRatio);
151
- const y = roundToDpr(event.movementY, win.devicePixelRatio);
152
- let hint = x > 0 ? "increment" : x < 0 ? "decrement" : null;
153
- if (isRtl && hint === "increment") hint = "decrement";
154
- if (isRtl && hint === "decrement") hint = "increment";
155
- const newPoint = { x: point.x + x, y: point.y + y };
156
- const width = win.innerWidth;
157
- const half = roundToDpr(7.5, win.devicePixelRatio);
158
- newPoint.x = wrap(newPoint.x + half, width) - half;
159
- return { hint, point: newPoint };
160
- };
161
- var createVirtualCursor = (ctx, point) => {
162
- const doc = ctx.getDoc();
163
- const el = doc.createElement("div");
164
- el.className = "scrubber--cursor";
165
- el.id = getCursorId(ctx);
166
- Object.assign(el.style, {
167
- width: "15px",
168
- height: "15px",
169
- position: "fixed",
170
- pointerEvents: "none",
171
- left: "0px",
172
- top: "0px",
173
- zIndex: MAX_Z_INDEX,
174
- transform: point ? `translate3d(${point.x}px, ${point.y}px, 0px)` : void 0,
175
- willChange: "transform"
176
- });
177
- el.innerHTML = `
178
- <svg width="46" height="15" style="left: -15.5px; position: absolute; top: 0; filter: drop-shadow(rgba(0, 0, 0, 0.4) 0px 1px 1.1px);">
179
- <g transform="translate(2 3)">
180
- <path fill-rule="evenodd" d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z" style="stroke-width: 2px; stroke: white;"></path>
181
- <path fill-rule="evenodd" d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z"></path>
182
- </g>
183
- </svg>`;
184
- doc.body.appendChild(el);
185
- };
186
-
187
- // src/number-input.connect.ts
188
- function connect(service, normalize) {
189
- const { state, send, prop, scope, computed } = service;
190
- const focused = state.hasTag("focus");
191
- const disabled = computed("isDisabled");
192
- const readOnly = !!prop("readOnly");
193
- const required = !!prop("required");
194
- const scrubbing = state.matches("scrubbing");
195
- const empty = computed("isValueEmpty");
196
- const invalid = prop("invalid") !== void 0 ? !!prop("invalid") : computed("isOutOfRange");
197
- const isIncrementDisabled = disabled || !computed("canIncrement") || readOnly;
198
- const isDecrementDisabled = disabled || !computed("canDecrement") || readOnly;
199
- const translations = prop("translations");
200
- return {
201
- focused,
202
- invalid,
203
- empty,
204
- value: computed("formattedValue"),
205
- valueAsNumber: computed("valueAsNumber"),
206
- setValue(value) {
207
- send({ type: "VALUE.SET", value });
208
- },
209
- clearValue() {
210
- send({ type: "VALUE.CLEAR" });
211
- },
212
- increment() {
213
- send({ type: "VALUE.INCREMENT" });
214
- },
215
- decrement() {
216
- send({ type: "VALUE.DECREMENT" });
217
- },
218
- setToMax() {
219
- send({ type: "VALUE.SET", value: prop("max") });
220
- },
221
- setToMin() {
222
- send({ type: "VALUE.SET", value: prop("min") });
223
- },
224
- focus() {
225
- getInputEl(scope)?.focus();
226
- },
227
- getRootProps() {
228
- return normalize.element({
229
- id: getRootId(scope),
230
- ...parts.root.attrs,
231
- dir: prop("dir"),
232
- "data-disabled": dataAttr(disabled),
233
- "data-focus": dataAttr(focused),
234
- "data-invalid": dataAttr(invalid),
235
- "data-scrubbing": dataAttr(scrubbing)
236
- });
237
- },
238
- getLabelProps() {
239
- return normalize.label({
240
- ...parts.label.attrs,
241
- dir: prop("dir"),
242
- "data-disabled": dataAttr(disabled),
243
- "data-focus": dataAttr(focused),
244
- "data-invalid": dataAttr(invalid),
245
- "data-required": dataAttr(required),
246
- "data-scrubbing": dataAttr(scrubbing),
247
- id: getLabelId(scope),
248
- htmlFor: getInputId(scope),
249
- onClick() {
250
- raf(() => {
251
- setCaretToEnd(getInputEl(scope));
252
- });
253
- }
254
- });
255
- },
256
- getControlProps() {
257
- return normalize.element({
258
- ...parts.control.attrs,
259
- dir: prop("dir"),
260
- role: "group",
261
- "aria-disabled": disabled,
262
- "data-focus": dataAttr(focused),
263
- "data-disabled": dataAttr(disabled),
264
- "data-invalid": dataAttr(invalid),
265
- "data-scrubbing": dataAttr(scrubbing),
266
- "aria-invalid": ariaAttr(invalid)
267
- });
268
- },
269
- getValueTextProps() {
270
- return normalize.element({
271
- ...parts.valueText.attrs,
272
- dir: prop("dir"),
273
- "data-disabled": dataAttr(disabled),
274
- "data-invalid": dataAttr(invalid),
275
- "data-focus": dataAttr(focused),
276
- "data-scrubbing": dataAttr(scrubbing)
277
- });
278
- },
279
- getInputProps() {
280
- return normalize.input({
281
- ...parts.input.attrs,
282
- dir: prop("dir"),
283
- name: prop("name"),
284
- form: prop("form"),
285
- id: getInputId(scope),
286
- role: "spinbutton",
287
- defaultValue: computed("formattedValue"),
288
- pattern: prop("formatOptions") ? void 0 : prop("pattern"),
289
- inputMode: prop("inputMode"),
290
- "aria-invalid": ariaAttr(invalid),
291
- "data-invalid": dataAttr(invalid),
292
- disabled,
293
- "data-disabled": dataAttr(disabled),
294
- readOnly,
295
- required: prop("required"),
296
- autoComplete: "off",
297
- autoCorrect: "off",
298
- spellCheck: "false",
299
- type: "text",
300
- "aria-roledescription": "numberfield",
301
- "aria-valuemin": prop("min"),
302
- "aria-valuemax": prop("max"),
303
- "aria-valuenow": Number.isNaN(computed("valueAsNumber")) ? void 0 : computed("valueAsNumber"),
304
- "aria-valuetext": computed("valueText"),
305
- "data-scrubbing": dataAttr(scrubbing),
306
- onFocus() {
307
- send({ type: "INPUT.FOCUS" });
308
- },
309
- onBlur() {
310
- send({ type: "INPUT.BLUR" });
311
- },
312
- onInput(event) {
313
- const selection = recordCursor(event.currentTarget, scope);
314
- send({ type: "INPUT.CHANGE", target: event.currentTarget, hint: "set", selection });
315
- },
316
- onBeforeInput(event) {
317
- try {
318
- const { selectionStart, selectionEnd, value } = event.currentTarget;
319
- const nextValue = value.slice(0, selectionStart) + (event.data ?? "") + value.slice(selectionEnd);
320
- const isValid = computed("parser").isValidPartialNumber(nextValue);
321
- if (!isValid) {
322
- event.preventDefault();
323
- }
324
- } catch {
325
- }
326
- },
327
- onKeyDown(event) {
328
- if (event.defaultPrevented) return;
329
- if (readOnly) return;
330
- if (isComposingEvent(event)) return;
331
- const step = getEventStep(event) * prop("step");
332
- const keyMap = {
333
- ArrowUp() {
334
- send({ type: "INPUT.ARROW_UP", step });
335
- event.preventDefault();
336
- },
337
- ArrowDown() {
338
- send({ type: "INPUT.ARROW_DOWN", step });
339
- event.preventDefault();
340
- },
341
- Home() {
342
- if (isModifierKey(event)) return;
343
- send({ type: "INPUT.HOME" });
344
- event.preventDefault();
345
- },
346
- End() {
347
- if (isModifierKey(event)) return;
348
- send({ type: "INPUT.END" });
349
- event.preventDefault();
350
- },
351
- Enter(event2) {
352
- const selection = recordCursor(event2.currentTarget, scope);
353
- send({ type: "INPUT.ENTER", selection });
354
- }
355
- };
356
- const exec = keyMap[event.key];
357
- exec?.(event);
358
- }
359
- });
360
- },
361
- getDecrementTriggerProps() {
362
- return normalize.button({
363
- ...parts.decrementTrigger.attrs,
364
- dir: prop("dir"),
365
- id: getDecrementTriggerId(scope),
366
- disabled: isDecrementDisabled,
367
- "data-disabled": dataAttr(isDecrementDisabled),
368
- "aria-label": translations.decrementLabel,
369
- type: "button",
370
- tabIndex: -1,
371
- "aria-controls": getInputId(scope),
372
- "data-scrubbing": dataAttr(scrubbing),
373
- onPointerDown(event) {
374
- if (isDecrementDisabled) return;
375
- if (!isLeftClick(event)) return;
376
- send({ type: "TRIGGER.PRESS_DOWN", hint: "decrement", pointerType: event.pointerType });
377
- if (event.pointerType === "mouse") {
378
- event.preventDefault();
379
- }
380
- if (event.pointerType === "touch") {
381
- event.currentTarget?.focus({ preventScroll: true });
382
- }
383
- },
384
- onPointerUp(event) {
385
- send({ type: "TRIGGER.PRESS_UP", hint: "decrement", pointerType: event.pointerType });
386
- },
387
- onPointerLeave() {
388
- if (isDecrementDisabled) return;
389
- send({ type: "TRIGGER.PRESS_UP", hint: "decrement" });
390
- }
391
- });
392
- },
393
- getIncrementTriggerProps() {
394
- return normalize.button({
395
- ...parts.incrementTrigger.attrs,
396
- dir: prop("dir"),
397
- id: getIncrementTriggerId(scope),
398
- disabled: isIncrementDisabled,
399
- "data-disabled": dataAttr(isIncrementDisabled),
400
- "aria-label": translations.incrementLabel,
401
- type: "button",
402
- tabIndex: -1,
403
- "aria-controls": getInputId(scope),
404
- "data-scrubbing": dataAttr(scrubbing),
405
- onPointerDown(event) {
406
- if (isIncrementDisabled || !isLeftClick(event)) return;
407
- send({ type: "TRIGGER.PRESS_DOWN", hint: "increment", pointerType: event.pointerType });
408
- if (event.pointerType === "mouse") {
409
- event.preventDefault();
410
- }
411
- if (event.pointerType === "touch") {
412
- event.currentTarget?.focus({ preventScroll: true });
413
- }
414
- },
415
- onPointerUp(event) {
416
- send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType });
417
- },
418
- onPointerLeave(event) {
419
- send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType });
420
- }
421
- });
422
- },
423
- getScrubberProps() {
424
- return normalize.element({
425
- ...parts.scrubber.attrs,
426
- dir: prop("dir"),
427
- "data-disabled": dataAttr(disabled),
428
- id: getScrubberId(scope),
429
- role: "presentation",
430
- "data-scrubbing": dataAttr(scrubbing),
431
- onMouseDown(event) {
432
- if (disabled) return;
433
- if (!isLeftClick(event)) return;
434
- const point = getEventPoint(event);
435
- const win = getWindow(event.currentTarget);
436
- const dpr = win.devicePixelRatio;
437
- point.x = point.x - roundToDpr(7.5, dpr);
438
- point.y = point.y - roundToDpr(7.5, dpr);
439
- send({ type: "SCRUBBER.PRESS_DOWN", point });
440
- event.preventDefault();
441
- raf(() => {
442
- setCaretToEnd(getInputEl(scope));
443
- });
444
- },
445
- style: {
446
- cursor: disabled ? void 0 : "ew-resize"
447
- }
448
- });
449
- }
450
- };
451
- }
452
- var createFormatter = (locale, options = {}) => {
453
- return new Intl.NumberFormat(locale, options);
454
- };
455
- var createParser = (locale, options = {}) => {
456
- return new NumberParser(locale, options);
457
- };
458
- var parseValue = (value, params) => {
459
- const { prop, computed } = params;
460
- if (!prop("formatOptions")) return parseFloat(value);
461
- if (value === "") return Number.NaN;
462
- return computed("parser").parse(value);
463
- };
464
- var formatValue = (value, params) => {
465
- const { prop, computed } = params;
466
- if (Number.isNaN(value)) return "";
467
- if (!prop("formatOptions")) return value.toString();
468
- return computed("formatter").format(value);
469
- };
470
- var getDefaultStep = (step, formatOptions) => {
471
- let defaultStep = step !== void 0 && !Number.isNaN(step) ? step : 1;
472
- if (formatOptions?.style === "percent" && (step === void 0 || Number.isNaN(step))) {
473
- defaultStep = 0.01;
474
- }
475
- return defaultStep;
476
- };
477
-
478
- // src/number-input.machine.ts
479
- var { choose, guards, createMachine } = setup();
480
- var { not, and } = guards;
481
- var machine = createMachine({
482
- props({ props: props2 }) {
483
- const step = getDefaultStep(props2.step, props2.formatOptions);
484
- return {
485
- dir: "ltr",
486
- locale: "en-US",
487
- focusInputOnChange: true,
488
- clampValueOnBlur: !props2.allowOverflow,
489
- allowOverflow: false,
490
- inputMode: "decimal",
491
- pattern: "-?[0-9]*(.[0-9]+)?",
492
- defaultValue: "",
493
- step,
494
- min: Number.MIN_SAFE_INTEGER,
495
- max: Number.MAX_SAFE_INTEGER,
496
- spinOnPress: true,
497
- ...props2,
498
- translations: {
499
- incrementLabel: "increment value",
500
- decrementLabel: "decrease value",
501
- ...props2.translations
502
- }
503
- };
504
- },
505
- initialState() {
506
- return "idle";
507
- },
508
- context({ prop, bindable, getComputed }) {
509
- return {
510
- value: bindable(() => ({
511
- defaultValue: prop("defaultValue"),
512
- value: prop("value"),
513
- onChange(value) {
514
- const computed = getComputed();
515
- const valueAsNumber = parseValue(value, { computed, prop });
516
- prop("onValueChange")?.({ value, valueAsNumber });
517
- }
518
- })),
519
- hint: bindable(() => ({ defaultValue: null })),
520
- scrubberCursorPoint: bindable(() => ({
521
- defaultValue: null,
522
- hash(value) {
523
- return value ? `x:${value.x}, y:${value.y}` : "";
524
- }
525
- })),
526
- fieldsetDisabled: bindable(() => ({ defaultValue: false }))
527
- };
528
- },
529
- computed: {
530
- isRtl: ({ prop }) => prop("dir") === "rtl",
531
- valueAsNumber: ({ context, computed, prop }) => parseValue(context.get("value"), { computed, prop }),
532
- formattedValue: ({ computed, prop }) => formatValue(computed("valueAsNumber"), { computed, prop }),
533
- isAtMin: ({ computed, prop }) => isValueAtMin(computed("valueAsNumber"), prop("min")),
534
- isAtMax: ({ computed, prop }) => isValueAtMax(computed("valueAsNumber"), prop("max")),
535
- isOutOfRange: ({ computed, prop }) => !isValueWithinRange(computed("valueAsNumber"), prop("min"), prop("max")),
536
- isValueEmpty: ({ context }) => context.get("value") === "",
537
- isDisabled: ({ prop, context }) => !!prop("disabled") || context.get("fieldsetDisabled"),
538
- canIncrement: ({ prop, computed }) => prop("allowOverflow") || !computed("isAtMax"),
539
- canDecrement: ({ prop, computed }) => prop("allowOverflow") || !computed("isAtMin"),
540
- valueText: ({ prop, context }) => prop("translations").valueText?.(context.get("value")),
541
- formatter: memo(
542
- ({ prop }) => [prop("locale"), prop("formatOptions")],
543
- ([locale, formatOptions]) => createFormatter(locale, formatOptions)
544
- ),
545
- parser: memo(
546
- ({ prop }) => [prop("locale"), prop("formatOptions")],
547
- ([locale, formatOptions]) => createParser(locale, formatOptions)
548
- )
549
- },
550
- watch({ track, action, context, computed, prop }) {
551
- track([() => context.get("value"), () => prop("locale"), () => JSON.stringify(prop("formatOptions"))], () => {
552
- action(["syncInputElement"]);
553
- });
554
- track([() => computed("isOutOfRange")], () => {
555
- action(["invokeOnInvalid"]);
556
- });
557
- track([() => context.hash("scrubberCursorPoint")], () => {
558
- action(["setVirtualCursorPosition"]);
559
- });
560
- },
561
- effects: ["trackFormControl"],
562
- on: {
563
- "VALUE.SET": {
564
- actions: ["setRawValue"]
565
- },
566
- "VALUE.CLEAR": {
567
- actions: ["clearValue"]
568
- },
569
- "VALUE.INCREMENT": {
570
- actions: ["increment"]
571
- },
572
- "VALUE.DECREMENT": {
573
- actions: ["decrement"]
574
- }
575
- },
576
- states: {
577
- idle: {
578
- on: {
579
- "TRIGGER.PRESS_DOWN": [
580
- { guard: "isTouchPointer", target: "before:spin", actions: ["setHint"] },
581
- {
582
- target: "before:spin",
583
- actions: ["focusInput", "invokeOnFocus", "setHint"]
584
- }
585
- ],
586
- "SCRUBBER.PRESS_DOWN": {
587
- target: "scrubbing",
588
- actions: ["focusInput", "invokeOnFocus", "setHint", "setCursorPoint"]
589
- },
590
- "INPUT.FOCUS": {
591
- target: "focused",
592
- actions: ["focusInput", "invokeOnFocus"]
593
- }
594
- }
595
- },
596
- focused: {
597
- tags: ["focus"],
598
- effects: ["attachWheelListener"],
599
- on: {
600
- "TRIGGER.PRESS_DOWN": [
601
- { guard: "isTouchPointer", target: "before:spin", actions: ["setHint"] },
602
- { target: "before:spin", actions: ["focusInput", "setHint"] }
603
- ],
604
- "SCRUBBER.PRESS_DOWN": {
605
- target: "scrubbing",
606
- actions: ["focusInput", "setHint", "setCursorPoint"]
607
- },
608
- "INPUT.ARROW_UP": {
609
- actions: ["increment"]
610
- },
611
- "INPUT.ARROW_DOWN": {
612
- actions: ["decrement"]
613
- },
614
- "INPUT.HOME": {
615
- actions: ["decrementToMin"]
616
- },
617
- "INPUT.END": {
618
- actions: ["incrementToMax"]
619
- },
620
- "INPUT.CHANGE": {
621
- actions: ["setValue", "setHint"]
622
- },
623
- "INPUT.BLUR": [
624
- {
625
- guard: and("clampValueOnBlur", not("isInRange")),
626
- target: "idle",
627
- actions: ["setClampedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
628
- },
629
- {
630
- guard: not("isInRange"),
631
- target: "idle",
632
- actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnInvalid", "invokeOnValueCommit"]
633
- },
634
- {
635
- target: "idle",
636
- actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
637
- }
638
- ],
639
- "INPUT.ENTER": {
640
- actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
641
- }
642
- }
643
- },
644
- "before:spin": {
645
- tags: ["focus"],
646
- effects: ["trackButtonDisabled", "waitForChangeDelay"],
647
- entry: choose([
648
- { guard: "isIncrementHint", actions: ["increment"] },
649
- { guard: "isDecrementHint", actions: ["decrement"] }
650
- ]),
651
- on: {
652
- CHANGE_DELAY: {
653
- target: "spinning",
654
- guard: and("isInRange", "spinOnPress")
655
- },
656
- "TRIGGER.PRESS_UP": [
657
- { guard: "isTouchPointer", target: "focused", actions: ["clearHint"] },
658
- { target: "focused", actions: ["focusInput", "clearHint"] }
659
- ]
660
- }
661
- },
662
- spinning: {
663
- tags: ["focus"],
664
- effects: ["trackButtonDisabled", "spinValue"],
665
- on: {
666
- SPIN: [
667
- {
668
- guard: "isIncrementHint",
669
- actions: ["increment"]
670
- },
671
- {
672
- guard: "isDecrementHint",
673
- actions: ["decrement"]
674
- }
675
- ],
676
- "TRIGGER.PRESS_UP": {
677
- target: "focused",
678
- actions: ["focusInput", "clearHint"]
679
- }
680
- }
681
- },
682
- scrubbing: {
683
- tags: ["focus"],
684
- effects: ["activatePointerLock", "trackMousemove", "setupVirtualCursor", "preventTextSelection"],
685
- on: {
686
- "SCRUBBER.POINTER_UP": {
687
- target: "focused",
688
- actions: ["focusInput", "clearCursorPoint"]
689
- },
690
- "SCRUBBER.POINTER_MOVE": [
691
- {
692
- guard: "isIncrementHint",
693
- actions: ["increment", "setCursorPoint"]
694
- },
695
- {
696
- guard: "isDecrementHint",
697
- actions: ["decrement", "setCursorPoint"]
698
- }
699
- ]
700
- }
701
- }
702
- },
703
- implementations: {
704
- guards: {
705
- clampValueOnBlur: ({ prop }) => prop("clampValueOnBlur"),
706
- spinOnPress: ({ prop }) => !!prop("spinOnPress"),
707
- isInRange: ({ computed }) => !computed("isOutOfRange"),
708
- isDecrementHint: ({ context, event }) => (event.hint ?? context.get("hint")) === "decrement",
709
- isIncrementHint: ({ context, event }) => (event.hint ?? context.get("hint")) === "increment",
710
- isTouchPointer: ({ event }) => event.pointerType === "touch"
711
- },
712
- effects: {
713
- waitForChangeDelay({ send }) {
714
- const id = setTimeout(() => {
715
- send({ type: "CHANGE_DELAY" });
716
- }, 300);
717
- return () => clearTimeout(id);
718
- },
719
- spinValue({ send }) {
720
- const id = setInterval(() => {
721
- send({ type: "SPIN" });
722
- }, 50);
723
- return () => clearInterval(id);
724
- },
725
- trackFormControl({ context, scope }) {
726
- const inputEl = getInputEl(scope);
727
- return trackFormControl(inputEl, {
728
- onFieldsetDisabledChange(disabled) {
729
- context.set("fieldsetDisabled", disabled);
730
- },
731
- onFormReset() {
732
- context.set("value", context.initial("value"));
733
- }
734
- });
735
- },
736
- setupVirtualCursor({ context, scope }) {
737
- const point = context.get("scrubberCursorPoint");
738
- return setupVirtualCursor(scope, point);
739
- },
740
- preventTextSelection({ scope }) {
741
- return preventTextSelection(scope);
742
- },
743
- trackButtonDisabled({ context, scope, send }) {
744
- const hint = context.get("hint");
745
- const btn = getPressedTriggerEl(scope, hint);
746
- return observeAttributes(btn, {
747
- attributes: ["disabled"],
748
- callback() {
749
- send({ type: "TRIGGER.PRESS_UP", src: "attr" });
750
- }
751
- });
752
- },
753
- attachWheelListener({ scope, send, prop }) {
754
- const inputEl = getInputEl(scope);
755
- if (!inputEl || !scope.isActiveElement(inputEl) || !prop("allowMouseWheel")) return;
756
- function onWheel(event) {
757
- event.preventDefault();
758
- const dir = Math.sign(event.deltaY) * -1;
759
- if (dir === 1) {
760
- send({ type: "VALUE.INCREMENT" });
761
- } else if (dir === -1) {
762
- send({ type: "VALUE.DECREMENT" });
763
- }
764
- }
765
- return addDomEvent(inputEl, "wheel", onWheel, { passive: false });
766
- },
767
- activatePointerLock({ scope }) {
768
- if (isSafari()) return;
769
- return requestPointerLock(scope.getDoc());
770
- },
771
- trackMousemove({ scope, send, context, computed }) {
772
- const doc = scope.getDoc();
773
- function onMousemove(event) {
774
- const point = context.get("scrubberCursorPoint");
775
- const isRtl = computed("isRtl");
776
- const value = getMousemoveValue(scope, { point, isRtl, event });
777
- if (!value.hint) return;
778
- send({
779
- type: "SCRUBBER.POINTER_MOVE",
780
- hint: value.hint,
781
- point: value.point
782
- });
783
- }
784
- function onMouseup() {
785
- send({ type: "SCRUBBER.POINTER_UP" });
786
- }
787
- return callAll(addDomEvent(doc, "mousemove", onMousemove, false), addDomEvent(doc, "mouseup", onMouseup, false));
788
- }
789
- },
790
- actions: {
791
- focusInput({ scope, prop }) {
792
- if (!prop("focusInputOnChange")) return;
793
- const inputEl = getInputEl(scope);
794
- if (scope.isActiveElement(inputEl)) return;
795
- raf(() => inputEl?.focus({ preventScroll: true }));
796
- },
797
- increment({ context, event, prop, computed }) {
798
- let nextValue = incrementValue(computed("valueAsNumber"), event.step ?? prop("step"));
799
- if (!prop("allowOverflow")) nextValue = clampValue(nextValue, prop("min"), prop("max"));
800
- context.set("value", formatValue(nextValue, { computed, prop }));
801
- },
802
- decrement({ context, event, prop, computed }) {
803
- let nextValue = decrementValue(computed("valueAsNumber"), event.step ?? prop("step"));
804
- if (!prop("allowOverflow")) nextValue = clampValue(nextValue, prop("min"), prop("max"));
805
- context.set("value", formatValue(nextValue, { computed, prop }));
806
- },
807
- setClampedValue({ context, prop, computed }) {
808
- const nextValue = clampValue(computed("valueAsNumber"), prop("min"), prop("max"));
809
- context.set("value", formatValue(nextValue, { computed, prop }));
810
- },
811
- setRawValue({ context, event, prop, computed }) {
812
- let nextValue = parseValue(event.value, { computed, prop });
813
- if (!prop("allowOverflow")) nextValue = clampValue(nextValue, prop("min"), prop("max"));
814
- context.set("value", formatValue(nextValue, { computed, prop }));
815
- },
816
- setValue({ context, event }) {
817
- const value = event.target?.value ?? event.value;
818
- context.set("value", value);
819
- },
820
- clearValue({ context }) {
821
- context.set("value", "");
822
- },
823
- incrementToMax({ context, prop, computed }) {
824
- const value = formatValue(prop("max"), { computed, prop });
825
- context.set("value", value);
826
- },
827
- decrementToMin({ context, prop, computed }) {
828
- const value = formatValue(prop("min"), { computed, prop });
829
- context.set("value", value);
830
- },
831
- setHint({ context, event }) {
832
- context.set("hint", event.hint);
833
- },
834
- clearHint({ context }) {
835
- context.set("hint", null);
836
- },
837
- invokeOnFocus({ computed, prop }) {
838
- prop("onFocusChange")?.({
839
- focused: true,
840
- value: computed("formattedValue"),
841
- valueAsNumber: computed("valueAsNumber")
842
- });
843
- },
844
- invokeOnBlur({ computed, prop }) {
845
- prop("onFocusChange")?.({
846
- focused: false,
847
- value: computed("formattedValue"),
848
- valueAsNumber: computed("valueAsNumber")
849
- });
850
- },
851
- invokeOnInvalid({ computed, prop, event }) {
852
- if (event.type === "INPUT.CHANGE") return;
853
- const reason = computed("valueAsNumber") > prop("max") ? "rangeOverflow" : "rangeUnderflow";
854
- prop("onValueInvalid")?.({
855
- reason,
856
- value: computed("formattedValue"),
857
- valueAsNumber: computed("valueAsNumber")
858
- });
859
- },
860
- invokeOnValueCommit({ computed, prop }) {
861
- prop("onValueCommit")?.({
862
- value: computed("formattedValue"),
863
- valueAsNumber: computed("valueAsNumber")
864
- });
865
- },
866
- syncInputElement({ context, event, computed, scope }) {
867
- const value = event.type.endsWith("CHANGE") ? context.get("value") : computed("formattedValue");
868
- const inputEl = getInputEl(scope);
869
- const sel = event.selection ?? recordCursor(inputEl, scope);
870
- raf(() => {
871
- setElementValue(inputEl, value);
872
- restoreCursor(inputEl, sel, scope);
873
- });
874
- },
875
- setFormattedValue({ context, computed, action }) {
876
- context.set("value", computed("formattedValue"));
877
- action(["syncInputElement"]);
878
- },
879
- setCursorPoint({ context, event }) {
880
- context.set("scrubberCursorPoint", event.point);
881
- },
882
- clearCursorPoint({ context }) {
883
- context.set("scrubberCursorPoint", null);
884
- },
885
- setVirtualCursorPosition({ context, scope }) {
886
- const cursorEl = getCursorEl(scope);
887
- const point = context.get("scrubberCursorPoint");
888
- if (!cursorEl || !point) return;
889
- cursorEl.style.transform = `translate3d(${point.x}px, ${point.y}px, 0px)`;
890
- }
891
- }
892
- }
893
- });
894
- var props = createProps()([
895
- "allowMouseWheel",
896
- "allowOverflow",
897
- "clampValueOnBlur",
898
- "dir",
899
- "disabled",
900
- "focusInputOnChange",
901
- "form",
902
- "formatOptions",
903
- "getRootNode",
904
- "id",
905
- "ids",
906
- "inputMode",
907
- "invalid",
908
- "locale",
909
- "max",
910
- "min",
911
- "name",
912
- "onFocusChange",
913
- "onValueChange",
914
- "onValueCommit",
915
- "onValueInvalid",
916
- "pattern",
917
- "required",
918
- "readOnly",
919
- "spinOnPress",
920
- "step",
921
- "translations",
922
- "value",
923
- "defaultValue"
924
- ]);
925
- var splitProps = createSplitProps(props);
926
-
927
- export { anatomy, connect, machine, props, splitProps };