@mantine/core
Version:
React components library focused on usability, accessibility and developer experience
246 lines (243 loc) • 7.33 kB
JavaScript
'use client';
import React, { useState, useRef, useEffect } from 'react';
import { useId, useUncontrolled, assignRef } from '@mantine/hooks';
import { getSize } from '../../core/utils/get-size/get-size.mjs';
import { createVarsResolver } from '../../core/styles-api/create-vars-resolver/create-vars-resolver.mjs';
import 'clsx';
import { useResolvedStylesApi } from '../../core/styles-api/use-resolved-styles-api/use-resolved-styles-api.mjs';
import { useStyles } from '../../core/styles-api/use-styles/use-styles.mjs';
import '../../core/MantineProvider/Mantine.context.mjs';
import '../../core/MantineProvider/default-theme.mjs';
import '../../core/MantineProvider/MantineProvider.mjs';
import '../../core/MantineProvider/MantineThemeProvider/MantineThemeProvider.mjs';
import { useProps } from '../../core/MantineProvider/use-props/use-props.mjs';
import '../../core/Box/Box.mjs';
import { factory } from '../../core/factory/factory.mjs';
import '../../core/DirectionProvider/DirectionProvider.mjs';
import { Group } from '../Group/Group.mjs';
import { Input } from '../Input/Input.mjs';
import '../Input/InputWrapper/InputWrapper.mjs';
import '../Input/InputDescription/InputDescription.mjs';
import '../Input/InputError/InputError.mjs';
import '../Input/InputLabel/InputLabel.mjs';
import '../Input/InputPlaceholder/InputPlaceholder.mjs';
import '../Input/InputWrapper.context.mjs';
import { InputBase } from '../InputBase/InputBase.mjs';
import { createPinArray } from './create-pin-array/create-pin-array.mjs';
import classes from './PinInput.module.css.mjs';
const regex = {
number: /^[0-9]+$/,
alphanumeric: /^[a-zA-Z0-9]+$/i
};
const defaultProps = {
gap: "sm",
length: 4,
manageFocus: true,
oneTimeCode: true,
placeholder: "\u25CB",
type: "alphanumeric",
ariaLabel: "PinInput"
};
const varsResolver = createVarsResolver((_, { size }) => ({
root: {
"--pin-input-size": getSize(size ?? defaultProps.size, "pin-input-size")
}
}));
const PinInput = factory((props, ref) => {
const {
name,
form,
className,
value,
defaultValue,
variant,
gap,
style,
size,
classNames,
styles,
unstyled,
length,
onChange,
onComplete,
manageFocus,
autoFocus,
error,
radius,
disabled,
oneTimeCode,
placeholder,
type,
mask,
readOnly,
inputType,
inputMode,
ariaLabel,
vars,
id,
hiddenInputProps,
...others
} = useProps("PinInput", defaultProps, props);
const uuid = useId(id);
const getStyles = useStyles({
name: "PinInput",
classes,
props,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver
});
const { resolvedClassNames, resolvedStyles } = useResolvedStylesApi({
classNames,
styles,
props
});
const [focusedIndex, setFocusedIndex] = useState(-1);
const [_value, setValues] = useUncontrolled({
value,
defaultValue,
finalValue: "",
onChange
});
const inputsRef = useRef([]);
const validate = (code) => {
const re = type instanceof RegExp ? type : type && type in regex ? regex[type] : null;
return re?.test(code);
};
const focusInputField = (dir, index) => {
if (!manageFocus)
return;
if (dir === "next") {
const nextIndex = index + 1;
inputsRef.current[nextIndex < (length ?? 0) ? nextIndex : index].focus();
}
if (dir === "prev") {
const nextIndex = index - 1;
inputsRef.current[nextIndex > -1 ? nextIndex : index].focus();
}
};
const setFieldValue = (val, index) => {
const values = [...createPinArray(length ?? 0, _value)];
values[index] = val;
setValues(values.join(""));
};
const handleChange = (event, index) => {
const inputValue = event.target.value;
const nextCharOrValue = inputValue.length === 2 ? inputValue.split("")[inputValue.length - 1] : inputValue;
const isValid = validate(nextCharOrValue);
if (nextCharOrValue.length < 2) {
if (isValid) {
setFieldValue(nextCharOrValue, index);
focusInputField("next", index);
} else {
setFieldValue("", index);
}
} else if (isValid) {
setValues(inputValue);
}
};
const handleKeyDown = (event, index) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
focusInputField("prev", index);
} else if (event.key === "ArrowRight") {
event.preventDefault();
focusInputField("next", index);
} else if (event.key === "Delete") {
event.preventDefault();
setFieldValue("", index);
} else if (event.key === "Backspace") {
event.preventDefault();
setFieldValue("", index);
if (length === index + 1) {
if (event.target.value === "") {
focusInputField("prev", index);
}
} else {
focusInputField("prev", index);
}
}
};
const handleFocus = (event, index) => {
event.target.select();
setFocusedIndex(index);
};
const handleBlur = () => {
setFocusedIndex(-1);
};
const handlePaste = (event) => {
event.preventDefault();
const copyValue = event.clipboardData.getData("Text");
const isValid = validate(copyValue.trim());
if (isValid) {
setValues(copyValue);
}
};
useEffect(() => {
if (_value.length !== length)
return;
onComplete?.(_value);
}, [_value]);
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
Group,
{
...others,
...getStyles("root"),
role: "group",
id: uuid,
gap,
unstyled,
wrap: "nowrap",
variant,
__size: size
},
createPinArray(length ?? 0, _value).map((char, index) => /* @__PURE__ */ React.createElement(
Input,
{
component: "input",
...getStyles("pinInput", {
style: {
"--_input-padding": "0",
"--_input-text-align": "center"
}
}),
classNames: resolvedClassNames,
styles: resolvedStyles,
size,
__staticSelector: "PinInput",
id: `${uuid}-${index + 1}`,
key: `${uuid}-${index}`,
inputMode: inputMode || (type === "number" ? "numeric" : "text"),
onChange: (event) => handleChange(event, index),
onKeyDown: (event) => handleKeyDown(event, index),
onFocus: (event) => handleFocus(event, index),
onBlur: handleBlur,
onPaste: handlePaste,
type: inputType || (mask ? "password" : type === "number" ? "tel" : "text"),
radius,
error,
variant,
disabled,
ref: (node) => {
index === 0 && assignRef(ref, node);
inputsRef.current[index] = node;
},
autoComplete: oneTimeCode ? "one-time-code" : "off",
placeholder: focusedIndex === index ? "" : placeholder,
value: char,
autoFocus: autoFocus && index === 0,
unstyled,
"aria-label": ariaLabel,
readOnly
}
))
), /* @__PURE__ */ React.createElement("input", { type: "hidden", name, form, value: _value, ...hiddenInputProps }));
});
PinInput.classes = { ...classes, ...InputBase.classes };
PinInput.displayName = "@mantine/core/PinInput";
export { PinInput };
//# sourceMappingURL=PinInput.mjs.map