@radix-ui/react-one-time-password-field 0.1.0-rc.1744661316162
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/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +612 -0
- package/dist/index.js.map +7 -0
- package/dist/index.mjs +580 -0
- package/dist/index.mjs.map +7 -0
- package/package.json +80 -0
- package/src/index.ts +16 -0
- package/src/one-time-password-field.test.tsx +50 -0
- package/src/one-time-password-field.tsx +823 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
HiddenInput: () => HiddenInput,
|
|
35
|
+
Input: () => Input,
|
|
36
|
+
OneTimePasswordField: () => OneTimePasswordField,
|
|
37
|
+
OneTimePasswordFieldHiddenInput: () => OneTimePasswordFieldHiddenInput,
|
|
38
|
+
OneTimePasswordFieldInput: () => OneTimePasswordFieldInput,
|
|
39
|
+
Root: () => Root2
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/one-time-password-field.tsx
|
|
44
|
+
var import_react_primitive = require("@radix-ui/react-primitive");
|
|
45
|
+
var import_react_compose_refs = require("@radix-ui/react-compose-refs");
|
|
46
|
+
var import_react_use_controllable_state = require("@radix-ui/react-use-controllable-state");
|
|
47
|
+
var import_primitive = require("@radix-ui/primitive");
|
|
48
|
+
var import_react_collection = require("@radix-ui/react-collection");
|
|
49
|
+
var RovingFocusGroup = __toESM(require("@radix-ui/react-roving-focus"));
|
|
50
|
+
var import_react_roving_focus = require("@radix-ui/react-roving-focus");
|
|
51
|
+
var import_react_use_is_hydrated = require("@radix-ui/react-use-is-hydrated");
|
|
52
|
+
var React = __toESM(require("react"));
|
|
53
|
+
var import_react_dom = require("react-dom");
|
|
54
|
+
var import_react_context = require("@radix-ui/react-context");
|
|
55
|
+
var import_react_direction = require("@radix-ui/react-direction");
|
|
56
|
+
var import_number = require("@radix-ui/number");
|
|
57
|
+
var import_react_use_effect_event = require("@radix-ui/react-use-effect-event");
|
|
58
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
59
|
+
var INPUT_VALIDATION_MAP = {
|
|
60
|
+
numeric: {
|
|
61
|
+
type: "numeric",
|
|
62
|
+
regexp: /[^\d]/g,
|
|
63
|
+
pattern: "\\d{1}",
|
|
64
|
+
inputMode: "numeric"
|
|
65
|
+
},
|
|
66
|
+
alpha: {
|
|
67
|
+
type: "alpha",
|
|
68
|
+
regexp: /[^a-zA-Z]/g,
|
|
69
|
+
pattern: "[a-zA-Z]{1}",
|
|
70
|
+
inputMode: "text"
|
|
71
|
+
},
|
|
72
|
+
alphanumeric: {
|
|
73
|
+
type: "alphanumeric",
|
|
74
|
+
regexp: /[^a-zA-Z0-9]/g,
|
|
75
|
+
pattern: "[a-zA-Z0-9]{1}",
|
|
76
|
+
inputMode: "text"
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var ONE_TIME_PASSWORD_FIELD_NAME = "OneTimePasswordField";
|
|
80
|
+
var [Collection, useCollection, createCollectionScope] = (0, import_react_collection.unstable_createCollection)(
|
|
81
|
+
ONE_TIME_PASSWORD_FIELD_NAME
|
|
82
|
+
);
|
|
83
|
+
var [createOneTimePasswordFieldContext] = (0, import_react_context.createContextScope)(ONE_TIME_PASSWORD_FIELD_NAME, [
|
|
84
|
+
createCollectionScope,
|
|
85
|
+
import_react_roving_focus.createRovingFocusGroupScope
|
|
86
|
+
]);
|
|
87
|
+
var useRovingFocusGroupScope = (0, import_react_roving_focus.createRovingFocusGroupScope)();
|
|
88
|
+
var [OneTimePasswordFieldContext, useOneTimePasswordFieldContext] = createOneTimePasswordFieldContext(ONE_TIME_PASSWORD_FIELD_NAME);
|
|
89
|
+
var OneTimePasswordFieldCollectionProvider = ({
|
|
90
|
+
__scopeOneTimePasswordField,
|
|
91
|
+
children
|
|
92
|
+
}) => {
|
|
93
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Provider, { scope: __scopeOneTimePasswordField, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Slot, { scope: __scopeOneTimePasswordField, children }) });
|
|
94
|
+
};
|
|
95
|
+
var OneTimePasswordFieldImpl = React.forwardRef(
|
|
96
|
+
function OneTimePasswordFieldImpl2({
|
|
97
|
+
__scopeOneTimePasswordField,
|
|
98
|
+
id,
|
|
99
|
+
defaultValue,
|
|
100
|
+
value: valueProp,
|
|
101
|
+
onValueChange,
|
|
102
|
+
autoSubmit,
|
|
103
|
+
children,
|
|
104
|
+
onPaste,
|
|
105
|
+
onAutoSubmit,
|
|
106
|
+
disabled = false,
|
|
107
|
+
readOnly = false,
|
|
108
|
+
autoComplete = "one-time-code",
|
|
109
|
+
autoFocus = false,
|
|
110
|
+
form,
|
|
111
|
+
name,
|
|
112
|
+
placeholder,
|
|
113
|
+
required = false,
|
|
114
|
+
type = "password",
|
|
115
|
+
// TODO: Change default to vertical when inputs use vertical writing mode
|
|
116
|
+
orientation = "horizontal",
|
|
117
|
+
dir,
|
|
118
|
+
validationType = "numeric",
|
|
119
|
+
...domProps
|
|
120
|
+
}, forwardedRef) {
|
|
121
|
+
const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeOneTimePasswordField);
|
|
122
|
+
const direction = (0, import_react_direction.useDirection)(dir);
|
|
123
|
+
const collection = useCollection(__scopeOneTimePasswordField);
|
|
124
|
+
const validation = validationType in INPUT_VALIDATION_MAP ? INPUT_VALIDATION_MAP[validationType] : void 0;
|
|
125
|
+
const controlledValue = React.useMemo(() => {
|
|
126
|
+
return valueProp != null ? sanitizeValue(valueProp, validation?.regexp) : void 0;
|
|
127
|
+
}, [valueProp, validation?.regexp]);
|
|
128
|
+
const [value, setValue] = (0, import_react_use_controllable_state.useControllableState)({
|
|
129
|
+
caller: "OneTimePasswordField",
|
|
130
|
+
prop: controlledValue,
|
|
131
|
+
defaultProp: defaultValue != null ? sanitizeValue(defaultValue, validation?.regexp) : [],
|
|
132
|
+
onChange: (value2) => onValueChange?.(value2.filter(Boolean).join(""))
|
|
133
|
+
});
|
|
134
|
+
const dispatch = (0, import_react_use_effect_event.useEffectEvent)((action) => {
|
|
135
|
+
switch (action.type) {
|
|
136
|
+
case "SET_CHAR": {
|
|
137
|
+
const { index, char } = action;
|
|
138
|
+
const currentTarget = collection.at(index)?.element;
|
|
139
|
+
if (value[index] === char) {
|
|
140
|
+
const next = currentTarget && collection.from(currentTarget, 1)?.element;
|
|
141
|
+
focusInput(next);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (char === "") {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (validation) {
|
|
148
|
+
const regexp = new RegExp(validation.regexp);
|
|
149
|
+
const clean = char.replace(regexp, "");
|
|
150
|
+
if (clean !== char) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (value.length >= collection.size) {
|
|
155
|
+
const newValue2 = [...value];
|
|
156
|
+
newValue2[index] = char;
|
|
157
|
+
(0, import_react_dom.flushSync)(() => setValue(newValue2));
|
|
158
|
+
const next = currentTarget && collection.from(currentTarget, 1)?.element;
|
|
159
|
+
focusInput(next);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const newValue = [
|
|
163
|
+
//
|
|
164
|
+
...value.slice(0, index),
|
|
165
|
+
char,
|
|
166
|
+
...value.slice(index)
|
|
167
|
+
];
|
|
168
|
+
const lastElement = collection.at(-1)?.element;
|
|
169
|
+
(0, import_react_dom.flushSync)(() => setValue(newValue));
|
|
170
|
+
if (currentTarget !== lastElement) {
|
|
171
|
+
const next = currentTarget && collection.from(currentTarget, 1)?.element;
|
|
172
|
+
focusInput(next);
|
|
173
|
+
} else {
|
|
174
|
+
currentTarget?.select();
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
case "CLEAR_CHAR": {
|
|
179
|
+
const { index, reason } = action;
|
|
180
|
+
if (!value[index]) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const newValue = value.filter((_, i) => i !== index);
|
|
184
|
+
const currentTarget = collection.at(index)?.element;
|
|
185
|
+
const previous = currentTarget && collection.from(currentTarget, -1)?.element;
|
|
186
|
+
(0, import_react_dom.flushSync)(() => setValue(newValue));
|
|
187
|
+
if (reason === "Backspace") {
|
|
188
|
+
focusInput(previous);
|
|
189
|
+
} else if (reason === "Delete" || reason === "Cut") {
|
|
190
|
+
focusInput(currentTarget);
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
case "CLEAR": {
|
|
195
|
+
if (value.length === 0) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (action.reason === "Backspace" || action.reason === "Delete") {
|
|
199
|
+
(0, import_react_dom.flushSync)(() => setValue([]));
|
|
200
|
+
focusInput(collection.at(0)?.element);
|
|
201
|
+
} else {
|
|
202
|
+
setValue([]);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
case "PASTE": {
|
|
207
|
+
const { value: pastedValue } = action;
|
|
208
|
+
const value2 = sanitizeValue(pastedValue, validation?.regexp);
|
|
209
|
+
if (!value2) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
(0, import_react_dom.flushSync)(() => setValue(value2));
|
|
213
|
+
focusInput(collection.at(value2.length - 1)?.element);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
const validationTypeRef = React.useRef(validation);
|
|
219
|
+
React.useEffect(() => {
|
|
220
|
+
if (!validation) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (validationTypeRef.current?.type !== validation.type) {
|
|
224
|
+
validationTypeRef.current = validation;
|
|
225
|
+
setValue(sanitizeValue(value, validation.regexp));
|
|
226
|
+
}
|
|
227
|
+
}, [setValue, validation, value]);
|
|
228
|
+
const hiddenInputRef = React.useRef(null);
|
|
229
|
+
const userActionRef = React.useRef(null);
|
|
230
|
+
const rootRef = React.useRef(null);
|
|
231
|
+
const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, rootRef);
|
|
232
|
+
const firstInput = collection.at(0)?.element;
|
|
233
|
+
const locateForm = React.useCallback(() => {
|
|
234
|
+
let formElement;
|
|
235
|
+
if (form) {
|
|
236
|
+
const associatedElement = (rootRef.current?.ownerDocument ?? document).getElementById(form);
|
|
237
|
+
if (isFormElement(associatedElement)) {
|
|
238
|
+
formElement = associatedElement;
|
|
239
|
+
}
|
|
240
|
+
} else if (hiddenInputRef.current) {
|
|
241
|
+
formElement = hiddenInputRef.current.form;
|
|
242
|
+
} else if (firstInput) {
|
|
243
|
+
formElement = firstInput.form;
|
|
244
|
+
}
|
|
245
|
+
return formElement ?? null;
|
|
246
|
+
}, [form, firstInput]);
|
|
247
|
+
const attemptSubmit = React.useCallback(() => {
|
|
248
|
+
const formElement = locateForm();
|
|
249
|
+
formElement?.requestSubmit();
|
|
250
|
+
}, [locateForm]);
|
|
251
|
+
React.useEffect(() => {
|
|
252
|
+
const form2 = locateForm();
|
|
253
|
+
if (form2) {
|
|
254
|
+
const reset = () => dispatch({ type: "CLEAR", reason: "Reset" });
|
|
255
|
+
form2.addEventListener("reset", reset);
|
|
256
|
+
return () => form2.removeEventListener("reset", reset);
|
|
257
|
+
}
|
|
258
|
+
}, [dispatch, locateForm]);
|
|
259
|
+
const currentValue = value.join("");
|
|
260
|
+
const valueRef = React.useRef(currentValue);
|
|
261
|
+
const length = collection.size;
|
|
262
|
+
React.useEffect(() => {
|
|
263
|
+
const previousValue = valueRef.current;
|
|
264
|
+
valueRef.current = currentValue;
|
|
265
|
+
if (previousValue === currentValue) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (autoSubmit && value.every((char) => char !== "") && value.length === length) {
|
|
269
|
+
onAutoSubmit?.(value.join(""));
|
|
270
|
+
attemptSubmit();
|
|
271
|
+
}
|
|
272
|
+
}, [attemptSubmit, autoSubmit, currentValue, length, onAutoSubmit, value]);
|
|
273
|
+
const preHydrationIndexTracker = React.useRef(0);
|
|
274
|
+
const isHydrated = (0, import_react_use_is_hydrated.useIsHydrated)();
|
|
275
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
276
|
+
OneTimePasswordFieldContext,
|
|
277
|
+
{
|
|
278
|
+
scope: __scopeOneTimePasswordField,
|
|
279
|
+
value,
|
|
280
|
+
attemptSubmit,
|
|
281
|
+
disabled,
|
|
282
|
+
readOnly,
|
|
283
|
+
autoComplete,
|
|
284
|
+
autoFocus,
|
|
285
|
+
form,
|
|
286
|
+
name,
|
|
287
|
+
placeholder,
|
|
288
|
+
required,
|
|
289
|
+
type,
|
|
290
|
+
hiddenInputRef,
|
|
291
|
+
userActionRef,
|
|
292
|
+
dispatch,
|
|
293
|
+
validationType,
|
|
294
|
+
orientation,
|
|
295
|
+
preHydrationIndexTracker,
|
|
296
|
+
isHydrated,
|
|
297
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
298
|
+
RovingFocusGroup.Root,
|
|
299
|
+
{
|
|
300
|
+
asChild: true,
|
|
301
|
+
...rovingFocusGroupScope,
|
|
302
|
+
orientation,
|
|
303
|
+
dir: direction,
|
|
304
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
305
|
+
import_react_primitive.Primitive.div,
|
|
306
|
+
{
|
|
307
|
+
...domProps,
|
|
308
|
+
role: "group",
|
|
309
|
+
ref: composedRefs,
|
|
310
|
+
onPaste: (0, import_primitive.composeEventHandlers)(
|
|
311
|
+
onPaste,
|
|
312
|
+
(event) => {
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
const pastedValue = event.clipboardData.getData("Text");
|
|
315
|
+
const value2 = sanitizeValue(pastedValue, validation?.regexp);
|
|
316
|
+
if (!value2) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
dispatch({ type: "PASTE", value: pastedValue });
|
|
320
|
+
}
|
|
321
|
+
),
|
|
322
|
+
children
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
var OneTimePasswordField = React.forwardRef(
|
|
332
|
+
function OneTimePasswordField2(props, ref) {
|
|
333
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OneTimePasswordFieldCollectionProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OneTimePasswordFieldImpl, { ...props, ref }) });
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
var OneTimePasswordFieldHiddenInput = React.forwardRef(function OneTimePasswordFieldHiddenInput2({ __scopeOneTimePasswordField, ...props }, forwardedRef) {
|
|
337
|
+
const { value, hiddenInputRef } = useOneTimePasswordFieldContext(
|
|
338
|
+
"OneTimePasswordFieldHiddenInput",
|
|
339
|
+
__scopeOneTimePasswordField
|
|
340
|
+
);
|
|
341
|
+
const ref = (0, import_react_compose_refs.useComposedRefs)(hiddenInputRef, forwardedRef);
|
|
342
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
343
|
+
"input",
|
|
344
|
+
{
|
|
345
|
+
ref,
|
|
346
|
+
...props,
|
|
347
|
+
type: "hidden",
|
|
348
|
+
readOnly: true,
|
|
349
|
+
value: value.join("").trim(),
|
|
350
|
+
autoComplete: "off",
|
|
351
|
+
autoFocus: false,
|
|
352
|
+
autoCapitalize: "off",
|
|
353
|
+
autoCorrect: "off",
|
|
354
|
+
autoSave: "off",
|
|
355
|
+
spellCheck: false
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
var OneTimePasswordFieldInput = React.forwardRef(function OneTimePasswordFieldInput2({ __scopeOneTimePasswordField, ...props }, forwardedRef) {
|
|
360
|
+
const {
|
|
361
|
+
value: _value,
|
|
362
|
+
defaultValue: _defaultValue,
|
|
363
|
+
disabled: _disabled,
|
|
364
|
+
readOnly: _readOnly,
|
|
365
|
+
autoComplete: _autoComplete,
|
|
366
|
+
autoFocus: _autoFocus,
|
|
367
|
+
form: _form,
|
|
368
|
+
name: _name,
|
|
369
|
+
placeholder: _placeholder,
|
|
370
|
+
required: _required,
|
|
371
|
+
type: _type,
|
|
372
|
+
...domProps
|
|
373
|
+
} = props;
|
|
374
|
+
const context = useOneTimePasswordFieldContext(
|
|
375
|
+
"OneTimePasswordFieldInput",
|
|
376
|
+
__scopeOneTimePasswordField
|
|
377
|
+
);
|
|
378
|
+
const { dispatch, userActionRef, validationType, preHydrationIndexTracker, isHydrated } = context;
|
|
379
|
+
const collection = useCollection(__scopeOneTimePasswordField);
|
|
380
|
+
const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeOneTimePasswordField);
|
|
381
|
+
const inputRef = React.useRef(null);
|
|
382
|
+
const [element, setElement] = React.useState(null);
|
|
383
|
+
let index;
|
|
384
|
+
if (!isHydrated) {
|
|
385
|
+
index = preHydrationIndexTracker.current;
|
|
386
|
+
preHydrationIndexTracker.current++;
|
|
387
|
+
} else {
|
|
388
|
+
index = element ? collection.indexOf(element) : -1;
|
|
389
|
+
}
|
|
390
|
+
const composedInputRef = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, inputRef, setElement);
|
|
391
|
+
const char = context.value[index] ?? "";
|
|
392
|
+
const keyboardActionTimeoutRef = React.useRef(null);
|
|
393
|
+
React.useEffect(() => {
|
|
394
|
+
return () => {
|
|
395
|
+
window.clearTimeout(keyboardActionTimeoutRef.current);
|
|
396
|
+
};
|
|
397
|
+
}, []);
|
|
398
|
+
const totalValue = context.value.join("").trim();
|
|
399
|
+
const lastSelectableIndex = (0, import_number.clamp)(totalValue.length, [0, collection.size - 1]);
|
|
400
|
+
const isFocusable = index <= lastSelectableIndex;
|
|
401
|
+
const validation = validationType in INPUT_VALIDATION_MAP ? INPUT_VALIDATION_MAP[validationType] : void 0;
|
|
402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.ItemSlot, { scope: __scopeOneTimePasswordField, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
403
|
+
RovingFocusGroup.Item,
|
|
404
|
+
{
|
|
405
|
+
...rovingFocusGroupScope,
|
|
406
|
+
asChild: true,
|
|
407
|
+
focusable: !context.disabled && isFocusable,
|
|
408
|
+
active: index === lastSelectableIndex,
|
|
409
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
410
|
+
import_react_primitive.Primitive.input,
|
|
411
|
+
{
|
|
412
|
+
ref: composedInputRef,
|
|
413
|
+
type: "text",
|
|
414
|
+
"aria-label": `Character ${index + 1} of ${collection.size}`,
|
|
415
|
+
autoComplete: index === 0 ? context.autoComplete : "off",
|
|
416
|
+
inputMode: validation?.inputMode,
|
|
417
|
+
maxLength: 1,
|
|
418
|
+
pattern: validation?.pattern,
|
|
419
|
+
readOnly: context.readOnly,
|
|
420
|
+
value: char,
|
|
421
|
+
"data-radix-otp-input": "",
|
|
422
|
+
"data-radix-index": index,
|
|
423
|
+
...domProps,
|
|
424
|
+
onFocus: (0, import_primitive.composeEventHandlers)(props.onFocus, (event) => {
|
|
425
|
+
event.currentTarget.select();
|
|
426
|
+
}),
|
|
427
|
+
onCut: (0, import_primitive.composeEventHandlers)(props.onCut, (event) => {
|
|
428
|
+
const currentValue = event.currentTarget.value;
|
|
429
|
+
if (currentValue !== "") {
|
|
430
|
+
userActionRef.current = {
|
|
431
|
+
type: "cut"
|
|
432
|
+
};
|
|
433
|
+
keyboardActionTimeoutRef.current = window.setTimeout(() => {
|
|
434
|
+
userActionRef.current = null;
|
|
435
|
+
}, 10);
|
|
436
|
+
}
|
|
437
|
+
}),
|
|
438
|
+
onChange: (0, import_primitive.composeEventHandlers)(props.onChange, (event) => {
|
|
439
|
+
const action = userActionRef.current;
|
|
440
|
+
userActionRef.current = null;
|
|
441
|
+
if (action) {
|
|
442
|
+
switch (action.type) {
|
|
443
|
+
case "cut":
|
|
444
|
+
dispatch({ type: "CLEAR_CHAR", index, reason: "Cut" });
|
|
445
|
+
return;
|
|
446
|
+
case "keydown": {
|
|
447
|
+
if (action.key === "Char") {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const isClearing = action.key === "Backspace" && (action.metaKey || action.ctrlKey);
|
|
451
|
+
if (isClearing) {
|
|
452
|
+
dispatch({ type: "CLEAR", reason: "Backspace" });
|
|
453
|
+
} else {
|
|
454
|
+
dispatch({ type: "CLEAR_CHAR", index, reason: action.key });
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
default:
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (event.target.validity.valid) {
|
|
463
|
+
if (event.target.value === "") {
|
|
464
|
+
let reason = "Backspace";
|
|
465
|
+
if (isInputEvent(event.nativeEvent)) {
|
|
466
|
+
const inputType = event.nativeEvent.inputType;
|
|
467
|
+
if (inputType === "deleteContentBackward") {
|
|
468
|
+
reason = "Backspace";
|
|
469
|
+
} else if (inputType === "deleteByCut") {
|
|
470
|
+
reason = "Cut";
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
dispatch({ type: "CLEAR_CHAR", index, reason });
|
|
474
|
+
} else {
|
|
475
|
+
dispatch({ type: "SET_CHAR", char: event.target.value, index, event });
|
|
476
|
+
}
|
|
477
|
+
} else {
|
|
478
|
+
const element2 = event.target;
|
|
479
|
+
requestAnimationFrame(() => {
|
|
480
|
+
if (element2.ownerDocument.activeElement === element2) {
|
|
481
|
+
element2.select();
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}),
|
|
486
|
+
onKeyDown: (0, import_primitive.composeEventHandlers)(props.onKeyDown, (event) => {
|
|
487
|
+
switch (event.key) {
|
|
488
|
+
case "Delete":
|
|
489
|
+
case "Backspace": {
|
|
490
|
+
const currentValue = event.currentTarget.value;
|
|
491
|
+
if (currentValue === "") {
|
|
492
|
+
if (event.key === "Delete") return;
|
|
493
|
+
const isClearing = event.metaKey || event.ctrlKey;
|
|
494
|
+
if (isClearing) {
|
|
495
|
+
dispatch({ type: "CLEAR", reason: "Backspace" });
|
|
496
|
+
} else {
|
|
497
|
+
const element2 = event.currentTarget;
|
|
498
|
+
requestAnimationFrame(() => {
|
|
499
|
+
focusInput(collection.from(element2, -1)?.element);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
userActionRef.current = {
|
|
504
|
+
type: "keydown",
|
|
505
|
+
key: event.key,
|
|
506
|
+
metaKey: event.metaKey,
|
|
507
|
+
ctrlKey: event.ctrlKey
|
|
508
|
+
};
|
|
509
|
+
keyboardActionTimeoutRef.current = window.setTimeout(() => {
|
|
510
|
+
userActionRef.current = null;
|
|
511
|
+
}, 10);
|
|
512
|
+
}
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
case "Enter": {
|
|
516
|
+
event.preventDefault();
|
|
517
|
+
context.attemptSubmit();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
case "ArrowDown":
|
|
521
|
+
case "ArrowUp": {
|
|
522
|
+
if (context.orientation === "horizontal") {
|
|
523
|
+
event.preventDefault();
|
|
524
|
+
}
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
// TODO: Handle left/right arrow keys in vertical writing mode
|
|
528
|
+
default: {
|
|
529
|
+
if (event.currentTarget.value === event.key) {
|
|
530
|
+
const element2 = event.currentTarget;
|
|
531
|
+
event.preventDefault();
|
|
532
|
+
focusInput(collection.from(element2, 1)?.element);
|
|
533
|
+
return;
|
|
534
|
+
} else if (
|
|
535
|
+
// input already has a value, but...
|
|
536
|
+
event.currentTarget.value && // the value is not selected
|
|
537
|
+
!(event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd != null && event.currentTarget.selectionEnd > 0)
|
|
538
|
+
) {
|
|
539
|
+
const attemptedValue = event.key;
|
|
540
|
+
if (event.key.length > 1 || event.key === " ") {
|
|
541
|
+
return;
|
|
542
|
+
} else {
|
|
543
|
+
const nextInput = collection.from(event.currentTarget, 1)?.element;
|
|
544
|
+
const lastInput = collection.at(-1)?.element;
|
|
545
|
+
if (nextInput !== lastInput && event.currentTarget !== lastInput) {
|
|
546
|
+
if (event.currentTarget.selectionStart === 0) {
|
|
547
|
+
dispatch({ type: "SET_CHAR", char: attemptedValue, index, event });
|
|
548
|
+
} else {
|
|
549
|
+
dispatch({
|
|
550
|
+
type: "SET_CHAR",
|
|
551
|
+
char: attemptedValue,
|
|
552
|
+
index: index + 1,
|
|
553
|
+
event
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
userActionRef.current = {
|
|
557
|
+
type: "keydown",
|
|
558
|
+
key: "Char",
|
|
559
|
+
metaKey: event.metaKey,
|
|
560
|
+
ctrlKey: event.ctrlKey
|
|
561
|
+
};
|
|
562
|
+
keyboardActionTimeoutRef.current = window.setTimeout(() => {
|
|
563
|
+
userActionRef.current = null;
|
|
564
|
+
}, 10);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}),
|
|
571
|
+
onPointerDown: (0, import_primitive.composeEventHandlers)(props.onPointerDown, (event) => {
|
|
572
|
+
if (index > lastSelectableIndex) {
|
|
573
|
+
event.preventDefault();
|
|
574
|
+
const element2 = collection.at(lastSelectableIndex)?.element;
|
|
575
|
+
focusInput(element2);
|
|
576
|
+
}
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
) });
|
|
582
|
+
});
|
|
583
|
+
var Root2 = OneTimePasswordField;
|
|
584
|
+
var Input = OneTimePasswordFieldInput;
|
|
585
|
+
var HiddenInput = OneTimePasswordFieldHiddenInput;
|
|
586
|
+
function isFormElement(element) {
|
|
587
|
+
return element?.tagName === "FORM";
|
|
588
|
+
}
|
|
589
|
+
function sanitizeValue(value, regexp) {
|
|
590
|
+
if (Array.isArray(value)) {
|
|
591
|
+
value = value.join("");
|
|
592
|
+
}
|
|
593
|
+
if (regexp) {
|
|
594
|
+
regexp = new RegExp(regexp);
|
|
595
|
+
return value.replace(regexp, "").split("").filter(Boolean);
|
|
596
|
+
}
|
|
597
|
+
return value.split("").filter(Boolean);
|
|
598
|
+
}
|
|
599
|
+
function focusInput(element) {
|
|
600
|
+
if (!element) return;
|
|
601
|
+
if (element.ownerDocument.activeElement === element) {
|
|
602
|
+
window.requestAnimationFrame(() => {
|
|
603
|
+
element.select?.();
|
|
604
|
+
});
|
|
605
|
+
} else {
|
|
606
|
+
element.focus();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function isInputEvent(event) {
|
|
610
|
+
return event.type === "input";
|
|
611
|
+
}
|
|
612
|
+
//# sourceMappingURL=index.js.map
|