Download src/components/FeedbackSurvey/useDebouncedDigitInput.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 2.72 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/components/FeedbackSurvey/useDebouncedDigitInput.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/components/FeedbackSurvey/useDebouncedDigitInput.ts
-
curl -L -o useDebouncedDigitInput.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/components/FeedbackSurvey/useDebouncedDigitInput.ts
2.72 kB
| import { useEffect, useRef } from 'react' | |
| import { normalizeFullWidthDigits } from '../../utils/stringUtils.js' | |
| // Delay before accepting a digit as a response, to prevent accidental | |
| // submissions when users start messages with numbers (e.g., numbered lists). | |
| // Short enough to feel instant for intentional presses, long enough to | |
| // cancel when the user types more characters. | |
| const DEFAULT_DEBOUNCE_MS = 400 | |
| /** | |
| * Detects when the user types a single valid digit into the prompt input, | |
| * debounces to avoid accidental submissions (e.g., "1. First item"), | |
| * trims the digit from the input, and fires a callback. | |
| * | |
| * Used by survey components that accept numeric responses typed directly | |
| * into the main prompt input. | |
| */ | |
| 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) | |
| // Latest-ref pattern so callers can pass inline callbacks without causing | |
| // the effect to re-run (which would reset the debounce timer every render). | |
| 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]) | |
| } | |