@zag-js/number-input 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/number-input",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Core logic for the number-input widget implemented as a state machine",
5
5
  "keywords": [
6
6
  "js",
@@ -27,15 +27,15 @@
27
27
  "url": "https://github.com/chakra-ui/zag/issues"
28
28
  },
29
29
  "dependencies": {
30
- "@zag-js/anatomy": "0.16.0",
31
- "@zag-js/core": "0.16.0",
32
- "@zag-js/dom-query": "0.16.0",
33
- "@zag-js/dom-event": "0.16.0",
34
- "@zag-js/form-utils": "0.16.0",
35
- "@zag-js/mutation-observer": "0.16.0",
36
- "@zag-js/number-utils": "0.16.0",
37
- "@zag-js/utils": "0.16.0",
38
- "@zag-js/types": "0.16.0"
30
+ "@zag-js/anatomy": "0.17.0",
31
+ "@zag-js/core": "0.17.0",
32
+ "@zag-js/dom-query": "0.17.0",
33
+ "@zag-js/dom-event": "0.17.0",
34
+ "@zag-js/form-utils": "0.17.0",
35
+ "@zag-js/mutation-observer": "0.17.0",
36
+ "@zag-js/number-utils": "0.17.0",
37
+ "@zag-js/utils": "0.17.0",
38
+ "@zag-js/types": "0.17.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "clean-package": "2.2.0"
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { anatomy } from "./number-input.anatomy"
2
2
  export { connect } from "./number-input.connect"
3
3
  export { machine } from "./number-input.machine"
4
- export type { UserDefinedContext as Context, PublicApi } from "./number-input.types"
4
+ export type { UserDefinedContext as Context, MachineApi as Api } from "./number-input.types"
@@ -4,10 +4,10 @@ import { roundToDevicePixel } from "@zag-js/number-utils"
4
4
  import type { NormalizeProps, PropTypes } from "@zag-js/types"
5
5
  import { parts } from "./number-input.anatomy"
6
6
  import { dom } from "./number-input.dom"
7
- import type { PublicApi, Send, State } from "./number-input.types"
7
+ import type { MachineApi, Send, State } from "./number-input.types"
8
8
  import { utils } from "./number-input.utils"
9
9
 
10
- export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>): PublicApi<T> {
10
+ export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>): MachineApi<T> {
11
11
  const isFocused = state.hasTag("focus")
12
12
  const isInvalid = state.context.isOutOfRange || !!state.context.invalid
13
13
 
@@ -54,7 +54,7 @@ export function machine(userContext: UserDefinedContext) {
54
54
  },
55
55
 
56
56
  watch: {
57
- value: ["invokeOnChange", "dispatchChangeEvent"],
57
+ value: ["syncInputElement"],
58
58
  isOutOfRange: ["invokeOnInvalid"],
59
59
  scrubberCursorPoint: ["setVirtualCursorPosition"],
60
60
  },
@@ -62,15 +62,9 @@ export function machine(userContext: UserDefinedContext) {
62
62
  entry: ["syncInputValue"],
63
63
 
64
64
  on: {
65
- SET_VALUE: [
66
- {
67
- guard: "clampOnBlur",
68
- actions: ["setValue", "clampValue", "setHintToSet"],
69
- },
70
- {
71
- actions: ["setValue", "setHintToSet"],
72
- },
73
- ],
65
+ SET_VALUE: {
66
+ actions: ["setValue", "clampValue", "setHintToSet"],
67
+ },
74
68
  CLEAR_VALUE: {
75
69
  actions: ["clearValue"],
76
70
  },
@@ -84,17 +78,19 @@ export function machine(userContext: UserDefinedContext) {
84
78
 
85
79
  states: {
86
80
  idle: {
87
- exit: "invokeOnFocus",
88
81
  on: {
89
82
  PRESS_DOWN: {
90
83
  target: "before:spin",
91
- actions: ["focusInput", "setHint"],
84
+ actions: ["focusInput", "invokeOnFocus", "setHint"],
92
85
  },
93
86
  PRESS_DOWN_SCRUBBER: {
94
87
  target: "scrubbing",
95
- actions: ["focusInput", "setHint", "setCursorPoint"],
88
+ actions: ["focusInput", "invokeOnFocus", "setHint", "setCursorPoint"],
89
+ },
90
+ FOCUS: {
91
+ target: "focused",
92
+ actions: ["focusInput", "invokeOnFocus"],
96
93
  },
97
- FOCUS: "focused",
98
94
  },
99
95
  },
100
96
 
@@ -290,35 +286,37 @@ export function machine(userContext: UserDefinedContext) {
290
286
  actions: {
291
287
  focusInput(ctx) {
292
288
  if (!ctx.focusInputOnChange) return
293
- const input = dom.getInputEl(ctx)
294
- raf(() => input?.focus())
289
+ const inputEl = dom.getInputEl(ctx)
290
+ raf(() => {
291
+ inputEl?.focus()
292
+ })
295
293
  },
296
294
  increment(ctx, evt) {
297
- ctx.value = utils.increment(ctx, evt.step)
295
+ set.value(ctx, utils.increment(ctx, evt.step))
298
296
  },
299
297
  decrement(ctx, evt) {
300
- ctx.value = utils.decrement(ctx, evt.step)
298
+ set.value(ctx, utils.decrement(ctx, evt.step))
301
299
  },
302
300
  clampValue(ctx) {
303
- ctx.value = utils.clamp(ctx)
301
+ set.value(ctx, utils.clamp(ctx))
304
302
  },
305
303
  roundValue(ctx) {
306
- if (ctx.value !== "") {
307
- ctx.value = utils.round(ctx)
308
- }
304
+ if (ctx.value === "") return
305
+ set.value(ctx, utils.round(ctx))
309
306
  },
310
307
  setValue(ctx, evt) {
311
- const value = evt.target?.value ?? evt.value
312
- ctx.value = utils.sanitize(ctx, utils.parse(ctx, value.toString()))
308
+ let value = evt.target?.value ?? evt.value
309
+ value = utils.sanitize(ctx, utils.parse(ctx, value.toString()))
310
+ set.value(ctx, value)
313
311
  },
314
312
  clearValue(ctx) {
315
- ctx.value = ""
313
+ set.value(ctx, "")
316
314
  },
317
315
  setToMax(ctx) {
318
- ctx.value = ctx.max.toString()
316
+ set.value(ctx, ctx.max.toString())
319
317
  },
320
318
  setToMin(ctx) {
321
- ctx.value = ctx.min.toString()
319
+ set.value(ctx, ctx.min.toString())
322
320
  },
323
321
  setHint(ctx, evt) {
324
322
  ctx.hint = evt.hint
@@ -329,12 +327,6 @@ export function machine(userContext: UserDefinedContext) {
329
327
  setHintToSet(ctx) {
330
328
  ctx.hint = "set"
331
329
  },
332
- invokeOnChange(ctx) {
333
- ctx.onChange?.({
334
- value: ctx.value,
335
- valueAsNumber: ctx.valueAsNumber,
336
- })
337
- },
338
330
  invokeOnFocus(ctx, evt) {
339
331
  let srcElement: HTMLElement | null = null
340
332
 
@@ -353,10 +345,7 @@ export function machine(userContext: UserDefinedContext) {
353
345
  })
354
346
  },
355
347
  invokeOnBlur(ctx) {
356
- ctx.onBlur?.({
357
- value: ctx.value,
358
- valueAsNumber: ctx.valueAsNumber,
359
- })
348
+ ctx.onBlur?.({ value: ctx.value, valueAsNumber: ctx.valueAsNumber })
360
349
  },
361
350
  invokeOnInvalid(ctx) {
362
351
  if (!ctx.isOutOfRange) return
@@ -367,12 +356,16 @@ export function machine(userContext: UserDefinedContext) {
367
356
  valueAsNumber: ctx.valueAsNumber,
368
357
  })
369
358
  },
370
- // sync input value, in event it was set from form libraries via `ref`, `bind:this`, etc.
359
+ syncInputElement(ctx) {
360
+ const inputEl = dom.getInputEl(ctx)
361
+ dom.setValue(inputEl, ctx.formattedValue)
362
+ },
371
363
  syncInputValue(ctx) {
372
- const input = dom.getInputEl(ctx)
373
- if (!input || input.value == ctx.value) return
374
- const value = utils.parse(ctx, input.value)
375
- ctx.value = utils.sanitize(ctx, value)
364
+ const inputEl = dom.getInputEl(ctx)
365
+ if (!inputEl || inputEl.value == ctx.value) return
366
+
367
+ const value = utils.parse(ctx, inputEl.value)
368
+ set.value(ctx, utils.sanitize(ctx, value))
376
369
  },
377
370
  setCursorPoint(ctx, evt) {
378
371
  ctx.scrubberCursorPoint = evt.point
@@ -386,11 +379,25 @@ export function machine(userContext: UserDefinedContext) {
386
379
  const { x, y } = ctx.scrubberCursorPoint
387
380
  cursor.style.transform = `translate3d(${x}px, ${y}px, 0px)`
388
381
  },
389
- dispatchChangeEvent(ctx) {
390
- const inputEl = dom.getInputEl(ctx)
391
- dispatchInputValueEvent(inputEl, { value: ctx.formattedValue })
392
- },
393
382
  },
394
383
  },
395
384
  )
396
385
  }
386
+
387
+ const invoke = {
388
+ onChange: (ctx: MachineContext) => {
389
+ // invoke callback
390
+ ctx.onChange?.({ value: ctx.value, valueAsNumber: ctx.valueAsNumber })
391
+
392
+ // form event
393
+ const inputEl = dom.getInputEl(ctx)
394
+ dispatchInputValueEvent(inputEl, { value: ctx.formattedValue })
395
+ },
396
+ }
397
+
398
+ const set = {
399
+ value: (ctx: MachineContext, value: string) => {
400
+ ctx.value = value
401
+ invoke.onChange(ctx)
402
+ },
403
+ }
@@ -154,68 +154,6 @@ type PublicContext = DirectionProperty &
154
154
  spinOnPress?: boolean
155
155
  }
156
156
 
157
- export type PublicApi<T extends PropTypes = PropTypes> = {
158
- /**
159
- * Whether the input is focused.
160
- */
161
- isFocused: boolean
162
- /**
163
- * Whether the input is invalid.
164
- */
165
- isInvalid: boolean
166
- /**
167
- * Whether the input value is empty.
168
- */
169
- isValueEmpty: boolean
170
- /**
171
- * The formatted value of the input.
172
- */
173
- value: string
174
- /**
175
- * The value of the input as a number.
176
- */
177
- valueAsNumber: number
178
- /**
179
- * Function to set the value of the input.
180
- */
181
- setValue(value: string | number): void
182
- /**
183
- * Function to clear the value of the input.
184
- */
185
- clearValue(): void
186
- /**
187
- * Function to increment the value of the input by the step.
188
- */
189
- increment(): void
190
- /**
191
- * Function to decrement the value of the input by the step.
192
- */
193
- decrement(): void
194
- /**
195
- * Function to set the value of the input to the max.
196
- */
197
- setToMax(): void
198
- /**
199
- * Function to set the value of the input to the min.
200
- */
201
- setToMin(): void
202
- /**
203
- * Function to focus the input.
204
- */
205
- focus(): void
206
- /**
207
- * Function to blur the input.
208
- */
209
- blur(): void
210
- rootProps: T["element"]
211
- labelProps: T["label"]
212
- controlProps: T["element"]
213
- inputProps: T["input"]
214
- decrementTriggerProps: T["button"]
215
- incrementTriggerProps: T["button"]
216
- scrubberProps: T["element"]
217
- }
218
-
219
157
  export type UserDefinedContext = RequiredBy<PublicContext, "id">
220
158
 
221
159
  type ComputedContext = Readonly<{
@@ -294,3 +232,65 @@ export type MachineState = {
294
232
  export type State = S.State<MachineContext, MachineState>
295
233
 
296
234
  export type Send = S.Send<S.AnyEventObject>
235
+
236
+ export type MachineApi<T extends PropTypes = PropTypes> = {
237
+ /**
238
+ * Whether the input is focused.
239
+ */
240
+ isFocused: boolean
241
+ /**
242
+ * Whether the input is invalid.
243
+ */
244
+ isInvalid: boolean
245
+ /**
246
+ * Whether the input value is empty.
247
+ */
248
+ isValueEmpty: boolean
249
+ /**
250
+ * The formatted value of the input.
251
+ */
252
+ value: string
253
+ /**
254
+ * The value of the input as a number.
255
+ */
256
+ valueAsNumber: number
257
+ /**
258
+ * Function to set the value of the input.
259
+ */
260
+ setValue(value: string | number): void
261
+ /**
262
+ * Function to clear the value of the input.
263
+ */
264
+ clearValue(): void
265
+ /**
266
+ * Function to increment the value of the input by the step.
267
+ */
268
+ increment(): void
269
+ /**
270
+ * Function to decrement the value of the input by the step.
271
+ */
272
+ decrement(): void
273
+ /**
274
+ * Function to set the value of the input to the max.
275
+ */
276
+ setToMax(): void
277
+ /**
278
+ * Function to set the value of the input to the min.
279
+ */
280
+ setToMin(): void
281
+ /**
282
+ * Function to focus the input.
283
+ */
284
+ focus(): void
285
+ /**
286
+ * Function to blur the input.
287
+ */
288
+ blur(): void
289
+ rootProps: T["element"]
290
+ labelProps: T["label"]
291
+ controlProps: T["element"]
292
+ inputProps: T["input"]
293
+ decrementTriggerProps: T["button"]
294
+ incrementTriggerProps: T["button"]
295
+ scrubberProps: T["element"]
296
+ }