UNPKG

react-pin-code-input

Version:

React component for entering and validating a PIN code.

161 lines (158 loc) 6.53 kB
import { jsx } from 'react/jsx-runtime'; import { createRef, useEffect, memo } from 'react'; const defaultStyle = { textAlign: 'center', }; const defaultInputStyle = { background: 'transparent', border: 'none', borderBottom: '1px solid #ccc', borderRadius: 0, boxShadow: 'none', color: '#3e4958', fontFamily: 'unset', fontSize: '2rem', height: '29px', marginRight: '8px', outline: 'none', padding: '0 0 4px 0', textAlign: 'center', width: '36px', userSelect: 'none', }; const defaultInvalidInputStyle = { ...defaultInputStyle, borderBottom: '1px solid #fcc', }; const keyCode = { BACKSPACE: 'Backspace', ENTER: 'Enter', LEFT_ARROW_COMPATIBILITY: 'Left', LEFT_ARROW: 'ArrowLeft', UP_ARROW_COMPATIBILITY: 'Up', UP_ARROW: 'ArrowUp', RIGHT_ARROW_COMPATIBILITY: 'Right', RIGHT_ARROW: 'ArrowRight', DOWN_ARROW_COMPATIBILITY: 'Down', DOWN_ARROW: 'ArrowDown', }; const patterns = { number: /^$|^([0-9])$/, tel: /^$|^([0-9])$/, text: /^$|^(.)$/, password: /^$|^(.)$/, }; const defaultProps = { type: 'number', autoFocus: true, disabled: false, invalid: false, disableInlineStyles: false, className: 'react-pin-code-input', id: 'react-pin-code-input', }; const getFocusPosition = (array) => { const position = array.findIndex((state) => state === ''); return position > -1 ? position : array.length; }; function ReactPinCodeInputComponent(props) { const { id, type, value: stateValues, onInputChange, className, autoFocus, pattern, disabled, required, invalid, disableInlineStyles, style, inputStyle, inputInvalidStyle, ...otherProps } = { ...defaultProps, ...props }; const focusIndex = autoFocus ? getFocusPosition(stateValues) : -1; const refs = Array.from({ length: stateValues.length }).map(() => createRef()); const checkPattern = pattern || patterns[type]; useEffect(() => { stateValues.forEach((value, index) => { var _a; const ref = (_a = refs[index]) === null || _a === void 0 ? void 0 : _a.current; if (ref) { ref.value = value; } }); }, [refs, stateValues]); const onChange = (value, index) => { const changedValues = [...stateValues]; changedValues[index] = value; onInputChange(changedValues); }; const onChangeAll = (values) => { for (const [key, value] of values.entries()) { if (checkPattern && !checkPattern.test(value)) { values[key] = ''; } } onInputChange(values); }; const onPaste = (e, index) => { var _a, _b; e.preventDefault(); const values = [...stateValues]; const clipboardValues = e.clipboardData.getData('Text').split(''); for (const [key, value] of clipboardValues.entries()) { const ref = (_a = refs[index + key]) === null || _a === void 0 ? void 0 : _a.current; if (!ref) { break; } values[index + key] = value; const next = (_b = refs[index + key + 1]) === null || _b === void 0 ? void 0 : _b.current; if (next) { next.focus(); continue; } ref.focus(); } onChangeAll(values); }; const onKeyDown = (e, index) => { var _a, _b, _c; const current = refs[index].current; const next = (_a = refs[index + 1]) === null || _a === void 0 ? void 0 : _a.current; const prev = (_b = refs[index - 1]) === null || _b === void 0 ? void 0 : _b.current; const isValidKey = checkPattern && checkPattern.test(e.key); if (!(e.ctrlKey || e.metaKey)) { e.preventDefault(); } switch (e.key) { case keyCode.BACKSPACE: if (e.ctrlKey || e.metaKey) { e.preventDefault(); onChangeAll(Array(stateValues.length).fill('')); (_c = refs[0].current) === null || _c === void 0 ? void 0 : _c.focus(); } else if ((current === null || current === void 0 ? void 0 : current.value) !== '') { onChange('', index); } else if ((current === null || current === void 0 ? void 0 : current.value) === '' && index > 0) { onChange('', index - 1); prev === null || prev === void 0 ? void 0 : prev.focus(); } break; case keyCode.LEFT_ARROW: case keyCode.LEFT_ARROW_COMPATIBILITY: case keyCode.UP_ARROW: case keyCode.UP_ARROW_COMPATIBILITY: prev === null || prev === void 0 ? void 0 : prev.focus(); break; case keyCode.RIGHT_ARROW: case keyCode.RIGHT_ARROW_COMPATIBILITY: case keyCode.DOWN_ARROW: case keyCode.DOWN_ARROW_COMPATIBILITY: next === null || next === void 0 ? void 0 : next.focus(); break; default: if ((current === null || current === void 0 ? void 0 : current.value) === e.key) { next === null || next === void 0 ? void 0 : next.focus(); } else if (current && isValidKey && !e.ctrlKey && !e.metaKey) { onChange(e.key, index); next === null || next === void 0 ? void 0 : next.focus(); } } }; return (jsx("div", Object.assign({ id: id, className: invalid ? `${className} invalid` : className, style: disableInlineStyles ? {} : { ...defaultStyle, ...style } }, otherProps, { children: refs.map((ref, index) => (jsx("input", { id: `${id}-${index}`, "data-id": `${index}`, type: type, autoFocus: index === focusIndex, value: stateValues[index], ref: ref, onChange: (e) => onChange(e.currentTarget.value, index), onKeyDown: (e) => onKeyDown(e, index), onPaste: (e) => onPaste(e, index), disabled: disabled, required: required, autoComplete: "off", style: disableInlineStyles ? {} : invalid ? { ...defaultInvalidInputStyle, ...inputInvalidStyle } : { ...defaultInputStyle, ...inputStyle } }, `${id}-${index}`))) }), void 0)); } const ReactPinCodeInput = memo(ReactPinCodeInputComponent); export { ReactPinCodeInput, ReactPinCodeInputComponent, defaultProps };