Primitives / State

Virtual: a windowed list

Virtual owns a virtualized list's scroll position, measured heights, and layout: Model { scrollTop, heights, scrolling, generation, estimatedHeight, overscan, gap, paddingStart, paddingEnd }, Messages Scrolled, Measured, Prune, Settled, args for the layout plus optional initialScrollTop and initialHeights restores (sanitized like live measurements) and a settleMs silence (default 150). Every scroll marks scrolling until the silence settles; suspend loaders and parallax on it.

Two Mounts feed it: Viewport reports the container's own scrolls and MeasureRow({ key }) a row's height. Four functions read it:

  • windowFor(model, keys, viewportHeight) answers which rows to render and the spacer height;
  • isAtEnd(model, keys, viewportHeight, threshold) is the infinite-scroll check (an empty list counts as ended), and distanceToEnd the pixels left, for a prefetch threshold;
  • offsetFor computes a scroll target the application actuates itself.

They share one table of row offsets, built once per keys array and heights record and then searched, so keep the keys array between renders (derive it where the list changes, not in the view) and a scroll costs O(log n) rather than a pass over every row. Both are compared by identity: never change a keys array in place. Prune drops heights for departed keys, since the bundle never sees key order. Poisoned positions and heights are ignored, never stored.

For a window-scrolled list, map the scroll entry into Scrolled; for follow-bottom, hold the end while isAtEnd and scroll on extend; to anchor a prepend, re-Scrolled by the totals' delta. Render each row keyed, with aria-rowcount and posinset from the window, so per-row placements keep identity.

stickyHeader(sections, start) answers which section header sticks; CSS position: sticky does the sticking. masonry(keys, heights, options) packs fixed-width columns shortest-first into { placements, totalHeight }: layout only, every placed item renders, so it fits hundreds of images rather than hundreds of thousands. The sums never name an axis: pass column widths as heights and a horizontal offset as the scroll position to window a carousel the same way. Windowed grids and per-index estimates stay out by design.

range

range(start, end, step?) counts half-open numbers; the page list above is range(1, (pageCount(model) ?? 0) + 1). A zero or non-finite step throws, naming it.

Failure

Clamping absorbs an out-of-range page or total; a negative total and a non-finite size are ignored or rejected at the boundary. A bad history capacity or range step throws where it is written.