UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

246 lines (243 loc) 7.33 kB
'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