import { ArrowTemplate, Chunk } from './html' import { Reactive, PropertyObserver, ReactiveTarget } from './reactive' /** * A queue of expressions to run as soon as an async slot opens up. */ const queueMarker = Symbol() type QueuedFunction = CallableFunction & { [queueMarker]?: boolean _n?: unknown _o?: unknown } let queueStack: QueuedFunction[] = [] /** * A stack of functions to run on the next tick. */ let nextTicks: CallableFunction[] = [] let cleanupCollector: Array<() => void> | null = null /** * Adds the ability to listen to the next tick. * @param {CallableFunction} fn? * @returns Promise */ export function nextTick(fn?: CallableFunction): Promise { return !queueStack.length ? Promise.resolve(fn?.()) : new Promise((resolve: (value?: unknown) => void) => nextTicks.push(() => { fn?.() resolve() }) ) } export function isTpl(template: unknown): template is ArrowTemplate { return typeof template === 'function' && !!(template as ArrowTemplate).isT } export function isO(obj: unknown): obj is ReactiveTarget { return obj !== null && typeof obj === 'object' } export function isR(obj: unknown): obj is Reactive { return isO(obj) && '$on' in obj } export function isChunk(chunk: unknown): chunk is Chunk { return isO(chunk) && 'ref' in chunk } /** * Queue an item to execute after all synchronous functions have been run. This * is used for `w()` to ensure multiple dependency mutations tracked on the * same expression do not result in multiple calls. * @param {CallableFunction} fn * @returns PropertyObserver */ export function queue( fn: PropertyObserver ): PropertyObserver { const queued = fn as QueuedFunction return (newValue?: T, oldValue?: T) => { if (!queued[queueMarker]) { queued[queueMarker] = true queued._n = newValue queued._o = oldValue if (!queueStack.length) { queueMicrotask(executeQueue) } queueStack.push(queued) } } } function executeQueue() { const queue = queueStack queueStack = [] const ticks = nextTicks nextTicks = [] for (let i = 0; i < queue.length; i++) { const fn = queue[i] const newValue = fn._n const oldValue = fn._o fn._n = undefined fn._o = undefined fn[queueMarker] = false fn(newValue, oldValue) } for (let i = 0; i < ticks.length; i++) ticks[i]() if (queueStack.length) { queueMicrotask(executeQueue) } } export function swapCleanupCollector(collector: Array<() => void> | null) { const previous = cleanupCollector cleanupCollector = collector return previous } export function registerCleanup(fn: () => void) { cleanupCollector?.push(fn) } export function onCleanup(fn: () => void) { const collector = cleanupCollector if (!collector) throw Error('onCleanup needs component') let active = 1 const dispose = () => active-- && (collector.splice(collector.indexOf(dispose), 1), fn()) return collector.push(dispose), dispose }