Skip to content

API Overview

SymbolPurposeExecution modeCommon gotcha
createVirtualizer()Core 1D virtualizerSynconChange fires on construction — wire DOM first
createDomVirtualList()DOM adapter for dropdown/listbox UIsSyncVirtualizer is created lazily on first setItems()
createVirtualScroller()Self-contained scroller (creates its own DOM)Syncdispose() removes the generated scroll element
createGroupedVirtualizer()Sectioned list with sticky headersSyncupdate() preserves measured sizes — call invalidate() only on font/layout changes
createGridVirtualizer()Two-dimensional grid virtualizerSynconRangeChange fires even when onChange is omitted
createReactiveVirtualizer()Virtualizer with reactive signal outputSynconChange must not be passed — it is wired internally
createReactiveGroupedVirtualizer()Grouped virtualizer with reactive signal outputSynconChange must not be passed — it is wired internally

Package Entry Point

Everything exports from a single entry:

ts
import {
  createVirtualizer,
  createDomVirtualList,
  createVirtualScroller,
  createGroupedVirtualizer,
  createGridVirtualizer,
  createReactiveVirtualizer,
  type Virtualizer,
  type VirtualItem,
  type VirtualizerState,
} from '@vielzeug/scroll';

createVirtualizer(target, options)

ts
createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer;

Creates and immediately attaches a virtualizer to the provided scroll container. onChange fires synchronously on construction with the initial visible window. Call dispose() on unmount.

ts
import { createVirtualizer } from '@vielzeug/scroll';

const virt = createVirtualizer(scrollEl, {
  count: rows.length,
  estimateSize: 36,
  gap: 8,
  onChange: ({ items, totalSize }) => {
    listEl.style.height = `${totalSize}px`;
    listEl.innerHTML = '';

    for (const item of items) {
      const row = document.createElement('div');
      row.style.cssText = `position:absolute;top:${item.start}px;left:0;right:0;height:${item.size}px;`;
      row.textContent = rows[item.index]?.label ?? '';
      listEl.appendChild(row);
    }
  },
});

Parameters

ParameterTypeDescription
targetHTMLElement | WindowScroll container to observe
optionsVirtualizerOptionsInitial options

VirtualizerOptions

OptionTypeDefaultDescription
countnumberrequiredTotal item count
estimateSizenumber | (index: number) => number36Fixed size or per-index estimate in pixels
gapnumber0Gap between adjacent items in pixels
getItemKey(index: number) => string | numberindex => indexStable key for the measurement cache
horizontalbooleanfalseVirtualize along the X axis instead of Y
initialOffsetnumberInitial scroll position; applied once on construction
measurementCacheMeasurementCacheShared external cache for scroll restoration or SSR pre-measurement
onChange(state: VirtualizerState) => voidCalled when the visible window changes. Fixed at construction.
onScrollEnd(offset: number) => voidCalled when scrolling settles (native scrollend or debounce). Fixed at construction.
onScrollingChange(isScrolling: boolean) => voidCalled when scroll activity starts or stops. Fixed at construction.
overscannumber | { start?: number; end?: number }3Extra items outside the viewport; number = symmetric on both sides
scrollEndDelaynumber150Debounce delay (ms) used to detect scroll end when native scrollend is unavailable
sticky(index: number) => booleanMark an item as a sticky header (pinned at viewport top)

onChange, onScrollEnd, onScrollingChange, and scrollEndDelay are fixed at construction — they cannot be changed via update().

Returns: Virtualizer

VirtualizerState

ts
interface VirtualizerState {
  readonly items: VirtualItem[];
  readonly stickyItems: VirtualItem[];
  readonly totalSize: number;
}

items contains the currently visible items plus overscan. stickyItems contains items marked sticky that are pinned at the viewport top.

Virtualizer — read-only properties

PropertyTypeDescription
countnumberCurrent item count
isScrollingbooleantrue while the user is scrolling; false once settled
itemsVirtualItem[]Currently rendered items. Always populated.
scrollOffsetnumberCurrent scroll position in pixels
stickyItemsVirtualItem[]Items pinned at the viewport top (requires sticky option)
totalSizenumberTotal height (or width in horizontal mode)

Virtualizer — methods

