Vanilla-Grid Technical Docs

This folder contains implementation-focused documentation for core Vanilla-Grid features.

Related component: Vanilla Grid Toolbar (<vn-grid-toolbar>) — a state-aware command surface bound to a grid. It consumes the additive grid API (getSearchTerm(), getSearchFields(), isBusy(), the vn-grid-total-row-count-changed event).

Documents

Listed in reading order — foundations first, then the features that build on them. See Suggested Reading Order for a shorter path through the set.

1. Core engine

2. Data access

3. Sorting, filtering, and column types

4. Row interaction

5. Column management

6. Presentation

7. Export

8. Persistence, delivery, and performance

9. Row grouping

Architecture

Vanilla-Grid splits responsibilities into feature modules loaded automatically from features/:

Feature file Concern
grid-events.js Frozen registry of every CustomEvent name dispatched by <vn-grid> (window.VanillaGridEvents) — loaded first so every other script and the host app can reference VanillaGridEvents.* instead of bare string literals
dom-scheduler.js FastDOM-style read/write batcher (window.VanillaGridScheduler) — coalesces layout reads and DOM writes into one rAF to prevent thrashing
templates.js Cached <template>-cloning helpers (window.VanillaGridTemplates) for the empty / error / initial-empty rows — escape-safe via textContent, no innerHTML interpolation
intl-cache.js Shared Intl.Collator cache (window.VanillaGridIntlCache) — unifies the locale-aware string-comparison cache used by vanilla-grid.js (_getCollator) and sorting.feature.js (_getSortCollator)
shimmer-threshold.js Shared row-count threshold parse/clamp rule (window.VanillaGridShimmerThreshold.resolve) for every "shimmer/worker above N rows" option — used by vanilla-grid.js (sortShimmerThreshold/sortWorkerThreshold), vanilla-grid-element.js (dataLoading.shimmerThreshold/workerThreshold), and static-data-manager.js (filter/search worker threshold)
main-thread-yield.js Shared main-thread yield primitive (window.VanillaGridYield.yieldToMainThread) for long chunked CPU passes — scheduler.yield() → a MessageChannel macrotask → setTimeout, deliberately not requestAnimationFrame (see dom-scheduler.js for the paint-aligned case). Used by static-data-manager.js (filter/search extraction) and sorting-worker.feature.js (per-column sort-key extraction), both with a plain-macrotask fallback when it is absent
filter-model.js Pure, shared filter-model utility (window.VanillaGridFilterModel) — type-aware operator catalog, arity, and normalizeFilterModel/validateOperatorForType/cloneFilterModel. Used by both data managers and the header-menu filter UI
features/datetime.feature.js Shared temporal parser/formatter (window.VanillaGridDateTimeFeature) — the single source of truth for date/datetime/time parsing. Owns the { ok, kind, numeric, raw } parse envelope (explicit null vs invalid), sourceFormat/inputPattern handling, bucketed compareTemporalValues, and the per-column Intl.DateTimeFormat cache. Used by vanilla-grid.js (_formatByType), sorting-comparator.feature.js (both sort paths), static-data-manager.js and odata-data-manager.js
column-key.js Shared column-key normalizer (window.VanillaGridColumnKey.normalizeColumnKey) — the single source of truth for trim/lowercase key comparison. Must load before columns.feature.js and persistence.feature.js, which call it directly with no fallback
column-access.js Shared per-row value helpers (window.VanillaGridColumnAccess) — compileColumnAccessor() (the compiled column._getValue, reused by every render, sort, group-boundary scan and export) and the tri-state coerceBoolean(). Extracted out of vanilla-grid.js so build.js can keep them off the heavy obfuscator profile via BUNDLE_FAST_PATH, which can only exempt whole files; vanilla-grid.js binds both at load time and keeps an inline fallback copy for partial bundles
aggregate-registry.js Named registry of group-aggregate reducers (window.VanillaGridAggregateRegistry) — the built-ins, host reducers added via VanillaGrid.registerAggregate(), and the column.type eligibility gate. Used by grouping.feature.js, header-menu.feature.js and vanilla-grid.js
storage/grid-storage-provider.js Storage provider contract + built-in null/local/remote implementations
persistence.feature.js Settings read/write for widths, order, hidden & frozen columns (sync + async aware, with background write queue)
selection.feature.js Row selection modes, key resolution, sticky select-all
sorting.feature.js Single/multi-sort state, comparator chaining, server-sort delegation (coordinator)
sorting-comparator.feature.js Locale-aware defaultCompareValues + cached Intl.Collator (extends VanillaGridSortingFeature.prototype)
sorting-worker.feature.js Off-thread sort: worker eligibility, worker source/pool, _sortViaWorker (extends VanillaGridSortingFeature.prototype)
grouping.feature.js Multi-level, client-side row grouping (window.VanillaGridGroupingFeature, standalone class like sorting.feature.js) — group-key encoding, the renderEntries projection, collapse state, and the grid-local availability gate
grouping-bar.feature.js The group bar — the grid-owned strip above the column header carrying one chip per applied group level, with its direction control and ungroup action (extends VanillaGrid.prototype; holds no state of its own, re-rendered from getGroupState())
columns.feature.js Column state coordinator — owns the shared column list/state; visibility, freezing, reordering and resizing are split into the four sub-features below
columns-visibility.feature.js Hide/show columns (extends VanillaGridColumnsFeature.prototype)
columns-freeze.feature.js Column freezing (extends VanillaGridColumnsFeature.prototype)
columns-reorder.feature.js Column reordering (extends VanillaGridColumnsFeature.prototype)
columns-resize.feature.js Column resizing (extends VanillaGridColumnsFeature.prototype)
stretch-to-fit.feature.js Smart, constraint-aware stretch-to-fit width distribution across resizable, non-selection, non-frozen columns (extends VanillaGridColumnsFeature.prototype)
header-menu.feature.js Header right-click / long-press context menu (extends VanillaGridColumnsFeature.prototype)
header-menu-submenu.feature.js The header menu's submenu flyout mechanism — hover/click/keyboard open, arrow traversal, edge flip, RTL (extends VanillaGridColumnsFeature.prototype; must load after header-menu.feature.js)
rendering.feature.js Header markup, virtual row pool, renderVisibleRows, empty/error/skeleton states, row-height CSS var
viewport.feature.js Viewport sizing math, ResizeObserver plumbing, viewport row snap, header/scrollbar alignment
interaction.feature.js Scroll/wheel handlers, custom scrollbar drag, hover & blur tracking (coordinator)
interaction-pointer.feature.js Click / body-checkbox / pointer-down handlers (extends VanillaGrid.prototype)
interaction-keyboard.feature.js Keyboard navigation + key-scroll handlers (extends VanillaGrid.prototype)
excel-export.feature.js Installs exportToExcel(options) on VanillaGrid.prototype (coordinator: rows, columns, styles, the chunked producer, progress, Cancel, errors)
excel-export-worker.feature.js The streaming .xlsx writer, the warm export Worker that runs it, the main-thread fallback, eligibility (shared VanillaGridExcelExportInternals namespace)
excel-export-delivery.feature.js iOS / Web-Share detection, file download, "Exporting…" overlay: progress, Cancel, failure dialog (shared VanillaGridExcelExportInternals namespace)
data-managers/data-manager.js DataManager base contract (fetchRows, buildRequestHeaders, transformRows, handleSort, …) that custom managers extend — see DataManager
data-managers/odata-data-manager.js Built-in ODataDataManager ($skip/$top/$orderby/$filter, separate $count)
data-managers/graphql-data-manager.js Built-in GraphQLDataManager (offset or cursor pagination, inline or separate total)
data-managers/static-data-manager.js Built-in StaticDataManager — client-side sort/filter/search over resident rows, with a filter/search Worker for large datasets

