@zag-js/pin-input 0.82.1 → 1.0.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.mjs CHANGED
@@ -1,28 +1,26 @@
1
1
  import { createAnatomy } from '@zag-js/anatomy';
2
- import { createScope, queryAll, dataAttr, visuallyHiddenStyle, ariaAttr, getBeforeInputValue, getNativeEvent, isComposingEvent, isModifierKey, getEventKey, raf, dispatchInputValueEvent } from '@zag-js/dom-query';
3
- import { invariant, compact, isEqual } from '@zag-js/utils';
4
- import { createMachine, choose } from '@zag-js/core';
2
+ import { dispatchInputValueEvent, raf, setElementValue, queryAll, dataAttr, visuallyHiddenStyle, ariaAttr, getBeforeInputValue, getNativeEvent, isComposingEvent, isModifierKey, getEventKey } from '@zag-js/dom-query';
3
+ import { isEqual, createSplitProps, invariant } from '@zag-js/utils';
4
+ import { setup } from '@zag-js/core';
5
+ import { createProps } from '@zag-js/types';
5
6
 
6
7
  // src/pin-input.anatomy.ts
7
8
  var anatomy = createAnatomy("pinInput").parts("root", "label", "input", "control");
8
9
  var parts = anatomy.build();
9
- var dom = createScope({
10
- getRootId: (ctx) => ctx.ids?.root ?? `pin-input:${ctx.id}`,
11
- getInputId: (ctx, id) => ctx.ids?.input?.(id) ?? `pin-input:${ctx.id}:${id}`,
12
- getHiddenInputId: (ctx) => ctx.ids?.hiddenInput ?? `pin-input:${ctx.id}:hidden`,
13
- getLabelId: (ctx) => ctx.ids?.label ?? `pin-input:${ctx.id}:label`,
14
- getControlId: (ctx) => ctx.ids?.control ?? `pin-input:${ctx.id}:control`,
15
- getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
16
- getInputEls: (ctx) => {
17
- const ownerId = CSS.escape(dom.getRootId(ctx));
18
- const selector = `input[data-ownedby=${ownerId}]`;
19
- return queryAll(dom.getRootEl(ctx), selector);
20
- },
21
- getInputEl: (ctx, id) => dom.getById(ctx, dom.getInputId(ctx, id)),
22
- getFocusedInputEl: (ctx) => dom.getInputEls(ctx)[ctx.focusedIndex],
23
- getFirstInputEl: (ctx) => dom.getInputEls(ctx)[0],
24
- getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx))
25
- });
10
+ var getRootId = (ctx) => ctx.ids?.root ?? `pin-input:${ctx.id}`;
11
+ var getInputId = (ctx, id) => ctx.ids?.input?.(id) ?? `pin-input:${ctx.id}:${id}`;
12
+ var getHiddenInputId = (ctx) => ctx.ids?.hiddenInput ?? `pin-input:${ctx.id}:hidden`;
13
+ var getLabelId = (ctx) => ctx.ids?.label ?? `pin-input:${ctx.id}:label`;
14
+ var getControlId = (ctx) => ctx.ids?.control ?? `pin-input:${ctx.id}:control`;
15
+ var getRootEl = (ctx) => ctx.getById(getRootId(ctx));
16
+ var getInputEls = (ctx) => {
17
+ const ownerId = CSS.escape(getRootId(ctx));
18
+ const selector = `input[data-ownedby=${ownerId}]`;
19
+ return queryAll(getRootEl(ctx), selector);
20
+ };
21
+ var getInputElAtIndex = (ctx, index) => getInputEls(ctx)[index];
22
+ var getFirstInputEl = (ctx) => getInputEls(ctx)[0];
23
+ var getHiddenInputEl = (ctx) => ctx.getById(getHiddenInputId(ctx));
26
24
 
27
25
  // src/pin-input.utils.ts
