/** * debounce - coalesce rapid calls into one trailing call after `ms` of quiet. * Used for remote search in the async dropdowns (SvMultiSelect/GridSelect/...), * but general-purpose. Returns the debounced function with `cancel()` (drop the * pending call) and `flush()` (run it now). Framework-free + pure. */ export type Debounced = ((...args: A) => void) & { cancel: () => void flush: () => void } export function debounce(fn: (...args: A) => void, ms: number): Debounced { let timer: ReturnType | null = null let pending: A | null = null const run = () => { timer = null const args = pending pending = null if (args) fn(...args) } const debounced = ((...args: A) => { pending = args if (timer) clearTimeout(timer) timer = setTimeout(run, ms) }) as Debounced debounced.cancel = () => { if (timer) clearTimeout(timer) timer = null pending = null } debounced.flush = () => { if (timer) { clearTimeout(timer) run() } } return debounced }