char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somperf(diff): virtualize rich rows with searchable text fallbacksece94e9

main
4.2 KiB143 linesraw
1type Chunk = {
2  element: HTMLElement;
3  near: boolean;
4  rich: boolean;
5  height: number;
6  dirty: boolean;
7  render(rich: boolean): void;
8  visible(visible: boolean): void;
9};
10
11/** Rich markup near the viewport, searchable text elsewhere, without disturbing a selection or find result. */
12export function viewport(root: HTMLElement) {
13  const chunks = new Map<Element, Chunk>();
14  const pending = new Set<Chunk>();
15  const pinned = new Set<Chunk>();
16  let frame = 0;
17  let width = 0;
18  let disposed = false;
19
20  function schedule() {
21    if (frame || disposed) return;
22    frame = requestAnimationFrame(flush);
23  }
24
25  function flush() {
26    frame = 0;
27    const until = performance.now() + 4;
28    const selection = document.getSelection();
29    for (const chunk of pending) {
30      pending.delete(chunk);
31      if (chunk.near === chunk.rich && !chunk.dirty) continue;
32      // firefox exposes the active native find match as a selection, too
33      if (
34        chunk.element.contains(document.activeElement) ||
35        (selection &&
36          Array.from({ length: selection.rangeCount }, (_, i) => selection.getRangeAt(i))
37            .some((range) => range.intersectsNode(chunk.element)))
38      ) {
39        pinned.add(chunk);
40        continue;
41      }
42      if (!chunk.near && chunk.height > 0) {
43        chunk.element.style.height = `${chunk.height}px`;
44      } else chunk.element.style.removeProperty("height");
45      chunk.rich = chunk.near;
46      chunk.dirty = false;
47      chunk.render(chunk.rich);
48      if (chunk.rich) sizes?.observe(chunk.element);
49      else sizes?.unobserve(chunk.element);
50      if (performance.now() >= until) break;
51    }
52    if (pending.size) schedule();
53  }
54
55  const nearby = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver(
56    (entries) => {
57      for (const entry of entries) {
58        const chunk = chunks.get(entry.target);
59        if (!chunk) continue;
60        chunk.near = entry.isIntersecting;
61        pending.add(chunk);
62      }
63      schedule();
64    },
65    { root, rootMargin: "800px 0px" },
66  );
67  const visible = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver(
68    (entries) => {
69      for (const entry of entries) chunks.get(entry.target)?.visible(entry.isIntersecting);
70    },
71    { root },
72  );
73  const sizes = typeof ResizeObserver === "undefined" ? null : new ResizeObserver((entries) => {
74    for (const entry of entries) {
75      if (entry.target === root) {
76        if (width === entry.contentRect.width) continue;
77        width = entry.contentRect.width;
78        for (const chunk of chunks.values()) {
79          chunk.height = 0;
80          chunk.element.style.removeProperty("height");
81        }
82      } else {
83        const chunk = chunks.get(entry.target);
84        if (chunk?.rich) chunk.height = entry.contentRect.height;
85      }
86    }
87  });
88  sizes?.observe(root);
89
90  const release = () => {
91    for (const chunk of pinned) pending.add(chunk);
92    pinned.clear();
93    if (pending.size) schedule();
94  };
95  document.addEventListener("selectionchange", release);
96  root.addEventListener("focusout", release);
97
98  return {
99    add(element: HTMLElement, render: Chunk["render"], onVisible: Chunk["visible"]) {
100      const chunk: Chunk = {
101        element,
102        near: nearby === null,
103        rich: nearby === null,
104        height: 0,
105        dirty: false,
106        render,
107        visible: onVisible,
108      };
109      chunks.set(element, chunk);
110      render(chunk.rich);
111      nearby?.observe(element);
112      visible?.observe(element);
113      return {
114        redraw() {
115          if (!chunk.rich) return;
116          chunk.dirty = true;
117          pending.add(chunk);
118          schedule();
119        },
120        dispose() {
121          nearby?.unobserve(element);
122          visible?.unobserve(element);
123          sizes?.unobserve(element);
124          chunks.delete(element);
125          pending.delete(chunk);
126          pinned.delete(chunk);
127        },
128      };
129    },
130    dispose() {
131      disposed = true;
132      cancelAnimationFrame(frame);
133      nearby?.disconnect();
134      visible?.disconnect();
135      sizes?.disconnect();
136      chunks.clear();
137      pending.clear();
138      pinned.clear();
139      document.removeEventListener("selectionchange", release);
140      root.removeEventListener("focusout", release);
141    },
142  };
143}