@zag-js/number-input 2.0.0-next.1 → 2.0.0-next.3

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,6 +38,7 @@ var import_utils = require("@zag-js/utils");
38
38
  var import_cursor = require("./cursor.js");
39
39
  var import_number_input = require("./number-input.anatomy.js");
40
40
  var dom = __toESM(require("./number-input.dom.js"));
41
+ var import_number_input2 = require("./number-input.translations.js");
41
42
  function connect(service, normalize) {
42
43
  const { state, send, prop, scope, computed, context } = service;
43
44
  const focused = state.hasTag("focus");
@@ -49,7 +50,7 @@ function connect(service, normalize) {
49
50
  const invalid = prop("invalid") !== void 0 ? !!prop("invalid") : computed("isOutOfRange");
50
51
  const isIncrementDisabled = disabled || !computed("canIncrement") || readOnly;
51
52
  const isDecrementDisabled = disabled || !computed("canDecrement") || readOnly;
52
- const translations = prop("translations");
53
+ const translations = (0, import_utils.mergeWithDefault)(import_number_input2.defaultTranslations, prop("translations"));
53
54
  function getRootState() {
54
55
  return { disabled, focused, invalid, scrubbing };
55
56
  }
@@ -70,22 +71,22 @@ function connect(service, normalize) {
70
71
  value: computed("formattedValue"),
71
72
  valueAsNumber: computed("valueAsNumber"),
72
73
  setValue(value) {
73
- send({ type: "VALUE.SET", value });
74
+ send({ type: "VALUE.SET", value, src: "script" });
74
75
  },
75
76
  clearValue() {
76
- send({ type: "VALUE.CLEAR" });
77
+ send({ type: "VALUE.CLEAR", src: "script" });
77
78
  },
78
79
  increment() {
79
- send({ type: "VALUE.INCREMENT" });
80
+ send({ type: "VALUE.INCREMENT", src: "script" });
80
81
  },
81
82
  decrement() {
82
- send({ type: "VALUE.DECREMENT" });
83
+ send({ type: "VALUE.DECREMENT", src: "script" });
83
84
  },
84
85
  setToMax() {
85
- send({ type: "VALUE.SET", value: prop("max") });
86
+ send({ type: "VALUE.SET", value: prop("max"), src: "script" });
86
87
  },
87
88
  setToMin() {
88
- send({ type: "VALUE.SET", value: prop("min") });
89
+ send({ type: "VALUE.SET", value: prop("min"), src: "script" });
89
90
  },
90
91
  focus() {
91
92
  dom.getInputEl(scope)?.focus();
@@ -176,11 +177,11 @@ function connect(service, normalize) {
176
177
  send({ type: "INPUT.FOCUS" });
177
178
  },
178
179
  onBlur() {
179
- send({ type: "INPUT.BLUR" });
180
+ send({ type: "INPUT.BLUR", src: "input-blur" });
180
181
  },
181
182
  onInput(event) {
182
183
  const selection = (0, import_cursor.recordCursor)(event.currentTarget, scope);
183
- send({ type: "INPUT.CHANGE", target: event.currentTarget, hint: "set", selection });
184
+ send({ type: "INPUT.CHANGE", target: event.currentTarget, hint: "set", selection, src: "input-change" });
184
185
  },
185
186
  onBeforeInput(event) {
186
187
  try {
@@ -204,30 +205,40 @@ function connect(service, normalize) {
204
205
  });
205
206
  const keyMap = {
206
207
  ArrowUp() {
207
- send({ type: "INPUT.ARROW_UP", step });
208
+ send({ type: "INPUT.ARROW_UP", step, src: "keyboard" });
208
209
  event.preventDefault();
209
210
  event.stopPropagation();
210
211
  },
211
212
  ArrowDown() {
212
- send({ type: "INPUT.ARROW_DOWN", step });
213
+ send({ type: "INPUT.ARROW_DOWN", step, src: "keyboard" });
214
+ event.preventDefault();
215
+ event.stopPropagation();
216
+ },
217
+ PageUp() {
218
+ send({ type: "INPUT.ARROW_UP", step, src: "keyboard" });
219
+ event.preventDefault();
220
+ event.stopPropagation();
221
+ },
222
+ PageDown() {
223
+ send({ type: "INPUT.ARROW_DOWN", step, src: "keyboard" });
213
224
  event.preventDefault();
214
225
  event.stopPropagation();
215
226
  },
216
227
  Home() {
217
228
  if ((0, import_dom_query.isModifierKey)(event)) return;
218
- send({ type: "INPUT.HOME" });
229
+ send({ type: "INPUT.HOME", src: "keyboard" });
219
230
  event.preventDefault();
220
231
  event.stopPropagation();
221
232
  },
222
233
  End() {
223
234
  if ((0, import_dom_query.isModifierKey)(event)) return;
224
- send({ type: "INPUT.END" });
235
+ send({ type: "INPUT.END", src: "keyboard" });
225
236
  event.preventDefault();
226
237
  event.stopPropagation();
227
238
  },
228
239
  Enter(event2) {
229
240
  const selection = (0, import_cursor.recordCursor)(event2.currentTarget, scope);
230
- send({ type: "INPUT.ENTER", selection });
241
+ send({ type: "INPUT.ENTER", selection, src: "keyboard" });
231
242
  }
232
243
  };
233
244
  const exec = keyMap[event.key];
@@ -251,7 +262,12 @@ function connect(service, normalize) {
251
262
  onPointerDown(event) {
252
263
  if (decrementTriggerState.disabled) return;
253
264
  if (!(0, import_dom_query.isLeftClick)(event)) return;
254
- send({ type: "TRIGGER.PRESS_DOWN", hint: "decrement", pointerType: event.pointerType });
265
+ send({
266
+ type: "TRIGGER.PRESS_DOWN",
267
+ hint: "decrement",
268
+ pointerType: event.pointerType,
269
+ src: "decrement-press"
270
+ });
255
271
  if (event.pointerType === "mouse") {
256
272
  event.preventDefault();
257
273
  }
@@ -260,11 +276,14 @@ function connect(service, normalize) {
260
276
  }
261
277
  },
262
278
  onPointerUp(event) {
263
- send({ type: "TRIGGER.PRESS_UP", hint: "decrement", pointerType: event.pointerType });
279
+ send({ type: "TRIGGER.PRESS_UP", hint: "decrement", pointerType: event.pointerType, src: "decrement-press" });
264
280
  },
265
281
  onPointerLeave() {
266
282
  if (decrementTriggerState.disabled) return;
267
- send({ type: "TRIGGER.PRESS_UP", hint: "decrement" });
283
+ send({ type: "TRIGGER.PRESS_UP", hint: "decrement", src: "decrement-press" });
284
+ },
285
+ onPointerCancel(event) {
286
+ send({ type: "TRIGGER.PRESS_UP", hint: "decrement", pointerType: event.pointerType, src: "decrement-press" });
268
287
  }
269
288
  });
270
289
  },
@@ -283,7 +302,12 @@ function connect(service, normalize) {
283
302
  "data-scrubbing": (0, import_dom_query.dataAttr)(scrubbing),
284
303
  onPointerDown(event) {
285
304
  if (incrementTriggerState.disabled || !(0, import_dom_query.isLeftClick)(event)) return;
286
- send({ type: "TRIGGER.PRESS_DOWN", hint: "increment", pointerType: event.pointerType });
305
+ send({
306
+ type: "TRIGGER.PRESS_DOWN",
307
+ hint: "increment",
308
+ pointerType: event.pointerType,
309
+ src: "increment-press"
310
+ });
287
311
  if (event.pointerType === "mouse") {
288
312
  event.preventDefault();
289
313
  }
@@ -292,10 +316,13 @@ function connect(service, normalize) {
292
316
  }
293
317
  },
294
318
  onPointerUp(event) {
295
- send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType });
319
+ send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType, src: "increment-press" });
296
320
  },