28
26
  var REGEX = {
@@ -30,29 +28,30 @@ var REGEX = {
30
28
  alphabetic: /^[A-Za-z]+$/,
31
29
  alphanumeric: /^[a-zA-Z0-9]+$/i
32
30
  };
33
- function isValidType(ctx, value) {
34
- if (!ctx.type) return true;
35
- return !!REGEX[ctx.type]?.test(value);
31
+ function isValidType(type, value) {
32
+ if (!type) return true;
33
+ return !!REGEX[type]?.test(value);
36
34
  }
37
- function isValidValue(ctx, value) {
38
- if (!ctx.pattern) return isValidType(ctx, value);
39
- const regex = new RegExp(ctx.pattern, "g");
35
+ function isValidValue(value, type, pattern) {
36
+ if (!pattern) return isValidType(type, value);
37
+ const regex = new RegExp(pattern, "g");
40
38
  return regex.test(value);
41
39
  }
42
40
 
43
41
  // src/pin-input.connect.ts
44
- function connect(state, send, normalize) {
45
- const complete = state.context.isValueComplete;
46
- const invalid = state.context.invalid;
47
- const focusedIndex = state.context.focusedIndex;
48
- const translations = state.context.translations;
42
+ function connect(service, normalize) {
43
+ const { send, context, computed, prop, scope } = service;
44
+ const complete = computed("isValueComplete");
45
+ const invalid = prop("invalid");
46
+ const translations = prop("translations");
47
+ const focusedIndex = context.get("focusedIndex");
49
48
  function focus() {
50
- dom.getFirstInputEl(state.context)?.focus();
49
+ getFirstInputEl(scope)?.focus();
51
50
  }
52
51
  return {
53
52
  focus,
54
- value: state.context.value,
55
- valueAsString: state.context.valueAsString,
53
+ value: context.get("value"),
54
+ valueAsString: computed("valueAsString"),
56
55
  complete,
57
56
  setValue(value) {
58
57
  if (!Array.isArray(value)) {
@@ -68,25 +67,25 @@ function connect(state, send, normalize) {
68
67
  },
69
68
  getRootProps() {
70
69
  return normalize.element({
71
- dir: state.context.dir,
70
+ dir: prop("dir"),
72
71
  ...parts.root.attrs,
73
- id: dom.getRootId(state.context),
72
+ id: getRootId(scope),
74
73
  "data-invalid": dataAttr(invalid),
75
- "data-disabled": dataAttr(state.context.disabled),
74
+ "data-disabled": dataAttr(prop("disabled")),
76
75
  "data-complete": dataAttr(complete),
77
- "data-readonly": dataAttr(state.context.readOnly)
76
+ "data-readonly": dataAttr(prop("readOnly"))
78
77
  });
79
78
  },
80
79
  getLabelProps() {
81
80
  return normalize.label({
82
81
  ...parts.label.attrs,
83
- dir: state.context.dir,
84
- htmlFor: dom.getHiddenInputId(state.context),
85
- id: dom.getLabelId(state.context),
82
+ dir: prop("dir"),
83
+ htmlFor: getHiddenInputId(scope),
84
+ id: getLabelId(scope),
86
85
  "data-invalid": dataAttr(invalid),
87
- "data-disabled": dataAttr(state.context.disabled),
86
+ "data-disabled": dataAttr(prop("disabled")),
88
87
  "data-complete": dataAttr(complete),
89
- "data-readonly": dataAttr(state.context.readOnly),
88
+ "data-readonly": dataAttr(prop("readOnly")),
90
89
  onClick(event) {
91
90
  event.preventDefault();
92
91
  focus();
@@ -98,49 +97,49 @@ function connect(state, send, normalize) {
98
97
  "aria-hidden": true,
99
98
  type: "text",
100
99
  tabIndex: -1,
101
- id: dom.getHiddenInputId(state.context),
102
- readOnly: state.context.readOnly,
103
- disabled: state.context.disabled,
104
- required: state.context.required,
105
- name: state.context.name,
106
- form: state.context.form,
100
+ id: getHiddenInputId(scope),
101
+ readOnly: prop("readOnly"),
102
+ disabled: prop("disabled"),
103
+ required: prop("required"),
104
+ name: prop("name"),
105
+ form: prop("form"),
107
106
  style: visuallyHiddenStyle,
108
- maxLength: state.context.valueLength,
109
- defaultValue: state.context.valueAsString
107
+ maxLength: computed("valueLength"),
108
+ defaultValue: computed("valueAsString")
110
109
  });
111
110
  },
112
111
  getControlProps() {
113
112
  return normalize.element({
114
113
  ...parts.control.attrs,
115
- dir: state.context.dir,
116
- id: dom.getControlId(state.context)
114
+ dir: prop("dir"),
115
+ id: getControlId(scope)
117
116
  });
118
117
  },
119
- getInputProps(props) {
120
- const { index } = props;
121
- const inputType = state.context.type === "numeric" ? "tel" : "text";
118
+ getInputProps(props2) {
119
+ const { index } = props2;
120
+ const inputType = prop("type") === "numeric" ? "tel" : "text";
122
121
  return normalize.input({
123
122
  ...parts.input.attrs,
124
- dir: state.context.dir,
125
- disabled: state.context.disabled,
126
- "data-disabled": dataAttr(state.context.disabled),
123
+ dir: prop("dir"),
124
+ disabled: prop("disabled"),
125
+ "data-disabled": dataAttr(prop("disabled")),
127
126
  "data-complete": dataAttr(complete),
128
- id: dom.getInputId(state.context, index.toString()),
129
- "data-ownedby": dom.getRootId(state.context),
130
- "aria-label": translations.inputLabel(index, state.context.valueLength),
131
- inputMode: state.context.otp || state.context.type === "numeric" ? "numeric" : "text",
127
+ id: getInputId(scope, index.toString()),
128
+ "data-ownedby": getRootId(scope),
129
+ "aria-label": translations?.inputLabel?.(index, computed("valueLength")),
130
+ inputMode: prop("otp") || prop("type") === "numeric" ? "numeric" : "text",
132
131
  "aria-invalid": ariaAttr(invalid),
133
132
  "data-invalid": dataAttr(invalid),
134
- type: state.context.mask ? "password" : inputType,
135
- defaultValue: state.context.value[index] || "",
136
- readOnly: state.context.readOnly,
133
+ type: prop("mask") ? "password" : inputType,
134
+ defaultValue: context.get("value")[index] || "",
135
+ readOnly: prop("readOnly"),
137
136
  autoCapitalize: "none",
138
- autoComplete: state.context.otp ? "one-time-code" : "off",
139
- placeholder: focusedIndex === index ? "" : state.context.placeholder,
137
+ autoComplete: prop("otp") ? "one-time-code" : "off",
138
+ placeholder: focusedIndex === index ? "" : prop("placeholder"),
140
139
  onBeforeInput(event) {
141
140
  try {
142
141
  const value = getBeforeInputValue(event);
143
- const isValid = isValidValue(state.context, value);
142
+ const isValid = isValidValue(value, prop("type"), prop("pattern"));
144
143
  if (!isValid) {
145
144
  send({ type: "VALUE.INVALID", value });
146
145
  event.preventDefault();
@@ -161,7 +160,7 @@ function connect(state, send, normalize) {
161
160
  return;
162
161
  }
163
162
  if (evt.inputType === "deleteContentBackward") {
164
- send("INPUT.BACKSPACE");
163
+ send({ type: "INPUT.BACKSPACE" });
165
164
  return;
166
165
  }
167
166
  send({ type: "INPUT.CHANGE", value, index });
@@ -172,29 +171,34 @@ function connect(state, send, normalize) {
172
171
  if (isModifierKey(event)) return;
173
172
  const keyMap = {
174
173
  Backspace() {
175
- send("INPUT.BACKSPACE");
174
+ send({ type: "INPUT.BACKSPACE" });
176
175
  },
177
176
  Delete() {
178
- send("INPUT.DELETE");
177
+ send({ type: "INPUT.DELETE" });
179
178
  },
180
179
  ArrowLeft() {
181
- send("INPUT.ARROW_LEFT");
180
+ send({ type: "INPUT.ARROW_LEFT" });
182
181
  },
183
182
  ArrowRight() {
184
- send("INPUT.ARROW_RIGHT");
183
+ send({ type: "INPUT.ARROW_RIGHT" });
185
184
  },
186
185
  Enter() {
187
- send("INPUT.ENTER");
186
+ send({ type: "INPUT.ENTER" });
188
187
  }
189
188
  };
190
- const exec = keyMap[getEventKey(event, state.context)];
189
+ const exec = keyMap[getEventKey(event, {
190
+ dir: prop("dir"),
191
+ orientation: "horizontal"
192
+ })];
191
193
  if (exec) {
192
194
  exec(event);
193
195
  event.preventDefault();
194
196
  }
195
197
  },
196
198
  onFocus() {
197
- send({ type: "INPUT.FOCUS", index });
199
+ queueMicrotask(() => {
200
+ send({ type: "INPUT.FOCUS", index });
201
+ });
198
202
  },
199
203
  onBlur() {
200
204
  send({ type: "INPUT.BLUR", index });
@@ -203,261 +207,310 @@ function connect(state, send, normalize) {
203
207
  }
204
208
  };
205
209
  }
206
- function machine(userContext) {
207
- const ctx = compact(userContext);
208
- return createMachine(
209
- {
210
- id: "pin-input",
211
- initial: "idle",
212
- context: {
213
- value: [],
214
- placeholder: "\u25CB",
215
- otp: false,
216
- type: "numeric",
217
- ...ctx,
218
- focusedIndex: -1,
219
- translations: {
220
- inputLabel: (index, length) => `pin code ${index + 1} of ${length}`,
221
- ...ctx.translations
210
+ var { choose, createMachine } = setup();
211
+ var machine = createMachine({
212
+ props({ props: props2 }) {
213
+ return {
214
+ placeholder: "\u25CB",
215
+ otp: false,
216
+ type: "numeric",
217
+ defaultValue: [],
218
+ ...props2,
219
+ translations: {
220
+ inputLabel: (index, length) => `pin code ${index + 1} of ${length}`,
221
+ ...props2.translations
222
+ }
223
+ };
224
+ },
225
+ initialState() {
226
+ return "idle";
227
+ },
228
+ context({ prop, bindable }) {
229
+ return {
230
+ value: bindable(() => ({
231
+ sync: true,
232
+ value: prop("value"),
233
+ defaultValue: prop("defaultValue"),
234
+ isEqual,
235
+ onChange(value) {
236
+ prop("onValueChange")?.({ value, valueAsString: value.join("") });
222
237
  }
238
+ })),
239
+ focusedIndex: bindable(() => ({
240
+ sync: true,
241
+ defaultValue: -1
242
+ }))
243
+ };
244
+ },
245
+ computed: {
246
+ valueLength: ({ context }) => context.get("value").length,
247
+ filledValueLength: ({ context }) => context.get("value").filter((v) => v?.trim() !== "").length,
248
+ isValueComplete: ({ context }) => {
249
+ const value = context.get("value");
250
+ const filledValueLength = value.filter((v) => v?.trim() !== "").length;
251
+ return value.length === filledValueLength;
252
+ },
253
+ valueAsString: ({ context }) => context.get("value").join(""),
254
+ focusedValue: ({ context }) => context.get("value")[context.get("focusedIndex")] || ""
255
+ },
256
+ entry: choose([
257
+ {
258
+ guard: "autoFocus",
259
+ actions: ["setupValue", "setFocusIndexToFirst"]
260
+ },
261
+ { actions: ["setupValue"] }
262
+ ]),
263
+ watch({ action, track, context, computed }) {
264
+ track([() => context.get("focusedIndex")], () => {
265
+ action(["focusInput", "selectInputIfNeeded"]);
266
+ });
267
+ track([() => context.get("value").join(",")], () => {
268
+ action(["syncInputElements", "dispatchInputEvent"]);
269
+ });
270
+ track([() => computed("isValueComplete")], () => {
271
+ action(["invokeOnComplete", "blurFocusedInputIfNeeded"]);
272
+ });
273
+ },
274
+ on: {
275
+ "VALUE.SET": [
276
+ {
277
+ guard: "hasIndex",
278
+ actions: ["setValueAtIndex"]
223
279
  },
224
- computed: {
225
- valueLength: (ctx2) => ctx2.value.length,
226
- filledValueLength: (ctx2) => ctx2.value.filter((v) => v?.trim() !== "").length,
227
- isValueComplete: (ctx2) => ctx2.valueLength === ctx2.filledValueLength,
228
- valueAsString: (ctx2) => ctx2.value.join(""),
229
- focusedValue: (ctx2) => ctx2.value[ctx2.focusedIndex] || ""
230
- },
231
- entry: choose([
232
- {
233
- guard: "autoFocus",
234
- actions: ["setupValue", "setFocusIndexToFirst"]
235
- },
236
- { actions: ["setupValue"] }
237
- ]),
238
- watch: {
239
- focusedIndex: ["focusInput", "selectInputIfNeeded"],
240
- value: ["syncInputElements"],
241
- isValueComplete: ["invokeOnComplete", "blurFocusedInputIfNeeded"]
242
- },
280
+ { actions: ["setValue"] }
281
+ ],
282
+ "VALUE.CLEAR": {
283
+ actions: ["clearValue", "setFocusIndexToFirst"]
284
+ }
285
+ },
286
+ states: {
287
+ idle: {
243
288
  on: {
244
- "VALUE.SET": [
245
- {
246
- guard: "hasIndex",
247
- actions: ["setValueAtIndex"]
248
- },
249
- { actions: ["setValue"] }
250
- ],
251
- "VALUE.CLEAR": {
252
- actions: ["clearValue", "setFocusIndexToFirst"]
253
- }
254
- },
255
- states: {
256
- idle: {
257
- on: {
258
- "INPUT.FOCUS": {
259
- target: "focused",
260
- actions: "setFocusedIndex"
261
- }
262
- }
263
- },
264
- focused: {
265
- on: {
266
- "INPUT.CHANGE": [
267
- {
268
- guard: "isFinalValue",
269
- actions: ["setFocusedValue", "syncInputValue"]
270
- },
271
- {
272
- actions: ["setFocusedValue", "setNextFocusedIndex", "syncInputValue"]
273
- }
274
- ],
275
- "INPUT.PASTE": {
276
- actions: ["setPastedValue", "setLastValueFocusIndex"]
277
- },
278
- "INPUT.BLUR": {
279
- target: "idle",
280
- actions: "clearFocusedIndex"
281
- },
282
- "INPUT.DELETE": {
283
- guard: "hasValue",
284
- actions: "clearFocusedValue"
285
- },
286
- "INPUT.ARROW_LEFT": {
287
- actions: "setPrevFocusedIndex"
288
- },
289
- "INPUT.ARROW_RIGHT": {
290
- actions: "setNextFocusedIndex"
291
- },
292
- "INPUT.BACKSPACE": [
293
- {
294
- guard: "hasValue",
295
- actions: ["clearFocusedValue"]
296
- },
297
- {
298
- actions: ["setPrevFocusedIndex", "clearFocusedValue"]
299
- }
300
- ],
301
- "INPUT.ENTER": {
302
- guard: "isValueComplete",
303
- actions: "requestFormSubmit"
304
- },
305
- "VALUE.INVALID": {
306
- actions: "invokeOnInvalid"
307
- }
308
- }
289
+ "INPUT.FOCUS": {
290
+ target: "focused",
291
+ actions: ["setFocusedIndex"]
309
292
  }
310
293
  }
311
294
  },
312
- {
313
- guards: {
314
- autoFocus: (ctx2) => !!ctx2.autoFocus,
315
- isValueEmpty: (_ctx, evt) => evt.value === "",
316
- hasValue: (ctx2) => ctx2.value[ctx2.focusedIndex] !== "",
317
- isValueComplete: (ctx2) => ctx2.isValueComplete,
318
- isFinalValue: (ctx2) => ctx2.filledValueLength + 1 === ctx2.valueLength && ctx2.value.findIndex((v) => v.trim() === "") === ctx2.focusedIndex,
319
- hasIndex: (_ctx, evt) => evt.index !== void 0,
320
- isDisabled: (ctx2) => !!ctx2.disabled
321
- },
322
- actions: {
323
- setupValue(ctx2) {
324
- if (ctx2.value.length) return;
325
- const inputEls = dom.getInputEls(ctx2);
326
- const emptyValues = Array.from({ length: inputEls.length }).fill("");
327
- assignValue(ctx2, emptyValues);
328
- },
329
- focusInput(ctx2) {
330
- if (ctx2.focusedIndex === -1) return;
331
- dom.getFocusedInputEl(ctx2)?.focus({ preventScroll: true });
332
- },
333
- selectInputIfNeeded(ctx2) {
334
- if (!ctx2.selectOnFocus || ctx2.focusedIndex === -1) return;
335
- raf(() => {
336
- dom.getFocusedInputEl(ctx2)?.select();
337
- });
338
- },
339
- invokeOnComplete(ctx2) {
340
- if (!ctx2.isValueComplete) return;
341
- ctx2.onValueComplete?.({
342
- value: Array.from(ctx2.value),
343
- valueAsString: ctx2.valueAsString
344
- });
345
- },
346
- invokeOnInvalid(ctx2, evt) {
347
- ctx2.onValueInvalid?.({
348
- value: evt.value,
349
- index: ctx2.focusedIndex
350
- });
351
- },
352
- clearFocusedIndex(ctx2) {
353
- ctx2.focusedIndex = -1;
354
- },
355
- setFocusedIndex(ctx2, evt) {
356
- ctx2.focusedIndex = evt.index;
357
- },
358
- setValue(ctx2, evt) {
359
- set.value(ctx2, evt.value);
360
- },
361
- setFocusedValue(ctx2, evt) {
362
- const nextValue = getNextValue(ctx2.focusedValue, evt.value);
363
- set.valueAtIndex(ctx2, ctx2.focusedIndex, nextValue);
364
- },
365
- revertInputValue(ctx2) {
366
- const inputEl = dom.getFocusedInputEl(ctx2);
367
- dom.setValue(inputEl, ctx2.focusedValue);
368
- },
369
- syncInputValue(ctx2, evt) {
370
- const inputEl = dom.getInputEl(ctx2, evt.index.toString());
371
- dom.setValue(inputEl, ctx2.value[evt.index]);
372
- },
373
- syncInputElements(ctx2) {
374
- const inputEls = dom.getInputEls(ctx2);
375
- inputEls.forEach((inputEl, index) => {
376
- dom.setValue(inputEl, ctx2.value[index]);
377
- });
378
- },
379
- setPastedValue(ctx2, evt) {
380
- raf(() => {
381
- const startIndex = Math.min(ctx2.focusedIndex, ctx2.filledValueLength);
382
- const left = startIndex > 0 ? ctx2.valueAsString.substring(0, ctx2.focusedIndex) : "";
383
- const right = evt.value.substring(0, ctx2.valueLength - startIndex);
384
- const value = left + right;
385
- set.value(ctx2, value.split(""));
386
- });
387
- },
388
- setValueAtIndex(ctx2, evt) {
389
- const nextValue = getNextValue(ctx2.focusedValue, evt.value);
390
- set.valueAtIndex(ctx2, evt.index, nextValue);
391
- },
392
- clearValue(ctx2) {
393
- const nextValue = Array.from({ length: ctx2.valueLength }).fill("");
394
- set.value(ctx2, nextValue);
395
- },
396
- clearFocusedValue(ctx2) {
397
- set.valueAtIndex(ctx2, ctx2.focusedIndex, "");
295
+ focused: {
296
+ on: {
297
+ "INPUT.CHANGE": [
298
+ {
299
+ guard: "isFinalValue",
300
+ actions: ["setFocusedValue", "syncInputValue"]
301
+ },
302
+ {
303
+ actions: ["setFocusedValue", "setNextFocusedIndex", "syncInputValue"]
304
+ }
305
+ ],
306
+ "INPUT.PASTE": {
307
+ actions: ["setPastedValue", "setLastValueFocusIndex"]
398
308
  },
399
- setFocusIndexToFirst(ctx2) {
400
- ctx2.focusedIndex = 0;
309
+ "INPUT.BLUR": {
310
+ target: "idle",
311
+ actions: ["clearFocusedIndex"]
401
312
  },
402
- setNextFocusedIndex(ctx2) {
403
- ctx2.focusedIndex = Math.min(ctx2.focusedIndex + 1, ctx2.valueLength - 1);
313
+ "INPUT.DELETE": {
314
+ guard: "hasValue",
315
+ actions: ["clearFocusedValue"]
404
316
  },
405
- setPrevFocusedIndex(ctx2) {
406
- ctx2.focusedIndex = Math.max(ctx2.focusedIndex - 1, 0);
317
+ "INPUT.ARROW_LEFT": {
318
+ actions: ["setPrevFocusedIndex"]
407
319
  },
408
- setLastValueFocusIndex(ctx2) {
409
- raf(() => {
410
- ctx2.focusedIndex = Math.min(ctx2.filledValueLength, ctx2.valueLength - 1);
411
- });
320
+ "INPUT.ARROW_RIGHT": {
321
+ actions: ["setNextFocusedIndex"]
412
322
  },
413
- blurFocusedInputIfNeeded(ctx2) {
414
- if (!ctx2.blurOnComplete) return;
415
- raf(() => {
416
- dom.getFocusedInputEl(ctx2)?.blur();
417
- });
323
+ "INPUT.BACKSPACE": [
324
+ {
325
+ guard: "hasValue",
326
+ actions: ["clearFocusedValue"]
327
+ },
328
+ {
329
+ actions: ["setPrevFocusedIndex", "clearFocusedValue"]
330
+ }
331
+ ],
332
+ "INPUT.ENTER": {
333
+ guard: "isValueComplete",
334
+ actions: ["requestFormSubmit"]
418
335
  },
419
- requestFormSubmit(ctx2) {
420
- if (!ctx2.name || !ctx2.isValueComplete) return;
421
- const inputEl = dom.getHiddenInputEl(ctx2);
422
- inputEl?.form?.requestSubmit();
336
+ "VALUE.INVALID": {
337
+ actions: ["invokeOnInvalid"]
423
338
  }
424
339
  }
425
340
  }
426
- );
427
- }
428
- function assignValue(ctx, value) {
429
- const arr = Array.isArray(value) ? value : value.split("").filter(Boolean);
430
- arr.forEach((value2, index) => {
431
- ctx.value[index] = value2;
432
- });
433
- }
341
+ },
342
+ implementations: {
343
+ guards: {
344
+ autoFocus: ({ prop }) => !!prop("autoFocus"),
345
+ hasValue: ({ context }) => context.get("value")[context.get("focusedIndex")] !== "",
346
+ isValueComplete: ({ computed }) => computed("isValueComplete"),
347
+ isFinalValue: ({ context, computed }) => computed("filledValueLength") + 1 === computed("valueLength") && context.get("value").findIndex((v) => v.trim() === "") === context.get("focusedIndex"),
348
+ hasIndex: ({ event }) => event.index !== void 0
349
+ },
350
+ actions: {
351
+ dispatchInputEvent({ computed, scope }) {
352
+ const inputEl = getHiddenInputEl(scope);
353
+ dispatchInputValueEvent(inputEl, { value: computed("valueAsString") });
354
+ },
355
+ setupValue({ context, scope }) {
356
+ queueMicrotask(() => {
357
+ if (context.get("value").length) return;
358
+ const inputEls = getInputEls(scope);
359
+ const emptyValues = Array.from({ length: inputEls.length }).fill("");
360
+ context.set("value", emptyValues);
361
+ });
362
+ },
363
+ focusInput({ context, scope }) {
364
+ const focusedIndex = context.get("focusedIndex");
365
+ if (focusedIndex === -1) return;
366
+ getInputElAtIndex(scope, focusedIndex)?.focus({ preventScroll: true });
367
+ },
368
+ selectInputIfNeeded({ context, prop, scope }) {
369
+ const focusedIndex = context.get("focusedIndex");
370
+ if (!prop("selectOnFocus") || focusedIndex === -1) return;
371
+ raf(() => {
372
+ getInputElAtIndex(scope, focusedIndex)?.select();
373
+ });
374
+ },
375
+ invokeOnComplete({ context, computed, prop }) {
376
+ if (!computed("isValueComplete")) return;
377
+ prop("onValueComplete")?.({
378
+ value: Array.from(context.get("value")),
379
+ valueAsString: computed("valueAsString")
380
+ });
381
+ },
382
+ invokeOnInvalid({ context, event, prop }) {
383
+ prop("onValueInvalid")?.({
384
+ value: event.value,
385
+ index: context.get("focusedIndex")
386
+ });
387
+ },
388
+ clearFocusedIndex({ context }) {
389
+ context.set("focusedIndex", -1);
390
+ },
391
+ setFocusedIndex({ context, event }) {
392
+ context.set("focusedIndex", event.index);
393
+ },
394
+ setValue({ context, event }) {
395
+ context.set("value", event.value);
396
+ },
397
+ setFocusedValue({ context, event, computed }) {
398
+ const focusedValue = computed("focusedValue");
399
+ const nextValue = getNextValue(focusedValue, event.value);
400
+ context.set("value", (prev) => {
401
+ const next = [...prev];
402
+ next[context.get("focusedIndex")] = nextValue;
403
+ return next;
404
+ });
405
+ },
406
+ revertInputValue({ context, computed, scope }) {
407
+ const inputEl = getInputElAtIndex(scope, context.get("focusedIndex"));
408
+ setElementValue(inputEl, computed("focusedValue"));
409
+ },
410
+ syncInputValue({ context, event, scope }) {
411
+ const value = context.get("value");
412
+ const inputEl = getInputElAtIndex(scope, event.index);
413
+ setElementValue(inputEl, value[event.index]);
414
+ },
415
+ syncInputElements({ context, scope }) {
416
+ const inputEls = getInputEls(scope);
417
+ inputEls.forEach((inputEl, index) => {
418
+ setElementValue(inputEl, context.get("value")[index]);
419
+ });
420
+ },
421
+ setPastedValue({ context, event, computed }) {
422
+ raf(() => {
423
+ const valueAsString = computed("valueAsString");
424
+ const focusedIndex = context.get("focusedIndex");
425
+ const filledValueLength = computed("filledValueLength");
426
+ const startIndex = Math.min(focusedIndex, filledValueLength);
427
+ const left = startIndex > 0 ? valueAsString.substring(0, focusedIndex) : "";
428
+ const right = event.value.substring(0, computed("valueLength") - startIndex);
429
+ const value = left + right;
430
+ context.set("value", value.split(""));
431
+ });
432
+ },
433
+ setValueAtIndex({ context, event, computed }) {
434
+ const nextValue = getNextValue(computed("focusedValue"), event.value);
435
+ context.set("value", (prev) => {
436
+ const next = [...prev];
437
+ next[event.index] = nextValue;
438
+ return next;
439
+ });
440
+ },
441
+ clearValue({ context, computed }) {
442
+ const nextValue = Array.from({ length: computed("valueLength") }).fill("");
443
+ context.set("value", nextValue);
444
+ },
445
+ clearFocusedValue({ context }) {
446
+ const focusedIndex = context.get("focusedIndex");
447
+ if (focusedIndex === -1) return;
448
+ context.set("value", (prev) => {
449
+ const next = [...prev];
450
+ next[focusedIndex] = "";
451
+ return next;
452
+ });
453
+ },
454
+ setFocusIndexToFirst({ context }) {
455
+ context.set("focusedIndex", 0);
456
+ },
457
+ setNextFocusedIndex({ context, computed }) {
458
+ context.set("focusedIndex", Math.min(context.get("focusedIndex") + 1, computed("valueLength") - 1));
459
+ },
460
+ setPrevFocusedIndex({ context }) {
461
+ context.set("focusedIndex", Math.max(context.get("focusedIndex") - 1, 0));
462
+ },
463
+ setLastValueFocusIndex({ context, computed }) {
464
+ raf(() => {
465
+ context.set("focusedIndex", Math.min(computed("filledValueLength"), computed("valueLength") - 1));
466
+ });
467
+ },
468
+ blurFocusedInputIfNeeded({ context, prop, scope }) {
469
+ if (!prop("blurOnComplete")) return;
470
+ raf(() => {
471
+ getInputElAtIndex(scope, context.get("focusedIndex"))?.blur();
472
+ });
473
+ },
474
+ requestFormSubmit({ computed, prop, scope }) {
475
+ if (!prop("name") || !computed("isValueComplete")) return;
476
+ const inputEl = getHiddenInputEl(scope);
477
+ inputEl?.form?.requestSubmit();
478
+ }
479
+ }
480
+ }
481
+ });
434
482
  function getNextValue(current, next) {
435
483
  let nextValue = next;
436
484
  if (current[0] === next[0]) nextValue = next[1];
437
485
  else if (current[0] === next[1]) nextValue = next[0];
438
486
  return nextValue.split("")[nextValue.length - 1];
439
487
  }
440
- var invoke = {
441
- change(ctx) {
442
- ctx.onValueChange?.({
443
- value: Array.from(ctx.value),
444
- valueAsString: ctx.valueAsString
445
- });
446
- const inputEl = dom.getHiddenInputEl(ctx);
447
- dispatchInputValueEvent(inputEl, { value: ctx.valueAsString });
448
- }
449
- };
450
- var set = {
451
- value(ctx, value) {
452
- if (isEqual(ctx.value, value)) return;
453
- assignValue(ctx, value);
454
- invoke.change(ctx);
455
- },
456
- valueAtIndex(ctx, index, value) {
457
- if (isEqual(ctx.value[index], value)) return;
458
- ctx.value[index] = value;
459
- invoke.change(ctx);
460
- }
461
- };
488
+ var props = createProps()([
489
+ "autoFocus",
490
+ "blurOnComplete",
491
+ "dir",
492
+ "disabled",
493
+ "form",
494
+ "getRootNode",
495
+ "id",
496
+ "ids",
497
+ "invalid",
498
+ "mask",
499
+ "name",
500
+ "onValueChange",
501
+ "onValueComplete",
502
+ "onValueInvalid",
503
+ "otp",
504
+ "readOnly",
505
+ "pattern",
506
+ "placeholder",
507
+ "required",
508
+ "selectOnFocus",
509
+ "translations",
510
+ "type",
511
+ "value",
512
+ "defaultValue"
513
+ ]);
514
+ var splitProps = createSplitProps(props);
462
515
 
463
- export { anatomy, connect, machine };
516
+ export { anatomy, connect, machine, props, splitProps };