UNPKG

@coreui/react-pro

Version:

UI Components Library for React.js

177 lines (174 loc) 10.8 kB
import { __rest } from '../../node_modules/tslib/tslib.es6.js'; import React, { forwardRef, useRef, useMemo, Children, useState, useEffect, useCallback, isValidElement, cloneElement } from 'react'; import PropTypes from 'prop-types'; import classNames from '../../_virtual/index.js'; import { CFormControlWrapper } from '../form/CFormControlWrapper.js'; import { isValidInput, extractValidChars } from './utils.js'; import getNextActiveElement from '../../utils/getNextActiveElement.js'; import isRTL from '../../utils/isRTL.js'; const COneTimePassword = forwardRef((_a, ref) => { var { ariaLabel = (index, total) => `Digit ${index + 1} of ${total}`, autoSubmit = false, children, className, defaultValue, disabled = false, feedback, feedbackInvalid, feedbackValid, floatingClassName, floatingLabel, id, invalid, label, linear = true, masked = false, name, onChange, onComplete, placeholder, readOnly = false, required = false, size, text, tooltipFeedback, type = 'number', valid, value } = _a, rest = __rest(_a, ["ariaLabel", "autoSubmit", "children", "className", "defaultValue", "disabled", "feedback", "feedbackInvalid", "feedbackValid", "floatingClassName", "floatingLabel", "id", "invalid", "label", "linear", "masked", "name", "onChange", "onComplete", "placeholder", "readOnly", "required", "size", "text", "tooltipFeedback", "type", "valid", "value"]); const inputRefs = useRef([]); const hiddenInputRef = useRef(null); // Count valid OTP input children const inputCount = useMemo(() => { return Children.count(children); }, [children]); const [inputValues, setInputValues] = useState(() => { var _a; const initialValue = String((_a = value !== null && value !== void 0 ? value : defaultValue) !== null && _a !== void 0 ? _a : ''); return Array.from({ length: inputCount }, (_, i) => initialValue[i] || ''); }); // Update input values when value prop changes (controlled mode) useEffect(() => { if (value !== undefined) { const valueString = String(value); setInputValues(Array.from({ length: inputCount }, (_, i) => valueString[i] || '')); } }, [value, inputCount]); // Update hidden input and trigger events const updateValue = useCallback((newValues) => { var _a; const newValue = newValues.join(''); if (hiddenInputRef.current) { hiddenInputRef.current.value = newValue; } onChange === null || onChange === void 0 ? void 0 : onChange(newValue); if (newValue.length === inputCount) { onComplete === null || onComplete === void 0 ? void 0 : onComplete(newValue); if (autoSubmit) { const form = (_a = hiddenInputRef.current) === null || _a === void 0 ? void 0 : _a.closest('form'); if (form && typeof form.requestSubmit === 'function') { form.requestSubmit(); } } } }, [autoSubmit, onChange, onComplete, inputCount]); const handleInputChange = useCallback((index, event) => { const inputValue = event.target.value; if (inputValue.length === 1 && !isValidInput(inputValue, type)) { return; } const newValues = [...inputValues]; newValues[index] = inputValue.length === 1 ? inputValue : ''; setInputValues(newValues); updateValue(newValues); if (inputValue.length === 1) { const nextInput = getNextActiveElement(inputRefs.current, event.target, true, false); nextInput === null || nextInput === void 0 ? void 0 : nextInput.focus(); } }, [inputValues, type, updateValue]); const handleInputFocus = useCallback((event) => { const { target } = event; if (target.value) { setTimeout(() => { target.select(); }, 0); return; } if (linear) { const firstEmptyInput = inputRefs.current.find((input) => !(input === null || input === void 0 ? void 0 : input.value)); if (firstEmptyInput && firstEmptyInput !== target) { firstEmptyInput.focus(); } } }, [linear]); const handleKeyDown = useCallback((event) => { const { key, target } = event; if (key === 'Backspace' && target.value === '') { const newValues = [...inputValues]; const prevInput = getNextActiveElement(inputRefs.current, target, false, false); if (prevInput) { const prevIndex = inputRefs.current.indexOf(prevInput); if (prevIndex !== -1) { newValues[prevIndex] = ''; setInputValues(newValues); updateValue(newValues); prevInput.focus(); } } return; } if (key === 'ArrowRight') { if (linear && target.value === '') { return; } const shouldMoveNext = !isRTL(target); const nextInput = getNextActiveElement(inputRefs.current, target, shouldMoveNext, false); nextInput === null || nextInput === void 0 ? void 0 : nextInput.focus(); return; } if (key === 'ArrowLeft') { const shouldMoveNext = isRTL(target); const prevInput = getNextActiveElement(inputRefs.current, target, shouldMoveNext, false); prevInput === null || prevInput === void 0 ? void 0 : prevInput.focus(); } }, [inputValues, linear, updateValue]); const handlePaste = useCallback((index, event) => { var _a, _b; event.preventDefault(); const pastedData = event.clipboardData.getData('text'); const validChars = extractValidChars(pastedData, type); if (!validChars) return; const newValues = [...inputValues]; const startIndex = index; for (let i = 0; i < validChars.length && startIndex + i < inputCount; i++) { newValues[startIndex + i] = validChars[i]; } setInputValues(newValues); updateValue(newValues); // Focus the next empty input or the last filled input const nextEmptyIndex = startIndex + validChars.length; if (nextEmptyIndex < inputCount) { (_a = inputRefs.current[nextEmptyIndex]) === null || _a === void 0 ? void 0 : _a.focus(); } else { (_b = inputRefs.current.at(-1)) === null || _b === void 0 ? void 0 : _b.focus(); } }, [inputValues, type, updateValue, inputCount]); let inputIndex = 0; return (React.createElement(CFormControlWrapper, { describedby: rest['aria-describedby'], feedback: feedback, feedbackInvalid: feedbackInvalid, feedbackValid: feedbackValid, floatingClassName: floatingClassName, floatingLabel: floatingLabel, id: id, invalid: invalid, label: label, text: text, tooltipFeedback: tooltipFeedback, valid: valid }, React.createElement("div", Object.assign({ className: classNames('form-otp', { [`form-otp-${size}`]: size, }, className), role: "group", ref: ref }, rest), Children.map(children, (child, index) => { var _a; if (!isValidElement(child) || ((_a = child.type) === null || _a === void 0 ? void 0 : _a.displayName) !== 'COneTimePasswordInput') { return child; } const inputChild = child; const currentInputIndex = inputIndex++; return cloneElement(inputChild, Object.assign(Object.assign({}, inputChild.props), { type: masked ? 'password' : 'text', className: classNames({ 'is-invalid': invalid, 'is-valid': valid, }, inputChild.props.className), id: inputChild.props.id || (id ? `${id}-${currentInputIndex}` : undefined), name: inputChild.props.name || (name ? `${name}-${currentInputIndex}` : undefined), placeholder: inputChild.props.placeholder || (placeholder && placeholder.length > 1 ? placeholder[currentInputIndex] : placeholder), value: inputValues[currentInputIndex] || '', tabIndex: currentInputIndex === 0 ? 0 : inputValues[currentInputIndex - 1] ? 0 : -1, disabled: disabled || inputChild.props.disabled, readOnly: readOnly || inputChild.props.readOnly, required: required || inputChild.props.required, 'aria-label': inputChild.props['aria-label'] || ariaLabel(currentInputIndex, inputCount), inputMode: type === 'number' ? 'numeric' : 'text', pattern: type === 'number' ? '[0-9]*' : '.*', onInput: (event) => { var _a, _b; handleInputChange(currentInputIndex, event); (_b = (_a = inputChild.props).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, event); }, onFocus: (event) => { var _a, _b; handleInputFocus(event); (_b = (_a = inputChild.props).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, event); }, onKeyDown: (event) => { var _a, _b; handleKeyDown(event); (_b = (_a = inputChild.props).onKeyDown) === null || _b === void 0 ? void 0 : _b.call(_a, event); }, onPaste: (event) => { var _a, _b; handlePaste(index, event); (_b = (_a = inputChild.props).onPaste) === null || _b === void 0 ? void 0 : _b.call(_a, event); }, ref: (el) => { inputRefs.current[currentInputIndex] = el; } })); }), React.createElement("input", { type: "hidden", id: id, name: name, value: inputValues.join(''), disabled: disabled, ref: hiddenInputRef })))); }); COneTimePassword.propTypes = Object.assign({ ariaLabel: PropTypes.func, autoSubmit: PropTypes.bool, children: PropTypes.node, className: PropTypes.string, defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), disabled: PropTypes.bool, id: PropTypes.string, linear: PropTypes.bool, masked: PropTypes.bool, name: PropTypes.string, onChange: PropTypes.func, onComplete: PropTypes.func, placeholder: PropTypes.string, readOnly: PropTypes.bool, required: PropTypes.bool, size: PropTypes.oneOf(['sm', 'lg']), type: PropTypes.oneOf(['number', 'text']), value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) }, CFormControlWrapper.propTypes); COneTimePassword.displayName = 'COneTimePassword'; export { COneTimePassword }; //# sourceMappingURL=COneTimePassword.js.map