Column layout and geometry
Configure column width modes, identity-based state, and geometry queries, with shared layout rules and coordinate spaces for React, Vue, and Angular grids.
@gp-grid/core owns grid geometry. Column widths, offsets, row bounds, hit
testing and scroll targets are resolved in one place and published to the
wrappers as a revisioned snapshot; a framework wrapper never recomputes a
position or a width.
Keeping these calculations in a dedicated service gives rendering, pointer input, keyboard navigation and overlays the same answers. A resize or reorder can update one layout snapshot instead of requiring React, Vue and Angular to keep separate formulas in sync. The numeric service can also be tested without a browser, while wrappers concentrate on measurement and rendering.
This provides a shared place to add variable row heights and frozen regions: their coordinate rules can be implemented once and consumed by every wrapper. The benefit is consistency and easier maintenance; bundle size and scrolling performance still need measurement as the service grows.
Displayed-width modes
columnLayout selects how displayed column widths are resolved. It is a
GridCore option and a prop/input of Grid (React), GpGrid (Vue) and
gp-grid (Angular). Default: "fit".
| Mode | Behaviour |
|---|---|
"fit" | Columns without an explicit pixel override expand proportionally so their total reaches the viewport width. fit only ever expands: a base total that already reaches or overflows the viewport keeps the declared/overridden widths. |
"fixed" | Displayed width equals the base width. Remaining space stays empty and the grid scrolls horizontally. |
Base width is the normalized pixel override when one exists, otherwise the
normalized declared width. A width that is not positive and finite falls back
to 50 px (DEFAULT_MIN_COLUMN_WIDTH) and is diagnosed once per column id.
Overrides are exact
A manual resize, an Alt+ArrowLeft/Right step, a fit and
columns.setState([{ columnId, width }]) store a pixel override. In fit mode an override keeps that exact width and the slack is
shared by the columns that have no override:
100 / 100 in a 300 px viewport -> 150 / 150
override the first to 100 -> 100 / 200
reset the first -> 150 / 150When every displayed column is overridden the slack stays empty.
columns.setState([{ columnId, width: null }]) drops the override alone and
restores the declared width (and its fit share), keeping the column's order,
visibility and pin; columns.resetState drops the column's whole state. A
definition width is never back-solved from a displayed width.
core.columns.setState([{ columnId: "name", width: 240 }]); // override
core.columns.setState([{ columnId: "name", width: null }]); // back to the declared widthGridCore.columns.getState() reports an optional width (present only while an
override exists) plus resolvedWidth, the displayed CSS px (0 while hidden).
resolvedWidth is output-only.
Changing the mode at runtime
- Core:
core.columns.setLayout("fixed") - React: the
columnLayoutprop - Vue: the
column-layoutprop - Angular: the
columnLayoutinput
A mode change republishes the layout without recreating the core. Setting the mode it already has emits nothing.
Pinning
Start/end pins, viewport admission, the mounted column window and the header pin toggle are documented in Column pinning.
Geometry queries
core.geometry is a read-only view of the committed layout:
| Query | Answer |
|---|---|
revision | Committed layout revision. Versions columns, the row axis, viewport dimensions and mapping parameters — not raw scroll positions. |
getColumnLayout() | { revision, mode, columns, totalWidth }; each displayed column carries columnId, layoutIndex, column, offset and width. |
getRowWindow() / getVisibleRowWindow() | Half-open { start, end }, overscanned / exact. |
getRowBounds(i, space?) / getColumnBounds(layoutIndex, space?) | { start, end } in the requested space. |
getCellBounds(rowIndex, layoutIndex, space?) | { top, left, width, height, columnId, coordinateSpace }. |
getRowEdgeOffset(boundaryIndex, space?) | Row boundary offset, including the end insertion edge rowCount. Frozen edges, up to and including the frozen count, do not scroll in viewport space. |
hitTest({ x, y, scrollTop?, scrollLeft? }) | { row, displayIndex, col, columnId? }. row and displayIndex (index into the displayed columns) are raw axis sentinels: -1 before, count past the end. col is the layout index, or -1 outside a column. |
getScrollTarget(rowIndex, layoutIndex, from?) | DOM { scrollTop?, scrollLeft? } for the axes that must move. |
getContentSize() | Logical body { width, height, coordinateSpace: "content" }. |
GridCore.cells.getBounds(rowId, columnId, space?) resolves identities through
the bounded current row window and the resident records. It never scans or
materializes a remote or columnar dataset, so a columnar identity outside the
current window answers undefined; callers that hold a view index can query
any valid row through core.geometry.
Coordinate spaces
Three named spaces, all in CSS px:
content— logical, uncompressed coordinates.xstarts at the first displayed column's left edge,yat row 0's top edge; the header is excluded. This is the space of the scrollable content.viewport— content minus the logical scroll offsets, relative to the body client area's top-left corner (inside its border). The default for bounds queries. A portalled overlay adds the body client area's screen origin.rows— local to the rows wrapper, the space ofMOVE_SLOT.translateY. Equal tocontentunless vertical scrolling is compressed; adapter-only.
In compressed mode, with logical top L, effective DOM top D and
first-visible-row offset A: a row's wrapper position is rowOffset − A and
the wrapper offset is D − (L − A), so
wrapperOffset + rowPosition − D === rowOffset − L at any scroll fraction,
including fractional touch overrides.
Windows are half-open ([start, end)). Legacy inclusive APIs are adapted at
the boundary as { start, end: end - 1 }, which is { start: 0, end: -1 }
for an empty range.
Browser adapters supply raw DOM scroll samples and normalized dimensions;
the core maps them to logical coordinates and clamps them. Compressed-scroll
conversion lives in ScrollVirtualizationManager and is reached through
core.geometry; a width-only viewport update performs no row work. A scroll
sample outside the reachable range is answered from its clamped value and
corrected with SCROLL_TO in the same batch. A measured zero-height viewport
renders an empty window; until the first measurement the core assumes 600 px.
Standalone helpers
calculateColumnPositions, getTotalWidth and findColumnAtX remain
supported for simple known-width lists. They operate on exactly the supplied
list (including hidden entries) and do not apply the grid's fallback width or
width modes. Use core.geometry for resolved grid geometry.
For the complete API contracts and advanced examples, see the upstream column layout and geometry guide.