| import { useEffect, useRef } from 'react' |
| import { normalizeFullWidthDigits } from '../../utils/stringUtils.js' |
|
|
| |
| |
| |
| |
| const DEFAULT_DEBOUNCE_MS = 400 |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function useDebouncedDigitInput<T extends string = string>({ |
| inputValue, |
| setInputValue, |
| isValidDigit, |
| onDigit, |
| enabled = true, |
| once = false, |
| debounceMs = DEFAULT_DEBOUNCE_MS, |
| }: { |
| inputValue: string |
| setInputValue: (value: string) => void |
| isValidDigit: (char: string) => char is T |
| onDigit: (digit: T) => void |
| enabled?: boolean |
| once?: boolean |
| debounceMs?: number |
| }): void { |
| const initialInputValue = useRef(inputValue) |
| const hasTriggeredRef = useRef(false) |
| const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null) |
|
|
| |
| |
| const callbacksRef = useRef({ setInputValue, isValidDigit, onDigit }) |
| callbacksRef.current = { setInputValue, isValidDigit, onDigit } |
|
|
| useEffect(() => { |
| if (!enabled || (once && hasTriggeredRef.current)) { |
| return |
| } |
|
|
| if (debounceRef.current !== null) { |
| clearTimeout(debounceRef.current) |
| debounceRef.current = null |
| } |
|
|
| if (inputValue !== initialInputValue.current) { |
| const lastChar = normalizeFullWidthDigits(inputValue.slice(-1)) |
| if (callbacksRef.current.isValidDigit(lastChar)) { |
| const trimmed = inputValue.slice(0, -1) |
| debounceRef.current = setTimeout( |
| (debounceRef, hasTriggeredRef, callbacksRef, trimmed, lastChar) => { |
| debounceRef.current = null |
| hasTriggeredRef.current = true |
| callbacksRef.current.setInputValue(trimmed) |
| callbacksRef.current.onDigit(lastChar) |
| }, |
| debounceMs, |
| debounceRef, |
| hasTriggeredRef, |
| callbacksRef, |
| trimmed, |
| lastChar, |
| ) |
| } |
| } |
|
|
| return () => { |
| if (debounceRef.current !== null) { |
| clearTimeout(debounceRef.current) |
| debounceRef.current = null |
| } |
| } |
| }, [inputValue, enabled, once, debounceMs]) |
| } |
|
|