UNPKG

@yamada-ui/pin-input

Version:

Yamada UI pin input component

300 lines (299 loc) • 10.8 kB
"use client" "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/pin-input.tsx var pin_input_exports = {}; __export(pin_input_exports, { PinInput: () => PinInput, PinInputField: () => PinInputField }); module.exports = __toCommonJS(pin_input_exports); var import_core = require("@yamada-ui/core"); var import_form_control = require("@yamada-ui/form-control"); var import_use_controllable_state = require("@yamada-ui/use-controllable-state"); var import_use_descendant = require("@yamada-ui/use-descendant"); var import_utils = require("@yamada-ui/utils"); var import_react = require("react"); var import_jsx_runtime = require("react/jsx-runtime"); var toArray = (value) => value == null ? void 0 : value.split(""); var validate = (value, type) => { const NUMERIC_REGEX = /^[0-9]+$/; const ALPHA_NUMERIC_REGEX = /^[a-zA-Z0-9]+$/i; const regex = type === "alphanumeric" ? ALPHA_NUMERIC_REGEX : NUMERIC_REGEX; return regex.test(value); }; var [PinInputProvider, usePinInputContext] = (0, import_utils.createContext)({ name: "PinInputContext", errorMessage: `PinInputContext returned is 'undefined'. Seems you forgot to wrap the components in "<PinInput />"` }); var { DescendantsContextProvider, useDescendant, useDescendants } = (0, import_use_descendant.createDescendant)(); var PinInput = (0, import_core.forwardRef)( ({ errorBorderColor, focusBorderColor, ...props }, ref) => { const [styles, mergedProps] = (0, import_core.useComponentMultiStyle)("PinInput", { errorBorderColor, focusBorderColor, ...props }); const uuid = (0, import_react.useId)(); const { id = uuid, type = "number", className, autoFocus, children, defaultValue, items = 4, manageFocus = true, mask, otp = false, placeholder = "\u25CB", value, onChange: onChangeProp, onComplete, ...rest } = (0, import_form_control.useFormControlProps)((0, import_core.omitThemeProps)(mergedProps)); const [ { "aria-readonly": _ariaReadonly, disabled, readOnly, ...formControlProps }, containerProps ] = (0, import_utils.splitObject)(rest, import_form_control.formControlProperties); const descendants = useDescendants(); const [moveFocus, setMoveFocus] = (0, import_react.useState)(true); const [focusedIndex, setFocusedIndex] = (0, import_react.useState)(-1); const [values, setValues] = (0, import_use_controllable_state.useControllableState)({ defaultValue: toArray(defaultValue) || [], value: toArray(value), onChange: (values2) => onChangeProp == null ? void 0 : onChangeProp(values2.join("")) }); const css = { alignItems: "center", display: "flex", ...styles.container }; const focusNext = (0, import_react.useCallback)( (index) => { if (!moveFocus || !manageFocus) return; const next = descendants.nextValue(index, void 0, false); if (!next) return; requestAnimationFrame(() => next.node.focus()); }, [descendants, moveFocus, manageFocus] ); const focusInputField = (0, import_react.useCallback)( (direction, index) => { const input = direction === "next" ? descendants.nextValue(index, void 0, false) : descendants.prevValue(index, void 0, false); if (!input) return; const valueLength = input.node.value.length; requestAnimationFrame(() => { input.node.focus(); input.node.setSelectionRange(0, valueLength); }); }, [descendants] ); const setValue = (0, import_react.useCallback)( (value2, index, isFocus = true) => { var _a; let nextValues = [...values]; nextValues[index] = value2; setValues(nextValues); nextValues = nextValues.filter(Boolean); const isComplete = value2 !== "" && nextValues.length === descendants.count(); if (isComplete) { onComplete == null ? void 0 : onComplete(nextValues.join("")); (_a = descendants.value(index)) == null ? void 0 : _a.node.blur(); } else if (isFocus) { focusNext(index); } }, [values, setValues, descendants, onComplete, focusNext] ); const getNextValue = (0, import_react.useCallback)( (value2, eventValue) => { let nextValue = eventValue; if (!(value2 == null ? void 0 : value2.length)) return nextValue; if (value2.startsWith(eventValue.charAt(0))) { nextValue = eventValue.charAt(1); } else if (value2.startsWith(eventValue.charAt(1))) { nextValue = eventValue.charAt(0); } return nextValue; }, [] ); const onChange = (0, import_react.useCallback)( (index) => ({ target }) => { var _a; const eventValue = target.value; const currentValue = values[index]; const nextValue = getNextValue(currentValue, eventValue); if (nextValue === "") { setValue("", index); return; } if (eventValue.length > 2) { if (!validate(eventValue, type)) return; const nextValue2 = eventValue.split("").filter((_, index2) => index2 < descendants.count()); setValues(nextValue2); if (nextValue2.length === descendants.count()) { onComplete == null ? void 0 : onComplete(nextValue2.join("")); (_a = descendants.value(index)) == null ? void 0 : _a.node.blur(); } } else { if (validate(nextValue, type)) setValue(nextValue, index); setMoveFocus(true); } }, [ descendants, getNextValue, onComplete, setValue, setValues, type, values ] ); const onKeyDown = (0, import_react.useCallback)( (index) => (ev) => { if (!manageFocus) return; const actions = { ArrowLeft: () => { ev.preventDefault(); focusInputField("prev", index); }, ArrowRight: () => { ev.preventDefault(); focusInputField("next", index); }, Backspace: () => { if (ev.target.value === "") { const prevInput = descendants.prevValue(index, void 0, false); if (!prevInput) return; setValue("", index - 1, false); prevInput.node.focus(); setMoveFocus(true); } else { setMoveFocus(false); } } }; const action = actions[ev.key]; if (!action) return; action(); }, [descendants, focusInputField, manageFocus, setValue] ); const onFocus = (0, import_react.useCallback)( (index) => () => setFocusedIndex(index), [] ); const onBlur = (0, import_react.useCallback)(() => setFocusedIndex(-1), []); (0, import_react.useEffect)(() => { if (!autoFocus) return; const firstValue = descendants.firstValue(); if (!firstValue) return; requestAnimationFrame(() => firstValue.node.focus()); }, [autoFocus, descendants]); const getInputProps = (0, import_react.useCallback)( ({ index, ...props2 }) => ({ type: mask ? "password" : type === "number" ? "tel" : "text", disabled, inputMode: type === "number" ? "numeric" : "text", readOnly, ...formControlProps, ...(0, import_utils.filterUndefined)(props2), id: `${id}-${index}`, autoComplete: otp ? "one-time-code" : "off", placeholder: focusedIndex === index && !readOnly && !props2.readOnly ? "" : placeholder, value: values[index] || "", onBlur: (0, import_utils.handlerAll)(props2.onBlur, onBlur), onChange: (0, import_utils.handlerAll)(props2.onChange, onChange(index)), onFocus: (0, import_utils.handlerAll)(props2.onFocus, onFocus(index)), onKeyDown: (0, import_utils.handlerAll)(props2.onKeyDown, onKeyDown(index)) }), [ type, mask, formControlProps, id, values, onChange, onKeyDown, onFocus, onBlur, otp, focusedIndex, disabled, readOnly, placeholder ] ); let cloneChildren = (0, import_utils.getValidChildren)(children); if (!cloneChildren.length) for (let i = 0; i < items; i++) { cloneChildren.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PinInputField, {}, i)); } return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DescendantsContextProvider, { value: descendants, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PinInputProvider, { value: { styles, getInputProps }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_core.ui.div, { ref, className: (0, import_utils.cx)("ui-pin-input", className), role: "group", __css: css, ...formControlProps, ...containerProps, children: cloneChildren } ) }) }); } ); PinInput.displayName = "PinInput"; PinInput.__ui__ = "PinInput"; var PinInputField = (0, import_core.forwardRef)( ({ className, ...rest }, ref) => { const { styles, getInputProps } = usePinInputContext(); const { index, register } = useDescendant(); const css = { ...styles.field }; rest = (0, import_form_control.useFormControlProps)(rest); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_core.ui.input, { className: (0, import_utils.cx)("ui-pin-input__field", className), ...getInputProps({ ...rest, ref: (0, import_utils.mergeRefs)(register, ref), index }), __css: css } ); } ); PinInputField.displayName = "PinInputField"; PinInputField.__ui__ = "PinInputField"; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { PinInput, PinInputField }); //# sourceMappingURL=pin-input.js.map