297
321
  onPointerLeave(event) {
298
- send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType });
322
+ send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType, src: "increment-press" });
323
+ },
324
+ onPointerCancel(event) {
325
+ send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType, src: "increment-press" });
299
326
  }
300
327
  });
301
328
  },
@@ -12,10 +12,11 @@ import {
12
12
  raf,
13
13
  setCaretToEnd
14
14
  } from "@zag-js/dom-query";
15
- import { roundToDpr, toPx } from "@zag-js/utils";
15
+ import { mergeWithDefault, roundToDpr, toPx } from "@zag-js/utils";
16
16
  import { recordCursor } from "./cursor.mjs";
17
17
  import { parts } from "./number-input.anatomy.mjs";
18
18
  import * as dom from "./number-input.dom.mjs";
19
+ import { defaultTranslations } from "./number-input.translations.mjs";
19
20
  function connect(service, normalize) {
20
21
  const { state, send, prop, scope, computed, context } = service;
21
22
  const focused = state.hasTag("focus");
@@ -27,7 +28,7 @@ function connect(service, normalize) {
27
28
  const invalid = prop("invalid") !== void 0 ? !!prop("invalid") : computed("isOutOfRange");
28
29
  const isIncrementDisabled = disabled || !computed("canIncrement") || readOnly;
29
30
  const isDecrementDisabled = disabled || !computed("canDecrement") || readOnly;
30
- const translations = prop("translations");
31
+ const translations = mergeWithDefault(defaultTranslations, prop("translations"));
31
32
  function getRootState() {
32
33
  return { disabled, focused, invalid, scrubbing };
33
34
  }
@@ -48,22 +49,22 @@ function connect(service, normalize) {
48
49
  value: computed("formattedValue"),
49
50
  valueAsNumber: computed("valueAsNumber"),
50
51
  setValue(value) {
51
- send({ type: "VALUE.SET", value });
52
+ send({ type: "VALUE.SET", value, src: "script" });
52
53
  },
53
54
  clearValue() {
54
- send({ type: "VALUE.CLEAR" });
55
+ send({ type: "VALUE.CLEAR", src: "script" });
55
56
  },
56
57
  increment() {
57
- send({ type: "VALUE.INCREMENT" });
58
+ send({ type: "VALUE.INCREMENT", src: "script" });
58
59
  },
59
60
  decrement() {
60
- send({ type: "VALUE.DECREMENT" });
61
+ send({ type: "VALUE.DECREMENT", src: "script" });
61
62
  },
62
63
  setToMax() {
63
- send({ type: "VALUE.SET", value: prop("max") });
64
+ send({ type: "VALUE.SET", value: prop("max"), src: "script" });
64
65
  },
65
66
  setToMin() {
66
- send({ type: "VALUE.SET", value: prop("min") });
67
+ send({ type: "VALUE.SET", value: prop("min"), src: "script" });
67
68
  },
68
69
  focus() {
69
70
  dom.getInputEl(scope)?.focus();
@@ -154,11 +155,11 @@ function connect(service, normalize) {
154
155
  send({ type: "INPUT.FOCUS" });
155
156
  },
156
157
  onBlur() {
157
- send({ type: "INPUT.BLUR" });
158
+ send({ type: "INPUT.BLUR", src: "input-blur" });
158
159
  },
159
160
  onInput(event) {
160
161
  const selection = recordCursor(event.currentTarget, scope);
161
- send({ type: "INPUT.CHANGE", target: event.currentTarget, hint: "set", selection });
162
+ send({ type: "INPUT.CHANGE", target: event.currentTarget, hint: "set", selection, src: "input-change" });
162
163
  },
163
164
  onBeforeInput(event) {
164
165
  try {
@@ -182,30 +183,40 @@ function connect(service, normalize) {
182
183
  });
183
184
  const keyMap = {
184
185
  ArrowUp() {
185
- send({ type: "INPUT.ARROW_UP", step });
186
+ send({ type: "INPUT.ARROW_UP", step, src: "keyboard" });
186
187
  event.preventDefault();
187
188
  event.stopPropagation();
188
189
  },
189
190
  ArrowDown() {
190
- send({ type: "INPUT.ARROW_DOWN", step });
191
+ send({ type: "INPUT.ARROW_DOWN", step, src: "keyboard" });
192
+ event.preventDefault();
193
+ event.stopPropagation();
194
+ },
195
+ PageUp() {
196
+ send({ type: "INPUT.ARROW_UP", step, src: "keyboard" });
197
+ event.preventDefault();
198
+ event.stopPropagation();
199
+ },
200
+ PageDown() {
201
+ send({ type: "INPUT.ARROW_DOWN", step, src: "keyboard" });
191
202
  event.preventDefault();
192
203
  event.stopPropagation();
193
204
  },
194
205
  Home() {
195
206
  if (isModifierKey(event)) return;
196
- send({ type: "INPUT.HOME" });
207
+ send({ type: "INPUT.HOME", src: "keyboard" });
197
208
  event.preventDefault();
198
209
  event.stopPropagation();
199
210
  },
200
211
  End() {
201
212
  if (isModifierKey(event)) return;
202
- send({ type: "INPUT.END" });
213
+ send({ type: "INPUT.END", src: "keyboard" });
203
214
  event.preventDefault();
204
215
  event.stopPropagation();
205
216
  },
206
217
  Enter(event2) {
207
218
  const selection = recordCursor(event2.currentTarget, scope);
208
- send({ type: "INPUT.ENTER", selection });
219
+ send({ type: "INPUT.ENTER", selection, src: "keyboard" });
209
220
  }
210
221
  };
211
222
  const exec = keyMap[event.key];
@@ -229,7 +240,12 @@ function connect(service, normalize) {
229
240
  onPointerDown(event) {
230
241
  if (decrementTriggerState.disabled) return;
231
242
  if (!isLeftClick(event)) return;
232
- send({ type: "TRIGGER.PRESS_DOWN", hint: "decrement", pointerType: event.pointerType });
243
+ send({
244
+ type: "TRIGGER.PRESS_DOWN",
245
+ hint: "decrement",
246
+ pointerType: event.pointerType,
247
+ src: "decrement-press"
248
+ });
233
249
  if (event.pointerType === "mouse") {
234
250
  event.preventDefault();
235
251
  }
@@ -238,11 +254,14 @@ function connect(service, normalize) {
238
254
  }
239
255
  },
240
256
  onPointerUp(event) {
241
- send({ type: "TRIGGER.PRESS_UP", hint: "decrement", pointerType: event.pointerType });
257
+ send({ type: "TRIGGER.PRESS_UP", hint: "decrement", pointerType: event.pointerType, src: "decrement-press" });
242
258
  },
243
259
  onPointerLeave() {
244
260
  if (decrementTriggerState.disabled) return;
245
- send({ type: "TRIGGER.PRESS_UP", hint: "decrement" });
261
+ send({ type: "TRIGGER.PRESS_UP", hint: "decrement", src: "decrement-press" });
262
+ },
263
+ onPointerCancel(event) {
264
+ send({ type: "TRIGGER.PRESS_UP", hint: "decrement", pointerType: event.pointerType, src: "decrement-press" });
246
265
  }
247
266
  });
248
267
  },
@@ -261,7 +280,12 @@ function connect(service, normalize) {
261
280
  "data-scrubbing": dataAttr(scrubbing),
262
281
  onPointerDown(event) {
263
282
  if (incrementTriggerState.disabled || !isLeftClick(event)) return;
264
- send({ type: "TRIGGER.PRESS_DOWN", hint: "increment", pointerType: event.pointerType });
283
+ send({
284
+ type: "TRIGGER.PRESS_DOWN",
285
+ hint: "increment",
286
+ pointerType: event.pointerType,
287
+ src: "increment-press"
288
+ });
265
289
  if (event.pointerType === "mouse") {
266
290
  event.preventDefault();
267
291
  }
@@ -270,10 +294,13 @@ function connect(service, normalize) {
270
294
  }
271
295
  },
272
296
  onPointerUp(event) {
273
- send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType });
297
+ send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType, src: "increment-press" });
274
298
  },