MethodSignatureDescription
update(next: VirtualizerUpdateOptions) => voidAtomically update live options
measure(index: number, size: number) => voidRecord one measured size; rebuild batched in microtask
measureBatch(entries: Array<{ index: number; size: number }>) => voidRecord many sizes; single rebuild
measureEl(index: number, el: HTMLElement) => () => voidAttach ResizeObserver to auto-measure. Returns a disconnect function
refresh() => voidRebuild offset table and re-emit; preserves cached measurements
prepend(additionalCount: number) => voidAdd items at the top; adjusts scroll offset to keep viewport stable
scrollToIndex(index: number, options?: ScrollToIndexOptions) => voidScroll to an item; out-of-range indices are clamped
scrollToOffset(offset: number, options?: { behavior?: ScrollBehavior }) => voidScroll to a raw pixel offset
scrollToTop(options?: { behavior?: ScrollBehavior }) => voidScroll to offset 0
scrollToBottom(options?: { behavior?: ScrollBehavior }) => voidScroll to the end of the list
invalidate() => voidClear all measurements and rebuild from estimates
dispose() => voidDetach listeners; idempotent
disposedbooleantrue after dispose() is called
[Symbol.dispose]() => voidDelegates to dispose() — enables using declarations

update(next)

Atomically updates one or more live options. Accepts: count, estimateSize, gap, getItemKey, measurementCache, overscan, sticky. Creation-time options (horizontal, initialOffset, onChange, onScrollEnd, onScrollingChange, scrollEndDelay) cannot be changed after construction.

When estimateSize changes, the measurement cache is cleared and a scroll anchor is applied to keep the current viewport position visually stable.

ts
virt.update({ count: rows.length });
virt.update({ estimateSize: 40 });
virt.update({ gap: 8, overscan: { start: 5, end: 5 } });

measure(index, size) and measureBatch(entries)

Report exact sizes for variable-height rows. Calls within one microtask tick coalesce into a single offset rebuild. measure() is a no-op when the new size equals the current effective size.

ts
virt.measure(item.index, el.offsetHeight);

// Prefer measureBatch for ResizeObserver batches
virt.measureBatch(entries.map((e) => ({ index: Number(e.target.dataset.index), size: e.contentRect.height })));

measureEl(index, el)

Attaches a ResizeObserver to auto-measure el on resize. Returns a disconnect function.

ts
const disconnect = virt.measureEl(item.index, rowEl);
// later: disconnect();

refresh()

Rebuilds the full offset table and re-emits. Preserves cached measurements. Use after reordering, filtering, or any data change where sizes may have changed.

prepend(additionalCount)

Adds additionalCount items at the front while adjusting scroll offset so the viewport stays visually stable. Use for "load previous page" patterns.

scrollToIndex(index, options?)

Scroll to an item. Out-of-range indices are clamped silently.

alignBehavior
'start'Item top at viewport top
'end'Item bottom at viewport bottom
'center'Item centered in the viewport
'auto' (default)No scroll if already fully visible; otherwise minimum scroll
ts
virt.scrollToIndex(0, { align: 'start' });
virt.scrollToIndex(500, { align: 'center', behavior: 'smooth' });
virt.scrollToIndex(focusedIndex, { align: 'auto' });

scrollToOffset(offset, options?)

ts
virt.scrollToOffset(Number(sessionStorage.getItem('scrollOffset') ?? '0'));

invalidate()

Clears all measured sizes and rebuilds from estimator values.

ts
document.fonts.ready.then(() => virt.invalidate());

dispose() and [Symbol.dispose]()

dispose() detaches observers and event listeners. It is idempotent.

ts
{
  using virt = createVirtualizer(scrollEl, { count: rows.length, onChange: render });
} // → dispose() called automatically

createDomVirtualList(options)

ts
createDomVirtualList<T>(options: DomVirtualListOptions<T>): DomVirtualListController<T>;

DOM-focused adapter. Manages virtualizer lifecycle, applies list-height styles automatically, and provides a node pool via recycle. The virtualizer is created lazily on the first non-empty setItems() call and destroyed automatically when setItems([]) is called.

ts
import { createDomVirtualList } from '@vielzeug/scroll';

const ctrl = createDomVirtualList<Row>({
  estimateSize: 36,
  getItemKey: (_, row) => row.id,
  listElement: listEl,
  scrollElement: scrollEl,
  render: ({ items, listEl, recycle }) => {
    for (const item of items) {
      const el = recycle(item.data.id, () => document.createElement('div'));
      el.style.cssText = `position:absolute;top:0;left:0;right:0;transform:translateY(${item.start}px);height:${item.size}px;`;
      el.textContent = item.data.label;
      listEl.appendChild(el);
    }
  },
});

ctrl.setItems(rows);
ctrl.scrollToIndex(focusedIndex, { align: 'auto' });
ctrl.dispose();

