@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.
- package/dist/index.d.mts +9 -26
- package/dist/index.d.ts +9 -26
- package/dist/index.js +260 -176
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +249 -165
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -9
- package/src/cursor.ts +51 -0
- package/src/number-input.anatomy.ts +1 -0
- package/src/number-input.connect.ts +78 -46
- package/src/number-input.machine.ts +138 -83
- package/src/number-input.types.ts +30 -26
- package/src/number-input.utils.ts +0 -42
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
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
|
|
6
|
-
import { callAll, compact, isEqual
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
79
|
+
"VALUE.SET": {
|
|
65
80
|
actions: ["setValue", "clampValue", "setHintToSet"],
|
|
66
81
|
},
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
130
|
+
"INPUT.HOME": {
|
|
131
|
+
actions: "decrementToMin",
|
|
117
132
|
},
|
|
118
|
-
END: {
|
|
119
|
-
actions: "
|
|
133
|
+
"INPUT.END": {
|
|
134
|
+
actions: "incrementToMax",
|
|
120
135
|
},
|
|
121
|
-
CHANGE: {
|
|
136
|
+
"INPUT.CHANGE": {
|
|
122
137
|
actions: ["setValue", "setHint"],
|
|
123
138
|
},
|
|
124
|
-
|
|
139
|
+
"INPUT.COMMIT": [
|
|
125
140
|
{
|
|
126
|
-
guard: "
|
|
141
|
+
guard: and("clampValueOnBlur", not("isInRange")),
|
|
127
142
|
target: "idle",
|
|
128
|
-
actions: ["
|
|
143
|
+
actions: ["clampValue", "syncInputElement", "clearHint", "invokeOnBlur"],
|
|
129
144
|
},
|
|
130
145
|
{
|
|
131
|
-
guard: and("clampOnBlur", not("isInRange"), not("isEmptyValue")),
|
|
132
146
|
target: "idle",
|
|
133
|
-
actions: ["
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
|
240
|
-
if (!
|
|
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
|
-
|
|
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: "
|
|
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("
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
})
|
|
312
|
+
if (dom.isActiveElement(ctx, inputEl)) return
|
|
313
|
+
raf(() => inputEl?.focus({ preventScroll: true }))
|
|
292
314
|
},
|
|
293
315
|
increment(ctx, evt) {
|
|
294
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
315
|
-
set.value(ctx, ctx.max
|
|
334
|
+
incrementToMax(ctx) {
|
|
335
|
+
set.value(ctx, String(ctx.max))
|
|
316
336
|
},
|
|
317
|
-
|
|
318
|
-
set.value(ctx, ctx.min
|
|
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
|
|
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
|
-
|
|
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
|
|
377
|
-
if (!
|
|
398
|
+
const cursorEl = dom.getCursorEl(ctx)
|
|
399
|
+
if (!cursorEl || !ctx.scrubberCursorPoint) return
|
|
378
400
|
const { x, y } = ctx.scrubberCursorPoint
|
|
379
|
-
|
|
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?.({
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
}
|