import React, { useEffect, useRef, useState } from 'react'; import { cn } from '../../lib/utils'; interface DebouncedTextareaProps extends Omit, 'value' | 'onChange'> { value: string; onCommit: (value: string) => void; /** Fires on every keystroke before the debounced commit (e.g. live CSS preview). */ onDraftChange?: (value: string) => void; debounceMs?: number; } /** * Keeps typing responsive by holding draft text locally and committing upstream * after a short pause — avoids re-rendering the full settings tree per keystroke. */ export const DebouncedTextarea: React.FC = ({ value, onCommit, onDraftChange, debounceMs = 300, className, ...props }) => { const [draft, setDraft] = useState(value); const draftRef = useRef(value); const committedRef = useRef(value); const commitTimerRef = useRef | null>(null); const onCommitRef = useRef(onCommit); const onDraftChangeRef = useRef(onDraftChange); onCommitRef.current = onCommit; onDraftChangeRef.current = onDraftChange; draftRef.current = draft; committedRef.current = value; useEffect(() => { setDraft(value); }, [value]); useEffect(() => { return () => { if (commitTimerRef.current) { clearTimeout(commitTimerRef.current); commitTimerRef.current = null; } if (draftRef.current !== committedRef.current) { onCommitRef.current(draftRef.current); } }; }, []); const scheduleCommit = (next: string) => { if (commitTimerRef.current) clearTimeout(commitTimerRef.current); commitTimerRef.current = setTimeout(() => { commitTimerRef.current = null; onCommitRef.current(next); }, debounceMs); }; return (