275
299
  onPointerLeave(event) {
276
- send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType });
300
+ send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType, src: "increment-press" });
301
+ },
302
+ onPointerCancel(event) {
303
+ send({ type: "TRIGGER.PRESS_UP", hint: "increment", pointerType: event.pointerType, src: "increment-press" });
277
304
  }
278
305
  });
279
306
  },
@@ -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",
@@ -62,26 +63,21 @@ var machine = createMachine({
62
63
  snapOnStep: false,
63
64
  ...props,
64
65
  largeStep: props.largeStep ?? 10 * step,
65
- smallStep: props.smallStep ?? step / 10,
66
- translations: {
67
- incrementLabel: "increment value",
68
- decrementLabel: "decrease value",
69
- ...props.translations
70
- }
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 = typeof event.value === "number" ? event.value : (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 {
@@ -18,10 +18,12 @@ import {
18
18
  isValueAtMax,
19
19
  isValueAtMin,
20
20
  isValueWithinRange,
21
- snapValueToStep
21
+ snapValueToStep,
22
+ mergeWithDefault
22
23
  } from "@zag-js/utils";
23
24
  import { recordCursor, restoreCursor } from "./cursor.mjs";
24
25
  import * as dom from "./number-input.dom.mjs";
26
+ import { defaultTranslations } from "./number-input.translations.mjs";
25
27
  import { createFormatter, createParser, formatValue, getDefaultStep, parseValue } from "./number-input.utils.mjs";
26
28
  var { choose, guards, createMachine } = setup();
27
29
  var { not, and } = guards;
@@ -46,18 +48,13 @@ var machine = createMachine({
46
48
  snapOnStep: false,
47
49
  ...props,
48
50
  largeStep: props.largeStep ?? 10 * step,
49
- smallStep: props.smallStep ?? step / 10,
50
- translations: {
51
- incrementLabel: "increment value",
52
- decrementLabel: "decrease value",
53
- ...props.translations
54
- }
51
+ smallStep: props.smallStep ?? step / 10
55
52
  };
56
53
  },
57
54
  initialState() {
58
55
  return "idle";
59
56
  },
60
- context({ prop, bindable, getComputed }) {
57
+ context({ prop, bindable, getComputed, getEvent }) {
61
58
  return {
62
59
  value: bindable(() => ({
63
60
  defaultValue: prop("defaultValue"),
@@ -65,7 +62,7 @@ var machine = createMachine({
65
62
  onChange(value) {
66
63
  const computed = getComputed();
67
64
  const valueAsNumber = parseValue(value, { computed, prop });
68
- prop("onValueChange")?.({ value, valueAsNumber });
65
+ prop("onValueChange")?.({ value, valueAsNumber, reason: getEvent().src });
69
66
  }
70
67
  })),
71
68
  hint: bindable(() => ({ defaultValue: null })),
@@ -92,7 +89,15 @@ var machine = createMachine({
92
89
  isDisabled: ({ prop, context }) => !!prop("disabled") || context.get("fieldsetDisabled"),
93
90
  canIncrement: ({ prop, computed }) => prop("allowOverflow") || !computed("isAtMax"),
94
91
  canDecrement: ({ prop, computed }) => prop("allowOverflow") || !computed("isAtMin"),
95
- valueText: ({ prop, context }) => prop("translations").valueText?.(context.get("value")),
92
+ // Only useful when the display differs from `aria-valuenow`, as with currency or percent.
93
+ valueText: ({ prop, context, computed }) => {
94
+ const translations = mergeWithDefault(defaultTranslations, prop("translations"));
95
+ const custom = translations.valueText?.(context.get("value"));
96
+ if (custom) return custom;
97
+ if (computed("isValueEmpty")) return void 0;
98
+ const formatted = computed("formattedValue");
99
+ return formatted === String(computed("valueAsNumber")) ? void 0 : formatted;
100
+ },
96
101
  formatter: memo(
97
102
  ({ prop }) => [prop("locale"), prop("formatOptions")],
98
103
  ([locale, formatOptions]) => createFormatter(locale, formatOptions)
@@ -107,6 +112,7 @@ var machine = createMachine({
107
112
  action(["syncInputElement"]);
108
113
  });
109
114
  track([() => computed("isOutOfRange")], () => {
115
+ if (!computed("isOutOfRange")) return;
110
116
  action(["invokeOnInvalid"]);
111
117
  });
112
118
  track([() => context.hash("scrubberCursorPoint")], () => {
@@ -115,32 +121,35 @@ var machine = createMachine({
115
121
  },
116
122
  effects: ["trackFormControl", "detectPointerLock"],
117
123
  on: {
124
+ "INPUT.FOCUS": {
125
+ actions: ["invokeOnFocus"]
126
+ },
118
127
  "VALUE.SET": {
119
- actions: ["setRawValue"]
128
+ actions: ["setRawValue", "invokeOnValueCommit"]
120
129
  },
121
130
  "VALUE.CLEAR": {
122
- actions: ["clearValue"]
131
+ actions: ["clearValue", "invokeOnValueCommit"]
123
132
  },
124
133
  "VALUE.INCREMENT": {
125
- actions: ["increment"]
134
+ actions: ["increment", "invokeOnValueCommit"]
126
135
  },
127
136
  "VALUE.DECREMENT": {
128
- actions: ["decrement"]
137
+ actions: ["decrement", "invokeOnValueCommit"]
129
138
  }
130
139
  },
131
140
  states: {
132
141
  idle: {
133
142
  on: {
134
143
  "TRIGGER.PRESS_DOWN": [
135
- { guard: "isTouchPointer", target: "before:spin", actions: ["setHint"] },
144
+ { guard: "isTouchPointer", target: "pressed", actions: ["setHint"] },
136
145
  {
137
- target: "before:spin",
138
- actions: ["focusInput", "invokeOnFocus", "setHint"]
146
+ target: "pressed",
147
+ actions: ["focusInput", "setHint"]
139
148
  }
140
149
  ],
141
150
  "SCRUBBER.PRESS_DOWN": {
142
151
  target: "scrubbing",
143
- actions: ["focusInput", "invokeOnFocus", "setHint", "setCursorPoint"]
152
+ actions: ["focusInput", "setHint", "setCursorPoint"]
144
153
  },
145
154
  "INPUT.FOCUS": {
146
155
  target: "focused",
@@ -153,24 +162,24 @@ var machine = createMachine({
153
162
  effects: ["attachWheelListener"],
154
163
  on: {
155
164
  "TRIGGER.PRESS_DOWN": [
156
- { guard: "isTouchPointer", target: "before:spin", actions: ["setHint"] },
157
- { target: "before:spin", actions: ["focusInput", "setHint"] }
165
+ { guard: "isTouchPointer", target: "pressed", actions: ["setHint"] },
166
+ { target: "pressed", actions: ["focusInput", "setHint"] }
158
167
  ],
159
168
  "SCRUBBER.PRESS_DOWN": {
160
169
  target: "scrubbing",
161
170
  actions: ["focusInput", "setHint", "setCursorPoint"]
162
171
  },
163
172
  "INPUT.ARROW_UP": {
164
- actions: ["increment"]
173
+ actions: ["increment", "invokeOnValueCommit"]
165
174
  },
166
175
  "INPUT.ARROW_DOWN": {
167
- actions: ["decrement"]
176
+ actions: ["decrement", "invokeOnValueCommit"]
168
177
  },
169
178
  "INPUT.HOME": {
170
- actions: ["decrementToMin"]
179
+ actions: ["decrementToMin", "invokeOnValueCommit"]
171
180
  },
172
181
  "INPUT.END": {
173
- actions: ["incrementToMax"]
182
+ actions: ["incrementToMax", "invokeOnValueCommit"]
174
183
  },
175
184
  "INPUT.CHANGE": {
176
185
  actions: ["setValue", "setHint"]
@@ -184,41 +193,66 @@ var machine = createMachine({
184
193
  {
185
194
  guard: not("isInRange"),
186
195
  target: "idle",
187
- actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnInvalid", "invokeOnValueCommit"]
196
+ actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
188
197
  },
189
198
  {
190
199
  target: "idle",
191
200
  actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
192
201
  }
193
202
  ],
203
+ // No target, so the input stays focused. It must not report a blur.
194
204
  "INPUT.ENTER": {
195
- actions: ["setFormattedValue", "clearHint", "invokeOnBlur", "invokeOnValueCommit"]
205
+ actions: ["setFormattedValue", "clearHint", "invokeOnValueCommit"]
196
206
  }
197
207
  }
198
208
  },
199
- "before:spin": {
209
+ pressed: {
200
210
  tags: ["focus"],
201
- effects: ["trackButtonDisabled", "waitForChangeDelay"],
202
- entry: choose([
203
- { guard: "isIncrementHint", actions: ["increment"] },
204
- { guard: "isDecrementHint", actions: ["decrement"] }
205
- ]),
211
+ initial: "waiting",
212
+ effects: ["trackButtonDisabled", "preventContextMenu"],
206
213
  on: {
207
- CHANGE_DELAY: {
208
- target: "spinning",
209
- guard: and("isInRange", "spinOnPress")
210
- },
211
214
  "TRIGGER.PRESS_UP": [
212
- { guard: "isTouchPointer", target: "focused", actions: ["clearHint"] },
213
- { target: "focused", actions: ["focusInput", "clearHint"] }
215
+ { guard: "isTouchPointer", target: "focused", actions: ["clearHint", "invokeOnValueCommit"] },
216
+ { target: "focused", actions: ["focusInput", "clearHint", "invokeOnValueCommit"] }
214
217
  ]
218
+ },
219
+ states: {
220
+ waiting: {
221
+ effects: ["waitForChangeDelay"],
222
+ entry: choose([
223
+ { guard: "isIncrementHint", actions: ["increment"] },
224
+ { guard: "isDecrementHint", actions: ["decrement"] }
225
+ ]),
226
+ on: {
227
+ CHANGE_DELAY: {
228
+ target: "repeating",
229
+ guard: and("isInRange", "spinOnPress")
230
+ }
231
+ }
232
+ },
233
+ repeating: {
234
+ effects: ["spinValue"],
235
+ on: {
236
+ SPIN: [
237
+ {
238
+ guard: "isIncrementHint",
239
+ actions: ["increment"]
240
+ },
241
+ {
242
+ guard: "isDecrementHint",
243
+ actions: ["decrement"]
244
+ }
245
+ ]
246
+ }
247
+ }
215
248
  }
216
249
  },
217
- spinning: {
250
+ scrubbing: {
218
251
  tags: ["focus"],
219
- effects: ["trackButtonDisabled", "spinValue"],
252
+ effects: ["activatePointerLock", "trackMousemove", "preventTextSelection", "trackVisualViewport"],
253
+ entry: ["clearCumulativeDelta"],
220
254
  on: {
221
- SPIN: [
255
+ "SCRUBBER.STEP": [
222
256
  {
223
257
  guard: "isIncrementHint",
224
258
  actions: ["increment"]
@@ -228,20 +262,9 @@ var machine = createMachine({
228
262
  actions: ["decrement"]
229
263
  }
230
264
  ],
231
- "TRIGGER.PRESS_UP": {
232
- target: "focused",
233
- actions: ["focusInput", "clearHint"]
234
- }
235
- }
236
- },
237
- scrubbing: {
238
- tags: ["focus"],
239
- effects: ["activatePointerLock", "trackMousemove", "preventTextSelection", "trackVisualViewport"],
240
- entry: ["clearCumulativeDelta"],
241
- on: {
242
265
  "SCRUBBER.POINTER_UP": {
243
266
  target: "focused",
244
- actions: ["focusInput", "clearCursorPoint", "clearCumulativeDelta"]
267
+ actions: ["focusInput", "clearCursorPoint", "clearCumulativeDelta", "invokeOnValueCommit"]
245
268
  },
246
269
  "SCRUBBER.POINTER_MOVE": {
247
270
  actions: ["accumulateDelta", "setCursorPoint"]
@@ -266,9 +289,13 @@ var machine = createMachine({
266
289
  }, 300);
267
290
  return () => clearTimeout(id);
268
291
  },
269
- spinValue({ send }) {
292
+ preventContextMenu({ scope }) {
293
+ return addDomEvent(scope.getWin(), "contextmenu", (event) => event.preventDefault());
294
+ },
295
+ spinValue({ send, context }) {
270
296
  const id = setInterval(() => {
271
- send({ type: "SPIN" });
297
+ const src = context.get("hint") === "increment" ? "increment-press" : "decrement-press";
298
+ send({ type: "SPIN", src });
272
299
  }, 50);
273
300
  return () => clearInterval(id);
274
301
  },
@@ -295,7 +322,8 @@ var machine = createMachine({
295
322
  return observeAttributes(btn, {
296
323
  attributes: ["disabled"],
297
324
  callback() {
298
- send({ type: "TRIGGER.PRESS_UP", src: "attr" });
325
+ const src = hint === "increment" ? "increment-press" : "decrement-press";
326
+ send({ type: "TRIGGER.PRESS_UP", src });
299
327
  }
300
328
  });
301
329
  },
@@ -306,9 +334,9 @@ var machine = createMachine({
306
334
  event.preventDefault();
307
335
  const dir = Math.sign(event.deltaY) * -1;
308
336
  if (dir === 1) {
309
- send({ type: "VALUE.INCREMENT" });
337
+ send({ type: "VALUE.INCREMENT", src: "wheel" });
310
338
  } else if (dir === -1) {
311
- send({ type: "VALUE.DECREMENT" });
339
+ send({ type: "VALUE.DECREMENT", src: "wheel" });
312
340
  }
313
341
  }
314
342
  return addDomEvent(inputEl, "wheel", onWheel, { passive: false });
@@ -349,7 +377,7 @@ var machine = createMachine({
349
377
  });
350
378
  }
351
379
  function onMouseup() {
352
- send({ type: "SCRUBBER.POINTER_UP" });
380
+ send({ type: "SCRUBBER.POINTER_UP", src: "scrub" });
353
381
  }
354
382
  return callAll(addDomEvent(doc, "mousemove", onMousemove, false), addDomEvent(doc, "mouseup", onMouseup, false));
355
383
  }
@@ -419,8 +447,7 @@ var machine = createMachine({
419
447
  valueAsNumber: computed("valueAsNumber")
420
448
  });
421
449
  },
422
- invokeOnInvalid({ computed, prop, event }) {
423
- if (event.type === "INPUT.CHANGE") return;
450
+ invokeOnInvalid({ computed, prop }) {
424
451
  const reason = computed("valueAsNumber") > prop("max") ? "rangeOverflow" : "rangeUnderflow";
425
452
  prop("onValueInvalid")?.({
426
453
  reason,
@@ -428,10 +455,11 @@ var machine = createMachine({
428
455
  valueAsNumber: computed("valueAsNumber")
429
456
  });
430
457
  },
431
- invokeOnValueCommit({ computed, prop }) {
458
+ invokeOnValueCommit({ computed, prop, event }) {
432
459
  prop("onValueCommit")?.({
433
460
  value: computed("formattedValue"),
434
- valueAsNumber: computed("valueAsNumber")
461
+ valueAsNumber: computed("valueAsNumber"),
462
+ reason: event.src
435
463
  });
436
464
  },
437
465
  syncInputElement({ context, event, computed, scope }) {
@@ -467,10 +495,8 @@ var machine = createMachine({
467
495
  if (Math.abs(newDelta) >= sensitivity) {
468
496
  const step = prop("step");
469
497
  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 });
498
+ if (hint === "increment" || hint === "decrement") {
499
+ send({ type: "SCRUBBER.STEP", step, hint, src: "scrub" });
474
500
  }
475
501
  context.set("cumulativeDelta", newDelta % sensitivity);
476
502
  } else {
@@ -0,0 +1,8 @@
1
+ import { Required } from '@zag-js/types';
2
+ import { IntlTranslations } from './number-input.types.mjs';
3
+ import '@internationalized/number';
4
+ import '@zag-js/core';
5
+
6
+ declare const defaultTranslations: Required<Pick<IntlTranslations, "incrementLabel" | "decrementLabel">>;
7
+
8
+ export { defaultTranslations };
@@ -0,0 +1,8 @@
1
+ import { Required } from '@zag-js/types';
2
+ import { IntlTranslations } from './number-input.types.js';
3
+ import '@internationalized/number';
4
+ import '@zag-js/core';
5
+
6
+ declare const defaultTranslations: Required<Pick<IntlTranslations, "incrementLabel" | "decrementLabel">>;
7
+
8
+ export { defaultTranslations };
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/number-input.translations.ts
21
+ var number_input_translations_exports = {};
22
+ __export(number_input_translations_exports, {
23
+ defaultTranslations: () => defaultTranslations
24
+ });
25
+ module.exports = __toCommonJS(number_input_translations_exports);
26
+ var defaultTranslations = {
27
+ incrementLabel: "increment value",
28
+ decrementLabel: "decrease value"
29
+ };
30
+ // Annotate the CommonJS export names for ESM import in node:
31
+ 0 && (module.exports = {
32
+ defaultTranslations
33
+ });
@@ -0,0 +1,8 @@
1
+ // src/number-input.translations.ts
2
+ var defaultTranslations = {
3
+ incrementLabel: "increment value",
4
+ decrementLabel: "decrease value"
5
+ };
6
+ export {
7
+ defaultTranslations
8
+ };
@@ -1,16 +1,26 @@
1
1
  import { NumberParser } from '@internationalized/number';
2
2
  import { Machine, EventObject, Service } from '@zag-js/core';
3
- import { PropTypes, RequiredBy, LocaleProperties, CommonProperties } from '@zag-js/types';
3
+ import { PropTypes, LocaleProperties, CommonProperties } from '@zag-js/types';
4
4
 
5
+ /**
6
+ * The reason for the number input value change
7
+ */
8
+ type ValueChangeReason = "input-change" | "input-blur" | "keyboard" | "increment-press" | "decrement-press" | "wheel" | "scrub" | "script";
5
9
  interface ValueChangeDetails {
6
10
  value: string;
7
11
  valueAsNumber: number;
12
+ reason?: ValueChangeReason | undefined;
8
13
  }
9
- interface FocusChangeDetails extends ValueChangeDetails {
14
+ interface FocusChangeDetails {
15
+ value: string;
16
+ valueAsNumber: number;
10
17
  focused: boolean;
11
18
  }
12
19
  type ValidityState = "rangeUnderflow" | "rangeOverflow";
13
- interface ValueInvalidDetails extends ValueChangeDetails {
20
+ /** Detached from `ValueChangeDetails`: here `reason` is why the value is invalid. */
21
+ interface ValueInvalidDetails {
22
+ value: string;
23
+ valueAsNumber: number;
14
24
  reason: ValidityState;
15
25
  }
16
26
  type InputMode = "text" | "tel" | "numeric" | "decimal";
@@ -143,7 +153,8 @@ interface NumberInputProps extends LocaleProperties, CommonProperties {
143
153
  */
144
154
  onFocusChange?: ((details: FocusChangeDetails) => void) | undefined;
145
155
  /**
146
- * Function invoked when the value is committed (when the input is blurred or the Enter key is pressed)
156
+ * Function invoked when the value settles: the input is blurred or `Enter` is pressed, a stepper
157
+ * or scrub gesture ends, or a key or wheel step completes.
147
158
  */
148
159
  onValueCommit?: ((details: ValueChangeDetails) => void) | undefined;
149
160
  /**
@@ -183,7 +194,7 @@ interface NumberInputProps extends LocaleProperties, CommonProperties {
183
194
  */
184
195
  snapOnStep?: boolean | undefined;
185
196
  }
186
- type PropsWithDefault = "dir" | "locale" | "focusInputOnChange" | "clampValueOnBlur" | "allowOverflow" | "inputMode" | "pattern" | "translations" | "step" | "largeStep" | "smallStep" | "spinOnPress" | "min" | "max" | "scrubberPixelSensitivity" | "scrubberDirection" | "snapOnStep";
197
+ type PropsWithDefault = "dir" | "locale" | "focusInputOnChange" | "clampValueOnBlur" | "allowOverflow" | "inputMode" | "pattern" | "step" | "largeStep" | "smallStep" | "spinOnPress" | "min" | "max" | "scrubberPixelSensitivity" | "scrubberDirection" | "snapOnStep";
187
198
  type ComputedContext = Readonly<{
188
199
  /**
189
200
  * The value of the input as a number
@@ -273,9 +284,10 @@ interface PrivateContext {
273
284
  visualScale: number;
274
285
  }
275
286
  interface NumberInputSchema {
276
- state: "idle" | "focused" | "spinning" | "before:spin" | "scrubbing";
287
+ state: "idle" | "focused" | "pressed" | "pressed.waiting" | "pressed.repeating" | "scrubbing";
277
288
  tag: "focus";
278
- props: RequiredBy<NumberInputProps, PropsWithDefault>;
289
+ props: NumberInputProps;
290
+ defaultPropKey: PropsWithDefault;
279
291
  context: PrivateContext;
280
292
  computed: ComputedContext;
281
293
  action: string;
@@ -413,4 +425,4 @@ interface NumberInputApi<T extends PropTypes = PropTypes> {
413
425
  getScrubberCursorProps: () => T["element"];
414
426
  }
415
427
 
416
- export type { DecrementTriggerState, ElementIds, FocusChangeDetails, HintValue, IncrementTriggerState, InputMode, InputState, IntlTranslations, NumberInputApi, NumberInputMachine, NumberInputProps, NumberInputSchema, NumberInputService, RootState, ValidityState, ValueChangeDetails, ValueInvalidDetails };
428
+ export type { DecrementTriggerState, ElementIds, FocusChangeDetails, HintValue, IncrementTriggerState, InputMode, InputState, IntlTranslations, NumberInputApi, NumberInputMachine, NumberInputProps, NumberInputSchema, NumberInputService, RootState, ValidityState, ValueChangeDetails, ValueChangeReason, ValueInvalidDetails };
@@ -1,16 +1,26 @@
1
1
  import { NumberParser } from '@internationalized/number';
2
2
  import { Machine, EventObject, Service } from '@zag-js/core';
3
- import { PropTypes, RequiredBy, LocaleProperties, CommonProperties } from '@zag-js/types';
3
+ import { PropTypes, LocaleProperties, CommonProperties } from '@zag-js/types';
4
4
 
5
+ /**
6
+ * The reason for the number input value change
7
+ */
8
+ type ValueChangeReason = "input-change" | "input-blur" | "keyboard" | "increment-press" | "decrement-press" | "wheel" | "scrub" | "script";
5
9
  interface ValueChangeDetails {
6
10
  value: string;
7
11
  valueAsNumber: number;
12
+ reason?: ValueChangeReason | undefined;
8
13
  }
9
- interface FocusChangeDetails extends ValueChangeDetails {
14
+ interface FocusChangeDetails {
15
+ value: string;
16
+ valueAsNumber: number;
10
17
  focused: boolean;
11
18
  }
12
19
  type ValidityState = "rangeUnderflow" | "rangeOverflow";
13
- interface ValueInvalidDetails extends ValueChangeDetails {
20
+ /** Detached from `ValueChangeDetails`: here `reason` is why the value is invalid. */
21
+ interface ValueInvalidDetails {
22
+ value: string;
23
+ valueAsNumber: number;
14
24
  reason: ValidityState;
15
25
  }
16
26
  type InputMode = "text" | "tel" | "numeric" | "decimal";
@@ -143,7 +153,8 @@ interface NumberInputProps extends LocaleProperties, CommonProperties {
143
153
  */
144
154
  onFocusChange?: ((details: FocusChangeDetails) => void) | undefined;
145
155
  /**
146
- * Function invoked when the value is committed (when the input is blurred or the Enter key is pressed)
156
+ * Function invoked when the value settles: the input is blurred or `Enter` is pressed, a stepper
157
+ * or scrub gesture ends, or a key or wheel step completes.
147
158
  */
148
159
  onValueCommit?: ((details: ValueChangeDetails) => void) | undefined;
149
160
  /**
@@ -183,7 +194,7 @@ interface NumberInputProps extends LocaleProperties, CommonProperties {
183
194
  */
184
195
  snapOnStep?: boolean | undefined;
185
196
  }
186
- type PropsWithDefault = "dir" | "locale" | "focusInputOnChange" | "clampValueOnBlur" | "allowOverflow" | "inputMode" | "pattern" | "translations" | "step" | "largeStep" | "smallStep" | "spinOnPress" | "min" | "max" | "scrubberPixelSensitivity" | "scrubberDirection" | "snapOnStep";
197
+ type PropsWithDefault = "dir" | "locale" | "focusInputOnChange" | "clampValueOnBlur" | "allowOverflow" | "inputMode" | "pattern" | "step" | "largeStep" | "smallStep" | "spinOnPress" | "min" | "max" | "scrubberPixelSensitivity" | "scrubberDirection" | "snapOnStep";
187
198
  type ComputedContext = Readonly<{
188
199
  /**
189
200
  * The value of the input as a number
@@ -273,9 +284,10 @@ interface PrivateContext {
273
284
  visualScale: number;
274
285
  }
275
286
  interface NumberInputSchema {
276
- state: "idle" | "focused" | "spinning" | "before:spin" | "scrubbing";
287
+ state: "idle" | "focused" | "pressed" | "pressed.waiting" | "pressed.repeating" | "scrubbing";
277
288
  tag: "focus";
278
- props: RequiredBy<NumberInputProps, PropsWithDefault>;
289
+ props: NumberInputProps;
290
+ defaultPropKey: PropsWithDefault;
279
291
  context: PrivateContext;
280
292
  computed: ComputedContext;
281
293
  action: string;
@@ -413,4 +425,4 @@ interface NumberInputApi<T extends PropTypes = PropTypes> {
413
425
  getScrubberCursorProps: () => T["element"];
414
426
  }
415
427
 
416
- export type { DecrementTriggerState, ElementIds, FocusChangeDetails, HintValue, IncrementTriggerState, InputMode, InputState, IntlTranslations, NumberInputApi, NumberInputMachine, NumberInputProps, NumberInputSchema, NumberInputService, RootState, ValidityState, ValueChangeDetails, ValueInvalidDetails };
428
+ export type { DecrementTriggerState, ElementIds, FocusChangeDetails, HintValue, IncrementTriggerState, InputMode, InputState, IntlTranslations, NumberInputApi, NumberInputMachine, NumberInputProps, NumberInputSchema, NumberInputService, RootState, ValidityState, ValueChangeDetails, ValueChangeReason, ValueInvalidDetails };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/number-input",
3
- "version": "2.0.0-next.1",
3
+ "version": "2.0.0-next.3",
4
4
  "description": "Core logic for the number-input widget implemented as a state machine",
5
5
  "keywords": [
6
6
  "js",
@@ -30,11 +30,11 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@internationalized/number": "3.6.7",
33
- "@zag-js/anatomy": "2.0.0-next.1",
34
- "@zag-js/core": "2.0.0-next.1",
35
- "@zag-js/dom-query": "2.0.0-next.1",
36
- "@zag-js/types": "2.0.0-next.1",
37
- "@zag-js/utils": "2.0.0-next.1"
33
+ "@zag-js/anatomy": "2.0.0-next.3",
34
+ "@zag-js/core": "2.0.0-next.3",
35
+ "@zag-js/dom-query": "2.0.0-next.3",
36
+ "@zag-js/types": "2.0.0-next.3",
37
+ "@zag-js/utils": "2.0.0-next.3"
38
38
  },
39
39
  "devDependencies": {
40
40
  "clean-package": "2.2.0"
@@ -68,7 +68,7 @@
68
68
  },
69
69
  "scripts": {
70
70
  "build": "tsup",
71
- "lint": "eslint src",
71
+ "lint": "oxlint src",
72
72
  "typecheck": "tsc --noEmit"
73
73
  }
74
74
  }