@zag-js/number-input 0.20.0 → 0.22.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.
@@ -1,12 +1,14 @@
1
- import { choose, createMachine, guards } from "@zag-js/core"
1
+ import { NumberParser } from "@internationalized/number"
2
+ import { choose, createMachine, guards, ref } from "@zag-js/core"
2
3
  import { addDomEvent, requestPointerLock } from "@zag-js/dom-event"
3
4
  import { isSafari, raf } from "@zag-js/dom-query"
5
+ import { trackFormControl } from "@zag-js/form-utils"
4
6
  import { observeAttributes } from "@zag-js/mutation-observer"
5
- import { isAtMax, isAtMin, isWithinRange, valueOf } from "@zag-js/number-utils"
6
- import { callAll, compact, isEqual, match } from "@zag-js/utils"
7
+ import { clamp, decrement, increment, isAtMax, isAtMin, isWithinRange } from "@zag-js/number-utils"
8
+ import { callAll, compact, isEqual } from "@zag-js/utils"
9
+ import { recordCursor, restoreCursor } from "./cursor"
7
10
  import { dom } from "./number-input.dom"
8
11
  import type { MachineContext, MachineState, UserDefinedContext } from "./number-input.types"
9
- import { utils } from "./number-input.utils"
10
12
 
11
13
  const { not, and } = guards
12
14
 
