import { extract } from "../extract/extract.svelte.js"; /** * Function that takes a callback, and returns a debounced version of it. * When calling the debounced function, it will wait for the specified time * before calling the original callback. If the debounced function is called * again before the time has passed, the timer will be reset. * * You can await the debounced function to get the value when it is eventually * called. * * The second parameter is the time to wait before calling the original callback. * Alternatively, it can also be a getter function that returns the time to wait. * * @see {@link https://runed.dev/docs/utilities/use-debounce} * * @param callback The callback to call when the time has passed. * @param wait The length of time to wait in ms, defaults to 250. */ export function useDebounce(callback, wait) { let context = $state(null); const wait$ = $derived(extract(wait, 250)); function debounced(...args) { if (context) { // Old context will be reused so callers awaiting the promise will get the // new value if (context.timeout) { clearTimeout(context.timeout); } } else { // No old context, create a new one let resolve; let reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); context = { timeout: null, runner: null, promise, resolve: resolve, reject: reject, }; } context.runner = async () => { // Grab the context and reset it // -> new debounced calls will create a new context if (!context) return; const ctx = context; context = null; try { ctx.resolve(await callback.apply(this, args)); } catch (error) { ctx.reject(error); } }; context.timeout = setTimeout(context.runner, wait$); return context.promise; } debounced.cancel = async () => { if (!context || context.timeout === null) { // Wait one event loop to see if something triggered the debounced function await new Promise((resolve) => setTimeout(resolve, 0)); if (!context || context.timeout === null) return; } clearTimeout(context.timeout); context.reject("Cancelled"); context = null; }; debounced.runScheduledNow = async () => { if (!context || !context.timeout) { // Wait one event loop to see if something triggered the debounced function await new Promise((resolve) => setTimeout(resolve, 0)); if (!context || !context.timeout) return; } clearTimeout(context.timeout); context.timeout = null; await context.runner?.(); }; Object.defineProperty(debounced, "pending", { enumerable: true, get() { return !!context?.timeout; }, }); return debounced; }