kerfjs 4.2.0-beta.5 → 4.2.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  ## [Unreleased]
8
8
 
9
9
  - **`renderDocument(node, options?)`** (main barrel) — a tiny SSR helper that prepends the doctype to a rendered document, so server routes stop reinventing `"<!DOCTYPE html>" + page.toString()`. Takes a `SafeHtml` or string; optional `{ doctype }` (default `'html'`). Pure string work, no DOM dependency.
10
- - **New `kerfjs/list` subpath** — `bindList(parent, source, options)`, a keyed list distinct from `each()`: each row is individually `mount()`ed, so a signal a row reads updates just that row (fine-grained, no full-list pass), and it can **virtualize** the viewport (`virtualize: { rowHeight }` renders only the visible rows, padding keeps `scrollHeight` honest). `rowHeight` is a fixed `number`, a `(item, index) => number` for **app-declared variable** row heights (kerf builds a prefix sum — rebuilt when the source changes, not per scroll frame — and binary-searches it for the window), **or** `{ estimate }` for **measured** heights: kerf sizes an unmeasured row by `estimate` and the app reports real heights via the returned handle's **`setHeight(key, px)`** (keyed by the list key, so reports survive reorders) or the new **`observeRowHeights(handle)`** helper (one `ResizeObserver` over the visible rows → `setHeight`); kerf anchor-corrects `scrollTop` when an above-viewport row is remeasured so on-screen content doesn't jump. `bindList` now returns a `BindListHandle` (`(() => void) & { setHeight }`) — still callable as the disposer. `render(item)` returns a `MountResult` (**content mode** — kerf creates the row element and mounts your content inside it) **or** an `HTMLElement` / `{ el, update?, dispose? }` (**element mode** — the element you return IS the row, so you own its tag / class / `data-*` / listeners; kerf **keys / moves / reuses** it — the SAME element survives an append, a remove elsewhere, a reorder, or a fresh item object at the same key, preserving focus / scroll / listeners — and runs your `dispose` only on genuine removal; return an `update(item)` to refresh a reused element's content). A list may mix the two. `source` is a `signal<readonly T[]>` or an `arraySignal<T>` — a non-virtualized `arraySignal` source applies its structural patches **granularly** (O(patches)), everything else uses a keyed diff (transparent optimization). A **`before`** option (a `Node` or `() => Node | null`) keeps the rows as a contiguous block ending just before a fixed trailing sibling, so a list can share its `parent` with an "add" button or an indicator instead of assuming exclusive ownership. Reach for it for surgical per-row updates, app-owned row elements, or long/windowed lists; `each()` stays the default for item-owned-state lists rendered to HTML strings. Optional and tree-shakeable.
10
+ - **New `kerfjs/list` subpath** — `bindList(parent, source, options)`, a keyed list distinct from `each()`: each row is individually `mount()`ed, so a signal a row reads updates just that row (fine-grained, no full-list pass), and it can **virtualize** the viewport (`virtualize: { rowHeight }` renders only the visible rows, padding keeps `scrollHeight` honest). `rowHeight` is a fixed `number`, a `(item, index) => number` for **app-declared variable** row heights (kerf builds a prefix sum — rebuilt when the source changes, not per scroll frame — and binary-searches it for the window), **or** `{ estimate }` for **measured** heights: kerf sizes an unmeasured row by `estimate` and the app reports real heights via the returned handle's **`setHeight(key, px)`** (keyed by the list key, so reports survive reorders) or the new **`observeRowHeights(handle)`** helper (one `ResizeObserver` over the visible rows → `setHeight`); kerf anchor-corrects `scrollTop` when an above-viewport row is remeasured so on-screen content doesn't jump. `bindList` now returns a `BindListHandle` (`(() => void) & { setHeight }`) — still callable as the disposer. `render(item)` returns a `MountResult` (**content mode** — kerf creates the row element and mounts your content inside it) **or** an `HTMLElement` / `{ el, update?, dispose? }` (**element mode** — the element you return IS the row, so you own its tag / class / `data-*` / listeners; kerf **keys / moves / reuses** it — the SAME element survives an append, a remove elsewhere, a reorder, or a fresh item object at the same key, preserving focus / scroll / listeners — and runs your `dispose` only on genuine removal; return an `update(item)` to refresh a reused element's content). A list may mix the two. `source` is a `signal<readonly T[]>` or an `arraySignal<T>` — a non-virtualized `arraySignal` source applies its structural patches **granularly** (O(patches)), everything else uses a keyed diff (transparent optimization). A **`before`** option (a `Node` or `() => Node | null`) keeps the rows as a contiguous block ending just before a fixed trailing sibling, so a list can share its `parent` with an "add" button or an indicator instead of assuming exclusive ownership. Three virtualization ergonomics from real adoption: **`virtualize.minRows`** renders every row (no windowing) while the list is shorter than it — one DOM structure whether short or long, so the call site never branches, and a short list stays visible to find-in-page / screen readers / DOM-count tests; **`virtualize.containerClass` / `containerId`** (and **`handle.container`**) name and expose the inner sizer kerf creates, replacing `parent.lastElementChild` guesswork; and kerf now re-windows on a **`ResizeObserver`** over `parent` (where available), so a list mounted before layout (`clientHeight` 0) fills in once sized and a resized container re-windows. Reach for it for surgical per-row updates, app-owned row elements, or long/windowed lists; `each()` stays the default for item-owned-state lists rendered to HTML strings. Optional and tree-shakeable.
11
11
  - **New `kerfjs/async` subpath** — `resource<T, I = void>()` models async state (`{ status, data, error, progress, input }`) with the stale-response guard built in. You write the fetch (Node `fetch` for SSR, browser `fetch` client-side); `.run(fetcher)` drives `idle` → `running` → `completed`/`failed` and drops out-of-order responses (only the latest run resolves the state). It never rejects — a failure lands in `value.error` — keeps previous data across a re-run (stale-while-revalidate), and supports opt-in progress via a callback the fetcher receives. The `.run(input, fetcher)` form threads the run's `input` to `value.input` for `running`/`completed`/`failed` (latest-wins under the stale guard), so a failure handler can recover **which request failed** (e.g. an inline error keyed by `value.input.fileId`) without reintroducing module-scope bookkeeping. Pass `resource({ cacheKey, equals })` for a real SWR section: **`cacheKey(input)`** keeps the last value **per key** (revisiting a loaded key paints its cached slice instantly while it revalidates; `reset()` clears the cache), and **`value.revision`** bumps only when `data` actually changes (by `equals`, default `Object.is`) so a consumer can skip a redundant paint (a poll returning identical data leaves it untouched). The cache is readable and evictable without running a key: **`cached(key)`** / **`cachedKeys()`** view it, and **`clearCache(key?)`** evicts one key (or all) without touching `value`. `value` is a tracking read. Signals only (no render core); tiny.
12
12
  - **New `kerfjs/attach` subpath** — `attach(node, setup)`, which binds a non-kerf widget's lifecycle to a single **existing** DOM node, purpose-built for the `data-morph-skip` escape hatch. `setup(node)` runs immediately (the node already exists — this is **not** React's `useEffect`: no dependency array, no re-run, no render-phase/hook-order scoping; it's closer to a Web Component's `connectedCallback`/`disconnectedCallback` pair, Svelte's `onMount(() => () => cleanup)`, or Solid's `onCleanup`) and may return a teardown; the teardown runs once when the node leaves the document (detected by a `MutationObserver`, so a morph swap, a `remountOn` replacement, or any removal triggers it) or when the returned idempotent disposer is called. Turns the "library owns this subtree" convention into a supported seam with real lifecycle guarantees. Re-creation is handled by pairing with `kerfjs/remount`. DOM only (no signals, no render core) — the smallest subpath. Optional and tree-shakeable.
13
13
  - **New `kerfjs/remount` subpath** — `remountOn(parent, key, render)`, the opposite of kerf's morph-by-default: **replace** a subtree wholesale when `key` changes instead of morphing it. Names the hand-rolled `data-key={`gen-${n}`}` + `data-morph-skip` counter trick, for library-owned subtrees (a highlighted diff, a chart, an editor) that must tear down and re-initialize on fresh DOM. `key` is a signal or a thunk `() => K`; an unchanged key (including a thunk whose inputs moved but whose value stayed equal) leaves the subtree alone, so per-row reactivity inside `render` still updates in place. An optional `onMount(root)` callback runs after each (re)mount with the live subtree — the place to bind an imperative widget (`kerfjs/attach`) to the fresh DOM; returning `attach`'s disposer from it makes teardown synchronous. `remountOn` owns `parent`'s children and returns a disposer. Optional and tree-shakeable.
package/dist/list.d.ts CHANGED
@@ -46,6 +46,14 @@ type BindListHandle = (() => void) & {
46
46
  * A no-op for fixed / declared-height lists and for unknown keys.
47
47
  */
48
48
  setHeight: (key: ListKey, height: number) => void;
49
+ /**
50
+ * The inner container element kerf creates to hold the rows in a **virtualized**
51
+ * list (the "sizer"). `undefined` for a non-virtualized list (there the rows
52
+ * live directly in `parent`). Use it to style, id, or otherwise reach the row
53
+ * block without guessing at `parent.lastElementChild` — though `containerClass`
54
+ * / `containerId` on `virtualize` set those declaratively.
55
+ */
56
+ container?: HTMLElement;
49
57
  };
50
58
  /** Options for {@link bindList}. */
51
59
  interface BindListOptions<T> {
@@ -96,10 +104,27 @@ interface BindListOptions<T> {
96
104
  * height via {@link BindListHandle.setHeight} (or the `observeRowHeights`
97
105
  * helper). kerf anchor-corrects `scrollTop` when an above-viewport row is
98
106
  * remeasured, so content doesn't jump.
107
+ *
108
+ * `minRows` renders **every** row (no windowing, zero padding) while the list
109
+ * is shorter than it, and windows only at or above it — the DOM structure (the
110
+ * inner container) is the same either way, so the call site never branches. A
111
+ * fully-rendered short list is friendlier to find-in-page, screen readers, and
112
+ * DOM-count assertions, which only see rows actually in the DOM.
113
+ *
114
+ * `containerClass` / `containerId` are set on the inner container kerf creates
115
+ * to hold the rows, so it's reachable from CSS and tests without guessing at
116
+ * `parent.lastElementChild` (it's also on the handle as `handle.container`).
117
+ *
118
+ * kerf re-windows on `parent`'s `scroll` and, where `ResizeObserver` exists, on
119
+ * `parent` resizing — so a list that mounts before layout (a hidden tab,
120
+ * `clientHeight` 0) fills in once it's sized, and a resized container re-windows.
99
121
  */
100
122
  virtualize?: {
101
123
  rowHeight: RowHeight<T>;
102
124
  overscan?: number;
125
+ minRows?: number;
126
+ containerClass?: string;
127
+ containerId?: string;
103
128
  };
104
129
  }
105
130
  /**
package/dist/list.js CHANGED
@@ -11,6 +11,7 @@ import './chunk-VVDJLWMP.js';
11
11
  function bindList(parent, source, options) {
12
12
  const { key, render, tag = "div", virtualize, before } = options;
13
13
  const overscan = virtualize?.overscan ?? 3;
14
+ const minRows = virtualize?.minRows;
14
15
  const endAnchor = () => {
15
16
  if (virtualize !== void 0 || before === void 0) return null;
16
17
  return (typeof before === "function" ? before() : before) ?? null;
@@ -24,7 +25,11 @@ function bindList(parent, source, options) {
24
25
  const patchSource = source;
25
26
  const granularEligible = virtualize === void 0 && patchSource[ARRAY_SIGNAL_BRAND] === true;
26
27
  const container = virtualize === void 0 ? parent : document.createElement("div");
27
- if (virtualize !== void 0) parent.appendChild(container);
28
+ if (virtualize !== void 0) {
29
+ if (virtualize.containerClass !== void 0) container.className = virtualize.containerClass;
30
+ if (virtualize.containerId !== void 0) container.id = virtualize.containerId;
31
+ parent.appendChild(container);
32
+ }
28
33
  const NOOP = () => {
29
34
  };
30
35
  const asElementRow = (rendered) => {
@@ -201,26 +206,37 @@ function bindList(parent, source, options) {
201
206
  return;
202
207
  }
203
208
  const total = items.length;
204
- const scrollTop = parent.scrollTop;
205
- const viewportBottom = scrollTop + parent.clientHeight;
206
209
  let start;
207
210
  let end;
208
211
  let padTop;
209
212
  let padBottom;
210
- if (fixedHeight !== null) {
211
- start = Math.max(0, Math.floor(scrollTop / fixedHeight) - overscan);
212
- end = Math.min(total, Math.ceil(viewportBottom / fixedHeight) + overscan);
213
- padTop = start * fixedHeight;
214
- padBottom = Math.max(0, total - end) * fixedHeight;
215
- } else {
216
- if (heightsDirty) {
213
+ if (minRows !== void 0 && total < minRows) {
214
+ if (fixedHeight === null && heightsDirty) {
217
215
  rebuildOffsets();
218
216
  heightsDirty = false;
219
217
  }
220
- start = Math.max(0, findStart(scrollTop, total) - overscan);
221
- end = Math.min(total, findEnd(viewportBottom, total) + overscan);
222
- padTop = offsets[start];
223
- padBottom = offsets[total] - offsets[end];
218
+ start = 0;
219
+ end = total;
220
+ padTop = 0;
221
+ padBottom = 0;
222
+ } else {
223
+ const scrollTop = parent.scrollTop;
224
+ const viewportBottom = scrollTop + parent.clientHeight;
225
+ if (fixedHeight !== null) {
226
+ start = Math.max(0, Math.floor(scrollTop / fixedHeight) - overscan);
227
+ end = Math.min(total, Math.ceil(viewportBottom / fixedHeight) + overscan);
228
+ padTop = start * fixedHeight;
229
+ padBottom = Math.max(0, total - end) * fixedHeight;
230
+ } else {
231
+ if (heightsDirty) {
232
+ rebuildOffsets();
233
+ heightsDirty = false;
234
+ }
235
+ start = Math.max(0, findStart(scrollTop, total) - overscan);
236
+ end = Math.min(total, findEnd(viewportBottom, total) + overscan);
237
+ padTop = offsets[start];
238
+ padBottom = offsets[total] - offsets[end];
239
+ }
224
240
  }
225
241
  syncRows(items.slice(start, end));
226
242
  sizeVisibleRows(start);
@@ -247,6 +263,9 @@ function bindList(parent, source, options) {
247
263
  });
248
264
  };
249
265
  if (virtualize !== void 0) parent.addEventListener("scroll", scheduleRender);
266
+ const RO = globalThis.ResizeObserver;
267
+ const parentResize = virtualize !== void 0 && RO !== void 0 ? new RO(scheduleRender) : void 0;
268
+ parentResize?.observe(parent);
250
269
  const setHeight = (k, height) => {
251
270
  if (!measuring) return;
252
271
  const idx = indexByKey.get(k);
@@ -269,6 +288,7 @@ function bindList(parent, source, options) {
269
288
  renderSubscribers.clear();
270
289
  if (virtualize !== void 0) {
271
290
  parent.removeEventListener("scroll", scheduleRender);
291
+ parentResize?.disconnect();
272
292
  container.remove();
273
293
  VIRTUAL_INTERNALS.delete(handle);
274
294
  }
@@ -276,6 +296,7 @@ function bindList(parent, source, options) {
276
296
  const handle = dispose;
277
297
  handle.setHeight = setHeight;
278
298
  if (virtualize !== void 0) {
299
+ handle.container = container;
279
300
  VIRTUAL_INTERNALS.set(handle, {
280
301
  visibleRows: () => order.map((row) => ({ key: key(row.item), el: row.el })),
281
302
  onRender: (cb) => {
package/dist/list.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/list.ts"],"names":["dispose"],"mappings":"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","file":"list.js","sourcesContent":["/**\n * `kerfjs/list` — `bindList`, a keyed list with a live per-row mount and\n * optional viewport virtualization.\n *\n * This is a DELIBERATE second list API, distinct from `each()`. It does two\n * things `each()` structurally cannot:\n * 1. **Per-row reactivity.** Every row is individually `mount()`ed, so a signal\n * the row's `render` reads updates just that row (fine-grained binding or a\n * one-row morph) without touching its siblings — no full-list pass.\n * 2. **Virtualization.** With `{ virtualize: { rowHeight } }` only the rows in\n * the scroll viewport are rendered; padding on the scroll container keeps\n * `scrollHeight` honest. `rowHeight` is a fixed `number` (O(1) windowing), a\n * `(item, index) => number` for **app-declared variable** heights (a prefix\n * sum + binary-search window), or `{ estimate }` for **measured** heights —\n * the app reports real heights via the returned handle's `setHeight` (or the\n * `observeRowHeights` helper) and kerf anchor-corrects `scrollTop`. See\n * `docs/17-list-virtualization.md`.\n *\n * `each()` stays the choice for item-owned-state lists rendered to HTML strings;\n * reach for `bindList` when you need surgical per-row updates or windowing.\n *\n * import { bindList } from 'kerfjs/list';\n *\n * const dispose = bindList(listEl, itemsSignal, {\n * key: (row) => row.id,\n * render: (row) => <span class={selected} data-id={row.id}>{row.label}</span>,\n * tag: 'li',\n * virtualize: { rowHeight: 32 },\n * });\n *\n * `render` reads signals for reactivity (external state like a `selectedId`, or\n * signals the item carries) — keep the item OBJECTS stable across renders and\n * drive structure (add/remove/move) through `itemsSignal`. A row whose item\n * object identity changes is rebuilt (same rule as `each()`'s memo). `bindList`\n * OWNS `parent`'s children by default (append/move to the end) — to share\n * `parent` with fixed trailing siblings (an \"add\" button, an indicator), pass\n * `before` so the rows end just before that node. It reads `itemsSignal.value`,\n * so a plain `signal<T[]>` or an `arraySignal<T>` both work.\n */\nimport { ARRAY_SIGNAL_BRAND, type ArrayPatch } from './array-signal.js';\nimport { mount, type MountResult } from './mount.js';\nimport { effect } from './reactive.js';\n\n/** A row's stable key. */\nexport type ListKey = string | number;\n\n/** Anything with a tracking `.value` array read — a `signal<readonly T[]>` or an `arraySignal<T>`. */\nexport interface ListSource<T> {\n readonly value: readonly T[];\n}\n\n/**\n * A row built imperatively by `render`: return the row **element** itself (kerf\n * keys/moves/reuses it and owns nothing inside it), or `{ el, update?, dispose? }`\n * to also hand back an `update(item)` — called on the SAME element when the row's\n * key persists but its item changes — and a `dispose` that runs only when the row\n * is removed.\n */\nexport type RowElement<T> =\n | HTMLElement\n | { el: HTMLElement; update?: (item: T) => void; dispose?: () => void };\n\n/**\n * The virtualization height model:\n * - **`number`** — every row is this fixed pixel height (O(1) windowing).\n * - **`(item, index) => number`** — app-declared **variable** heights, derived\n * purely from the item and its index.\n * - **`{ estimate }`** — **measured** heights: kerf uses `estimate` for a row\n * until the app reports its real height through {@link BindListHandle.setHeight}\n * (or the `observeRowHeights` helper). See `docs/17-list-virtualization.md`.\n */\nexport type RowHeight<T> =\n | number\n | ((item: T, index: number) => number)\n | { estimate: number | ((item: T, index: number) => number) };\n\n/**\n * The value {@link bindList} returns: a disposer you call to tear the list down,\n * augmented with `setHeight` for the **measured** virtualization mode.\n */\nexport type BindListHandle = (() => void) & {\n /**\n * Report a row's real pixel height (measured after layout) for\n * `virtualize: { rowHeight: { estimate } }` lists. Keyed by the list `key`, so\n * a report survives reorders. kerf recomputes the window and, if the row sits\n * ABOVE the viewport, anchor-corrects `scrollTop` so content doesn't jump.\n * A no-op for fixed / declared-height lists and for unknown keys.\n */\n setHeight: (key: ListKey, height: number) => void;\n};\n\n/** Options for {@link bindList}. */\nexport interface BindListOptions<T> {\n /** Stable per-row key. Rows are matched, moved, and reused by this. */\n key: (item: T) => ListKey;\n /**\n * Build a row. Two modes, chosen per call by what you return:\n * - **Content mode** (a `MountResult` — JSX / `SafeHtml`): kerf creates the\n * row element (`tag`) and `mount()`s your content inside it, so signals your\n * content reads drive per-row reactivity.\n * - **Element mode** (an `HTMLElement`, or `{ el, update?, dispose? }`): the\n * element you return IS the row, so you own its tag, class, `data-*`, and\n * listeners. kerf **keys/moves/reuses** it — the SAME element survives an\n * append/remove/reorder or a fresh item object at the same key. Refresh its\n * content by reading signals inside it, or by returning an `update(item)`\n * that kerf calls on the existing element when the item changes. `dispose`\n * runs only when the row is genuinely removed.\n */\n render: (item: T) => MountResult | RowElement<T>;\n /** Row element tag for **content mode**. Default `'div'` (use `'li'` inside a `<ul>`, `'tr'` inside a `<tbody>`, …). Ignored in element mode. */\n tag?: string;\n /**\n * Keep the rows as a contiguous block that ENDS just before this node, instead\n * of at the very end of `parent`. Use it when `parent` also holds non-row\n * siblings that must stay put — a trailing \"add\" button, a sliding indicator:\n * `before: () => addButton`. The node (a function is re-read each reconcile, or\n * pass the node directly) must be a child of `parent`. Without it, bindList\n * assumes exclusive ownership and appends rows to the end. Ignored when\n * virtualized (the rows own bindList's inner sizer exclusively).\n */\n before?: Node | (() => Node | null);\n /**\n * Turn on viewport virtualization. `parent` must be a scroll container (your\n * CSS: a fixed height + `overflow: auto`). `overscan` (default 3) is how many\n * extra rows to render above and below the viewport.\n *\n * `rowHeight` (a {@link RowHeight}) is the height model:\n * - **`number`** — every row is this fixed pixel height. O(1) windowing, no\n * cumulative model built.\n * - **`(item, index) => number`** — app-declared **variable** heights, derived\n * purely from the item and its index. kerf builds a prefix sum of the\n * heights (rebuilt when the source array changes, not per scroll frame) and\n * binary-searches it to find the visible window. Return a non-negative\n * number of pixels.\n * - **`{ estimate }`** — **measured** heights for rows whose height is only\n * known after layout. kerf sizes an unmeasured row by `estimate` (a number\n * or an `(item, index) => number`), and the app reports each row's real\n * height via {@link BindListHandle.setHeight} (or the `observeRowHeights`\n * helper). kerf anchor-corrects `scrollTop` when an above-viewport row is\n * remeasured, so content doesn't jump.\n */\n virtualize?: { rowHeight: RowHeight<T>; overscan?: number };\n}\n\ninterface Row<T> {\n el: HTMLElement;\n item: T;\n dispose: () => void;\n /** True for element-mode rows (the caller owns the element — reuse it, don't rebuild on item change). */\n elementMode: boolean;\n /** Element mode only: refresh the existing element when the item changes at the same key. */\n update?: (item: T) => void;\n}\n\n/**\n * Bind a keyed, per-row-reactive list to `parent`, driven by `source` (a\n * `signal<readonly T[]>` or an `arraySignal<T>`). Returns a disposer that tears\n * down every row mount, the scroll listener (if virtualized), and the source\n * subscription.\n */\nexport function bindList<T>(\n parent: HTMLElement,\n source: ListSource<T>,\n options: BindListOptions<T>,\n): BindListHandle {\n const { key, render, tag = 'div', virtualize, before } = options;\n const overscan = virtualize?.overscan ?? 3;\n\n // The node the row block ends before — `before` (KF-496) when the list shares\n // `parent` with trailing siblings, else the end of the container. Never applies\n // when virtualized: the rows own bindList's inner sizer exclusively.\n const endAnchor = (): Node | null => {\n if (virtualize !== undefined || before === undefined) return null;\n return (typeof before === 'function' ? before() : before) ?? null;\n };\n\n const rows = new Map<ListKey, Row<T>>();\n // The current DOM order of rows, kept in step by both the keyed-diff and the\n // granular patch paths so index-based patches can address rows directly.\n const order: Array<Row<T>> = [];\n let items: readonly T[] = [];\n let disposed = false;\n let rafPending = false;\n let firstRender = true;\n\n // Granular fast path (KF-478): when the source is an `arraySignal` and the\n // list is NOT virtualized, apply its insert/remove/move/update patches\n // directly in O(patches) instead of diffing the whole snapshot. Virtualized\n // lists keep the keyed diff — their visible set is just the window (cheap),\n // and absolute-index patches don't compose with a shifting window. A plain\n // `signal<T[]>` has no patches, so it always uses the keyed diff.\n const patchSource = source as {\n [ARRAY_SIGNAL_BRAND]?: boolean;\n _consumePatches?: () => ArrayPatch<T>[];\n };\n const granularEligible = virtualize === undefined && patchSource[ARRAY_SIGNAL_BRAND] === true;\n\n // Virtualized lists put the windowing padding + rows on an INNER sizer, so the\n // padding never inflates the scroll container's clientHeight (padding counts\n // toward clientHeight). `parent` stays the clean scroll viewport; `container`\n // holds the rows. Non-virtualized lists render straight into `parent`.\n const container: HTMLElement = virtualize === undefined ? parent : document.createElement('div');\n if (virtualize !== undefined) parent.appendChild(container);\n\n const NOOP = (): void => { /* element-mode rows with no caller teardown */ };\n\n // Detect element mode from a render result: a raw `HTMLElement`, or a\n // `{ el, dispose? }` object. Everything else (SafeHtml / string / nullish) is\n // content mode. SafeHtml is an object but has no `el`, so it never matches.\n const asElementRow = (\n rendered: MountResult | RowElement<T>,\n ): { el: HTMLElement; dispose: () => void; update?: (item: T) => void } | null => {\n if (rendered instanceof HTMLElement) return { el: rendered, dispose: NOOP };\n if (\n rendered !== null\n && typeof rendered === 'object'\n && 'el' in rendered\n && (rendered as { el: unknown }).el instanceof HTMLElement\n ) {\n const r = rendered as { el: HTMLElement; update?: (item: T) => void; dispose?: () => void };\n return { el: r.el, dispose: r.dispose ?? NOOP, update: r.update };\n }\n return null;\n };\n\n const makeRow = (item: T): Row<T> => {\n // One call decides the mode per row (so a list may mix element + content rows).\n const elementRow = asElementRow(render(item));\n if (elementRow !== null) {\n // Element mode: the returned element IS the row; the caller owns its\n // content + cleanup. bindList sizes it for the windowing math per render\n // (see `sizeVisibleRows`), not here, since a variable height depends on the\n // row's current index in the full list.\n return { el: elementRow.el, item, dispose: elementRow.dispose, elementMode: true, update: elementRow.update };\n }\n // Content mode: kerf creates the row element and mounts `render` inside it,\n // so the content is per-row reactive. (In content mode `render` runs once\n // more here for the mode probe than the mount itself needs — keep it a pure\n // projection, which bindList already requires.)\n const el = document.createElement(tag);\n // Content mode: `render` returns a MountResult here (element results were\n // handled above), so narrowing it for `mount` is sound.\n const dispose = mount(el, () => render(item) as MountResult);\n return { el, item, dispose, elementMode: false };\n };\n\n // A row whose KEY persists but whose item object changed. Content-mode rows are\n // rebuilt (their mount re-renders the fresh item); element-mode rows are REUSED\n // — the caller owns the element, so we keep it (preserving focus / scroll /\n // listeners) and refresh via the optional `update(item)`. Returns the row to\n // use at that key (a fresh one for content, the same one for element).\n const reconcileItem = (row: Row<T>, k: ListKey, item: T): Row<T> => {\n if (row.item === item) return row;\n if (row.elementMode) {\n row.item = item;\n row.update?.(item);\n return row;\n }\n row.dispose();\n row.el.remove();\n rows.delete(k);\n const fresh = makeRow(item);\n rows.set(k, fresh);\n return fresh;\n };\n\n // Reconcile the live rows to exactly `visible`, in order, keyed.\n const syncRows = (visible: readonly T[]): void => {\n const wanted = new Set<ListKey>();\n for (const item of visible) wanted.add(key(item));\n\n // Remove rows that are gone from the window.\n for (const [k, row] of rows) {\n if (!wanted.has(k)) {\n row.dispose();\n row.el.remove();\n rows.delete(k);\n }\n }\n\n // Create missing rows; reuse existing ones by key (element rows keep their\n // element across item changes; content rows rebuild on identity change).\n order.length = 0;\n for (const item of visible) {\n const k = key(item);\n const existing = rows.get(k);\n let row: Row<T>;\n if (existing !== undefined) {\n row = reconcileItem(existing, k, item);\n } else {\n row = makeRow(item);\n rows.set(k, row);\n }\n order.push(row);\n }\n\n // Reverse pass: move only rows that are out of position.\n let ref: Node | null = endAnchor();\n for (let i = order.length - 1; i >= 0; i--) {\n const el = order[i].el;\n if (el.parentNode !== container || el.nextSibling !== ref) {\n container.insertBefore(el, ref);\n }\n ref = el;\n }\n };\n\n // Apply arraySignal structural patches directly to `order` + the DOM, in\n // O(patches). Indices are always valid by construction: `order` reflects the\n // last-rendered state and the patches are exactly the delta from it (bindList\n // drains the queue every render, and `replace` is filtered out by the caller,\n // which snapshots instead). The `splice()`s mirror `arraySignal`'s own\n // `_items` mutations exactly.\n const applyPatches = (patches: readonly ArrayPatch<T>[]): void => {\n for (const patch of patches) {\n if (patch.type === 'insert') {\n const row = makeRow(patch.item);\n rows.set(key(patch.item), row);\n order.splice(patch.index, 0, row);\n container.insertBefore(row.el, order[patch.index + 1]?.el ?? endAnchor());\n } else if (patch.type === 'remove') {\n const [row] = order.splice(patch.index, 1);\n row.dispose();\n row.el.remove();\n rows.delete(key(row.item));\n } else if (patch.type === 'move') {\n const [row] = order.splice(patch.from, 1);\n order.splice(patch.to, 0, row);\n container.insertBefore(row.el, order[patch.to + 1]?.el ?? endAnchor());\n } else if (patch.type === 'update') {\n // An item whose OBJECT identity changed: content rows rebuild (their mount\n // re-renders the fresh item); element rows are REUSED — keep the caller's\n // element and refresh via update(), re-keying if the key changed. A\n // same-ref update needs nothing (the row's mount reacts to its signals).\n const current = order[patch.index];\n if (current.item !== patch.item) {\n if (current.elementMode) {\n const oldKey = key(current.item);\n const newKey = key(patch.item);\n current.item = patch.item;\n if (newKey !== oldKey) {\n rows.delete(oldKey);\n rows.set(newKey, current);\n }\n current.update?.(patch.item);\n } else {\n current.dispose();\n current.el.remove();\n rows.delete(key(current.item));\n const row = makeRow(patch.item);\n rows.set(key(patch.item), row);\n order[patch.index] = row;\n container.insertBefore(row.el, order[patch.index + 1]?.el ?? endAnchor());\n }\n }\n }\n // 'replace' never reaches here — the caller snapshots on it.\n }\n };\n\n // Virtualization height model, three modes:\n // - `fixedHeight` (a `number`): the O(1) fast path — no cumulative model.\n // - `variableHeightAt` (a function): app-declared per-row heights.\n // - measuring (`{ estimate }`): `variableHeightAt` returns the measured height\n // when the app has reported one (via `setHeight`), else the estimate.\n // In the two variable cases, `offsets[i]` is the total height of rows 0..i-1\n // (a prefix sum, length total+1), so `offsets[i+1] - offsets[i]` is row i's\n // height and `offsets[total]` is the full scroll height. It is rebuilt only\n // when `items` changes or a height is reported (heightsDirty), never per scroll\n // frame — a scroll reuses the prefix sum and pays only the O(log n) searches.\n const rowHeight = virtualize?.rowHeight;\n const fixedHeight = typeof rowHeight === 'number' ? rowHeight : null;\n const measuring = typeof rowHeight === 'object' && rowHeight !== null;\n const measured = new Map<ListKey, number>(); // key → real reported height\n const estimateAt = (index: number): number => {\n const est = (rowHeight as { estimate: number | ((item: T, index: number) => number) }).estimate;\n return typeof est === 'function' ? est(items[index], index) : est;\n };\n const variableHeightAt: ((index: number) => number) | null =\n fixedHeight !== null\n ? null\n : measuring\n ? (index): number => {\n const k = key(items[index]);\n return measured.has(k) ? (measured.get(k) as number) : estimateAt(index);\n }\n : (index): number => (rowHeight as (item: T, index: number) => number)(items[index], index);\n\n let offsets: number[] = [0];\n let heightsDirty = true;\n // Measuring only: key → current absolute index, so `setHeight(key, …)` locates\n // the row in O(1). Rebuilt with the prefix sum when `items` changes.\n const indexByKey = new Map<ListKey, number>();\n // Accumulated scroll-anchor correction: the summed height delta of remeasured\n // rows that sit entirely ABOVE the viewport top, applied to `scrollTop` before\n // the next window render so on-screen content does not jump.\n let pendingAnchorDelta = 0;\n\n const rebuildOffsets = (): void => {\n const fn = variableHeightAt as (index: number) => number;\n const total = items.length;\n offsets = new Array<number>(total + 1);\n offsets[0] = 0;\n if (measuring) indexByKey.clear();\n for (let i = 0; i < total; i++) {\n offsets[i + 1] = offsets[i] + fn(i);\n if (measuring) indexByKey.set(key(items[i]), i);\n }\n };\n\n // Greatest index i in [0, total] with `offsets[i] <= target` — the first row\n // whose top is at or above `target` (the viewport top).\n const findStart = (target: number, total: number): number => {\n let lo = 0;\n let hi = total;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (offsets[mid] <= target) lo = mid;\n else hi = mid - 1;\n }\n return lo;\n };\n\n // Smallest index i in [0, total] with `offsets[i] >= target` — one past the\n // last row that starts before `target` (the viewport bottom). `total` if none.\n const findEnd = (target: number, total: number): number => {\n let lo = 0;\n let hi = total;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (offsets[mid] >= target) hi = mid;\n else lo = mid + 1;\n }\n return lo;\n };\n\n // Size each visible row for the windowing math. `order` holds the visible rows\n // in order, so `order[j]` is the item at absolute index `start + j`.\n // MEASURED mode is the exception: the row must take its NATURAL height so the\n // app (or `observeRowHeights`) can read the real `offsetHeight` — forcing a\n // height here would make the measurement echo the estimate. Its offsets come\n // from `setHeight` reports instead.\n const sizeVisibleRows = (start: number): void => {\n if (measuring) return;\n for (let j = 0; j < order.length; j++) {\n const abs = start + j;\n const h = fixedHeight !== null ? fixedHeight : offsets[abs + 1] - offsets[abs];\n order[j].el.style.height = `${h}px`;\n }\n };\n\n // Called after each virtualized window render (used by `observeRowHeights` to\n // re-observe the current visible rows).\n const renderSubscribers = new Set<() => void>();\n\n const renderWindow = (): void => {\n if (virtualize === undefined) {\n if (granularEligible) {\n // Always drain to keep the single patch queue clean (so patches never\n // double-apply). Take the granular path past the first render, when\n // there are patches, and none is a `replace` (which reshapes the whole\n // array — snapshot instead). Otherwise fall through to a keyed diff.\n const patches = patchSource._consumePatches!();\n if (\n !firstRender\n && patches.length > 0\n && !patches.some((p) => p.type === 'replace')\n ) {\n applyPatches(patches);\n return;\n }\n }\n syncRows(items);\n firstRender = false;\n return;\n }\n const total = items.length;\n const scrollTop = parent.scrollTop;\n const viewportBottom = scrollTop + parent.clientHeight;\n let start: number;\n let end: number;\n let padTop: number;\n let padBottom: number;\n if (fixedHeight !== null) {\n start = Math.max(0, Math.floor(scrollTop / fixedHeight) - overscan);\n end = Math.min(total, Math.ceil(viewportBottom / fixedHeight) + overscan);\n padTop = start * fixedHeight;\n padBottom = Math.max(0, total - end) * fixedHeight;\n } else {\n if (heightsDirty) {\n rebuildOffsets();\n heightsDirty = false;\n }\n start = Math.max(0, findStart(scrollTop, total) - overscan);\n end = Math.min(total, findEnd(viewportBottom, total) + overscan);\n padTop = offsets[start];\n padBottom = offsets[total] - offsets[end];\n }\n syncRows(items.slice(start, end));\n sizeVisibleRows(start);\n container.style.paddingTop = `${padTop}px`;\n container.style.paddingBottom = `${padBottom}px`;\n for (const cb of renderSubscribers) cb();\n };\n\n const stopEffect = effect(() => {\n items = source.value; // tracking read — re-runs on any structural change\n heightsDirty = true; // items changed → the prefix sum (if any) is stale\n renderWindow();\n });\n\n // One rAF-coalesced render, shared by scroll and by measurement reports. A\n // pending anchor correction is applied to `scrollTop` first (which itself may\n // fire a scroll, but with the delta already cleared the follow-up is a no-op).\n const scheduleRender = (): void => {\n if (rafPending) return;\n rafPending = true;\n globalThis.requestAnimationFrame(() => {\n rafPending = false;\n if (disposed) return;\n if (pendingAnchorDelta !== 0) {\n parent.scrollTop += pendingAnchorDelta;\n pendingAnchorDelta = 0;\n }\n renderWindow();\n });\n };\n if (virtualize !== undefined) parent.addEventListener('scroll', scheduleRender);\n\n // Measured mode: report a row's real height. No-op for fixed / declared lists\n // and for keys not currently in the list.\n const setHeight = (k: ListKey, height: number): void => {\n if (!measuring) return;\n const idx = indexByKey.get(k);\n if (idx === undefined) return;\n const oldHeight = measured.has(k) ? (measured.get(k) as number) : estimateAt(idx);\n if (height === oldHeight) return;\n measured.set(k, height);\n // A row whose bottom is at/above the viewport top shifts everything below it\n // (the on-screen content) by the height delta — correct `scrollTop` to match.\n // Uses the CURRENT (pre-rebuild) offsets, which reflect the on-screen layout.\n if (offsets[idx + 1] <= parent.scrollTop) pendingAnchorDelta += height - oldHeight;\n heightsDirty = true;\n scheduleRender();\n };\n\n const dispose = ((): void => {\n disposed = true;\n stopEffect();\n for (const row of rows.values()) {\n row.dispose();\n if (virtualize === undefined) row.el.remove();\n }\n rows.clear();\n renderSubscribers.clear();\n if (virtualize !== undefined) {\n parent.removeEventListener('scroll', scheduleRender);\n container.remove(); // removes the inner sizer and its rows in one go\n VIRTUAL_INTERNALS.delete(handle);\n }\n }) as BindListHandle;\n const handle = dispose;\n handle.setHeight = setHeight;\n\n // Register the coordination surface the `observeRowHeights` helper needs, kept\n // off the public type (a GC-tied WeakMap, so it doesn't count against Design\n // rule 5). Only virtualized lists have a window to observe.\n if (virtualize !== undefined) {\n VIRTUAL_INTERNALS.set(handle, {\n visibleRows: () => order.map((row) => ({ key: key(row.item), el: row.el })),\n onRender: (cb) => {\n renderSubscribers.add(cb);\n return () => renderSubscribers.delete(cb);\n },\n });\n }\n\n return handle;\n}\n\n/** Internal coordination surface between {@link bindList} and {@link observeRowHeights}. */\ninterface VirtualInternals {\n /** The current visible rows, in order, with their keys. */\n visibleRows: () => Array<{ key: ListKey; el: HTMLElement }>;\n /** Subscribe to each window render; returns an unsubscribe. */\n onRender: (cb: () => void) => () => void;\n}\n\n// GC-tied (WeakMap) coordination store — a pure cache, not counted against\n// Design rule 5 (same class as `bindings.ts:insertedTextNodes`).\nconst VIRTUAL_INTERNALS = new WeakMap<object, VirtualInternals>();\n\n/**\n * Drive a **measured** virtualized `bindList` (`virtualize: { rowHeight: {\n * estimate } }`) from real layout: install ONE `ResizeObserver` over the visible\n * rows and forward each row's `offsetHeight` to `handle.setHeight`, re-observing\n * as the window shifts. Returns a disposer.\n *\n * This is the batteries-included measurement path; it is deliberately separate\n * from `bindList` (which never depends on `ResizeObserver`) — you can measure\n * however you like and call `handle.setHeight` yourself instead. A no-op for a\n * non-virtualized handle or where `ResizeObserver` is unavailable (SSR).\n *\n * const list = bindList(scrollEl, source, { key, render, virtualize: { rowHeight: { estimate: 64 } } });\n * const stopMeasuring = observeRowHeights(list);\n */\nexport function observeRowHeights(handle: BindListHandle): () => void {\n const internals = VIRTUAL_INTERNALS.get(handle);\n const RO = globalThis.ResizeObserver;\n if (internals === undefined || RO === undefined) return () => { /* nothing to observe */ };\n\n const keyByEl = new WeakMap<Element, ListKey>();\n const observer = new RO((entries) => {\n for (const entry of entries) {\n const k = keyByEl.get(entry.target);\n if (k !== undefined) handle.setHeight(k, (entry.target as HTMLElement).offsetHeight);\n }\n });\n\n const resync = (): void => {\n observer.disconnect();\n for (const { key: k, el } of internals.visibleRows()) {\n keyByEl.set(el, k);\n observer.observe(el);\n }\n };\n\n const unsubscribe = internals.onRender(resync);\n resync(); // observe the initial window\n\n return () => {\n observer.disconnect();\n unsubscribe();\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/list.ts"],"names":["dispose"],"mappings":"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","file":"list.js","sourcesContent":["/**\n * `kerfjs/list` — `bindList`, a keyed list with a live per-row mount and\n * optional viewport virtualization.\n *\n * This is a DELIBERATE second list API, distinct from `each()`. It does two\n * things `each()` structurally cannot:\n * 1. **Per-row reactivity.** Every row is individually `mount()`ed, so a signal\n * the row's `render` reads updates just that row (fine-grained binding or a\n * one-row morph) without touching its siblings — no full-list pass.\n * 2. **Virtualization.** With `{ virtualize: { rowHeight } }` only the rows in\n * the scroll viewport are rendered; padding on the scroll container keeps\n * `scrollHeight` honest. `rowHeight` is a fixed `number` (O(1) windowing), a\n * `(item, index) => number` for **app-declared variable** heights (a prefix\n * sum + binary-search window), or `{ estimate }` for **measured** heights —\n * the app reports real heights via the returned handle's `setHeight` (or the\n * `observeRowHeights` helper) and kerf anchor-corrects `scrollTop`. See\n * `docs/17-list-virtualization.md`.\n *\n * `each()` stays the choice for item-owned-state lists rendered to HTML strings;\n * reach for `bindList` when you need surgical per-row updates or windowing.\n *\n * import { bindList } from 'kerfjs/list';\n *\n * const dispose = bindList(listEl, itemsSignal, {\n * key: (row) => row.id,\n * render: (row) => <span class={selected} data-id={row.id}>{row.label}</span>,\n * tag: 'li',\n * virtualize: { rowHeight: 32 },\n * });\n *\n * `render` reads signals for reactivity (external state like a `selectedId`, or\n * signals the item carries) — keep the item OBJECTS stable across renders and\n * drive structure (add/remove/move) through `itemsSignal`. A row whose item\n * object identity changes is rebuilt (same rule as `each()`'s memo). `bindList`\n * OWNS `parent`'s children by default (append/move to the end) — to share\n * `parent` with fixed trailing siblings (an \"add\" button, an indicator), pass\n * `before` so the rows end just before that node. It reads `itemsSignal.value`,\n * so a plain `signal<T[]>` or an `arraySignal<T>` both work.\n */\nimport { ARRAY_SIGNAL_BRAND, type ArrayPatch } from './array-signal.js';\nimport { mount, type MountResult } from './mount.js';\nimport { effect } from './reactive.js';\n\n/** A row's stable key. */\nexport type ListKey = string | number;\n\n/** Anything with a tracking `.value` array read — a `signal<readonly T[]>` or an `arraySignal<T>`. */\nexport interface ListSource<T> {\n readonly value: readonly T[];\n}\n\n/**\n * A row built imperatively by `render`: return the row **element** itself (kerf\n * keys/moves/reuses it and owns nothing inside it), or `{ el, update?, dispose? }`\n * to also hand back an `update(item)` — called on the SAME element when the row's\n * key persists but its item changes — and a `dispose` that runs only when the row\n * is removed.\n */\nexport type RowElement<T> =\n | HTMLElement\n | { el: HTMLElement; update?: (item: T) => void; dispose?: () => void };\n\n/**\n * The virtualization height model:\n * - **`number`** — every row is this fixed pixel height (O(1) windowing).\n * - **`(item, index) => number`** — app-declared **variable** heights, derived\n * purely from the item and its index.\n * - **`{ estimate }`** — **measured** heights: kerf uses `estimate` for a row\n * until the app reports its real height through {@link BindListHandle.setHeight}\n * (or the `observeRowHeights` helper). See `docs/17-list-virtualization.md`.\n */\nexport type RowHeight<T> =\n | number\n | ((item: T, index: number) => number)\n | { estimate: number | ((item: T, index: number) => number) };\n\n/**\n * The value {@link bindList} returns: a disposer you call to tear the list down,\n * augmented with `setHeight` for the **measured** virtualization mode.\n */\nexport type BindListHandle = (() => void) & {\n /**\n * Report a row's real pixel height (measured after layout) for\n * `virtualize: { rowHeight: { estimate } }` lists. Keyed by the list `key`, so\n * a report survives reorders. kerf recomputes the window and, if the row sits\n * ABOVE the viewport, anchor-corrects `scrollTop` so content doesn't jump.\n * A no-op for fixed / declared-height lists and for unknown keys.\n */\n setHeight: (key: ListKey, height: number) => void;\n /**\n * The inner container element kerf creates to hold the rows in a **virtualized**\n * list (the \"sizer\"). `undefined` for a non-virtualized list (there the rows\n * live directly in `parent`). Use it to style, id, or otherwise reach the row\n * block without guessing at `parent.lastElementChild` — though `containerClass`\n * / `containerId` on `virtualize` set those declaratively.\n */\n container?: HTMLElement;\n};\n\n/** Options for {@link bindList}. */\nexport interface BindListOptions<T> {\n /** Stable per-row key. Rows are matched, moved, and reused by this. */\n key: (item: T) => ListKey;\n /**\n * Build a row. Two modes, chosen per call by what you return:\n * - **Content mode** (a `MountResult` — JSX / `SafeHtml`): kerf creates the\n * row element (`tag`) and `mount()`s your content inside it, so signals your\n * content reads drive per-row reactivity.\n * - **Element mode** (an `HTMLElement`, or `{ el, update?, dispose? }`): the\n * element you return IS the row, so you own its tag, class, `data-*`, and\n * listeners. kerf **keys/moves/reuses** it — the SAME element survives an\n * append/remove/reorder or a fresh item object at the same key. Refresh its\n * content by reading signals inside it, or by returning an `update(item)`\n * that kerf calls on the existing element when the item changes. `dispose`\n * runs only when the row is genuinely removed.\n */\n render: (item: T) => MountResult | RowElement<T>;\n /** Row element tag for **content mode**. Default `'div'` (use `'li'` inside a `<ul>`, `'tr'` inside a `<tbody>`, …). Ignored in element mode. */\n tag?: string;\n /**\n * Keep the rows as a contiguous block that ENDS just before this node, instead\n * of at the very end of `parent`. Use it when `parent` also holds non-row\n * siblings that must stay put — a trailing \"add\" button, a sliding indicator:\n * `before: () => addButton`. The node (a function is re-read each reconcile, or\n * pass the node directly) must be a child of `parent`. Without it, bindList\n * assumes exclusive ownership and appends rows to the end. Ignored when\n * virtualized (the rows own bindList's inner sizer exclusively).\n */\n before?: Node | (() => Node | null);\n /**\n * Turn on viewport virtualization. `parent` must be a scroll container (your\n * CSS: a fixed height + `overflow: auto`). `overscan` (default 3) is how many\n * extra rows to render above and below the viewport.\n *\n * `rowHeight` (a {@link RowHeight}) is the height model:\n * - **`number`** — every row is this fixed pixel height. O(1) windowing, no\n * cumulative model built.\n * - **`(item, index) => number`** — app-declared **variable** heights, derived\n * purely from the item and its index. kerf builds a prefix sum of the\n * heights (rebuilt when the source array changes, not per scroll frame) and\n * binary-searches it to find the visible window. Return a non-negative\n * number of pixels.\n * - **`{ estimate }`** — **measured** heights for rows whose height is only\n * known after layout. kerf sizes an unmeasured row by `estimate` (a number\n * or an `(item, index) => number`), and the app reports each row's real\n * height via {@link BindListHandle.setHeight} (or the `observeRowHeights`\n * helper). kerf anchor-corrects `scrollTop` when an above-viewport row is\n * remeasured, so content doesn't jump.\n *\n * `minRows` renders **every** row (no windowing, zero padding) while the list\n * is shorter than it, and windows only at or above it — the DOM structure (the\n * inner container) is the same either way, so the call site never branches. A\n * fully-rendered short list is friendlier to find-in-page, screen readers, and\n * DOM-count assertions, which only see rows actually in the DOM.\n *\n * `containerClass` / `containerId` are set on the inner container kerf creates\n * to hold the rows, so it's reachable from CSS and tests without guessing at\n * `parent.lastElementChild` (it's also on the handle as `handle.container`).\n *\n * kerf re-windows on `parent`'s `scroll` and, where `ResizeObserver` exists, on\n * `parent` resizing — so a list that mounts before layout (a hidden tab,\n * `clientHeight` 0) fills in once it's sized, and a resized container re-windows.\n */\n virtualize?: {\n rowHeight: RowHeight<T>;\n overscan?: number;\n minRows?: number;\n containerClass?: string;\n containerId?: string;\n };\n}\n\ninterface Row<T> {\n el: HTMLElement;\n item: T;\n dispose: () => void;\n /** True for element-mode rows (the caller owns the element — reuse it, don't rebuild on item change). */\n elementMode: boolean;\n /** Element mode only: refresh the existing element when the item changes at the same key. */\n update?: (item: T) => void;\n}\n\n/**\n * Bind a keyed, per-row-reactive list to `parent`, driven by `source` (a\n * `signal<readonly T[]>` or an `arraySignal<T>`). Returns a disposer that tears\n * down every row mount, the scroll listener (if virtualized), and the source\n * subscription.\n */\nexport function bindList<T>(\n parent: HTMLElement,\n source: ListSource<T>,\n options: BindListOptions<T>,\n): BindListHandle {\n const { key, render, tag = 'div', virtualize, before } = options;\n const overscan = virtualize?.overscan ?? 3;\n const minRows = virtualize?.minRows;\n\n // The node the row block ends before — `before` (KF-496) when the list shares\n // `parent` with trailing siblings, else the end of the container. Never applies\n // when virtualized: the rows own bindList's inner sizer exclusively.\n const endAnchor = (): Node | null => {\n if (virtualize !== undefined || before === undefined) return null;\n return (typeof before === 'function' ? before() : before) ?? null;\n };\n\n const rows = new Map<ListKey, Row<T>>();\n // The current DOM order of rows, kept in step by both the keyed-diff and the\n // granular patch paths so index-based patches can address rows directly.\n const order: Array<Row<T>> = [];\n let items: readonly T[] = [];\n let disposed = false;\n let rafPending = false;\n let firstRender = true;\n\n // Granular fast path (KF-478): when the source is an `arraySignal` and the\n // list is NOT virtualized, apply its insert/remove/move/update patches\n // directly in O(patches) instead of diffing the whole snapshot. Virtualized\n // lists keep the keyed diff — their visible set is just the window (cheap),\n // and absolute-index patches don't compose with a shifting window. A plain\n // `signal<T[]>` has no patches, so it always uses the keyed diff.\n const patchSource = source as {\n [ARRAY_SIGNAL_BRAND]?: boolean;\n _consumePatches?: () => ArrayPatch<T>[];\n };\n const granularEligible = virtualize === undefined && patchSource[ARRAY_SIGNAL_BRAND] === true;\n\n // Virtualized lists put the windowing padding + rows on an INNER sizer, so the\n // padding never inflates the scroll container's clientHeight (padding counts\n // toward clientHeight). `parent` stays the clean scroll viewport; `container`\n // holds the rows. Non-virtualized lists render straight into `parent`.\n const container: HTMLElement = virtualize === undefined ? parent : document.createElement('div');\n if (virtualize !== undefined) {\n if (virtualize.containerClass !== undefined) container.className = virtualize.containerClass;\n if (virtualize.containerId !== undefined) container.id = virtualize.containerId;\n parent.appendChild(container);\n }\n\n const NOOP = (): void => { /* element-mode rows with no caller teardown */ };\n\n // Detect element mode from a render result: a raw `HTMLElement`, or a\n // `{ el, dispose? }` object. Everything else (SafeHtml / string / nullish) is\n // content mode. SafeHtml is an object but has no `el`, so it never matches.\n const asElementRow = (\n rendered: MountResult | RowElement<T>,\n ): { el: HTMLElement; dispose: () => void; update?: (item: T) => void } | null => {\n if (rendered instanceof HTMLElement) return { el: rendered, dispose: NOOP };\n if (\n rendered !== null\n && typeof rendered === 'object'\n && 'el' in rendered\n && (rendered as { el: unknown }).el instanceof HTMLElement\n ) {\n const r = rendered as { el: HTMLElement; update?: (item: T) => void; dispose?: () => void };\n return { el: r.el, dispose: r.dispose ?? NOOP, update: r.update };\n }\n return null;\n };\n\n const makeRow = (item: T): Row<T> => {\n // One call decides the mode per row (so a list may mix element + content rows).\n const elementRow = asElementRow(render(item));\n if (elementRow !== null) {\n // Element mode: the returned element IS the row; the caller owns its\n // content + cleanup. bindList sizes it for the windowing math per render\n // (see `sizeVisibleRows`), not here, since a variable height depends on the\n // row's current index in the full list.\n return { el: elementRow.el, item, dispose: elementRow.dispose, elementMode: true, update: elementRow.update };\n }\n // Content mode: kerf creates the row element and mounts `render` inside it,\n // so the content is per-row reactive. (In content mode `render` runs once\n // more here for the mode probe than the mount itself needs — keep it a pure\n // projection, which bindList already requires.)\n const el = document.createElement(tag);\n // Content mode: `render` returns a MountResult here (element results were\n // handled above), so narrowing it for `mount` is sound.\n const dispose = mount(el, () => render(item) as MountResult);\n return { el, item, dispose, elementMode: false };\n };\n\n // A row whose KEY persists but whose item object changed. Content-mode rows are\n // rebuilt (their mount re-renders the fresh item); element-mode rows are REUSED\n // — the caller owns the element, so we keep it (preserving focus / scroll /\n // listeners) and refresh via the optional `update(item)`. Returns the row to\n // use at that key (a fresh one for content, the same one for element).\n const reconcileItem = (row: Row<T>, k: ListKey, item: T): Row<T> => {\n if (row.item === item) return row;\n if (row.elementMode) {\n row.item = item;\n row.update?.(item);\n return row;\n }\n row.dispose();\n row.el.remove();\n rows.delete(k);\n const fresh = makeRow(item);\n rows.set(k, fresh);\n return fresh;\n };\n\n // Reconcile the live rows to exactly `visible`, in order, keyed.\n const syncRows = (visible: readonly T[]): void => {\n const wanted = new Set<ListKey>();\n for (const item of visible) wanted.add(key(item));\n\n // Remove rows that are gone from the window.\n for (const [k, row] of rows) {\n if (!wanted.has(k)) {\n row.dispose();\n row.el.remove();\n rows.delete(k);\n }\n }\n\n // Create missing rows; reuse existing ones by key (element rows keep their\n // element across item changes; content rows rebuild on identity change).\n order.length = 0;\n for (const item of visible) {\n const k = key(item);\n const existing = rows.get(k);\n let row: Row<T>;\n if (existing !== undefined) {\n row = reconcileItem(existing, k, item);\n } else {\n row = makeRow(item);\n rows.set(k, row);\n }\n order.push(row);\n }\n\n // Reverse pass: move only rows that are out of position.\n let ref: Node | null = endAnchor();\n for (let i = order.length - 1; i >= 0; i--) {\n const el = order[i].el;\n if (el.parentNode !== container || el.nextSibling !== ref) {\n container.insertBefore(el, ref);\n }\n ref = el;\n }\n };\n\n // Apply arraySignal structural patches directly to `order` + the DOM, in\n // O(patches). Indices are always valid by construction: `order` reflects the\n // last-rendered state and the patches are exactly the delta from it (bindList\n // drains the queue every render, and `replace` is filtered out by the caller,\n // which snapshots instead). The `splice()`s mirror `arraySignal`'s own\n // `_items` mutations exactly.\n const applyPatches = (patches: readonly ArrayPatch<T>[]): void => {\n for (const patch of patches) {\n if (patch.type === 'insert') {\n const row = makeRow(patch.item);\n rows.set(key(patch.item), row);\n order.splice(patch.index, 0, row);\n container.insertBefore(row.el, order[patch.index + 1]?.el ?? endAnchor());\n } else if (patch.type === 'remove') {\n const [row] = order.splice(patch.index, 1);\n row.dispose();\n row.el.remove();\n rows.delete(key(row.item));\n } else if (patch.type === 'move') {\n const [row] = order.splice(patch.from, 1);\n order.splice(patch.to, 0, row);\n container.insertBefore(row.el, order[patch.to + 1]?.el ?? endAnchor());\n } else if (patch.type === 'update') {\n // An item whose OBJECT identity changed: content rows rebuild (their mount\n // re-renders the fresh item); element rows are REUSED — keep the caller's\n // element and refresh via update(), re-keying if the key changed. A\n // same-ref update needs nothing (the row's mount reacts to its signals).\n const current = order[patch.index];\n if (current.item !== patch.item) {\n if (current.elementMode) {\n const oldKey = key(current.item);\n const newKey = key(patch.item);\n current.item = patch.item;\n if (newKey !== oldKey) {\n rows.delete(oldKey);\n rows.set(newKey, current);\n }\n current.update?.(patch.item);\n } else {\n current.dispose();\n current.el.remove();\n rows.delete(key(current.item));\n const row = makeRow(patch.item);\n rows.set(key(patch.item), row);\n order[patch.index] = row;\n container.insertBefore(row.el, order[patch.index + 1]?.el ?? endAnchor());\n }\n }\n }\n // 'replace' never reaches here — the caller snapshots on it.\n }\n };\n\n // Virtualization height model, three modes:\n // - `fixedHeight` (a `number`): the O(1) fast path — no cumulative model.\n // - `variableHeightAt` (a function): app-declared per-row heights.\n // - measuring (`{ estimate }`): `variableHeightAt` returns the measured height\n // when the app has reported one (via `setHeight`), else the estimate.\n // In the two variable cases, `offsets[i]` is the total height of rows 0..i-1\n // (a prefix sum, length total+1), so `offsets[i+1] - offsets[i]` is row i's\n // height and `offsets[total]` is the full scroll height. It is rebuilt only\n // when `items` changes or a height is reported (heightsDirty), never per scroll\n // frame — a scroll reuses the prefix sum and pays only the O(log n) searches.\n const rowHeight = virtualize?.rowHeight;\n const fixedHeight = typeof rowHeight === 'number' ? rowHeight : null;\n const measuring = typeof rowHeight === 'object' && rowHeight !== null;\n const measured = new Map<ListKey, number>(); // key → real reported height\n const estimateAt = (index: number): number => {\n const est = (rowHeight as { estimate: number | ((item: T, index: number) => number) }).estimate;\n return typeof est === 'function' ? est(items[index], index) : est;\n };\n const variableHeightAt: ((index: number) => number) | null =\n fixedHeight !== null\n ? null\n : measuring\n ? (index): number => {\n const k = key(items[index]);\n return measured.has(k) ? (measured.get(k) as number) : estimateAt(index);\n }\n : (index): number => (rowHeight as (item: T, index: number) => number)(items[index], index);\n\n let offsets: number[] = [0];\n let heightsDirty = true;\n // Measuring only: key → current absolute index, so `setHeight(key, …)` locates\n // the row in O(1). Rebuilt with the prefix sum when `items` changes.\n const indexByKey = new Map<ListKey, number>();\n // Accumulated scroll-anchor correction: the summed height delta of remeasured\n // rows that sit entirely ABOVE the viewport top, applied to `scrollTop` before\n // the next window render so on-screen content does not jump.\n let pendingAnchorDelta = 0;\n\n const rebuildOffsets = (): void => {\n const fn = variableHeightAt as (index: number) => number;\n const total = items.length;\n offsets = new Array<number>(total + 1);\n offsets[0] = 0;\n if (measuring) indexByKey.clear();\n for (let i = 0; i < total; i++) {\n offsets[i + 1] = offsets[i] + fn(i);\n if (measuring) indexByKey.set(key(items[i]), i);\n }\n };\n\n // Greatest index i in [0, total] with `offsets[i] <= target` — the first row\n // whose top is at or above `target` (the viewport top).\n const findStart = (target: number, total: number): number => {\n let lo = 0;\n let hi = total;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (offsets[mid] <= target) lo = mid;\n else hi = mid - 1;\n }\n return lo;\n };\n\n // Smallest index i in [0, total] with `offsets[i] >= target` — one past the\n // last row that starts before `target` (the viewport bottom). `total` if none.\n const findEnd = (target: number, total: number): number => {\n let lo = 0;\n let hi = total;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (offsets[mid] >= target) hi = mid;\n else lo = mid + 1;\n }\n return lo;\n };\n\n // Size each visible row for the windowing math. `order` holds the visible rows\n // in order, so `order[j]` is the item at absolute index `start + j`.\n // MEASURED mode is the exception: the row must take its NATURAL height so the\n // app (or `observeRowHeights`) can read the real `offsetHeight` — forcing a\n // height here would make the measurement echo the estimate. Its offsets come\n // from `setHeight` reports instead.\n const sizeVisibleRows = (start: number): void => {\n if (measuring) return;\n for (let j = 0; j < order.length; j++) {\n const abs = start + j;\n const h = fixedHeight !== null ? fixedHeight : offsets[abs + 1] - offsets[abs];\n order[j].el.style.height = `${h}px`;\n }\n };\n\n // Called after each virtualized window render (used by `observeRowHeights` to\n // re-observe the current visible rows).\n const renderSubscribers = new Set<() => void>();\n\n const renderWindow = (): void => {\n if (virtualize === undefined) {\n if (granularEligible) {\n // Always drain to keep the single patch queue clean (so patches never\n // double-apply). Take the granular path past the first render, when\n // there are patches, and none is a `replace` (which reshapes the whole\n // array — snapshot instead). Otherwise fall through to a keyed diff.\n const patches = patchSource._consumePatches!();\n if (\n !firstRender\n && patches.length > 0\n && !patches.some((p) => p.type === 'replace')\n ) {\n applyPatches(patches);\n return;\n }\n }\n syncRows(items);\n firstRender = false;\n return;\n }\n const total = items.length;\n let start: number;\n let end: number;\n let padTop: number;\n let padBottom: number;\n if (minRows !== undefined && total < minRows) {\n // Below the threshold: render EVERY row, no windowing, zero padding — one\n // DOM structure (the inner container) shared with the windowed path, so the\n // caller never branches. Rows are still sized (declared/fixed) from the\n // prefix sum, which we still build for the sizing pass.\n if (fixedHeight === null && heightsDirty) {\n rebuildOffsets();\n heightsDirty = false;\n }\n start = 0;\n end = total;\n padTop = 0;\n padBottom = 0;\n } else {\n const scrollTop = parent.scrollTop;\n const viewportBottom = scrollTop + parent.clientHeight;\n if (fixedHeight !== null) {\n start = Math.max(0, Math.floor(scrollTop / fixedHeight) - overscan);\n end = Math.min(total, Math.ceil(viewportBottom / fixedHeight) + overscan);\n padTop = start * fixedHeight;\n padBottom = Math.max(0, total - end) * fixedHeight;\n } else {\n if (heightsDirty) {\n rebuildOffsets();\n heightsDirty = false;\n }\n start = Math.max(0, findStart(scrollTop, total) - overscan);\n end = Math.min(total, findEnd(viewportBottom, total) + overscan);\n padTop = offsets[start];\n padBottom = offsets[total] - offsets[end];\n }\n }\n syncRows(items.slice(start, end));\n sizeVisibleRows(start);\n container.style.paddingTop = `${padTop}px`;\n container.style.paddingBottom = `${padBottom}px`;\n for (const cb of renderSubscribers) cb();\n };\n\n const stopEffect = effect(() => {\n items = source.value; // tracking read — re-runs on any structural change\n heightsDirty = true; // items changed → the prefix sum (if any) is stale\n renderWindow();\n });\n\n // One rAF-coalesced render, shared by scroll and by measurement reports. A\n // pending anchor correction is applied to `scrollTop` first (which itself may\n // fire a scroll, but with the delta already cleared the follow-up is a no-op).\n const scheduleRender = (): void => {\n if (rafPending) return;\n rafPending = true;\n globalThis.requestAnimationFrame(() => {\n rafPending = false;\n if (disposed) return;\n if (pendingAnchorDelta !== 0) {\n parent.scrollTop += pendingAnchorDelta;\n pendingAnchorDelta = 0;\n }\n renderWindow();\n });\n };\n if (virtualize !== undefined) parent.addEventListener('scroll', scheduleRender);\n\n // Re-window when `parent` RESIZES, not just on scroll. This makes two cases\n // robust that the scroll-only model missed: a list mounted before layout\n // (`clientHeight` 0 — a hidden tab, pre-first-paint) fills in once it's sized,\n // and a container resized while open re-windows. ResizeObserver fires an\n // initial callback on observe, so the 0-height case self-heals with no synthetic\n // scroll. Absent (older SSR/runtime) → scroll-only, as before.\n const RO = globalThis.ResizeObserver;\n const parentResize = virtualize !== undefined && RO !== undefined ? new RO(scheduleRender) : undefined;\n parentResize?.observe(parent);\n\n // Measured mode: report a row's real height. No-op for fixed / declared lists\n // and for keys not currently in the list.\n const setHeight = (k: ListKey, height: number): void => {\n if (!measuring) return;\n const idx = indexByKey.get(k);\n if (idx === undefined) return;\n const oldHeight = measured.has(k) ? (measured.get(k) as number) : estimateAt(idx);\n if (height === oldHeight) return;\n measured.set(k, height);\n // A row whose bottom is at/above the viewport top shifts everything below it\n // (the on-screen content) by the height delta — correct `scrollTop` to match.\n // Uses the CURRENT (pre-rebuild) offsets, which reflect the on-screen layout.\n if (offsets[idx + 1] <= parent.scrollTop) pendingAnchorDelta += height - oldHeight;\n heightsDirty = true;\n scheduleRender();\n };\n\n const dispose = ((): void => {\n disposed = true;\n stopEffect();\n for (const row of rows.values()) {\n row.dispose();\n if (virtualize === undefined) row.el.remove();\n }\n rows.clear();\n renderSubscribers.clear();\n if (virtualize !== undefined) {\n parent.removeEventListener('scroll', scheduleRender);\n parentResize?.disconnect();\n container.remove(); // removes the inner sizer and its rows in one go\n VIRTUAL_INTERNALS.delete(handle);\n }\n }) as BindListHandle;\n const handle = dispose;\n handle.setHeight = setHeight;\n\n // Register the coordination surface the `observeRowHeights` helper needs, kept\n // off the public type (a GC-tied WeakMap, so it doesn't count against Design\n // rule 5). Only virtualized lists have a window to observe.\n if (virtualize !== undefined) {\n handle.container = container;\n VIRTUAL_INTERNALS.set(handle, {\n visibleRows: () => order.map((row) => ({ key: key(row.item), el: row.el })),\n onRender: (cb) => {\n renderSubscribers.add(cb);\n return () => renderSubscribers.delete(cb);\n },\n });\n }\n\n return handle;\n}\n\n/** Internal coordination surface between {@link bindList} and {@link observeRowHeights}. */\ninterface VirtualInternals {\n /** The current visible rows, in order, with their keys. */\n visibleRows: () => Array<{ key: ListKey; el: HTMLElement }>;\n /** Subscribe to each window render; returns an unsubscribe. */\n onRender: (cb: () => void) => () => void;\n}\n\n// GC-tied (WeakMap) coordination store — a pure cache, not counted against\n// Design rule 5 (same class as `bindings.ts:insertedTextNodes`).\nconst VIRTUAL_INTERNALS = new WeakMap<object, VirtualInternals>();\n\n/**\n * Drive a **measured** virtualized `bindList` (`virtualize: { rowHeight: {\n * estimate } }`) from real layout: install ONE `ResizeObserver` over the visible\n * rows and forward each row's `offsetHeight` to `handle.setHeight`, re-observing\n * as the window shifts. Returns a disposer.\n *\n * This is the batteries-included measurement path; it is deliberately separate\n * from `bindList` (which never depends on `ResizeObserver`) — you can measure\n * however you like and call `handle.setHeight` yourself instead. A no-op for a\n * non-virtualized handle or where `ResizeObserver` is unavailable (SSR).\n *\n * const list = bindList(scrollEl, source, { key, render, virtualize: { rowHeight: { estimate: 64 } } });\n * const stopMeasuring = observeRowHeights(list);\n */\nexport function observeRowHeights(handle: BindListHandle): () => void {\n const internals = VIRTUAL_INTERNALS.get(handle);\n const RO = globalThis.ResizeObserver;\n if (internals === undefined || RO === undefined) return () => { /* nothing to observe */ };\n\n const keyByEl = new WeakMap<Element, ListKey>();\n const observer = new RO((entries) => {\n for (const entry of entries) {\n const k = keyByEl.get(entry.target);\n if (k !== undefined) handle.setHeight(k, (entry.target as HTMLElement).offsetHeight);\n }\n });\n\n const resync = (): void => {\n observer.disconnect();\n for (const { key: k, el } of internals.visibleRows()) {\n keyByEl.set(el, k);\n observer.observe(el);\n }\n };\n\n const unsubscribe = internals.onRender(resync);\n resync(); // observe the initial window\n\n return () => {\n observer.disconnect();\n unsubscribe();\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kerfjs",
3
- "version": "4.2.0-beta.5",
3
+ "version": "4.2.0-beta.6",
4
4
  "description": "Tiny reactive UI framework — fine-grained signals + DOM morphing + JSX. Apply the smallest possible cut to update your DOM.",
5
5
  "type": "module",
6
6
  "sideEffects": [