DomVirtualListOptions<T>

OptionTypeDefaultDescription
scrollElementHTMLElement | WindowrequiredScroll container to observe
listElementHTMLElementrequiredElement that receives height and item children
render(args: DomVirtualListRenderArgs<T>) => voidrequiredCalled on every visible-window change
estimateSizenumber | (index, item) => number36Fixed or per-item size estimate
gapnumber0Gap between items in pixels
getItemKey(index, item) => string | numberStable key; keeps measurements across setItems() calls
horizontalbooleanfalseVirtualize along X axis
measurementCacheMeasurementCacheExternal measurement cache
overscannumber | { start?: number; end?: number }3Extra items outside the viewport; number = symmetric
sticky(index: number, item: T) => booleanMark items as sticky headers
clear(listEl: HTMLElement) => voidCustom teardown for listEl; defaults to textContent = ''

Without getItemKey, each setItems() call drops cached measurements.

DomVirtualListRenderArgs<T>

ts
type DomVirtualListRenderArgs<T> = {
  items: Array<VirtualRenderItem<T>>; // visible items — each has .data + layout fields
  listEl: HTMLElement;
  recycle: RecycleFn; // node pool — returns existing node or calls create()
  stickyItems: Array<VirtualRenderItem<T>>; // sticky items (requires sticky option)
  totalSize: number;
};

VirtualRenderItem<T> is VirtualItem (start, end, size, index) enriched with data: T.

recycle(key, create) returns a live node for key if one exists in the pool, or calls create() for a new one. Nodes not reused in a render cycle are removed automatically. listEl.style.height is set before render is called — you do not need to set it yourself.

DomVirtualListController<T>

Extends Virtualizer (minus prepend and update) with setItems(). All virtualizer methods and live getters are available directly.

MemberDescription
setItems(items)Set the current item array. Spawns virtualizer on first non-empty call; destroys it on []
countCurrent item count (live getter)
itemsCurrently rendered virtual items (live getter)
totalSizeTotal list size in pixels (live getter)
scrollOffsetCurrent scroll position (live getter)
stickyItemsSticky items pinned at viewport top (live getter)
measureDelegate to underlying virtualizer; no-op before first setItems
measureBatchBatch measurement delegate
measureElAttach auto-measuring ResizeObserver
refreshRebuild offset table and re-emit
invalidateClear measurements and rebuild from estimates
scrollToIndexScroll to an item
scrollToOffsetScroll to a pixel offset
disposeTeardown; idempotent
disposedtrue after dispose() is called (live getter)
[Symbol.dispose]Delegates to dispose()

createVirtualScroller(container, options)

ts
createVirtualScroller<T>(container: HTMLElement, options: VirtualScrollerOptions<T>): DomVirtualListController<T>;

Creates a scroll container div and inner list div, appends them to container, and returns a fully wired DomVirtualListController. Useful when the scroll DOM doesn't already exist.

ts
const list = createVirtualScroller<Row>(document.getElementById('root')!, {
  estimateSize: 36,
  render: ({ items, listEl, recycle }) => {
    for (const item of items) {
      const el = recycle(item.data.id, () => document.createElement('div'));
      el.textContent = item.data.label;
      el.style.cssText = `position:absolute;top:0;left:0;right:0;transform:translateY(${item.start}px);`;
      listEl.appendChild(el);
    }
  },
});

list.setItems(rows);
list.dispose(); // also removes the generated scroll container

VirtualScrollerOptions<T> is DomVirtualListOptions<T> minus listElement/scrollElement, plus:

OptionTypeDescription
containerClassstringCSS class applied to the generated scroll element

dispose() removes the generated scroll container from the DOM.

createGroupedVirtualizer(target, options)

ts
createGroupedVirtualizer<T>(target: ScrollTarget, options: GroupVirtualizerOptions<T>): GroupVirtualizer<T>;

Virtualizes a sectioned list. Headers are automatically sticky (pinned at viewport top while the section is in view).

ts
import { createGroupedVirtualizer } from '@vielzeug/scroll';

type Contact = { id: number; name: string };

