@yamada-ui/pin-input
Version:
Yamada UI pin input component
300 lines (299 loc) • 10.8 kB
JavaScript
"use client"
;
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