Events
All events emitted by the Grid instance - render lifecycle, pagination, viewport changes, highlights, and performance metrics.
The Grid class is an event emitter. It forwards layout events, including the cell pointer events, and adds its own highlight events. All events are emitted asynchronously (via setTimeout).
Subscribing to events
// Subscribe - returns an unsubscribe function
const unsub = grid.on("renderComplete", (payload) => {
console.log(`Rendered rows ${payload.y0}-${payload.y1}`);
});
// Unsubscribe
unsub();
// Or unsubscribe by passing the handler
grid.off("renderComplete", handler);
// Remove all listeners for an event
grid.off("renderComplete");Grid events
renderComplete
Fired after every render cycle completes. The payload contains the viewport bounds that were rendered.
grid.on("renderComplete", (payload) => {
// payload: { x0: number, y0: number, x1: number, y1: number }
statusBar.textContent = `Showing rows ${payload.y0}-${payload.y1}`;
});Use this for status bars, analytics, or triggering post-render side effects.
viewDataEmpty
Fired when the viewport extends beyond the loaded data range - the user scrolled past what the viewmodel contains. This is the pagination trigger.
grid.on("viewDataEmpty", (payload) => {
// payload: { startRow: number, endRow: number }
fetchPage(payload.startRow, payload.endRow);
});The layout detects this by comparing the logical scroll position against the viewmodel's offsetTop and loaded row count. The event is debounced by GridConfig.dataFetchDebounceMs (default: 150ms) to prevent flooding the data source during fast scrolling.
viewModelDataChanged
Forwarded from the viewmodel's viewportDataChange callback. Fires when the viewport bounds change during getViewportData() calls.
grid.on("viewModelDataChanged", (payload) => {
// payload: { x0: number, y0: number, x1: number, y1: number }
});This fires on the viewmodel side (with a 50ms debounce) whenever getViewportData is called with different bounds. Use it for lazy operations that depend on which data is currently visible.
highlightAdded
Fired when a highlight is added via any of the highlight methods (highlightCellByDataIndex, highlightRowByDataIndex, highlightColumnByDataIndex, highlightRangeByDataIndex).
grid.on("highlightAdded", (payload) => {
// payload: { hash: string, fromRow: number, fromCol: number, toRow: number, toCol: number }
console.log(`Matching ${payload.hash}: rows ${payload.fromRow}-${payload.toRow}`);
});The hash is a stable identifier for the highlight, useful for tracking which highlight was added or removed.
highlightRemoved
Fired when a highlight is removed - either explicitly via the unsubscribe function returned by a highlight method, or implicitly when a new highlight of a different type replaces existing ones.
grid.on("highlightRemoved", (payload) => {
// payload: { hash: string, fromRow: number, fromCol: number, toRow: number, toCol: number }
console.log(`Matching ${payload.hash} removed`);
});Matching conflict rules:
- Adding a range highlight clears all previous highlights.
- Adding a cell highlight keeps existing cell highlights, but clears row/column highlights (and vice versa).
- Highlights of the same type accumulate.
cellMouseOver
Fired when the pointer enters a cell. Fires once per cell: moving between elements inside the same cell does not re-fire it.
grid.on("cellMouseOver", (payload) => {
// payload: CellTarget & { cell: HTMLElement, originalEvent: MouseEvent }
if (payload.cellType === "value") console.log(payload.row, payload.col);
});CellTarget is a union keyed on cellType, mirroring the data-cell-type attribute on the cell:
cellType | Fields | Notes |
|---|---|---|
value | row, col | Data cell indices |
row-facet | level, row, toRow | Full span of the facet value, resolved against the viewmodel, not only the visible part |
column-facet | level, col, toCol | Same for columns |
header | level, trackIndex | Corner cells, fixture headers and fixture spacer cells |
fixture | side, index, row? / col? | row for left/right fixtures, col for top/bottom |
Emission pauses while the grid scrolls or redraws. Once that activity settles (100ms of quiet) the cell under the pointer is re-resolved, so a cell that scrolled under a stationary cursor still produces an out/over pair. Resolution only runs when at least one listener is registered.
cellMouseOut
Fired when the pointer leaves a cell, with the same payload shape as cellMouseOver. Always precedes the next cellMouseOver, and fires when the pointer leaves the grid.
cellClick
Fired on click with the same payload shape. Clicks on a resize handle, and the click the browser synthesizes at the end of a column drag, are not reported.
grid.on("cellClick", ({ cellType, originalEvent, ...target }) => {
if (cellType === "value") grid.highlightRowByDataIndex(target.row);
});Hover effect and setHover
The grid ships with a hover effect (GridConfig.hoverEffect, default "cross") that is itself a listener on cellMouseOver / cellMouseOut and calls grid.setHover(). Set hoverEffect: "none" and call setHover from your own listeners to get different behavior, for example tinting a whole row on click:
grid.on("cellClick", (payload) => {
if (payload.cellType === "value") grid.setHover([{ rows: [payload.row, payload.row] }]);
});setHover takes a list of rectangles. Each is the intersection of its rows: [from, to] and cols: [from, to] spans, and an omitted axis means the whole axis: { rows: [i, i] } is row i including its leaf facet and fixture cells, { cols: [j, j] } is column j, { rows: [i, i], cols: [j, j] } is the single value cell, and a cross is the two single-axis rectangles together.
setHover does not redraw. It writes a CSS rule into a stylesheet scoped to the grid; rendered cells match it through their data attributes, and the rule is re-clipped to the viewport after each render. The rule text comes from GridConfig.hoverStyleRenderer; the default mixes the theme's highlightBaseColor into each surface's background by hoverColorMixPercentage.
debug_perf:metrics
Render performance data emitted after every render cycle. Useful for debugging render performance.
grid.on("debug_perf:metrics", (payload) => {
// payload: {
// timeToRender: number, // ms for this render cycle
// renderCount: number, // total renders since layout creation
// nodesActive: number, // cells currently in the DOM
// nodesAppendedInThisFrame: number,
// nodesDeletedInThisFrame: number,
// contentCellRerenderCount: number,
// poolSize: number, // cells available in the pool
// }
});Grid & Layout
The Grid class is the public entry point for rendering. It receives a GridDataViewModel and renders the grid. It wraps a layout engine, a cell pool, and a selection rule store. The layout handles viewport calculation, cell placement, and DOM management.
Matching Rules
The matchAll API selects cells by predicate - matching on facet dimension, facet value, or cell data - and applies attributes to them. Attributes include CSS styles, renderer overrides, column sizing, and data grouping tags.