a post-component library for building user-interfaces on the web.
at push-yuxxrwtzskvm 75 lines 2.4 kB view raw
1import type { Displayable, Renderable } from 'dhtml' 2import { assert, is_renderable } from '../shared.ts' 3import { type Cleanup } from './util.ts' 4 5export type Key = string | number | bigint | boolean | symbol | object | null 6 7export interface Controller { 8 _mount_callbacks?: Set<Cleanup> // undefined if mounted 9 _unmount_callbacks: Set<Cleanup> 10 11 _invalidate_queued?: Promise<void> 12 _invalidate?: () => void 13 _parent_node?: Node 14} 15 16const controllers: WeakMap<Renderable, Controller> = new WeakMap() 17 18export function get_controller(renderable: Renderable): Controller { 19 let controller = controllers.get(renderable) 20 if (controller) return controller 21 22 controller = { 23 _mount_callbacks: new Set(), 24 _unmount_callbacks: new Set(), 25 } 26 27 controllers.set(renderable, controller) 28 return controller 29} 30export function delete_controller(renderable: Renderable): void { 31 controllers.delete(renderable) 32} 33 34const keys: WeakMap<Displayable & object, Key> = new WeakMap() 35 36export function invalidate(renderable: Renderable): Promise<void> { 37 const controller = controllers.get(renderable) 38 assert(controller?._invalidate, 'the renderable has not been rendered') 39 return (controller._invalidate_queued ??= Promise.resolve().then(() => { 40 delete controller._invalidate_queued 41 controller._invalidate!() 42 })) 43} 44 45export function onMount(renderable: Renderable, callback: () => Cleanup): void { 46 assert(is_renderable(renderable), 'expected a renderable') 47 48 const controller = get_controller(renderable) 49 if (controller._mount_callbacks) { 50 controller._mount_callbacks.add(callback) 51 } else { 52 controller._unmount_callbacks.add(callback()) 53 } 54} 55 56export function onUnmount(renderable: Renderable, callback: () => void): void { 57 onMount(renderable, () => callback) 58} 59 60export function getParentNode(renderable: Renderable): Node { 61 const controller = get_controller(renderable) 62 assert(controller._parent_node, 'the renderable has not been rendered') 63 return controller._parent_node 64} 65 66export function keyed<T extends Displayable & object>(displayable: T, key: Key): T { 67 assert(!keys.has(displayable), 'renderable already has a key') 68 keys.set(displayable, key) 69 return displayable 70} 71 72export function get_key(displayable: unknown): unknown { 73 // the cast is fine because getting any non-object will return null 74 return keys.get(displayable as object) ?? displayable 75}