@zag-js/number-input 2.0.0-next.0 → 2.0.0-next.2

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.
@@ -38,12 +38,13 @@ var import_dom_query = require("@zag-js/dom-query");
38
38
  var import_utils = require("@zag-js/utils");
39
39
  var import_cursor = require("./cursor.js");
40
40
  var dom = __toESM(require("./number-input.dom.js"));
41
- var import_number_input = require("./number-input.utils.js");
41
+ var import_number_input = require("./number-input.translations.js");
42
+ var import_number_input2 = require("./number-input.utils.js");
42
43
  var { choose, guards, createMachine } = (0, import_core.setup)();
43
44
  var { not, and } = guards;
44
45
  var machine = createMachine({
45
46
  props({ props }) {
46
- const step = (0, import_number_input.getDefaultStep)(props.step, props.formatOptions);
47
+ const step = (0, import_number_input2.getDefaultStep)(props.step, props.formatOptions);
47
48
  return {
48
49
  dir: "ltr",
49
50
  locale: "en-US",
@@ -54,8 +55,6 @@ var machine = createMachine({
54
55
  pattern: "-?[0-9]*(.[0-9]+)?",
55
56
  defaultValue: "",
56
57
  step,
57
- largeStep: props.largeStep ?? step * 10,
58
- smallStep: props.smallStep ?? step * 0.1,
59
58
  min: Number.MIN_SAFE_INTEGER,
60
59
  max: Number.MAX_SAFE_INTEGER,
61
60
  spinOnPress: true,
@@ -63,25 +62,22 @@ var machine = createMachine({
63
62
  scrubberDirection: "horizontal",
64
63
  snapOnStep: false,
65
64
  ...props,
66
- translations: {
67
- incrementLabel: "increment value",
68
- decrementLabel: "decrease value",
69
- ...props.translations
70
- }
65
+ largeStep: props.largeStep ?? 10 * step,
66
+ smallStep: props.smallStep ?? step / 10
71
67
  };
72
68
  },
73
69
  initialState() {
74
70
  return "idle";
75
71
  },
76
- context({ prop, bindable, getComputed }) {
72
+ context({ prop, bindable, getComputed, getEvent }) {
77
73
  return {
78
74
  value: bindable(() => ({
79
75
  defaultValue: prop("defaultValue"),
80
76
  value: prop("value"),
81
77
  onChange(value) {
82
78
  const computed = getComputed();
83
- const valueAsNumber = (0, import_number_input.parseValue)(value, { computed, prop });
84
- prop("onValueChange")?.({ value, valueAsNumber });
79
+ const valueAsNumber = (0, import_number_input2.parseValue)(value, { computed, prop });
80
+ prop("onValueChange")?.({ value, valueAsNumber, reason: getEvent().src });
85
81
  }
86
82
  })),
87
83
  hint: bindable(() => ({ defaultValue: null })),
@@ -99,8 +95,8 @@ var machine = createMachine({
99
95
  },
100
96
  computed: {
101
97
  isRtl: ({ prop }) => prop("dir") === "rtl",
102
- valueAsNumber: ({ context, computed, prop }) => (0, import_number_input.parseValue)(context.get("value"), { computed, prop }),
103
- formattedValue: ({ computed, prop }) => (0, import_number_input.formatValue)(computed("valueAsNumber"), { computed, prop }),
98
+ valueAsNumber: ({ context, computed, prop }) => (0, import_number_input2.parseValue)(context.get("value"), { computed, prop }),
99
+ formattedValue: ({ computed, prop }) => (0, import_number_input2.formatValue)(computed("valueAsNumber"), { computed, prop }),
104
100
  isAtMin: ({ computed, prop }) => (0, import_utils.isValueAtMin)(computed("valueAsNumber"), prop("min")),
105
101
  isAtMax: ({ computed, prop }) => (0, import_utils.isValueAtMax)(computed("valueAsNumber"), prop("max")),
106
102
  isOutOfRange: ({ computed, prop }) => !(0, import_utils.isValueWithinRange)(computed("valueAsNumber"), prop("min"), prop("max")),
@@ -108,14 +104,22 @@ var machine = createMachine({
108
104
  isDisabled: ({ prop, context }) => !!prop("disabled") || context.get("fieldsetDisabled"),
109
105
  canIncrement: ({ prop, computed }) => prop("allowOverflow") || !computed("isAtMax"),
110
106
  canDecrement: ({ prop, computed }) => prop("allowOverflow") || !computed("isAtMin"),
111
- valueText: ({ prop, context }) => prop("translations").valueText?.(context.get("value")),
107
+ // Only useful when the display differs from `aria-valuenow`, as with currency or percent.
108
+ valueText: ({ prop, context, computed }) => {
109
+ const translations = (0, import_utils.mergeWithDefault)(import_number_input.defaultTranslations, prop("translations"));
110
+ const custom = translations.valueText?.(context.get("value"));
111
+ if (custom) return custom;
112
+ if (computed("isValueEmpty")) return void 0;
113
+ const formatted = computed("formattedValue");
114
+ return formatted === String(computed("valueAsNumber")) ? void 0 : formatted;
115
+ },
112
116
  formatter: (0, import_core.memo)(
113
117
  ({ prop }) => [prop("locale"), prop("formatOptions")],
114
- ([locale, formatOptions]) => (0, import_number_input.createFormatter)(locale, formatOptions)
118
+ ([locale, formatOptions]) => (0, import_number_input2.createFormatter)(locale, formatOptions)
115
119
  ),
116
120
  parser: (0, import_core.memo)(
117
121
  ({ prop }) => [prop("locale"), prop("formatOptions")],
118
- ([locale, formatOptions]) => (0, import_number_input.createParser)(locale, formatOptions)
122
+ ([locale, formatOptions]) => (0, import_number_input2.createParser)(locale, formatOptions)
119
123
  )
120
124
  },
121
125
  watch({ track, action, context, computed, prop }) {
@@ -123,6 +127,7 @@ var machine = createMachine({
123
127
  action(["syncInputElement"]);
124
128
  });
125
129
  track([() => computed("isOutOfRange")], () => {
130
+ if (!computed("isOutOfRange")) return;
126
131
  action(["invokeOnInvalid"]);
127
132
  });
128
133
  track([() => context.hash("scrubberCursorPoint")], () => {
@@ -131,32 +136,35 @@ var machine = createMachine({
131
136
  },
132
137
  effects: ["trackFormControl", "detectPointerLock"],
133
138
  on: {
139
+ "INPUT.FOCUS": {
140
+ actions: ["invokeOnFocus"]
141
+ },
134
142
  "VALUE.SET": {
135
- actions: ["setRawValue"]
143
+ actions: ["setRawValue", "invokeOnValueCommit"]
136
144
  },
137
145
  "VALUE.CLEAR": {
138
- actions: ["clearValue"]
146
+ actions: ["clearValue", "invokeOnValueCommit"]
139
147
  },
140
148
  "VALUE.INCREMENT": {
141
- actions: ["increment"]
149
+ actions: ["increment", "invokeOnValueCommit"]
142
150
  },
143
151
  "VALUE.DECREMENT": {
144
- actions: ["decrement"]
152
+ actions: ["decrement", "invokeOnValueCommit"]
145
153
  }
146
154
  },
147
155
  states: {
148
156
  idle: {
149
157
  on: {
150
158
  "TRIGGER.PRESS_DOWN": [
151
- { guard: "isTouchPointer", target: "before:spin", actions: ["setHint"] },
159
+ { guard: "isTouchPointer", target: "pressed", actions: ["setHint"] },
152
160
  {
153
- target: "before:spin",
154
- actions: ["focusInput", "invokeOnFocus", "setHint"]
161
+ target: "pressed",
162
+ actions: ["focusInput", "setHint"]
155
163
  }
156
164
  ],
157
165
  "SCRUBBER.PRESS_DOWN": {
158
166
  target: "scrubbing",
159
- actions: ["focusInput", "invokeOnFocus", "setHint", "setCursorPoint"]
167
+ actions: ["focusInput", "setHint", "setCursorPoint"]
160
168
  },
161
169
  "INPUT.FOCUS": {
162
170
  target: "focused",
@@ -169,24 +177,24 @@ var machine = createMachine({
169
177
  effects: ["attachWheelListener"],
170
178
  on: {
171
179
  "TRIGGER.PRESS_DOWN": [
172
- { guard: "isTouchPointer", target: "before:spin", actions: ["setHint"] },
173
- { target: "before:spin", actions: ["focusInput", "setHint"] }
180
+ { guard: "isTouchPointer", target: "pressed", actions: ["setHint"] },
181
+ { target: "pressed", actions: ["focusInput", "setHint"] }
174
182
  ],
175
183
  "SCRUBBER.PRESS_DOWN": {
176
184
  target: "scrubbing",
177
185
  actions: ["focusInput", "setHint", "setCursorPoint"]
178
186
  },
179
187
  "INPUT.ARROW_UP": {
180
- actions: ["increment"]
188
+ actions: ["increment", "invokeOnValueCommit"]
181
189
  },
182
190
  "INPUT.ARROW_DOWN": {
183
- actions: ["decrement"]
191
+ actions: ["decrement", "invokeOnValueCommit"]
184
192
  },
185
193
  "INPUT.HOME": {
186
- actions: ["decrementToMin"]
194
+ actions: ["decrementToMin", "invokeOnValueCommit"]
187
195
  },
188
196
  "INPUT.END": {
189
- actions: ["incrementToMax"]
197
+ actions: ["incrementToMax", "invokeOnValueCommit"]
190
198
  },
191
199
  "INPUT.CHANGE": {
192
200
  actions: ["setValue", "setHint"]
@@ -200,41 +208,66 @@ var machine = createMachine({
200
208
  {
201
209
  guard: not("isInRange"),
202
210
  target: "idle",
203
- actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnInvalid", "invokeOnValueCommit"]
211
+ actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
204
212
  },
205
213
  {
206
214
  target: "idle",
207
215
  actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
208
216
  }
209
217
  ],
218
+ // No target, so the input stays focused. It must not report a blur.
210
219
  "INPUT.ENTER": {
211
- actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
220
+ actions: ["setFormattedValue", "clearHint", "invokeOnValueCommit"]
212
221
  }
213
222
  }
214
223
  },
215
- "before:spin": {
224
+ pressed: {
216
225
  tags: ["focus"],
217
- effects: ["trackButtonDisabled", "waitForChangeDelay"],
218
- entry: choose([
219
- { guard: "isIncrementHint", actions: ["increment"] },
220
- { guard: "isDecrementHint", actions: ["decrement"] }
221
- ]),
226
+ initial: "waiting",
227
+ effects: ["trackButtonDisabled", "preventContextMenu"],
222
228
  on: {
223
- CHANGE_DELAY: {
224
- target: "spinning",
225
- guard: and("isInRange", "spinOnPress")
226
- },
227
229
  "TRIGGER.PRESS_UP": [
228
- { guard: "isTouchPointer", target: "focused", actions: ["clearHint"] },
229
- { target: "focused", actions: ["focusInput", "clearHint"] }
230
+ { guard: "isTouchPointer", target: "focused", actions: ["clearHint", "invokeOnValueCommit"] },
231
+ { target: "focused", actions: ["focusInput", "clearHint", "invokeOnValueCommit"] }
230
232
  ]
233
+ },
234
+ states: {
235
+ waiting: {
236
+ effects: ["waitForChangeDelay"],
237
+ entry: choose([
238
+ { guard: "isIncrementHint", actions: ["increment"] },
239
+ { guard: "isDecrementHint", actions: ["decrement"] }
240
+ ]),
241
+ on: {
242
+ CHANGE_DELAY: {
243
+ target: "repeating",
244
+ guard: and("isInRange", "spinOnPress")
245
+ }
246
+ }
247
+ },
248
+ repeating: {
249
+ effects: ["spinValue"],
250
+ on: {
251
+ SPIN: [
252
+ {
253
+ guard: "isIncrementHint",
254
+ actions: ["increment"]
255
+ },
256
+ {
257
+ guard: "isDecrementHint",
258
+ actions: ["decrement"]
259
+ }
260
+ ]
261
+ }
262
+ }
231
263
  }
232
264
  },
233
- spinning: {
265
+ scrubbing: {
234
266
  tags: ["focus"],
235
- effects: ["trackButtonDisabled", "spinValue"],
267
+ effects: ["activatePointerLock", "trackMousemove", "preventTextSelection", "trackVisualViewport"],
268
+ entry: ["clearCumulativeDelta"],
236
269
  on: {
237
- SPIN: [
270
+ "SCRUBBER.STEP": [
238
271
  {
239
272
  guard: "isIncrementHint",
240
273
  actions: ["increment"]
@@ -244,20 +277,9 @@ var machine = createMachine({
244
277
  actions: ["decrement"]
245
278
  }
246
279
  ],
247
- "TRIGGER.PRESS_UP": {
248
- target: "focused",
249
- actions: ["focusInput", "clearHint"]
250
- }
251
- }
252
- },
253
- scrubbing: {
254
- tags: ["focus"],
255
- effects: ["activatePointerLock", "trackMousemove", "preventTextSelection", "trackVisualViewport"],
256
- entry: ["clearCumulativeDelta"],
257
- on: {
258
280
  "SCRUBBER.POINTER_UP": {
259
281
  target: "focused",
260
- actions: ["focusInput", "clearCursorPoint", "clearCumulativeDelta"]
282
+ actions: ["focusInput", "clearCursorPoint", "clearCumulativeDelta", "invokeOnValueCommit"]
261
283
  },
262
284
  "SCRUBBER.POINTER_MOVE": {
263
285
  actions: ["accumulateDelta", "setCursorPoint"]
@@ -282,9 +304,13 @@ var machine = createMachine({
282
304
  }, 300);
283
305
  return () => clearTimeout(id);
284
306
  },
285
- spinValue({ send }) {
307
+ preventContextMenu({ scope }) {
308
+ return (0, import_dom_query.addDomEvent)(scope.getWin(), "contextmenu", (event) => event.preventDefault());
309
+ },
310
+ spinValue({ send, context }) {
286
311
  const id = setInterval(() => {
287
- send({ type: "SPIN" });
312
+ const src = context.get("hint") === "increment" ? "increment-press" : "decrement-press";
313
+ send({ type: "SPIN", src });
288
314
  }, 50);
289
315
  return () => clearInterval(id);
290
316
  },
@@ -311,7 +337,8 @@ var machine = createMachine({
311
337
  return (0, import_dom_query.observeAttributes)(btn, {
312
338
  attributes: ["disabled"],
313
339
  callback() {
314
- send({ type: "TRIGGER.PRESS_UP", src: "attr" });
340
+ const src = hint === "increment" ? "increment-press" : "decrement-press";
341
+ send({ type: "TRIGGER.PRESS_UP", src });
315
342
  }
316
343
  });
317
344
  },
@@ -322,9 +349,9 @@ var machine = createMachine({
322
349
  event.preventDefault();
323
350
  const dir = Math.sign(event.deltaY) * -1;
324
351
  if (dir === 1) {
325
- send({ type: "VALUE.INCREMENT" });
352
+ send({ type: "VALUE.INCREMENT", src: "wheel" });
326
353
  } else if (dir === -1) {
327
- send({ type: "VALUE.DECREMENT" });
354
+ send({ type: "VALUE.DECREMENT", src: "wheel" });
328
355
  }
329
356
  }
330
357
  return (0, import_dom_query.addDomEvent)(inputEl, "wheel", onWheel, { passive: false });
@@ -365,7 +392,7 @@ var machine = createMachine({
365
392
  });
366
393
  }
367
394
  function onMouseup() {
368
- send({ type: "SCRUBBER.POINTER_UP" });
395
+ send({ type: "SCRUBBER.POINTER_UP", src: "scrub" });
369
396
  }
370
397
  return (0, import_utils.callAll)((0, import_dom_query.addDomEvent)(doc, "mousemove", onMousemove, false), (0, import_dom_query.addDomEvent)(doc, "mouseup", onMouseup, false));
371
398
  }
@@ -382,23 +409,23 @@ var machine = createMachine({
382
409
  if (!prop("allowOverflow")) nextValue = (0, import_utils.clampValue)(nextValue, prop("min"), prop("max"));
383
410
  if (prop("snapOnStep"))
384
411
  nextValue = (0, import_utils.snapValueToStep)(nextValue, prop("min"), prop("max"), event.step ?? prop("step"));
385
- context.set("value", (0, import_number_input.formatValue)(nextValue, { computed, prop }));
412
+ context.set("value", (0, import_number_input2.formatValue)(nextValue, { computed, prop }));
386
413
  },
387
414
  decrement({ context, event, prop, computed }) {
388
415
  let nextValue = (0, import_utils.decrementValue)(computed("valueAsNumber"), event.step ?? prop("step"));
389
416
  if (!prop("allowOverflow")) nextValue = (0, import_utils.clampValue)(nextValue, prop("min"), prop("max"));
390
417
  if (prop("snapOnStep"))
391
418
  nextValue = (0, import_utils.snapValueToStep)(nextValue, prop("min"), prop("max"), event.step ?? prop("step"));
392
- context.set("value", (0, import_number_input.formatValue)(nextValue, { computed, prop }));
419
+ context.set("value", (0, import_number_input2.formatValue)(nextValue, { computed, prop }));
393
420
  },
394
421
  setClampedValue({ context, prop, computed }) {
395
422
  const nextValue = (0, import_utils.clampValue)(computed("valueAsNumber"), prop("min"), prop("max"));
396
- context.set("value", (0, import_number_input.formatValue)(nextValue, { computed, prop }));
423
+ context.set("value", (0, import_number_input2.formatValue)(nextValue, { computed, prop }));
397
424
  },
398
425
  setRawValue({ context, event, prop, computed }) {
399
- let nextValue = (0, import_number_input.parseValue)(event.value, { computed, prop });
426
+ let nextValue = typeof event.value === "number" ? event.value : (0, import_number_input2.parseValue)(event.value, { computed, prop });
400
427
  if (!prop("allowOverflow")) nextValue = (0, import_utils.clampValue)(nextValue, prop("min"), prop("max"));
401
- context.set("value", (0, import_number_input.formatValue)(nextValue, { computed, prop }));
428
+ context.set("value", (0, import_number_input2.formatValue)(nextValue, { computed, prop }));
402
429
  },
403
430
  setValue({ context, event }) {
404
431
  const value = event.target?.value ?? event.value;
@@ -408,11 +435,11 @@ var machine = createMachine({
408
435
  context.set("value", "");
409
436
  },
410
437
  incrementToMax({ context, prop, computed }) {
411
- const value = (0, import_number_input.formatValue)(prop("max"), { computed, prop });
438
+ const value = (0, import_number_input2.formatValue)(prop("max"), { computed, prop });
412
439
  context.set("value", value);
413
440
  },
414
441
  decrementToMin({ context, prop, computed }) {
415
- const value = (0, import_number_input.formatValue)(prop("min"), { computed, prop });
442
+ const value = (0, import_number_input2.formatValue)(prop("min"), { computed, prop });
416
443
  context.set("value", value);
417
444
  },
418
445
  setHint({ context, event }) {
@@ -435,8 +462,7 @@ var machine = createMachine({
435
462
  valueAsNumber: computed("valueAsNumber")
436
463
  });
437
464
  },
438
- invokeOnInvalid({ computed, prop, event }) {
439
- if (event.type === "INPUT.CHANGE") return;
465
+ invokeOnInvalid({ computed, prop }) {
440
466
  const reason = computed("valueAsNumber") > prop("max") ? "rangeOverflow" : "rangeUnderflow";
441
467
  prop("onValueInvalid")?.({
442
468
  reason,
@@ -444,10 +470,11 @@ var machine = createMachine({
444
470
  valueAsNumber: computed("valueAsNumber")
445
471
  });
446
472
  },
447
- invokeOnValueCommit({ computed, prop }) {
473
+ invokeOnValueCommit({ computed, prop, event }) {
448
474
  prop("onValueCommit")?.({
449
475
  value: computed("formattedValue"),
450
- valueAsNumber: computed("valueAsNumber")
476
+ valueAsNumber: computed("valueAsNumber"),
477
+ reason: event.src
451
478
  });
452
479
  },
453
480
  syncInputElement({ context, event, computed, scope }) {
@@ -483,10 +510,8 @@ var machine = createMachine({
483
510
  if (Math.abs(newDelta) >= sensitivity) {
484
511
  const step = prop("step");
485
512
  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 });
513
+ if (hint === "increment" || hint === "decrement") {
514
+ send({ type: "SCRUBBER.STEP", step, hint, src: "scrub" });
490
515
  }
491
516
  context.set("cumulativeDelta", newDelta % sensitivity);
492
517
  } else {