vanilla-grid.js auto-loads these scripts relative to its own path, so consumers only need a single <script> tag.

Loading strategy

All dependency <script> tags are injected in one synchronous pass with async = false: the HTML spec guarantees in-order execution while the downloads proceed fully in parallel, so boot latency is ~1×RTT + max(download) instead of the ~N×RTT of one-at-a-time injection. The bootstrap exposes window.VanillaGridReady, a Promise that resolves once every feature has loaded (and rejects with the failing URL if any script errors). Hosts must await window.VanillaGridReady (directly, or via the <vn-grid> element's ready() method which delegates to it) before instantiating a grid. Setting window.VanillaGridSequentialLoad = true before vanilla-grid.js runs restores the previous strictly-sequential injection as a temporary escape hatch. For synchronous bootstraps use the pre-built vanilla-grid.bundle.js artifact (see below), which inlines every feature.

Historical note: a document.write-based synchronous path used to handle the parser-active case but was removed — it triggered warnings in every modern browser, blocked the parser on slow networks, and was incompatible with HTTP/2 server push and ad-blockers.

Auto-loading can be disabled by either of:

TypeScript declarations

Hand-authored declaration file src/vanilla-grid/vanilla-grid.d.ts ships alongside the JavaScript sources. It covers:

Usage (TypeScript project):

/// <reference path="path/to/vanilla-grid/vanilla-grid.d.ts" />
// or add to tsconfig.json:
// "typeRoots": ["node_modules/@types", "path/to/vanilla-grid"]

const grid = document.querySelector('vn-grid') as VanillaGridElement;
await grid.ready();
grid.addEventListener('vn-grid-loaded', (e: CustomEvent<VanillaGridLoadedDetail>) => {
    console.log(e.detail.rows);
});

Pre-bundled artifact

The npm run build step produces dist/vanilla-components/latest/vanilla-grid/vanilla-grid.bundle.js, a single file that concatenates the storage provider, every feature, all data managers, vanilla-grid.js, and vanilla-grid-element.js in the correct order. The bundle declares window.VanillaGridSkipAutoload = true at the top, so the runtime auto-loader is a no-op and only one <script> tag is required:

<script src="vanilla-grid/vanilla-grid.bundle.js"></script>

Module load-order enforcement (build-time)

Every non-minified JS file in src/vanilla-grid/ carries Depends on: and Used by: header comments that make its evaluation-time requirements explicit:

// VanillaGridPersistenceFeature
// Depends on:  storage/grid-storage-provider.js (VanillaGridStorageProvider)
// Used by:     vanilla-grid.js (instantiates as this._persistence)

build.js reads these annotations at the start of every build and runs a topological sort (checkModuleLoadOrder()). The build fails immediately if:

This makes load-order constraints self-documenting and guarantees that BUNDLE_PARTS (and the auto-loader array in vanilla-grid.js) is always consistent with the declared dependency graph.

Constructor Architecture

VanillaGrid's constructor delegates to a fixed sequence of private _init*() helpers so each concern is independently readable and testable:

Helper Concern Key dependency
_initLogger(options) Normalises options.logger into a bound logger object
_initStrictMode(options) Resolves options.strict flag
_initStorage(options) Resolves storageMode + storageProvider _initLogger (logger passed to provider resolver)
_initFormatting(options) Stores DOM entry-point refs (header, body, viewport …) and all formatting callbacks (locale, messages, formatInteger …)
_initLayout(options) Resolves row/header height, buffer row count, scrollbar, stretch-to-fit, scroll-speed multiplier
_initSorting(options) Resolves sort options and column-reorder behaviour
_initSelection(options) Resolves selection mode, key resolution, and checkbox options
_initPersistence(options) Resolves persistence keys and write-debounce/flush policy; also resolves infinite-scroll options
_initFeatures(options) Initialises all internal state variables; resolves derived DOM refs (bodyTable, container …); applies CSS custom properties; instantiates _persistence, _selection, _sorting, _columns; wires delegation bindings; resolves storage keys; kicks off the async persisted-state read All helpers above
_initDomReferences() Creates bound handler references (boundScroll, boundWheel …) _initFeatures (prototype methods installed by feature files must be present)
_initEventListeners() Attaches all addEventListener calls on viewport, document, window _initDomReferences (bound refs must exist)
_initResizeObserver() Creates the ResizeObserver, calls _initCustomScrollbar(), and performs initial viewport-geometry measurement _initEventListeners (final setup step)

The call order in the constructor is load-order-sensitive. The two non-obvious rules are:

  1. Logger before storage_resolveStorageProvider receives this.logger as a parameter.
  2. All option resolution before _initFeatures — the four feature constructors consume every field set by the preceding helpers via getter-function closures.

Host Integration Points (injectable adapters)

VanillaGrid's constructor accepts a few host-replaceable adapters so the same code path serves browsers, SSR, sandboxed iframes, and tests:

Removed (May 2026): the legacy options.storage Web-Storage-like adapter ({ getItem, setItem, removeItem, hasItem? }) is no longer accepted. Hosts that previously passed options.storage should switch to options.storageProvider (or storageMode: 'local' | 'remote').

Static, host-wide configuration (call once at startup, before constructing grids):

See Localization Implementation, Themes Implementation, and Local Storage Settings Implementation for full details.

Public API — Return-type contract

All public methods on VanillaGrid and <vn-grid> (VanillaGridElement) follow these invariants:

Shape Rule
"Not configured" Return undefined — the value was never set or is not applicable. Example: getTotalRowCount() returns undefined when the total row count is unknown.
"Explicitly cleared" Return null — the host passed null / called a clear operation.
Collection getters Always return a freshly-allocated Array — never expose an internal Set or Map reference. Examples: getSelectedKeys(), getFrozenColumns(), getHiddenColumns().
Boolean getters Always return a strict true or false — never undefined or a truthy/falsy non-boolean. Examples: isFrozenColumn(), canHideColumn(), isRowSelected(), hasActiveSort().
Setters Return void — no fluent/chainable API unless explicitly documented. Examples: setDataManager(), setAutoReloadOnConfigChange(), setStorageProvider().

isRowSelected(row) on <vn-grid>

VanillaGridElement now exposes isRowSelected(row) as a first-class public method, mirroring the same method on the internal VanillaGrid instance. Returns false when the grid has not been initialized yet (safe to call before initializeGrid()).

Mobile / touch integration

Touch-platform hardening distinct from scroll-performance tuning (see Row Virtualization and Custom Scrollbars for the momentum-scrolling side). What the component guarantees vs. what the host page owes:

Guaranteed by the component:

Owed by the host page: the component cannot suppress document-level rubber-band bounce or pull-to-refresh from inside — that requires styling html/body, which a well-behaved web component must never do on its own (it would break embedders that want PTR elsewhere on the page). A full-viewport grid app needs one line of host CSS:

html, body {
    overscroll-behavior-y: none;   /* stops PTR on Chrome/Edge Android and
                                       chaining/PTR on iOS Safari 16+ */
}

plus a fixed app shell — height: 100dvh on body (the modern viewport unit; see people-cities-js through wikipedia-pages-vue) is the recommended pattern. overscroll-behavior is silently ignored on iOS Safari < 16, so hosts that must support it need the classic fallback instead: position: fixed; inset: 0; on the app shell, which prevents any document panning at all regardless of engine support.

iOS-version caveats: unprefixed user-select: none is honored from iOS Safari 16.4 (March 2023) — every earlier version needs the -webkit-user-select twin the component already ships. overscroll-behavior is supported from iOS Safari 16 — earlier versions ignore both the component's viewport rule and the host's html/body rule; the position: fixed shell is the only way to fully cover them.

Suggested Reading Order

The Documents list above is already ordered: read the nine groups in sequence and each one only depends on the groups before it. Group 1 explains what the element is and how rows reach the screen; group 2 explains where rows come from; groups 3–5 are the features that operate on those rows and columns; groups 6–8 layer presentation, export, and persistence on top; group 9 (row grouping) depends on group 1's render pipeline and group 3's sort machinery.

If you only need enough context to work on one feature, read this short path first:

  1. Web Component Implementation — how <vn-grid> wraps and exposes the core grid.
  2. Row Virtualization and Custom Scrollbars — the rendering and scroll engine.
  3. DataManager Implementation — the data access contract and lifecycle wiring.
  4. Row Key Management — stable row identity, which selection and re-render both depend on.
  5. Themes Implementation — the CSS variable system every feature's UI is styled through.

Then jump straight to the document for the feature you are changing.