const virt = createGroupedVirtualizer<Contact>(scrollEl, {
  estimateHeaderSize: 32,
  estimateItemSize: 48,
  sections: [
    { label: 'A', items: [{ id: 1, name: 'Alice' }] },
    { label: 'B', items: [{ id: 2, name: 'Bob' }] },
  ],
  onChange: ({ headers, items, stickyHeader, totalSize }) => {
    listEl.style.height = `${totalSize}px`;
    listEl.innerHTML = '';

    if (stickyHeader) {
      const el = document.createElement('div');
      el.className = 'sticky-header';
      el.textContent = stickyHeader.label;
      listEl.appendChild(el);
    }

    for (const header of headers) {
      const el = document.createElement('div');
      el.style.cssText = `position:absolute;top:${header.start}px;height:${header.size}px;`;
      el.textContent = header.label;
      listEl.appendChild(el);
    }

    for (const item of items) {
      const el = document.createElement('div');
      el.style.cssText = `position:absolute;top:${item.start}px;height:${item.size}px;`;
      el.textContent = item.data.name;
      listEl.appendChild(el);
    }
  },
});

virt.scrollToSection(1, { align: 'start' });
virt.update(nextSections);
virt.dispose();

GroupVirtualizerOptions<T>

OptionTypeDefaultDescription
sectionsArray<GroupSection<T>>requiredInitial sections
onChange(state: GroupVirtualizerState<T>) => voidCalled when the visible window changes. Fixed at construction.
onScrollEnd(offset: number) => voidCalled when scrolling settles. Fixed at construction.
onScrollingChange(isScrolling: boolean) => voidCalled when scroll activity starts or stops. Fixed at construction.
estimateHeaderSizenumber | (section, sectionIndex) => number36Header height estimate
estimateItemSizenumber | (item, itemIndex, sectionIndex) => number36Item height estimate
getItemKey(item: T, itemIndex: number, sectionIndex: number) => VirtualKeyStable key for measurement cache
horizontalbooleanfalseVirtualize along X axis
measurementCacheMeasurementCacheExternal measurement cache
overscannumber | { start?: number; end?: number }3Overscan on each side (number = symmetric)
scrollEndDelaynumber150Debounce delay (ms) for scroll-end detection

GroupSection<T>

ts
interface GroupSection<T> {
  items: T[];
  label: string;
}

GroupVirtualizerState<T>

ts
interface GroupVirtualizerState<T> {
  readonly headers: GroupVirtualHeader[];
  readonly items: Array<GroupVirtualItem<T>>;
  readonly stickyHeader: GroupVirtualHeader | null;
  readonly totalSize: number;
}

stickyHeader is the header of the section currently at or above the viewport top, or null when at the very top. Render it as a floating overlay above the list.

GroupVirtualItem<T> and GroupVirtualHeader

ts
interface GroupVirtualItem<T> extends VirtualItem {
  data: T;
  itemIndex: number; // index within the section
  sectionIndex: number;
}

interface GroupVirtualHeader extends VirtualItem {
  label: string;
  sectionIndex: number;
}

GroupVirtualizer<T> — methods

GroupVirtualizer<T> is an independent interface that exposes all core virtualizer methods directly, plus grouped-specific navigation.

Method / PropertyDescription
update(sections)Replace all sections; rebuilds flat index, preserves cached measurements
scrollToSection(i, options?)Scroll to section header at index i. Out-of-range is a no-op
scrollToItem(s, i, options?)Scroll to item i in section s. Out-of-range is a no-op
scrollToIndex(i, options?)Scroll to flat index i (from underlying virtualizer)
scrollToOffset(offset, options?)Scroll to a raw pixel offset
scrollToTop(options?)Scroll to offset 0
scrollToBottom(options?)Scroll to the end of the list
measure(index, size)Record a measurement for a flat index
measureBatch(entries)Batch-record measurements for flat indices
measureEl(index, el)Attach auto-measuring ResizeObserver. Returns disconnect function
invalidate()Clear all measurements and rebuild
refresh()Rebuild offset table without clearing measurements
isScrollingtrue while the user is scrolling; false once scroll settles
scrollOffsetCurrent scroll position in pixels (live getter)
dispose()Teardown; idempotent
disposedtrue after dispose() is called
[Symbol.dispose]()Delegates to dispose()

All scroll methods accept an optional ScrollToIndexOptions object ({ align?, behavior?, onComplete? }).

createGridVirtualizer(target, options)

ts
createGridVirtualizer(target: ScrollTarget, options: GridVirtualizerOptions): GridVirtualizer;

Two-dimensional virtualizer. Fires onChange with visible row and column descriptors. Callers form the cross-product rows × cols to render visible cells.

ts
import { createGridVirtualizer } from '@vielzeug/scroll';

