/** * Debounce a function. The returned function has a `.cancel()` method * so a pending invocation can be cleared in a `useEffect` cleanup to * avoid a setState-after-unmount on the trailing call. * * Intentionally a tiny, no-dependency implementation — `lodash.debounce` * pulls 200kB of lodash with it; this is 15 lines, type-safe, and tree- * shake-friendly. */ export function debounce) => void>( func: T, wait: number ): ((...args: Parameters) => void) & { cancel: () => void } { let timeout: ReturnType | null = null; const debounced = (...args: Parameters) => { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { func(...args); }, wait); }; debounced.cancel = () => { if (timeout) { clearTimeout(timeout); timeout = null; } }; return debounced; }