@@ -18,20 +20,25 @@ export function machine(userContext: UserDefinedContext) {
18
20
  initial: "idle",
19
21
  context: {
20
22
  dir: "ltr",
23
+ locale: "en-US",
21
24
  focusInputOnChange: true,
22
25
  clampValueOnBlur: true,
23
26
  allowOverflow: false,
24
27
  inputMode: "decimal",
25
28
  pattern: "[0-9]*(.[0-9]+)?",
26
- hint: null,
27
29
  value: "",
28
30
  step: 1,
29
31
  min: Number.MIN_SAFE_INTEGER,
30
32
  max: Number.MAX_SAFE_INTEGER,
31
- scrubberCursorPoint: null,
32
33
  invalid: false,
33
34
  spinOnPress: true,
34
35
  ...ctx,
36
+ hint: null,
37
+ scrubberCursorPoint: null,
38
+ composing: false,
39
+ fieldsetDisabled: false,
40
+ formatter: createFormatter(ctx.locale || "en-US", ctx.formatOptions),
41
+ parser: createParser(ctx.locale || "en-US", ctx.formatOptions),
35
42
  translations: {
36
43
  incrementLabel: "increment value",
37
44
  decrementLabel: "decrease value",
@@ -41,18 +48,24 @@ export function machine(userContext: UserDefinedContext) {
41
48
 
42
49
  computed: {
43
50
  isRtl: (ctx) => ctx.dir === "rtl",
44
- valueAsNumber: (ctx) => valueOf(ctx.value),
51
+ valueAsNumber: (ctx) => ctx.parser.parse(String(ctx.value)) ?? 0,
45
52
  isAtMin: (ctx) => isAtMin(ctx.value, ctx),
46
53
  isAtMax: (ctx) => isAtMax(ctx.value, ctx),
47
54
  isOutOfRange: (ctx) => !isWithinRange(ctx.value, ctx),
48
55
  isValueEmpty: (ctx) => ctx.value === "",
56
+ isDisabled: (ctx) => !!ctx.disabled || ctx.fieldsetDisabled,
49
57
  canIncrement: (ctx) => ctx.allowOverflow || !ctx.isAtMax,
50
58
  canDecrement: (ctx) => ctx.allowOverflow || !ctx.isAtMin,
51
59
  valueText: (ctx) => ctx.translations.valueText?.(ctx.value),
52
- formattedValue: (ctx) => ctx.format?.(ctx.value).toString() ?? ctx.value,
60
+ formattedValue: (ctx) => {
61
+ if (Number.isNaN(ctx.valueAsNumber)) return ctx.value
62
+ return ctx.formatter.format(ctx.valueAsNumber)
63
+ },
53
64
  },
54
65
 
55
66
  watch: {
67
+ formatOptions: ["setFormatterAndParser", "syncInputElement"],
68
+ locale: ["setFormatterAndParser", "syncInputElement"],
56
69
  value: ["syncInputElement"],
57
70
  isOutOfRange: ["invokeOnInvalid"],
58
71
  scrubberCursorPoint: ["setVirtualCursorPosition"],
@@ -60,17 +73,19 @@ export function machine(userContext: UserDefinedContext) {
60
73
 
61
74
  entry: ["syncInputValue"],
62
75
 
76
+ activities: ["trackFormControl"],
77
+
63
78
  on: {
64
- SET_VALUE: {
79
+ "VALUE.SET": {
65
80
  actions: ["setValue", "clampValue", "setHintToSet"],
66
81
  },
67
- CLEAR_VALUE: {
82
+ "VALUE.CLEAR": {
68
83
  actions: ["clearValue"],
69
84
  },
70
- INCREMENT: {
85
+ "VALUE.INCREMENT": {
71
86
  actions: ["increment"],
72
87
  },
73
- DECREMENT: {
88
+ "VALUE.DECREMENT": {
74
89
  actions: ["decrement"],
75
90
  },
76
91
  },
@@ -78,15 +93,15 @@ export function machine(userContext: UserDefinedContext) {
78
93
  states: {
79
94
  idle: {
80
95
  on: {
81
- PRESS_DOWN: {
96
+ "TRIGGER.PRESS_DOWN": {
82
97
  target: "before:spin",
83
98
  actions: ["focusInput", "invokeOnFocus", "setHint"],
84
99
  },
85
- PRESS_DOWN_SCRUBBER: {
100
+ "SCRUBBER.PRESS_DOWN": {
86
101
  target: "scrubbing",
87
102
  actions: ["focusInput", "invokeOnFocus", "setHint", "setCursorPoint"],
88
103
  },
89
- FOCUS: {
104
+ "INPUT.FOCUS": {
90
105
  target: "focused",
91
106
  actions: ["focusInput", "invokeOnFocus"],
92
107
  },
@@ -98,45 +113,46 @@ export function machine(userContext: UserDefinedContext) {
98
113
  entry: "focusInput",
99
114
  activities: "attachWheelListener",
100
115
  on: {
101
- PRESS_DOWN: {
116
+ "TRIGGER.PRESS_DOWN": {
102
117
  target: "before:spin",
103
118
  actions: ["focusInput", "setHint"],
104
119
  },
105
- PRESS_DOWN_SCRUBBER: {
120
+ "SCRUBBER.PRESS_DOWN": {
106
121
  target: "scrubbing",
107
122
  actions: ["focusInput", "setHint", "setCursorPoint"],
108
123
  },
109
- ARROW_UP: {
124
+ "INPUT.ARROW_UP": {
110
125
  actions: "increment",
111
126
  },
112
- ARROW_DOWN: {
127
+ "INPUT.ARROW_DOWN": {
113
128
  actions: "decrement",
114
129
  },
115
- HOME: {
116
- actions: "setToMin",
130
+ "INPUT.HOME": {
131
+ actions: "decrementToMin",
117
132
  },
118
- END: {
119
- actions: "setToMax",
133
+ "INPUT.END": {
134
+ actions: "incrementToMax",
120
135
  },
121
- CHANGE: {
136
+ "INPUT.CHANGE": {
122
137
  actions: ["setValue", "setHint"],
123
138
  },
124
- BLUR: [
139
+ "INPUT.COMMIT": [
125
140
  {
126
- guard: "isInvalidExponential",
141
+ guard: and("clampValueOnBlur", not("isInRange")),
127
142
  target: "idle",
128
- actions: ["clearValue", "clearHint", "invokeOnBlur"],
143
+ actions: ["clampValue", "syncInputElement", "clearHint", "invokeOnBlur"],
129
144
  },
130
145
  {
131
- guard: and("clampOnBlur", not("isInRange"), not("isEmptyValue")),
132
146
  target: "idle",
133
- actions: ["clampValue", "clearHint", "invokeOnBlur"],
134
- },
135
- {
136
- target: "idle",
137
- actions: ["roundValue", "invokeOnBlur"],
147
+ actions: ["syncInputElement", "invokeOnBlur"],
138
148
  },
139
149
  ],
150
+ "INPUT.COMPOSITION_START": {
151
+ actions: "setComposing",
152
+ },
153
+ "INPUT.COMPOSITION_END": {
154
+ actions: "clearComposing",
155
+ },
140
156
  },
141
157
  },
142
158
 
@@ -154,7 +170,7 @@ export function machine(userContext: UserDefinedContext) {
154
170
  },
155
171
  },
156
172
  on: {
157
- PRESS_UP: {
173
+ "TRIGGER.PRESS_UP": {
158
174
  target: "focused",
159
175
  actions: "clearHint",
160
176
  },
@@ -177,7 +193,7 @@ export function machine(userContext: UserDefinedContext) {
177
193
  },
178
194
  ],
179
195
  on: {
180
- PRESS_UP: {
196
+ "TRIGGER.PRESS_UP": {
181
197
  target: "focused",
182
198
  actions: "clearHint",
183
199
  },
@@ -189,8 +205,8 @@ export function machine(userContext: UserDefinedContext) {
189
205
  exit: "clearCursorPoint",
190
206
  activities: ["activatePointerLock", "trackMousemove", "setupVirtualCursor", "preventTextSelection"],
191
207
  on: {
192
- POINTER_UP_SCRUBBER: "focused",
193
- POINTER_MOVE_SCRUBBER: [
208
+ "SCRUBBER.POINTER_UP": "focused",
209
+ "SCRUBBER.POINTER_MOVE": [
194
210
  {
195
211
  guard: "isIncrementHint",
196
212
  actions: ["increment", "setCursorPoint"],
@@ -211,18 +227,27 @@ export function machine(userContext: UserDefinedContext) {
211
227
  },
212
228
 
213
229
  guards: {
214
- clampOnBlur: (ctx) => !!ctx.clampValueOnBlur,
230
+ clampValueOnBlur: (ctx) => ctx.clampValueOnBlur,
215
231
  isAtMin: (ctx) => ctx.isAtMin,
216
232
  spinOnPress: (ctx) => !!ctx.spinOnPress,
217
233
  isAtMax: (ctx) => ctx.isAtMax,
218
234
  isInRange: (ctx) => !ctx.isOutOfRange,
219
235
  isDecrementHint: (ctx, evt) => (evt.hint ?? ctx.hint) === "decrement",
220
- isEmptyValue: (ctx) => ctx.isValueEmpty,
221
236
  isIncrementHint: (ctx, evt) => (evt.hint ?? ctx.hint) === "increment",
222
- isInvalidExponential: (ctx) => ctx.value.toString().startsWith("e"),
223
237
  },
224
238
 
225
239
  activities: {
240
+ trackFormControl(ctx, _evt, { initialContext }) {
241
+ const inputEl = dom.getInputEl(ctx)
242
+ return trackFormControl(inputEl, {
243
+ onFieldsetDisabledChange(disabled) {
244
+ ctx.fieldsetDisabled = disabled
245
+ },
246
+ onFormReset() {
247
+ set.value(ctx, initialContext.value)
248
+ },
249
+ })
250
+ },
226
251
  setupVirtualCursor(ctx) {
227
252
  return dom.setupVirtualCursor(ctx)
228
253
  },
@@ -232,26 +257,24 @@ export function machine(userContext: UserDefinedContext) {
232
257
  trackButtonDisabled(ctx, _evt, { send }) {
233
258
  const btn = dom.getPressedTriggerEl(ctx, ctx.hint)
234
259
  return observeAttributes(btn, ["disabled"], () => {
235
- send("PRESS_UP")
260
+ send({ type: "TRIGGER.PRESS_UP", src: "attr" })
236
261
  })
237
262
  },
238
263
  attachWheelListener(ctx, _evt, { send }) {
239
- const input = dom.getInputEl(ctx)
240
- if (!input) return
264
+ const inputEl = dom.getInputEl(ctx)
265
+ if (!inputEl || !dom.isActiveElement(ctx, inputEl) || !ctx.allowMouseWheel) return
241
266
 
242
267
  function onWheel(event: WheelEvent) {
243
- const isInputFocused = dom.getDoc(ctx).activeElement === input
244
- if (!ctx.allowMouseWheel || !isInputFocused) return
245
268
  event.preventDefault()
246
-
247
269
  const dir = Math.sign(event.deltaY) * -1
248
270
  if (dir === 1) {
249
- send("INCREMENT")
271
+ send("VALUE.INCREMENT")
250
272
  } else if (dir === -1) {
251
- send("DECREMENT")
273
+ send("VALUE.DECREMENT")
252
274
  }
253
275
  }
254
- return addDomEvent(input, "wheel", onWheel, { passive: false })
276
+
277
+ return addDomEvent(inputEl, "wheel", onWheel, { passive: false })
255
278
  },
256
279
  activatePointerLock(ctx) {
257
280
  if (isSafari()) return
@@ -265,14 +288,14 @@ export function machine(userContext: UserDefinedContext) {
265
288
  const value = dom.getMousementValue(ctx, event)
266
289
  if (!value.hint) return
267
290
  send({
268
- type: "POINTER_MOVE_SCRUBBER",
291
+ type: "SCRUBBER.POINTER_MOVE",
269
292
  hint: value.hint,
270
293
  point: value.point,
271
294
  })
272
295
  }
273
296
 
274
297
  function onMouseup() {
275
- send("POINTER_UP_SCRUBBER")
298
+ send("SCRUBBER.POINTER_UP")
276
299
  }
277
300
 
278
301
  return callAll(
@@ -286,36 +309,33 @@ export function machine(userContext: UserDefinedContext) {
286
309
  focusInput(ctx) {
287
310
  if (!ctx.focusInputOnChange) return
288
311
  const inputEl = dom.getInputEl(ctx)
289
- raf(() => {
290
- inputEl?.focus()
291
- })
312
+ if (dom.isActiveElement(ctx, inputEl)) return
313
+ raf(() => inputEl?.focus({ preventScroll: true }))
292
314
  },
293
315
  increment(ctx, evt) {
294
- set.value(ctx, utils.increment(ctx, evt.step))
316
+ const nextValue = increment(ctx.value, evt.step ?? ctx.step)
317
+ set.value(ctx, String(clamp(nextValue, ctx)))
295
318
  },
296
319
  decrement(ctx, evt) {
297
- set.value(ctx, utils.decrement(ctx, evt.step))
320
+ const nextValue = decrement(ctx.value, evt.step ?? ctx.step)
321
+ set.value(ctx, String(clamp(nextValue, ctx)))
298
322
  },
299
323
  clampValue(ctx) {
300
- set.value(ctx, utils.clamp(ctx))
301
- },
302
- roundValue(ctx) {
303
- if (ctx.value === "") return
304
- set.value(ctx, utils.round(ctx))
324
+ const nextValue = clamp(ctx.value, ctx)
325
+ set.value(ctx, String(nextValue))
305
326
  },
306
327
  setValue(ctx, evt) {
307
- let value = evt.target?.value ?? evt.value
308
- value = utils.sanitize(ctx, utils.parse(ctx, value.toString()))
328
+ const value = evt.target?.value ?? evt.value
309
329
  set.value(ctx, value)
310
330
  },
311
331
  clearValue(ctx) {
312
332
  set.value(ctx, "")
313
333
  },
314
- setToMax(ctx) {
315
- set.value(ctx, ctx.max.toString())
334
+ incrementToMax(ctx) {
335
+ set.value(ctx, String(ctx.max))
316
336
  },
317
- setToMin(ctx) {
318
- set.value(ctx, ctx.min.toString())
337
+ decrementToMin(ctx) {
338
+ set.value(ctx, String(ctx.min))
319
339
  },
320
340
  setHint(ctx, evt) {
321
341
  ctx.hint = evt.hint
@@ -326,15 +346,9 @@ export function machine(userContext: UserDefinedContext) {
326
346
  setHintToSet(ctx) {
327
347
  ctx.hint = "set"
328
348
  },
329
- invokeOnFocus(ctx, evt) {
330
- const srcElement: HTMLElement | null = match(evt.type, {
331
- PRESS_DOWN: dom.getPressedTriggerEl(ctx, evt.hint),
332
- FOCUS: dom.getInputEl(ctx),
333
- PRESS_DOWN_SCRUBBER: dom.getScrubberEl(ctx),
334
- })
349
+ invokeOnFocus(ctx) {
335
350
  ctx.onFocusChange?.({
336
351
  focused: true,
337
- srcElement,
338
352
  value: ctx.formattedValue,
339
353
  valueAsNumber: ctx.valueAsNumber,
340
354
  })
@@ -355,16 +369,24 @@ export function machine(userContext: UserDefinedContext) {
355
369
  valueAsNumber: ctx.valueAsNumber,
356
370
  })
357
371
  },
358
- syncInputElement(ctx) {
372
+ syncInputElement(ctx, evt) {
359
373
  const inputEl = dom.getInputEl(ctx)
360
- dom.setValue(inputEl, ctx.formattedValue)
374
+ const inputValue = evt.type.endsWith("CHANGE") ? ctx.value : ctx.formattedValue
375
+
376
+ // record cursor position before updating input value
377
+ const sel = recordCursor(inputEl!)
378
+
379
+ dom.setValue(inputEl, inputValue)
380
+
381
+ // restore cursor position after updating input value
382
+ raf(() => {
383
+ restoreCursor(inputEl!, sel)
384
+ })
361
385
  },
362
386
  syncInputValue(ctx) {
363
387
  const inputEl = dom.getInputEl(ctx)
364
388
  if (!inputEl || inputEl.value == ctx.value) return
365
-
366
- const value = utils.parse(ctx, inputEl.value)
367
- set.value(ctx, utils.sanitize(ctx, value))
389
+ set.value(ctx, ctx.formattedValue)
368
390
  },
369
391
  setCursorPoint(ctx, evt) {
370
392
  ctx.scrubberCursorPoint = evt.point
@@ -373,19 +395,52 @@ export function machine(userContext: UserDefinedContext) {
373
395
  ctx.scrubberCursorPoint = null
374
396
  },
375
397
  setVirtualCursorPosition(ctx) {
376
- const cursor = dom.getCursorEl(ctx)
377
- if (!cursor || !ctx.scrubberCursorPoint) return
398
+ const cursorEl = dom.getCursorEl(ctx)
399
+ if (!cursorEl || !ctx.scrubberCursorPoint) return
378
400
  const { x, y } = ctx.scrubberCursorPoint
379
- cursor.style.transform = `translate3d(${x}px, ${y}px, 0px)`
401
+ cursorEl.style.transform = `translate3d(${x}px, ${y}px, 0px)`
402
+ },
403
+ setComposing(ctx) {
404
+ ctx.composing = true
380
405
  },
406
+ clearComposing(ctx) {
407
+ ctx.composing = false
408
+ },
409
+ setFormatterAndParser(ctx) {
410
+ if (!ctx.locale) return
411
+ ctx.formatter = createFormatter(ctx.locale, ctx.formatOptions)
412
+ ctx.parser = createParser(ctx.locale, ctx.formatOptions)
413
+ },
414
+ },
415
+ compareFns: {
416
+ formatOptions: (a, b) => isEqual(a, b),
381
417
  },
382
418
  },
383
419
  )
384
420
  }
385
421
 
422
+ const defaultFormatOptions: Intl.NumberFormatOptions = {
423
+ style: "decimal",
424
+ minimumFractionDigits: 0,
425
+ maximumFractionDigits: 20,
426
+ }
427
+
428
+ const createFormatter = (locale: string, options: Intl.NumberFormatOptions = {}) => {
429
+ const formatOptions = Object.assign({}, defaultFormatOptions, options)
430
+ return ref(new Intl.NumberFormat(locale, formatOptions))
431
+ }
432
+
433
+ const createParser = (locale: string, options: Intl.NumberFormatOptions = {}) => {
434
+ const formatOptions = Object.assign({}, defaultFormatOptions, options)
435
+ return ref(new NumberParser(locale, formatOptions))
436
+ }
437
+
386
438
  const invoke = {
387
439
  onChange: (ctx: MachineContext) => {
388
- ctx.onValueChange?.({ value: ctx.value, valueAsNumber: ctx.valueAsNumber })
440
+ ctx.onValueChange?.({
441
+ value: ctx.value,
442
+ valueAsNumber: ctx.valueAsNumber,
443
+ })
389
444
  },
390
445
  }
391
446
 
@@ -1,5 +1,6 @@
1
+ import type { NumberFormatter, NumberParser } from "@internationalized/number"
1
2
  import type { StateMachine as S } from "@zag-js/core"
2
- import type { CommonProperties, Context, DirectionProperty, PropTypes, RequiredBy } from "@zag-js/types"
3
+ import type { CommonProperties, Context, LocaleProperties, PropTypes, RequiredBy } from "@zag-js/types"
3
4
 
4
5
  /* -----------------------------------------------------------------------------
5
6
  * Callback details
@@ -12,7 +13,6 @@ export interface ValueChangeDetails {
12
13
 
13
14
  export interface FocusChangeDetails extends ValueChangeDetails {
14
15
  focused: boolean
15
- srcElement?: HTMLElement | null
16
16
  }
17
17
 
18
18
  type ValidityState = "rangeUnderflow" | "rangeOverflow"
@@ -52,7 +52,7 @@ type IntlTranslations = {
52
52
  decrementLabel: string
53
53
  }
54
54
 
55
- interface PublicContext extends DirectionProperty, CommonProperties {
55
+ interface PublicContext extends LocaleProperties, CommonProperties {
56
56
  /**
57
57
  * The ids of the elements in the number input. Useful for composition.
58
58
  */
@@ -109,11 +109,6 @@ interface PublicContext extends DirectionProperty, CommonProperties {
109
109
  * @default true
110
110
  */
111
111
  allowOverflow: boolean
112
- /**
113
- * Whether the pressed key should be allowed in the input.
114
- * The default behavior is to allow DOM floating point characters defined by /^[Ee0-9+\-.]$/
115
- */
116
- validateCharacter?: (char: string) => boolean
117
112
  /**
118
113
  * Whether to clamp the value when the input loses focus (blur)
119
114
  * @default true
@@ -129,13 +124,9 @@ interface PublicContext extends DirectionProperty, CommonProperties {
129
124
  */
130
125
  translations: IntlTranslations
131
126
  /**
132
- * If using a custom display format, this converts the custom format to a format `parseFloat` understands.
133
- */
134
- parse?: (value: string) => string
135
- /**
136
- * If using a custom display format, this converts the default format to the custom format.
127
+ * The options to pass to the `Intl.NumberFormat` constructor
137
128
  */
138
- format?: (value: string) => string | number
129
+ formatOptions?: Intl.NumberFormatOptions
139
130
  /**
140
131
  * Hints at the type of data that might be entered by the user. It also determines
141
132
  * the type of keyboard shown to the user on mobile devices
@@ -154,14 +145,6 @@ interface PublicContext extends DirectionProperty, CommonProperties {
154
145
  * Function invoked when the number input is focused
155
146
  */
156
147
  onFocusChange?: (details: FocusChangeDetails) => void
157
- /**
158
- * The minimum number of fraction digits to use. Possible values are from 0 to 20
159
- */
160
- minFractionDigits?: number
161
- /**
162
- * The maximum number of fraction digits to use. Possible values are from 0 to 20;
163
- */
164
- maxFractionDigits?: number
165
148
  /**
166
149
  * Whether to spin the value when the increment/decrement button is pressed
167
150
  */
@@ -196,6 +179,11 @@ type ComputedContext = Readonly<{
196
179
  * Whether the value is empty
197
180
  */
198
181
  isValueEmpty: boolean
182
+ /**
183
+ * @computed
184
+ * Whether the color picker is disabled
185
+ */
186
+ isDisabled: boolean
199
187
  /**
200
188
  * @computed
201
189
  * Whether the increment button is enabled
@@ -234,6 +222,26 @@ type PrivateContext = Context<{
234
222
  * The scrubber cursor position
235
223
  */
236
224
  scrubberCursorPoint: { x: number; y: number } | null
225
+ /**
226
+ * @internal
227
+ * The number i18n formatter
228
+ */
229
+ formatter: NumberFormatter
230
+ /**
231
+ * @internal
232
+ * The number i18n parser
233
+ */
234
+ parser: NumberParser
235
+ /**
236
+ * @internal
237
+ * Whether the input is composing
238
+ */
239
+ composing: boolean
240
+ /**
241
+ * @internal
242
+ * Whether the checkbox's fieldset is disabled
243
+ */
244
+ fieldsetDisabled: boolean
237
245
  }>
238
246
 
239
247
  export interface MachineContext extends PublicContext, PrivateContext, ComputedContext {}
@@ -300,10 +308,6 @@ export interface MachineApi<T extends PropTypes = PropTypes> {
300
308
  * Function to focus the input.
301
309
  */
302
310
  focus(): void
303
- /**
304
- * Function to blur the input.
305
- */
306
- blur(): void
307
311
  rootProps: T["element"]
308
312
  labelProps: T["label"]
309
313
  controlProps: T["element"]
@@ -1,42 +0,0 @@
1
- import { isModifiedEvent } from "@zag-js/dom-event"
2
- import { clamp, decrement, formatDecimal, increment } from "@zag-js/number-utils"
3
- import type { JSX } from "@zag-js/types"
4
- import type { MachineContext as Ctx } from "./number-input.types"
5
-
6
- export const utils = {
7
- isValidNumericEvent: (ctx: Ctx, event: JSX.KeyboardEvent) => {
8
- if (event.key == null) return true
9
- const isModifier = isModifiedEvent(event)
10
- const isSingleKey = event.key.length === 1
11
- if (isModifier || !isSingleKey) return true
12
- return ctx.validateCharacter?.(event.key) ?? utils.isFloatingPoint(event.key)
13
- },
14
- isFloatingPoint: (v: string) => /^[0-9+\-.]$/.test(v),
15
- sanitize: (ctx: Ctx, value: string) => {
16
- return value
17
- .split("")
18
- .filter(ctx.validateCharacter ?? utils.isFloatingPoint)
19
- .join("")
20
- },
21
- increment: (ctx: Ctx, step?: number) => {
22
- const value = increment(ctx.value, step ?? ctx.step)
23
- return formatDecimal(clamp(value, ctx), ctx)
24
- },
25
- decrement: (ctx: Ctx, step?: number) => {
26
- const value = decrement(ctx.value, step ?? ctx.step)
27
- return formatDecimal(clamp(value, ctx), ctx)
28
- },
29
- clamp: (ctx: Ctx) => {
30
- return formatDecimal(clamp(ctx.value, ctx), ctx)
31
- },
32
- parse: (ctx: Ctx, value: string) => {
33
- return ctx.parse?.(value) ?? value
34
- },
35
- format: (ctx: Ctx, value: string | number) => {
36
- const _val = value.toString()
37
- return ctx.format?.(_val) ?? _val
38
- },
39
- round: (ctx: Ctx) => {
40
- return formatDecimal(ctx.value, ctx)
41
- },
42
- }