a post-component library for building user-interfaces on the web.
1import type { Renderable } from '../index.ts'
2import { assert, is_renderable } from '../shared.ts'
3import { type Cleanup } from './util.ts'
4
5export interface Controller {
6 _mount_callbacks: (() => Cleanup)[]
7 _unmount_callbacks: Cleanup[]
8 _invalidate: Map<object, () => void>
9}
10
11export const controllers: WeakMap<Renderable, Controller> = new WeakMap()
12const invalidated_controllers: Set<Controller> = new Set()
13let invalidate_queued: null | Promise<void> = null
14
15export function get_controller(renderable: Renderable): Controller {
16 let controller = controllers.get(renderable)
17 if (!controller)
18 controllers.set(
19 renderable,
20 (controller = {
21 _mount_callbacks: [],
22 _unmount_callbacks: [],
23 _invalidate: new Map(),
24 }),
25 )
26 return controller
27}
28
29export function invalidate(...renderables: Renderable[]): Promise<void> {
30 for (const renderable of renderables) invalidated_controllers.add(get_controller(renderable))
31
32 return (invalidate_queued ??= Promise.resolve()
33 .then(() => {
34 for (const controller of invalidated_controllers) {
35 invalidated_controllers.delete(controller)
36 controller._invalidate.forEach(invalidate => invalidate())
37 }
38 })
39 .finally(() => {
40 invalidate_queued = null
41 }))
42}
43
44export function onMount(renderable: Renderable, callback: () => Cleanup): void {
45 assert(is_renderable(renderable), 'expected a renderable')
46 const controller = get_controller(renderable)
47 if (controller._invalidate.size) {
48 controller._unmount_callbacks.push(callback())
49 } else {
50 controller._mount_callbacks.push(callback)
51 }
52}
53
54export function onUnmount(renderable: Renderable, callback: () => void): void {
55 onMount(renderable, () => callback)
56}