a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 3.6 kB view raw
1/** 2 * Scope metadata management system 3 * 4 * Stores metadata for each reactive scope using WeakMap to avoid polluting scope objects. 5 * Metadata includes origin element, pin registry, UID counter, and optional parent reference. 6 */ 7 8import type { Scope, ScopeMetadata } from "$types/volt"; 9 10/** 11 * WeakMap storing metadata for each scope. 12 * WeakMap ensures metadata is garbage collected when scope is no longer referenced. 13 */ 14const scopeMetadataMap = new WeakMap<Scope, ScopeMetadata>(); 15 16/** 17 * Create and store metadata for a scope. 18 * 19 * @param scope - The reactive scope object 20 * @param origin - The root element that owns this scope 21 * @param parent - Optional parent scope for debugging/inspection 22 * @returns The created metadata object 23 * 24 * @example 25 * ```ts 26 * const scope = { count: signal(0) }; 27 * const metadata = createScopeMetadata(scope, rootElement); 28 * ``` 29 */ 30export function createScopeMetadata(scope: Scope, origin: Element, parent?: Scope): ScopeMetadata { 31 const metadata: ScopeMetadata = { origin, pins: new Map<string, Element>(), uidCounter: 0, parent }; 32 33 scopeMetadataMap.set(scope, metadata); 34 return metadata; 35} 36 37/** 38 * Get metadata for a scope. 39 * 40 * @param scope - The scope to get metadata for 41 * @returns The metadata object, or undefined if not found 42 * 43 * @example 44 * ```ts 45 * const metadata = getScopeMetadata(scope); 46 * if (metadata) { 47 * console.log('Origin element:', metadata.origin); 48 * } 49 * ``` 50 */ 51export function getScopeMetadata(scope: Scope): ScopeMetadata | undefined { 52 return scopeMetadataMap.get(scope); 53} 54 55/** 56 * Register a pinned element in the scope's pin registry. 57 * 58 * @param scope - The scope to register the pin in 59 * @param name - The pin name 60 * @param element - The element to pin 61 * 62 * @example 63 * ```ts 64 * registerPin(scope, 'submitButton', buttonElement); 65 * // Later accessible via $pins.submitButton 66 * ``` 67 */ 68export function registerPin(scope: Scope, name: string, element: Element): void { 69 const metadata = scopeMetadataMap.get(scope); 70 if (metadata) { 71 metadata.pins.set(name, element); 72 } 73} 74 75/** 76 * Get a pinned element by name from the scope. 77 * 78 * @param scope - The scope to search in 79 * @param name - The pin name to retrieve 80 * @returns The pinned element, or undefined if not found 81 * 82 * @example 83 * ```ts 84 * const button = getPin(scope, 'submitButton'); 85 * if (button) { 86 * button.focus(); 87 * } 88 * ``` 89 */ 90export function getPin(scope: Scope, name: string): Element | undefined { 91 const metadata = scopeMetadataMap.get(scope); 92 return metadata?.pins.get(name); 93} 94 95/** 96 * Get all pins for a scope as a record object. 97 * This is what gets injected as $pins in the scope. 98 * 99 * @param scope - The scope to get pins for 100 * @returns Record mapping pin names to elements 101 * 102 * @example 103 * ```ts 104 * const pins = getPins(scope); 105 * // Access as: pins.submitButton, pins.inputField, etc. 106 * ``` 107 */ 108export function getPins(scope: Scope): Record<string, Element> { 109 const metadata = scopeMetadataMap.get(scope); 110 if (!metadata) return {}; 111 112 const pins: Record<string, Element> = {}; 113 for (const [name, element] of metadata.pins) { 114 pins[name] = element; 115 } 116 return pins; 117} 118 119/** 120 * Increment and return the UID counter for a scope. 121 * Used internally by $uid() to generate deterministic IDs. 122 * 123 * @param scope - The scope to increment counter for 124 * @returns The next UID number 125 */ 126export function incrementUidCounter(scope: Scope): number { 127 const metadata = scopeMetadataMap.get(scope); 128 if (!metadata) return 0; 129 130 return ++metadata.uidCounter; 131}