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