{"version":3,"file":"PinInput.cjs","names":[],"sources":["../../../src/components/PinInput/PinInput.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — one input per digit, so length,\n * type, masked and autoFocus shape the boxes while\n * value/defaultValue/onChange/onComplete drive them as a single value. The body owns\n * the per-box refs, paste distribution and backspace traversal, which only make\n * sense with the whole array in scope.\n */\nimport { forwardRef, useEffect, useId, useRef, useState } from \"react\";\nimport type { ChangeEvent, ClipboardEvent, KeyboardEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./PinInput.module.css\";\n\nexport type PinInputType = \"numeric\" | \"alphanumeric\";\nexport type PinInputSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface PinInputProps {\n    /** Number of cells. Default `6`. */\n    length?: number;\n    /** Allowed character set. `numeric` (default) rejects letters, `alphanumeric` allows both. */\n    type?: PinInputType;\n    /** Visual size. Default `\"md\"`. */\n    size?: PinInputSize;\n    /** Controlled value. */\n    value?: string;\n    /** Initial value (uncontrolled mode). */\n    defaultValue?: string;\n    /** Fires on every change with the current concatenated value. */\n    onChange?: (value: string) => void;\n    /** Fires when the user fills the last cell. */\n    onComplete?: (value: string) => void;\n    /** Show characters obscured (`*`). Default `false`. */\n    masked?: boolean;\n    /** Label rendered above the cells. */\n    label?: string;\n    /** Helper text below the cells. */\n    helperText?: string;\n    /** Error message — turns cells red and replaces helperText. */\n    error?: string;\n    /** Disable all cells. */\n    disabled?: boolean;\n    /** Auto-focus the first cell on mount. Default `false`. */\n    autoFocus?: boolean;\n    /** id for the wrapping group label association. */\n    id?: string;\n    className?: string;\n}\n\nconst NUMERIC = /[0-9]/;\nconst ALNUM = /[A-Za-z0-9]/;\n\n/**\n * One-time-password style input — N independent cells, paste support, auto-\n * advance on input, backspace flows back, arrow keys navigate.\n *\n * @example\n * <PinInput length={6} type=\"numeric\" onComplete={(otp) => verify(otp)} />\n */\nexport const PinInput = forwardRef<HTMLDivElement, PinInputProps>(function PinInput(\n    {\n        length = 6,\n        type = \"numeric\",\n        size = \"md\",\n        value,\n        defaultValue,\n        onChange,\n        onComplete,\n        masked = false,\n        label,\n        helperText,\n        error,\n        disabled = false,\n        autoFocus = false,\n        id,\n        className,\n    },\n    ref,\n) {\n    const internalId = useId();\n    const wrapperId = id ?? internalId;\n    const isControlled = value !== undefined;\n    const [internal, setInternal] = useState<string>(defaultValue ?? \"\");\n    const current = isControlled ? (value ?? \"\") : internal;\n    const cells = Array.from({ length }, (_, index) => current[index] ?? \"\");\n    const inputsRef = useRef<(HTMLInputElement | null)[]>([]);\n    const pattern = type === \"numeric\" ? NUMERIC : ALNUM;\n\n    useEffect(() => {\n        if (autoFocus) inputsRef.current[0]?.focus();\n    }, [autoFocus]);\n\n    const update = (next: string): void => {\n        const trimmed = next.slice(0, length);\n        if (!isControlled) setInternal(trimmed);\n        onChange?.(trimmed);\n        if (trimmed.length === length) onComplete?.(trimmed);\n    };\n\n    const focusCell = (index: number): void => {\n        const safe = Math.max(0, Math.min(length - 1, index));\n        inputsRef.current[safe]?.focus();\n        inputsRef.current[safe]?.select();\n    };\n\n    const onCellChange = (index: number) => (event: ChangeEvent<HTMLInputElement>) => {\n        const char = event.target.value.slice(-1);\n        if (char && !pattern.test(char)) return;\n        const next = cells.slice();\n        next[index] = char;\n        update(next.join(\"\"));\n        if (char) focusCell(index + 1);\n    };\n\n    const onCellKeyDown = (index: number) => (event: KeyboardEvent<HTMLInputElement>) => {\n        if (event.key === \"Backspace\") {\n            if (!cells[index] && index > 0) {\n                event.preventDefault();\n                const next = cells.slice();\n                next[index - 1] = \"\";\n                update(next.join(\"\"));\n                focusCell(index - 1);\n            }\n        } else if (event.key === \"ArrowLeft\") {\n            event.preventDefault();\n            focusCell(index - 1);\n        } else if (event.key === \"ArrowRight\") {\n            event.preventDefault();\n            focusCell(index + 1);\n        }\n    };\n\n    const onPaste = (event: ClipboardEvent<HTMLInputElement>): void => {\n        const text = event.clipboardData\n            .getData(\"text\")\n            .split(\"\")\n            .filter((c) => pattern.test(c))\n            .join(\"\");\n        if (!text) return;\n        event.preventDefault();\n        update(text);\n        focusCell(Math.min(length - 1, text.length));\n    };\n\n    return (\n        <div\n            ref={ref}\n            className={cn(styles.wrapper, error && styles.error, className)}\n            id={wrapperId}\n        >\n            {label && <label className={styles.label}>{label}</label>}\n            <div className={cn(styles.cells, styles[size])} role=\"group\" aria-label={label}>\n                {cells.map((cell, index) => (\n                    <input\n                        key={index}\n                        ref={(node) => {\n                            inputsRef.current[index] = node;\n                        }}\n                        type={masked ? \"password\" : \"text\"}\n                        inputMode={type === \"numeric\" ? \"numeric\" : \"text\"}\n                        autoComplete={index === 0 ? \"one-time-code\" : \"off\"}\n                        maxLength={1}\n                        value={cell}\n                        disabled={disabled}\n                        className={styles.cell}\n                        aria-label={`Dígito ${index + 1}`}\n                        aria-invalid={!!error}\n                        onChange={onCellChange(index)}\n                        onKeyDown={onCellKeyDown(index)}\n                        onPaste={onPaste}\n                    />\n                ))}\n            </div>\n            {error ? (\n                <span className={styles.errorText}>{error}</span>\n            ) : helperText ? (\n                <span className={styles.helper}>{helperText}</span>\n            ) : null}\n        </div>\n    );\n});\n"],"mappings":"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"}