@tldraw/utils
Version:
tldraw infinite canvas SDK (private utilities).
8 lines (7 loc) • 9.3 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/lib/timers.ts"],
"sourcesContent": ["/* eslint-disable no-restricted-properties */\n\n/**\n * A utility class for managing timeouts, intervals, and animation frames with context-based organization and automatic cleanup.\n * Helps prevent memory leaks by organizing timers into named contexts that can be cleared together.\n * @example\n * ```ts\n * const timers = new Timers()\n *\n * // Set timers with context organization\n * timers.setTimeout('ui', () => console.log('Auto save'), 5000)\n * timers.setInterval('ui', () => console.log('Refresh'), 1000)\n * timers.requestAnimationFrame('ui', () => console.log('Render'))\n *\n * // Clear all timers for a context\n * timers.dispose('ui')\n *\n * // Or get context-bound functions\n * const uiTimers = timers.forContext('ui')\n * uiTimers.setTimeout(() => console.log('Contextual timeout'), 1000)\n * ```\n * @public\n */\nexport class Timers {\n\tprivate timeouts = new Map<string, number[]>()\n\tprivate intervals = new Map<string, number[]>()\n\tprivate rafs = new Map<string, number[]>()\n\n\t/**\n\t * Creates a new Timers instance with bound methods for safe callback usage.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * // Methods are pre-bound, safe to use as callbacks\n\t * element.addEventListener('click', timers.dispose)\n\t * ```\n\t */\n\tconstructor() {\n\t\tthis.setTimeout = this.setTimeout.bind(this)\n\t\tthis.setInterval = this.setInterval.bind(this)\n\t\tthis.requestAnimationFrame = this.requestAnimationFrame.bind(this)\n\t\tthis.dispose = this.dispose.bind(this)\n\t}\n\n\t/**\n\t * Creates a timeout that will be tracked under the specified context.\n\t * @param contextId - The context identifier to group this timer under.\n\t * @param handler - The function to execute when the timeout expires.\n\t * @param timeout - The delay in milliseconds (default: 0).\n\t * @param args - Additional arguments to pass to the handler.\n\t * @returns The timer ID that can be used with clearTimeout.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * const id = timers.setTimeout('autosave', () => save(), 5000)\n\t * // Timer will be automatically cleared when 'autosave' context is disposed\n\t * ```\n\t * @public\n\t */\n\tsetTimeout(contextId: string, handler: TimerHandler, timeout?: number, ...args: any[]): number {\n\t\tconst id = window.setTimeout(handler, timeout, args)\n\t\tconst current = this.timeouts.get(contextId) ?? []\n\t\tthis.timeouts.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/**\n\t * Creates an interval that will be tracked under the specified context.\n\t * @param contextId - The context identifier to group this timer under.\n\t * @param handler - The function to execute repeatedly.\n\t * @param timeout - The delay in milliseconds between executions (default: 0).\n\t * @param args - Additional arguments to pass to the handler.\n\t * @returns The interval ID that can be used with clearInterval.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * const id = timers.setInterval('refresh', () => updateData(), 1000)\n\t * // Interval will be automatically cleared when 'refresh' context is disposed\n\t * ```\n\t * @public\n\t */\n\tsetInterval(contextId: string, handler: TimerHandler, timeout?: number, ...args: any[]): number {\n\t\tconst id = window.setInterval(handler, timeout, args)\n\t\tconst current = this.intervals.get(contextId) ?? []\n\t\tthis.intervals.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/**\n\t * Requests an animation frame that will be tracked under the specified context.\n\t * @param contextId - The context identifier to group this animation frame under.\n\t * @param callback - The function to execute on the next animation frame.\n\t * @returns The request ID that can be used with cancelAnimationFrame.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * const id = timers.requestAnimationFrame('render', () => draw())\n\t * // Animation frame will be automatically cancelled when 'render' context is disposed\n\t * ```\n\t * @public\n\t */\n\trequestAnimationFrame(contextId: string, callback: FrameRequestCallback): number {\n\t\tconst id = window.requestAnimationFrame(callback)\n\t\tconst current = this.rafs.get(contextId) ?? []\n\t\tthis.rafs.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/**\n\t * Disposes of all timers associated with the specified context.\n\t * Clears all timeouts, intervals, and animation frames for the given context ID.\n\t * @param contextId - The context identifier whose timers should be cleared.\n\t * @returns void\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * timers.setTimeout('ui', () => console.log('timeout'), 1000)\n\t * timers.setInterval('ui', () => console.log('interval'), 500)\n\t *\n\t * // Clear all 'ui' context timers\n\t * timers.dispose('ui')\n\t * ```\n\t * @public\n\t */\n\tdispose(contextId: string) {\n\t\tthis.timeouts.get(contextId)?.forEach((id) => clearTimeout(id))\n\t\tthis.intervals.get(contextId)?.forEach((id) => clearInterval(id))\n\t\tthis.rafs.get(contextId)?.forEach((id) => cancelAnimationFrame(id))\n\n\t\tthis.timeouts.delete(contextId)\n\t\tthis.intervals.delete(contextId)\n\t\tthis.rafs.delete(contextId)\n\t}\n\n\t/**\n\t * Disposes of all timers across all contexts.\n\t * Clears every timeout, interval, and animation frame managed by this instance.\n\t * @returns void\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * timers.setTimeout('ui', () => console.log('ui'), 1000)\n\t * timers.setTimeout('background', () => console.log('bg'), 2000)\n\t *\n\t * // Clear everything\n\t * timers.disposeAll()\n\t * ```\n\t * @public\n\t */\n\tdisposeAll() {\n\t\tfor (const contextId of this.timeouts.keys()) {\n\t\t\tthis.dispose(contextId)\n\t\t}\n\t}\n\n\t/**\n\t * Returns an object with timer methods bound to a specific context.\n\t * Convenient for getting context-specific timer functions without repeatedly passing the contextId.\n\t * @param contextId - The context identifier to bind the returned methods to.\n\t * @returns An object with setTimeout, setInterval, requestAnimationFrame, and dispose methods bound to the context.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * const uiTimers = timers.forContext('ui')\n\t *\n\t * // These are equivalent to calling timers.setTimeout('ui', ...)\n\t * uiTimers.setTimeout(() => console.log('timeout'), 1000)\n\t * uiTimers.setInterval(() => console.log('interval'), 500)\n\t * uiTimers.requestAnimationFrame(() => console.log('frame'))\n\t *\n\t * // Dispose only this context\n\t * uiTimers.dispose()\n\t * ```\n\t * @public\n\t */\n\tforContext(contextId: string) {\n\t\treturn {\n\t\t\tsetTimeout: (handler: TimerHandler, timeout?: number, ...args: any[]) =>\n\t\t\t\tthis.setTimeout(contextId, handler, timeout, args),\n\t\t\tsetInterval: (handler: TimerHandler, timeout?: number, ...args: any[]) =>\n\t\t\t\tthis.setInterval(contextId, handler, timeout, args),\n\t\t\trequestAnimationFrame: (callback: FrameRequestCallback) =>\n\t\t\t\tthis.requestAnimationFrame(contextId, callback),\n\t\t\tdispose: () => this.dispose(contextId),\n\t\t}\n\t}\n}\n"],
"mappings": "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",
"names": []
}