Fixtures
Fixtures are custom content areas pinned to the edges of the grid. They scroll with the grid on one axis and stay fixed on the other.
Fixtures are content areas attached to the edges of the grid - top, left, bottom, or right. Each fixture occupies a fixed-size track that scrolls with the grid on one axis:
- Top/bottom fixtures have a fixed height and scroll horizontally with the data columns.
- Left/right fixtures have a fixed width and scroll vertically with the data rows.
Use fixtures for row numbers, aggregation rows, freeze panes, status indicators, or any content that should stay visible while scrolling.
| V-Fixture Left | Corner | Column Facets | V-Fixture Right |
| spacer | Horizontal Fixture - Top | spacer | |
| V-Fixture Left | Row Facets | Data Cells | V-Fixture Right |
| spacer | Horizontal Fixture - Bottom | spacer | |
Blue regions are fixtures. Vertical fixtures (left/right) start at the column facets row, span the data rows, and have spacer cells where they intersect horizontal fixtures. Horizontal fixtures (top/bottom) start with a spacer at the left vertical fixture, span across row facets and data cells, and end with a spacer at the right vertical fixture. The corner cell sits at the intersection of row facets and column facets. Spacers have no interactivity.
Fixture types
There are two abstract base classes, one for each orientation:
PVerticalFixture (left/right)
Vertical fixtures occupy a column on the left or right edge of the grid. They scroll vertically with data rows but stay fixed horizontally.
abstract class PVerticalFixture extends WithCellPlacement(PFixture) {
abstract headerCell(ctx: HeaderCellContext): HTMLElement | HTMLElement[] | string | null;
}
type ColAutoSizeConfig = | IColAutoSizeStrategyMaxCell
| IColAutoSizeStrategyClampedWidth
| IColAutoSizeStrategyStatic;
interface IColAutoSizeStrategyMaxCell extends IColAutoSize {
strategy: "max-cell";
}
interface IColAutoSize {
strategy: "max-cell" | "clamped-width" | "static";
excludeColumnFacets?: boolean;
}
interface IColAutoSizeStrategyClampedWidth extends IColAutoSize {
strategy: "clamped-width";
maxWidthInPx?: number;
minWidthInPx?: number;
}
interface IColAutoSizeStrategyStatic extends IColAutoSize {
strategy: "static";
width: number;
unit: "%" | "fr" | "px";
}
interface HeaderCellContext {
viewModel: GridDataViewModel;
level: number;
key: string;
cell: HTMLElement;
container: HTMLElement;
render: (viewModel: GridDataViewModel) => void;
}getCellsToRender- called on each render cycle with the current viewport. Returns DOM nodes to append to the grid container. Use the cell placement API (inherited fromWithCellPlacement) to create or reuse cells.headerCell- renders the fixture's header cell in the column facet area (the corner where column headers and the fixture column intersect). Returnnullto skip the header.colSize- controls the column width strategy. Defaults to"max-cell"(auto-size to content).
PHorizontalFixture (top/bottom)
Horizontal fixtures occupy a row at the top or bottom edge of the grid. They scroll horizontally with data columns but stay fixed vertically.
abstract class PHorizontalFixture extends WithCellPlacement(PFixture) {
abstract getHeight(): number;
headerCell(_ctx: HeaderCellContext): HTMLElement | HTMLElement[] | string | null | undefined;
}
interface HeaderCellContext {
viewModel: GridDataViewModel;
level: number;
key: string;
cell: HTMLElement;
container: HTMLElement;
render: (viewModel: GridDataViewModel) => void;
}getHeight- returns the fixture's row height in pixels. Called during measurement to calculate the grid's total height.getCellsToRender- same as vertical fixtures; called each render cycle.headerCell- optional; renders a header cell in the row facet area. Default implementation returnsundefined(no header).
BaseFixtureViewModel
The fixtureViewModel parameter passed to getCellsToRender contains positioning information:
interface BaseFixtureViewModel {
offset: number; // pixel offset for sticky positioning
track: number; // the grid track index assigned to this fixture
suggestedCls: string[]; // CSS classes to apply (e.g. ["left-fixture", "first"])
}Registering fixtures
Pass fixture classes (not instances) via GridConfig.fixtures. The layout instantiates them internally.
import { GridConfig, PVerticalFixture } from "grid";
class RowNumberFixture extends PVerticalFixture {
viewModelKey() { return "row-numbers"; }
headerCell() { return "#"; }
getCellsToRender(viewModel, fixtureViewModel, sliceData) {
const nodesToAppend: HTMLElement[] = [];
for (let j = 0; j < sliceData.sliceNumRows; j++) {
const rowIndex = viewModel.y0 + j;
const key = `row-num-${rowIndex}`;
const [cell, needAppend] = this.cellManager.acquire(key);
cell.className = "cell left-fixture";
cell.textContent = String(rowIndex + 1);
cell.style.gridRow = String(fixtureViewModel.track + j);
cell.style.gridColumn = String(fixtureViewModel.track);
cell.style.left = `${fixtureViewModel.offset}px`;
if (needAppend) nodesToAppend.push(cell);
}
return { nodesToAppend };
}
}
const config: Partial<GridConfig> = {
fixtures: {
top: [],
left: [RowNumberFixture],
bottom: [],
right: [],
},
};
const grid = new Grid(config, mountEl, "flat");Multiple fixtures on the same side are stacked in array order. For example, two left fixtures produce two sticky columns on the left edge. Each fixture gets its own grid track and offset position.
Positioning
Fixtures use CSS sticky positioning within the grid's CSS Grid layout. The layout calculates cumulative offset positions for each fixture level:
- Left fixtures: offset accumulates left-to-right starting at 0.
- Right fixtures: offset accumulates right-to-left starting at 0.
- Top fixtures: offset starts after column facets.
- Bottom fixtures: offset accumulates bottom-to-top starting at 0.
The fixture's offset in BaseFixtureViewModel is the pixel position for the left, right, top, or bottom CSS property, depending on the fixture's side.