/ ink / node-cache.ts
node-cache.ts
 1  import type { DOMElement } from './dom.js'
 2  import type { Rectangle } from './layout/geometry.js'
 3  
 4  /**
 5   * Cached layout bounds for each rendered node (used for blit + clearing).
 6   * `top` is the yoga-local getComputedTop() — stored so ScrollBox viewport
 7   * culling can skip yoga reads for clean children whose position hasn't
 8   * shifted (O(dirty) instead of O(mounted) first-pass).
 9   */
10  export type CachedLayout = {
11    x: number
12    y: number
13    width: number
14    height: number
15    top?: number
16  }
17  
18  export const nodeCache = new WeakMap<DOMElement, CachedLayout>()
19  
20  /** Rects of removed children that need clearing on next render */
21  export const pendingClears = new WeakMap<DOMElement, Rectangle[]>()
22  
23  /**
24   * Set when a pendingClear is added for an absolute-positioned node.
25   * Signals renderer to disable blit for the next frame: the removed node
26   * may have painted over non-siblings (e.g. an overlay over a ScrollBox
27   * earlier in tree order), so their blits from prevScreen would restore
28   * the overlay's pixels. Normal-flow removals are already handled by
29   * hasRemovedChild at the parent level; only absolute positioning paints
30   * cross-subtree. Reset at the start of each render.
31   */
32  let absoluteNodeRemoved = false
33  
34  export function addPendingClear(
35    parent: DOMElement,
36    rect: Rectangle,
37    isAbsolute: boolean,
38  ): void {
39    const existing = pendingClears.get(parent)
40    if (existing) {
41      existing.push(rect)
42    } else {
43      pendingClears.set(parent, [rect])
44    }
45    if (isAbsolute) {
46      absoluteNodeRemoved = true
47    }
48  }
49  
50  export function consumeAbsoluteRemovedFlag(): boolean {
51    const had = absoluteNodeRemoved
52    absoluteNodeRemoved = false
53    return had
54  }