/* ============================================================================= * ui_template/ui_template_window_watch.ts * 창 · 문서 듣기를 화면 노드 수명에 묶음(298-3) — 종류마다 듣개 하나만 걸고 노드는 약하게 쥠. * 페이지를 다시 세워도(노선 [저장]) 옛 페이지를 붙들지 않고 창 듣개 수가 늘지 않음. * 노드가 살아 있는 동안은 떼었다 다시 붙여도(M01) 계속 들음 · 버려지면 저절로 빠짐. * ========================================================================== */ type Call = (event: Event) => void; interface Hub { nodes: Set>; /** 노드 → 부를 것 — 값이 노드를 쥐어도 노드와 함께 풀림(WeakMap). */ calls: WeakMap; } const hubs = new WeakMap>(); function hubOf(target: EventTarget, type: string, capture: boolean): Hub { let byType = hubs.get(target); if (!byType) hubs.set(target, (byType = new Map())); const key = `${type}:${capture}`; const found = byType.get(key); if (found) return found; const hub: Hub = { nodes: new Set(), calls: new WeakMap() }; target.addEventListener( type, (event) => hub.nodes.forEach((ref) => { const node = ref.deref(); if (!node) hub.nodes.delete(ref); else hub.calls.get(node)?.forEach((call) => call(event)); }), capture, ); byType.set(key, hub); return hub; } /** `target` 의 `type` 을 `node` 가 살아 있는 동안 들음. */ export function listenWhileAlive( target: EventTarget, type: string, node: object, call: (event: E) => void, capture = false, ): void { const hub = hubOf(target, type, capture); const calls = hub.calls.get(node); if (calls) calls.push(call as Call); else { hub.calls.set(node, [call as Call]); hub.nodes.add(new WeakRef(node)); } }