const grid = createGridVirtualizer(scrollEl, {
  rowCount: 10_000,
  colCount: 50,
  estimateRowSize: 36,
  estimateColSize: 120,
  onChange: ({ rows, cols, totalHeight, totalWidth }) => {
    containerEl.style.cssText = `position:relative;height:${totalHeight}px;width:${totalWidth}px;`;
    containerEl.innerHTML = '';

    for (const row of rows) {
      for (const col of cols) {
        const cell = document.createElement('div');
        cell.style.cssText = `position:absolute;top:${row.start}px;left:${col.start}px;height:${row.size}px;width:${col.size}px;`;
        cell.textContent = `${row.index},${col.index}`;
        containerEl.appendChild(cell);
      }
    }
  },
});

grid.scrollToCell(500, 10, { rowAlign: 'center', colAlign: 'start' });
grid.dispose();

GridVirtualizerOptions

OptionTypeDefaultDescription
rowCountnumberrequiredTotal row count
colCountnumberrequiredTotal column count
estimateRowSizenumber | (row) => number36Row height estimate
estimateColSizenumber | (col) => number36Column width estimate
rowGapnumber0Gap between rows
colGapnumber0Gap between columns
overscanY{ start?: number; end?: number }{ start: 3, end: 3 }Row overscan
overscanX{ start?: number; end?: number }{ start: 3, end: 3 }Column overscan
initialScrollTopnumberInitial vertical scroll position
initialScrollLeftnumberInitial horizontal scroll position
onChange(state: GridVirtualizerState) => voidCalled when the visible window changes
onRangeChange(range: GridRangeChangeEvent) => voidZero-allocation range callback
rowMeasurementCacheMap<number, number>External row measurement cache
colMeasurementCacheMap<number, number>External column measurement cache

GridVirtualizerState

ts
interface GridVirtualizerState {
  readonly cols: VirtualItem[];
  readonly rows: VirtualItem[];
  readonly totalHeight: number;
  readonly totalWidth: number;
}

GridVirtualizer — properties and methods

Read-only properties: rows, cols, scrollTop, scrollLeft, totalHeight, totalWidth

MethodDescription
update(next)Atomically update row/col counts, estimates, gaps, and overscan
measureRow(row, size)Record a row height
measureColumn(col, size)Record a column width
measureBatch(rows, cols)Measure rows and columns in a single coordinated rebuild pass
measureRowEl(row, el)Auto-measure row height via ResizeObserver. Returns disconnect fn
measureColEl(col, el)Auto-measure column width via ResizeObserver. Returns disconnect fn
refresh()Rebuild offset tables from current measurements
invalidate()Clear all measurements and rebuild from estimates
scrollToCell(row, col, options?)Scroll to bring a cell into view; no-op when rowCount === 0 or colCount === 0
scrollToRow(row, options?)Scroll to bring a row into view; rowAlign controls alignment
scrollToColumn(col, options?)Scroll to bring a column into view; colAlign controls alignment
prependRows(n)Add n rows at the top; adjusts scroll offset to keep viewport stable
dispose()Teardown; idempotent
[Symbol.dispose]()Delegates to dispose()

ScrollToCellOptions

ts
interface ScrollToCellOptions {
  behavior?: ScrollBehavior;
  colAlign?: 'auto' | 'center' | 'end' | 'start';
  rowAlign?: 'auto' | 'center' | 'end' | 'start';
}

createReactiveGroupedVirtualizer(target, options)

ts
createReactiveGroupedVirtualizer<T>(
  target: ScrollTarget,
  options: Omit<GroupVirtualizerOptions<T>, 'onChange'>,
): ReactiveGroupVirtualizer<T>;

Wraps createGroupedVirtualizer and exposes state as a Signal<GroupVirtualizerState<T>> from @vielzeug/ripple. All GroupVirtualizer<T> methods and live getters are available. onChange must not be provided — it is wired internally.

ts
import { createReactiveGroupedVirtualizer } from '@vielzeug/scroll';
import { effect } from '@vielzeug/ripple';

const virt = createReactiveGroupedVirtualizer<Contact>(scrollEl, {
  estimateHeaderSize: 32,
  estimateItemSize: 48,
  sections,
});

effect(() => {
  const { headers, items, stickyHeader, totalSize } = virt.state.value;
  render(headers, items, stickyHeader, totalSize);
});

virt.update(nextSections);
virt.dispose();

ReactiveGroupVirtualizer<T>

ts
interface ReactiveGroupVirtualizer<T> extends GroupVirtualizer<T> {
  readonly state: Signal<GroupVirtualizerState<T>>;
}

The state signal is updated synchronously on every render cycle. All live getters remain current via Proxy.

