@zag-js/pin-input 0.9.2 → 0.10.1

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/pin-input",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
4
4
  "description": "Core logic for the pin-input widget implemented as a state machine",
5
5
  "keywords": [
6
6
  "js",
@@ -17,7 +17,8 @@
17
17
  "repository": "https://github.com/chakra-ui/zag/tree/main/packages/pin-input",
18
18
  "sideEffects": false,
19
19
  "files": [
20
- "dist/**/*"
20
+ "dist",
21
+ "src"
21
22
  ],
22
23
  "publishConfig": {
23
24
  "access": "public"
@@ -26,14 +27,14 @@
26
27
  "url": "https://github.com/chakra-ui/zag/issues"
27
28
  },
28
29
  "dependencies": {
29
- "@zag-js/anatomy": "0.9.2",
30
- "@zag-js/dom-query": "0.9.2",
31
- "@zag-js/dom-event": "0.9.2",
32
- "@zag-js/form-utils": "0.9.2",
33
- "@zag-js/visually-hidden": "0.9.2",
34
- "@zag-js/utils": "0.9.2",
35
- "@zag-js/core": "0.9.2",
36
- "@zag-js/types": "0.9.2"
30
+ "@zag-js/anatomy": "0.10.1",
31
+ "@zag-js/dom-query": "0.10.1",
32
+ "@zag-js/dom-event": "0.10.1",
33
+ "@zag-js/form-utils": "0.10.1",
34
+ "@zag-js/visually-hidden": "0.10.1",
35
+ "@zag-js/utils": "0.10.1",
36
+ "@zag-js/core": "0.10.1",
37
+ "@zag-js/types": "0.10.1"
37
38
  },
38
39
  "devDependencies": {
39
40
  "clean-package": "2.2.0"
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { anatomy } from "./pin-input.anatomy"
2
+ export { connect } from "./pin-input.connect"
3
+ export { machine } from "./pin-input.machine"
4
+ export type { UserDefinedContext as Context } from "./pin-input.types"
@@ -0,0 +1,4 @@
1
+ import { createAnatomy } from "@zag-js/anatomy"
2
+
3
+ export const anatomy = createAnatomy("pinInput").parts("root", "label", "hiddenInput", "input", "control")
4
+ export const parts = anatomy.build()
@@ -0,0 +1,174 @@
1
+ import { EventKeyMap, getEventKey, getNativeEvent, isModifiedEvent } from "@zag-js/dom-event"
2
+ import { ariaAttr, dataAttr } from "@zag-js/dom-query"
3
+ import type { NormalizeProps, PropTypes } from "@zag-js/types"
4
+ import { invariant } from "@zag-js/utils"
5
+ import { visuallyHiddenStyle } from "@zag-js/visually-hidden"
6
+ import { parts } from "./pin-input.anatomy"
7
+ import { dom } from "./pin-input.dom"
8
+ import type { Send, State } from "./pin-input.types"
9
+
10
+ export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>) {
11
+ const isValueComplete = state.context.isValueComplete
12
+ const isInvalid = state.context.invalid
13
+ const focusedIndex = state.context.focusedIndex
14
+ const translations = state.context.translations
15
+
16
+ function focus() {
17
+ dom.getFirstInputEl(state.context)?.focus()
18
+ }
19
+
20
+ return {
21
+ /**
22
+ * The value of the input as an array of strings.
23
+ */
24
+ value: state.context.value,
25
+ /**
26
+ * The value of the input as a string.
27
+ */
28
+ valueAsString: state.context.valueAsString,
29
+ /**
30
+ * Whether all inputs are filled.
31
+ */
32
+ isValueComplete: isValueComplete,
33
+ /**
34
+ * Function to set the value of the inputs.
35
+ */
36
+ setValue(value: string[]) {
37
+ if (!Array.isArray(value)) {
38
+ invariant("[pin-input/setValue] value must be an array")
39
+ }
40
+ send({ type: "SET_VALUE", value })
41
+ },
42
+ /**
43
+ * Function to clear the value of the inputs.
44
+ */
45
+ clearValue() {
46
+ send({ type: "CLEAR_VALUE" })
47
+ },
48
+ /**
49
+ * Function to set the value of the input at a specific index.
50
+ */
51
+ setValueAtIndex(index: number, value: string) {
52
+ send({ type: "SET_VALUE", value, index })
53
+ },
54
+ /**
55
+ * Function to focus the pin-input. This will focus the first input.
56
+ */
57
+ focus,
58
+
59
+ rootProps: normalize.element({
60
+ dir: state.context.dir,
61
+ ...parts.root.attrs,
62
+ id: dom.getRootId(state.context),
63
+ "data-invalid": dataAttr(isInvalid),
64
+ "data-disabled": dataAttr(state.context.disabled),
65
+ "data-complete": dataAttr(isValueComplete),
66
+ }),
67
+
68
+ labelProps: normalize.label({
69
+ ...parts.label.attrs,
70
+ htmlFor: dom.getHiddenInputId(state.context),
71
+ id: dom.getLabelId(state.context),
72
+ "data-invalid": dataAttr(isInvalid),
73
+ "data-disabled": dataAttr(state.context.disabled),
74
+ "data-complete": dataAttr(isValueComplete),
75
+ onClick: (event) => {
76
+ event.preventDefault()
77
+ focus()
78
+ },
79
+ }),
80
+
81
+ hiddenInputProps: normalize.input({
82
+ ...parts.hiddenInput.attrs,
83
+ "aria-hidden": true,
84
+ type: "text",
85
+ tabIndex: -1,
86
+ id: dom.getHiddenInputId(state.context),
87
+ name: state.context.name,
88
+ form: state.context.form,
89
+ style: visuallyHiddenStyle,
90
+ maxLength: state.context.valueLength,
91
+ defaultValue: state.context.valueAsString,
92
+ }),
93
+
94
+ controlProps: normalize.element({
95
+ ...parts.control.attrs,
96
+ id: dom.getControlId(state.context),
97
+ }),
98
+
99
+ getInputProps({ index }: { index: number }) {
100
+ const inputType = state.context.type === "numeric" ? "tel" : "text"
101
+ return normalize.input({
102
+ ...parts.input.attrs,
103
+ disabled: state.context.disabled,
104
+ "data-disabled": dataAttr(state.context.disabled),
105
+ "data-complete": dataAttr(isValueComplete),
106
+ id: dom.getInputId(state.context, index.toString()),
107
+ "data-ownedby": dom.getRootId(state.context),
108
+ "aria-label": translations.inputLabel(index, state.context.valueLength),
109
+ inputMode: state.context.otp || state.context.type === "numeric" ? "numeric" : "text",
110
+ "aria-invalid": ariaAttr(isInvalid),
111
+ "data-invalid": dataAttr(isInvalid),
112
+ type: state.context.mask ? "password" : inputType,
113
+ defaultValue: state.context.value[index] || "",
114
+ autoCapitalize: "none",
115
+ autoComplete: state.context.otp ? "one-time-code" : "off",
116
+ placeholder: focusedIndex === index ? "" : state.context.placeholder,
117
+ onChange(event) {
118
+ const evt = getNativeEvent(event)
119
+ const { value } = event.currentTarget
120
+ if (evt.inputType === "insertFromPaste" || value.length > 2) {
121
+ send({ type: "PASTE", value })
122
+ event.preventDefault()
123
+ return
124
+ }
125
+
126
+ if (evt.inputType === "deleteContentBackward") {
127
+ send("BACKSPACE")
128
+ return
129
+ }
130
+ send({ type: "INPUT", value, index })
131
+ },
132
+ onKeyDown(event) {
133
+ const evt = getNativeEvent(event)
134
+ if (evt.isComposing || isModifiedEvent(evt)) return
135
+
136
+ const keyMap: EventKeyMap = {
137
+ Backspace() {
138
+ send("BACKSPACE")
139
+ },
140
+ Delete() {
141
+ send("DELETE")
142
+ },
143
+ ArrowLeft() {
144
+ send("ARROW_LEFT")
145
+ },
146
+ ArrowRight() {
147
+ send("ARROW_RIGHT")
148
+ },
149
+ Enter() {
150
+ send("ENTER")
151
+ },
152
+ }
153
+
154
+ const key = getEventKey(event, { dir: state.context.dir })
155
+ const exec = keyMap[key]
156
+
157
+ if (exec) {
158
+ exec(event)
159
+ event.preventDefault()
160
+ } else {
161
+ if (key === "Tab") return
162
+ send({ type: "KEY_DOWN", value: key, preventDefault: () => event.preventDefault() })
163
+ }
164
+ },
165
+ onFocus() {
166
+ send({ type: "FOCUS", index })
167
+ },
168
+ onBlur() {
169
+ send({ type: "BLUR", index })
170
+ },
171
+ })
172
+ },
173
+ }
174
+ }
@@ -0,0 +1,21 @@
1
+ import { createScope, queryAll } from "@zag-js/dom-query"
2
+ import type { MachineContext as Ctx } from "./pin-input.types"
3
+
4
+ export const dom = createScope({
5
+ getRootId: (ctx: Ctx) => ctx.ids?.root ?? `pin-input:${ctx.id}`,
6
+ getInputId: (ctx: Ctx, id: string) => ctx.ids?.input?.(id) ?? `pin-input:${ctx.id}:${id}`,
7
+ getHiddenInputId: (ctx: Ctx) => ctx.ids?.hiddenInput ?? `pin-input:${ctx.id}:hidden`,
8
+ getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `pin-input:${ctx.id}:label`,
9
+ getControlId: (ctx: Ctx) => ctx.ids?.control ?? `pin-input:${ctx.id}:control`,
10
+
11
+ getRootEl: (ctx: Ctx) => dom.getById(ctx, dom.getRootId(ctx)),
12
+ getElements: (ctx: Ctx) => {
13
+ const ownerId = CSS.escape(dom.getRootId(ctx))
14
+ const selector = `input[data-ownedby=${ownerId}]`
15
+ return queryAll<HTMLInputElement>(dom.getRootEl(ctx), selector)
16
+ },
17
+ getInputEl: (ctx: Ctx, id: string) => dom.getById<HTMLInputElement>(ctx, dom.getInputId(ctx, id)),
18
+ getFocusedInputEl: (ctx: Ctx) => dom.getElements(ctx)[ctx.focusedIndex],
19
+ getFirstInputEl: (ctx: Ctx) => dom.getElements(ctx)[0],
20
+ getHiddenInputEl: (ctx: Ctx) => dom.getById<HTMLInputElement>(ctx, dom.getHiddenInputId(ctx)),
21
+ })
@@ -0,0 +1,284 @@
1
+ import { createMachine, guards } from "@zag-js/core"
2
+ import { raf } from "@zag-js/dom-query"
3
+ import { dispatchInputValueEvent } from "@zag-js/form-utils"
4
+ import { compact } from "@zag-js/utils"
5
+ import { dom } from "./pin-input.dom"
6
+ import type { MachineContext, MachineState, UserDefinedContext } from "./pin-input.types"
7
+
8
+ const { and, not } = guards
9
+
10
+ export function machine(userContext: UserDefinedContext) {
11
+ const ctx = compact(userContext)
12
+ return createMachine<MachineContext, MachineState>(
13
+ {
14
+ id: "pin-input",
15
+ initial: ctx.autoFocus ? "focused" : "idle",
16
+ context: {
17
+ value: [],
18
+ focusedIndex: -1,
19
+ placeholder: "○",
20
+ otp: false,
21
+ type: "numeric",
22
+ ...ctx,
23
+ translations: {
24
+ inputLabel: (index, length) => `pin code ${index + 1} of ${length}`,
25
+ ...ctx.translations,
26
+ },
27
+ },
28
+
29
+ computed: {
30
+ valueLength: (ctx) => ctx.value.length,
31
+ filledValueLength: (ctx) => ctx.value.filter((v) => v?.trim() !== "").length,
32
+ isValueComplete: (ctx) => ctx.valueLength === ctx.filledValueLength,
33
+ valueAsString: (ctx) => ctx.value.join(""),
34
+ focusedValue: (ctx) => ctx.value[ctx.focusedIndex],
35
+ },
36
+
37
+ watch: {
38
+ focusedIndex: ["focusInput", "setInputSelection"],
39
+ value: ["dispatchInputEvent", "syncInputElements"],
40
+ isValueComplete: ["invokeOnComplete", "blurFocusedInputIfNeeded"],
41
+ },
42
+
43
+ entry: ctx.autoFocus ? ["setupValue", "setFocusIndexToFirst"] : ["setupValue"],
44
+
45
+ on: {
46
+ SET_VALUE: [
47
+ {
48
+ guard: "hasIndex",
49
+ actions: ["setValueAtIndex", "invokeOnChange"],
50
+ },
51
+ { actions: ["setValue", "invokeOnChange"] },
52
+ ],
53
+ CLEAR_VALUE: [
54
+ {
55
+ guard: "isDisabled",
56
+ actions: ["clearValue", "invokeOnChange"],
57
+ },
58
+ {
59
+ actions: ["clearValue", "invokeOnChange", "setFocusIndexToFirst"],
60
+ },
61
+ ],
62
+ },
63
+
64
+ states: {
65
+ idle: {
66
+ on: {
67
+ FOCUS: {
68
+ target: "focused",
69
+ actions: "setFocusedIndex",
70
+ },
71
+ },
72
+ },
73
+ focused: {
74
+ on: {
75
+ INPUT: [
76
+ {
77
+ guard: and("isFinalValue", "isValidValue"),
78
+ actions: ["setFocusedValue", "invokeOnChange", "syncInputValue"],
79
+ },
80
+ {
81
+ guard: "isValidValue",
82
+ actions: ["setFocusedValue", "invokeOnChange", "setNextFocusedIndex", "syncInputValue"],
83
+ },
84
+ ],
85
+ PASTE: [
86
+ {
87
+ guard: "isValidValue",
88
+ actions: ["setPastedValue", "invokeOnChange", "setLastValueFocusIndex"],
89
+ },
90
+ { actions: ["resetFocusedValue", "invokeOnChange"] },
91
+ ],
92
+ BLUR: {
93
+ target: "idle",
94
+ actions: "clearFocusedIndex",
95
+ },
96
+ DELETE: {
97
+ guard: "hasValue",
98
+ actions: ["clearFocusedValue", "invokeOnChange"],
99
+ },
100
+ ARROW_LEFT: {
101
+ actions: "setPrevFocusedIndex",
102
+ },
103
+ ARROW_RIGHT: {
104
+ actions: "setNextFocusedIndex",
105
+ },
106
+ BACKSPACE: [
107
+ {
108
+ guard: "hasValue",
109
+ actions: ["clearFocusedValue", "invokeOnChange"],
110
+ },
111
+ {
112
+ actions: ["setPrevFocusedIndex", "clearFocusedValue", "invokeOnChange"],
113
+ },
114
+ ],
115
+ ENTER: {
116
+ guard: "isValueComplete",
117
+ actions: "requestFormSubmit",
118
+ },
119
+ KEY_DOWN: {
120
+ guard: not("isValidValue"),
121
+ actions: ["preventDefault", "invokeOnInvalid"],
122
+ },
123
+ },
124
+ },
125
+ },
126
+ },
127
+ {
128
+ guards: {
129
+ autoFocus: (ctx) => !!ctx.autoFocus,
130
+ isValueEmpty: (_ctx, evt) => evt.value === "",
131
+ hasValue: (ctx) => ctx.value[ctx.focusedIndex] !== "",
132
+ isValueComplete: (ctx) => ctx.isValueComplete,
133
+ isValidValue: (ctx, evt) => {
134
+ if (!ctx.pattern) return isValidType(evt.value, ctx.type)
135
+ const regex = new RegExp(ctx.pattern, "g")
136
+ return regex.test(evt.value)
137
+ },
138
+ isFinalValue: (ctx) => {
139
+ return (
140
+ ctx.filledValueLength + 1 === ctx.valueLength &&
141
+ ctx.value.findIndex((v) => v.trim() === "") === ctx.focusedIndex
142
+ )
143
+ },
144
+ isLastInputFocused: (ctx) => ctx.focusedIndex === ctx.valueLength - 1,
145
+ hasIndex: (_ctx, evt) => evt.index !== undefined,
146
+ isDisabled: (ctx) => !!ctx.disabled,
147
+ },
148
+ actions: {
149
+ setupValue: (ctx) => {
150
+ if (ctx.value.length) return
151
+ const inputs = dom.getElements(ctx)
152
+ const emptyValues = Array.from<string>({ length: inputs.length }).fill("")
153
+ assign(ctx, emptyValues)
154
+ },
155
+ focusInput: (ctx) => {
156
+ raf(() => {
157
+ if (ctx.focusedIndex === -1) return
158
+ dom.getFocusedInputEl(ctx)?.focus()
159
+ })
160
+ },
161
+ setInputSelection: (ctx) => {
162
+ raf(() => {
163
+ if (ctx.focusedIndex === -1) return
164
+ const input = dom.getFocusedInputEl(ctx)
165
+ const length = input.value.length
166
+ input.selectionStart = ctx.selectOnFocus ? 0 : length
167
+ input.selectionEnd = length
168
+ })
169
+ },
170
+ invokeOnComplete: (ctx) => {
171
+ if (!ctx.isValueComplete) return
172
+ ctx.onComplete?.({ value: Array.from(ctx.value), valueAsString: ctx.valueAsString })
173
+ },
174
+ invokeOnChange: (ctx) => {
175
+ ctx.onChange?.({ value: Array.from(ctx.value) })
176
+ },
177
+ dispatchInputEvent: (ctx) => {
178
+ const inputEl = dom.getHiddenInputEl(ctx)
179
+ dispatchInputValueEvent(inputEl, { value: ctx.valueAsString })
180
+ },
181
+ invokeOnInvalid: (ctx, evt) => {
182
+ ctx.onInvalid?.({ value: evt.value, index: ctx.focusedIndex })
183
+ },
184
+ clearFocusedIndex: (ctx) => {
185
+ ctx.focusedIndex = -1
186
+ },
187
+ setValue: (ctx, evt) => {
188
+ assign(ctx, evt.value)
189
+ },
190
+ setFocusedIndex: (ctx, evt) => {
191
+ ctx.focusedIndex = evt.index
192
+ },
193
+ setFocusedValue: (ctx, evt) => {
194
+ ctx.value[ctx.focusedIndex] = getNextValue(ctx.focusedValue, evt.value)
195
+ },
196
+ syncInputValue(ctx, evt) {
197
+ const input = dom.getInputEl(ctx, evt.index.toString())
198
+ if (!input) return
199
+ input.value = ctx.value[evt.index]
200
+ },
201
+ syncInputElements(ctx) {
202
+ const inputs = dom.getElements(ctx)
203
+ inputs.forEach((input, index) => {
204
+ input.value = ctx.value[index]
205
+ })
206
+ },
207
+ setPastedValue(ctx, evt) {
208
+ raf(() => {
209
+ const startIndex = ctx.focusedValue ? 1 : 0
210
+ const value = evt.value.substring(startIndex, startIndex + ctx.valueLength)
211
+ assign(ctx, value)
212
+ })
213
+ },
214
+ setValueAtIndex: (ctx, evt) => {
215
+ ctx.value[evt.index] = getNextValue(ctx.focusedValue, evt.value)
216
+ },
217
+ clearValue: (ctx) => {
218
+ const nextValue = Array.from<string>({ length: ctx.valueLength }).fill("")
219
+ assign(ctx, nextValue)
220
+ },
221
+ clearFocusedValue: (ctx) => {
222
+ ctx.value[ctx.focusedIndex] = ""
223
+ },
224
+ resetFocusedValue: (ctx) => {
225
+ const input = dom.getFocusedInputEl(ctx)
226
+ input.value = ctx.focusedValue
227
+ },
228
+ setFocusIndexToFirst: (ctx) => {
229
+ ctx.focusedIndex = 0
230
+ },
231
+ setNextFocusedIndex: (ctx) => {
232
+ ctx.focusedIndex = Math.min(ctx.focusedIndex + 1, ctx.valueLength - 1)
233
+ },
234
+ setPrevFocusedIndex: (ctx) => {
235
+ ctx.focusedIndex = Math.max(ctx.focusedIndex - 1, 0)
236
+ },
237
+ setLastValueFocusIndex: (ctx) => {
238
+ raf(() => {
239
+ ctx.focusedIndex = Math.min(ctx.filledValueLength, ctx.valueLength - 1)
240
+ })
241
+ },
242
+ preventDefault(_, evt) {
243
+ evt.preventDefault()
244
+ },
245
+ blurFocusedInputIfNeeded(ctx) {
246
+ if (!ctx.blurOnComplete) return
247
+ raf(() => {
248
+ dom.getFocusedInputEl(ctx)?.blur()
249
+ })
250
+ },
251
+ requestFormSubmit(ctx) {
252
+ if (!ctx.name || !ctx.isValueComplete) return
253
+ const input = dom.getHiddenInputEl(ctx)
254
+ input?.form?.requestSubmit()
255
+ },
256
+ },
257
+ },
258
+ )
259
+ }
260
+
261
+ const REGEX = {
262
+ numeric: /^[0-9]+$/,
263
+ alphabetic: /^[A-Za-z]+$/,
264
+ alphanumeric: /^[a-zA-Z0-9]+$/i,
265
+ }
266
+
267
+ function isValidType(value: string, type: MachineContext["type"]) {
268
+ if (!type) return true
269
+ return !!REGEX[type]?.test(value)
270
+ }
271
+
272
+ function assign(ctx: MachineContext, value: string | string[]) {
273
+ const arr = Array.isArray(value) ? value : value.split("").filter(Boolean)
274
+ arr.forEach((value, index) => {
275
+ ctx.value[index] = value
276
+ })
277
+ }
278
+
279
+ function getNextValue(current: string, next: string) {
280
+ let nextValue = next
281
+ if (current[0] === next[0]) nextValue = next[1]
282
+ else if (current[0] === next[1]) nextValue = next[0]
283
+ return nextValue
284
+ }
@@ -0,0 +1,139 @@
1
+ import type { StateMachine as S } from "@zag-js/core"
2
+ import type { CommonProperties, Context, DirectionProperty, RequiredBy } from "@zag-js/types"
3
+
4
+ type IntlTranslations = {
5
+ inputLabel: (index: number, length: number) => string
6
+ }
7
+
8
+ type ElementIds = Partial<{
9
+ root: string
10
+ hiddenInput: string
11
+ label: string
12
+ control: string
13
+ input(id: string): string
14
+ }>
15
+
16
+ type PublicContext = DirectionProperty &
17
+ CommonProperties & {
18
+ /**
19
+ * The name of the input element. Useful for form submission.
20
+ */
21
+ name?: string
22
+ /**
23
+ * The associate form of the underlying input element.
24
+ */
25
+ form?: string
26
+ /**
27
+ * The regular expression that the user-entered input value is checked against.
28
+ */
29
+ pattern?: string
30
+ /**
31
+ * The ids of the elements in the pin input. Useful for composition.
32
+ */
33
+ ids?: ElementIds
34
+ /**
35
+ * Whether the inputs are disabled
36
+ */
37
+ disabled?: boolean
38
+ /**
39
+ * The placeholder text for the input
40
+ */
41
+ placeholder?: string
42
+ /**
43
+ * Whether to auto-focus the first input.
44
+ */
45
+ autoFocus?: boolean
46
+ /**
47
+ * Whether the pin input is in the invalid state
48
+ */
49
+ invalid?: boolean
50
+ /**
51
+ * If `true`, the pin input component signals to its fields that they should
52
+ * use `autocomplete="one-time-code"`.
53
+ */
54
+ otp?: boolean
55
+ /**
56
+ * The value of the the pin input.
57
+ */
58
+ value: string[]
59
+ /**
60
+ * The type of value the pin-input should allow
61
+ */
62
+ type?: "alphanumeric" | "numeric" | "alphabetic"
63
+ /**
64
+ * Function called when all inputs have valid values
65
+ */
66
+ onComplete?: (details: { value: string[]; valueAsString: string }) => void
67
+ /**
68
+ * Function called on input change
69
+ */
70
+ onChange?: (details: { value: string[] }) => void
71
+ /**
72
+ * Function called when an invalid value is entered
73
+ */
74
+ onInvalid?: (details: { value: string; index: number }) => void
75
+ /**
76
+ * If `true`, the input's value will be masked just like `type=password`
77
+ */
78
+ mask?: boolean
79
+ /**
80
+ * Whether to blur the input when the value is complete
81
+ */
82
+ blurOnComplete?: boolean
83
+ /**
84
+ * Whether to select input value when input is focused
85
+ */
86
+ selectOnFocus?: boolean
87
+ /**
88
+ * Specifies the localized strings that identifies the accessibility elements and their states
89
+ */
90
+ translations: IntlTranslations
91
+ }
92
+
93
+ export type UserDefinedContext = RequiredBy<PublicContext, "id">
94
+
95
+ type ComputedContext = Readonly<{
96
+ /**
97
+ * @computed
98
+ * The number of inputs
99
+ */
100
+ valueLength: number
101
+ /**
102
+ * @computed
103
+ * The number of inputs that are not empty
104
+ */
105
+ filledValueLength: number
106
+ /**
107
+ * @computed
108
+ * Whether all input values are valid
109
+ */
110
+ isValueComplete: boolean
111
+ /**
112
+ * @computed
113
+ * The string representation of the input values
114
+ */
115
+ valueAsString: string
116
+ /**
117
+ * @computed
118
+ * The value at focused index
119
+ */
120
+ focusedValue: string
121
+ }>
122
+
123
+ type PrivateContext = Context<{
124
+ /**
125
+ * @internal
126
+ * The index of the input field that has focus
127
+ */
128
+ focusedIndex: number
129
+ }>
130
+
131
+ export type MachineContext = PublicContext & PrivateContext & ComputedContext
132
+
133
+ export type MachineState = {
134
+ value: "idle" | "focused"
135
+ }
136
+
137
+ export type State = S.State<MachineContext, MachineState>
138
+
139
+ export type Send = S.Send<S.AnyEventObject>