@efflore/pulse
Version:
Pulse - scheduled DOM updates, debounced with requestAnimationFrame
61 lines (48 loc) • 1.37 kB
text/typescript
/* === Polyfills === */
if (!('requestAnimationFrame' in globalThis))
globalThis.requestAnimationFrame = callback => setTimeout(callback, 16)
/* === Types === */
type EnqueueDedupe = [Element, string]
/* === Internal === */
const dedupeMap = new Map()
let queue: Array<() => void> = []
let requestId: number | null
const flush = () => {
requestId = null
queue.forEach(fn => fn())
queue = []
dedupeMap.clear()
}
const requestTick = () => {
if (requestId) cancelAnimationFrame(requestId)
requestId = requestAnimationFrame(flush)
}
queueMicrotask(flush) // initial flush when the call stack is empty
/* === Exported Functions === */
const enqueue = <T>(
callback: () => T,
dedupe?: EnqueueDedupe
) => new Promise<T>((resolve, reject) => {
const wrappedCallback = () => {
try {
resolve(callback())
} catch (error) {
reject(error)
}
}
if (dedupe) {
const [el, op] = dedupe
if (!dedupeMap.has(el)) dedupeMap.set(el, new Map())
const elementMap = dedupeMap.get(el)
if (elementMap.has(op)) {
const idx = queue.indexOf(callback)
if (idx > -1) queue.splice(idx, 1)
}
elementMap.set(op, wrappedCallback)
}
queue.push(wrappedCallback)
requestTick()
})
const animationFrame = /*#__PURE__*/ async () =>
new Promise(requestAnimationFrame)
export { type EnqueueDedupe, enqueue, animationFrame }