createReactiveVirtualizer(target, options)

ts
createReactiveVirtualizer(
  target: ScrollTarget,
  options: Omit<VirtualizerOptions, 'onChange'>,
): ReactiveVirtualizer;

Wraps createVirtualizer and exposes state as a Signal<VirtualizerState> from @vielzeug/ripple. All Virtualizer methods and live getters are available on the returned object. onChange must not be provided — it is wired internally.

ts
import { createReactiveVirtualizer } from '@vielzeug/scroll';
import { effect } from '@vielzeug/ripple';

const virt = createReactiveVirtualizer(scrollEl, {
  count: 1000,
  estimateSize: 40,
});

effect(() => {
  const { items, totalSize } = virt.state.value;
  listEl.style.height = `${totalSize}px`;
  listEl.innerHTML = '';
  for (const item of items) {
    const el = document.createElement('div');
    el.style.cssText = `position:absolute;top:${item.start}px;height:${item.size}px;`;
    listEl.appendChild(el);
  }
});

virt.update({ count: 2000 });
virt.dispose();

ReactiveVirtualizer

ts
interface ReactiveVirtualizer extends Virtualizer {
  readonly state: Signal<VirtualizerState>;
}

The state signal is updated synchronously whenever the visible window changes. All live getters (count, items, totalSize, scrollOffset, stickyItems) remain current.

Types

VirtualItem

ts
interface VirtualItem {
  end: number;
  index: number;
  size: number;
  start: number;
}

VirtualizerState

ts
interface VirtualizerState {
  readonly items: VirtualItem[];
  readonly stickyItems: VirtualItem[];
  readonly totalSize: number;
}

ScrollToIndexOptions

ts
interface ScrollToIndexOptions {
  align?: 'auto' | 'center' | 'end' | 'start';
  behavior?: ScrollBehavior;
  /** Called when the scroll animation completes (instant scrolls: next microtask). */
  onComplete?: () => void;
}

Overscan

ts
type Overscan = number | { end?: number; start?: number };

Passing a number is shorthand for symmetric overscan on both sides.

VirtualKey

ts
type VirtualKey = number | string;

VirtualRenderItem<T>

ts
type VirtualRenderItem<T> = VirtualItem & { readonly data: T };

ScrollTarget

ts
type ScrollTarget = HTMLElement | Window;

MeasurementCache

ts
type MeasurementCache = Map<VirtualKey, number>;

Use createMeasurementCache() to create an empty cache:

ts
import { createMeasurementCache } from '@vielzeug/scroll';

const cache = createMeasurementCache();
const virt1 = createVirtualizer(el1, { count: 100, measurementCache: cache });
const virt2 = createVirtualizer(el2, { count: 100, measurementCache: cache });

RecycleFn

ts
type RecycleFn = (key: VirtualKey, create: () => HTMLElement) => HTMLElement;

VirtualizerUpdateOptions

ts
interface VirtualizerUpdateOptions {
  count?: number;
  estimateSize?: number | ((index: number) => number);
  gap?: number;
  getItemKey?: (index: number) => VirtualKey;
  /** Replace the active measurement cache. Existing entries are used immediately on the next rebuild. */
  measurementCache?: MeasurementCache;
  overscan?: Overscan;
  sticky?: (index: number) => boolean;
}

VirtualScrollerOptions<T>

DomVirtualListOptions<T> minus listElement and scrollElement, plus:

ts
type VirtualScrollerOptions<T> = Omit<DomVirtualListOptions<T>, 'listElement' | 'scrollElement'> & {
  /** CSS class applied to the generated scroll container element. */
  containerClass?: string;
};

GridVirtualizerUpdateOptions

ts
interface GridVirtualizerUpdateOptions {
  colCount?: number;
  colGap?: number;
  estimateColSize?: number | ((col: number) => number);
  estimateRowSize?: number | ((row: number) => number);
  overscanX?: { end?: number; start?: number };
  overscanY?: { end?: number; start?: number };
  rowCount?: number;
  rowGap?: number;
}

GridRangeChangeEvent

Fired by onRangeChange on createGridVirtualizer. Zero-allocation alternative to onChange — no rows/cols arrays are allocated.

ts
interface GridRangeChangeEvent {
  firstCol: number;
  firstRow: number;
  lastCol: number;
  lastRow: number;
}

Constants

ts
const DEFAULT_ESTIMATE_SIZE = 36; // default estimateSize
const DEFAULT_OVERSCAN = 3; // default overscan on each side