All files / src/packlets/scrollable-canvas reconciler.ts

100% Statements 21/21
100% Branches 4/4
100% Functions 3/3
100% Lines 21/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68                                                      9x       9x       16x   16x 20x 20x 20x 5x 5x   15x 15x 15x       16x 24x 4x 4x 4x           2x 2x 2x   2x      
/**
 * @packageDocumentation
 *
 * Handle lifecycle reconciler for the scrollable canvas.
 *
 * Owns the `Map<string, RenderHandle>` and drives creation, update,
 * and disposal of render handles based on successive `RenderObject[]`
 * snapshots.  DOM (or test) concerns are injected via `ReconcileCallbacks`.
 */
 
import type { RenderHandle, RenderObject } from "./index";
 
export interface ReconcileCallbacks {
  onAdd(key: string, handle: RenderHandle, obj: RenderObject): void;
  onUpdate(key: string, handle: RenderHandle, obj: RenderObject): void;
  onRemove(key: string, handle: RenderHandle): void;
}
 
/**
 * Reconciles a stream of `RenderObject` snapshots against an internal
 * `Map<string, RenderHandle>`.
 *
 * - New keys → `renderer()` → `onAdd`
 * - Existing keys → `handle.update()` → `onUpdate`
 * - Stale keys → `handle[Symbol.dispose]()` → `onRemove`
 */
export class RenderObjectReconciler {
  private handles = new Map<string, RenderHandle>();
  private callbacks: ReconcileCallbacks;
 
  constructor(callbacks: ReconcileCallbacks) {
    this.callbacks = callbacks;
  }
 
  reconcile(objects: RenderObject[]): void {
    const activeKeys = new Set<string>();
 
    for (const obj of objects) {
      activeKeys.add(obj.key);
      const existing = this.handles.get(obj.key);
      if (existing) {
        existing.update(obj.data);
        this.callbacks.onUpdate(obj.key, existing, obj);
      } else {
        const handle = obj.renderer(obj.data);
        this.handles.set(obj.key, handle);
        this.callbacks.onAdd(obj.key, handle, obj);
      }
    }
 
    for (const [key, handle] of this.handles) {
      if (!activeKeys.has(key)) {
        handle[Symbol.dispose]?.();
        this.callbacks.onRemove(key, handle);
        this.handles.delete(key);
      }
    }
  }
 
  disposeAll(): void {
    for (const [key, handle] of this.handles) {
      handle[Symbol.dispose]?.();
      this.callbacks.onRemove(key, handle);
    }
    this.handles.clear();
  }
}