This file is auto-loaded by Claude Code and other AI coding assistants. It provides the orientation needed to work in this codebase without exploring files first.
@opencorestack/opengridx — a high-performance React DataGrid component library.
- Language: TypeScript 5.9 strict mode. Zero
any, zeroeslint-disable. - Framework: React 19
- Tests: Vitest 4 +
@testing-library/react. Two projects:unit(jsdom,npm test) andbrowser(real Chromium, Firefox and WebKit via Playwright,npm run test:browser) - Build:
npm run build:libproduces the publisheddist/opengridx.es.js/dist/opengridx.umd.js.npm run buildtype-checks the lib and builds the demo site. - Published: yes — on npm as
@opencorestack/opengridxwith external consumers. Treat public types and behaviour changes as semver-relevant. - Lint:
npm run lint(ESLint). Must pass before every commit. - Current version: 3.5.1
Props enter DataGrid.tsx (the orchestration layer — no logic lives here, only hook calls and JSX). Put new logic in a hook or pure util and call it from there. Hook call order is load-bearing (effects run in call order; several hooks install apiRef methods): the full ordered list is in docs/architecture/datagrid-orchestration.md.
DataGridProps
│
├─ useGridControlledState — normalises controlled/uncontrolled prop pairs
│ (sort, pagination, selection, column visibility, pivot, aggregation)
├─ useGridPivot → resolveGridModes (utils/gridModes) — which features are in effect
├─ useGridGroupingColumn — the synthetic __group__ column + its auto-pin
├─ useDataGrid — row store, dimensions, live apiRef
│ useGridRowIdOf / useGridApiRefBinding
├─ useGridHierarchy — useTreeData + useRowGrouping → active handlers + rowMetaMap
├─ useGridEditing, useGridLiveRowSelection, useGridDataSource, useGridDetailPanel
│
├─ useGridColumns — inject hierarchy renderers; resolve order/visibility
│ reads rowMetaMap instead of row._* fields
├─ useGridRowPipeline — filter → sort → paginate → split pinned rows
├─ useGridRowInteractions — selection + row click / checkbox handlers
├─ useGridApiMethods, useGridAggregationApi — imperative API
├─ useLayout, useGridSpanning, useGridClipboardApi, useGridScrollToIndexesApi
│
├─ useGridViewport — scroll sync + viewport measurement
├─ useGridVirtualization — compute render window (which rows/columns are visible)
├─ useGridKeyboardNavigation — focus + keys; useGridSortHandlers / useGridPointerFocusHandlers /
│ useGridColumnMenuHandlers for header and cell events
├─ useGridAriaRows — aria-rowcount / aria-rowindex bases
├─ useGridVisibleRows — merge pinned + virtual center → { row, rowIndex }[]
├─ useGridToolbarProps — props for slots.toolbar
│
└─ JSX renders:
Header, GridPinnedRows, GridVirtualRows, GridAggregationFooter, GridPaginationArea,
GridLiveRegion, GridListView (list view), overlays
Hierarchy (tree data, row grouping) is handled by useTreeData / useRowGrouping. They produce flat renderable row arrays of the consumer's own row objects (unchanged — nothing is injected since v3.0) and a rowMetaMap: Map<GridRowId, GridRowMeta> that carries all hierarchy information.
| File | Purpose |
|---|---|
lib/types/index.ts |
All public types: DataGridProps, GridColDef, GridRowModel, GridRowMeta, GridLocaleText, GridRenderCellParams, etc. |
lib/components/DataGrid/DataGrid.tsx |
Orchestration only — hooks + JSX, no business logic. Hook order: docs/architecture/datagrid-orchestration.md |
lib/hooks/core/ |
Core pipeline hooks (always active): useGridControlledState, useGridRowPipeline, useGridColumns, useGridVirtualization, useGridVisibleRows, useGridScrollSync, useGridKeyboardNavigation, useLayout; DataGrid wiring: useGridHierarchy, useGridGroupingColumn, useGridRowIdOf, useGridDetailPanel, useGridRowInteractions, useGridViewport, useGridAriaRows, useGridToolbarProps, useGridHeaderHandlers, useGridFocusHandlers, and the apiRef installers useGridApiRefBinding / useGridApiMethods / useGridAggregationApi / useGridClipboardApi / useGridScrollToIndexesApi |
lib/utils/gridModes.ts |
resolveGridModes: the one place features override each other (pivot vs tree/grouping/reorder, grouping vs pagination, infinite vs pages, list view needs listViewColumn) |
lib/hooks/features/ |
Opt-in feature hooks: useAggregation, usePivot, useGridEditing, useGridSpanning, useGridDataSource, useGridClipboard |
lib/hooks/useTreeData.ts |
Tree-data hierarchy — flat row array + rowMetaMap |
lib/hooks/useRowGrouping.ts |
Row-grouping hierarchy — flat row array + rowMetaMap |
lib/components/Cell/Cell.tsx |
Cell render — invokes renderCell, wraps with CellErrorBoundary |
lib/components/Cell/CellErrorBoundary.tsx |
Class error boundary wrapping renderCell output |
lib/utils/filtering/ |
Client-side filter operators |
lib/utils/sorting/ |
Client-side sort comparators |
lib/utils/aggregation/ |
The single set of aggregation functions (sum/avg/count/min/max/unique) used by footer, row grouping and exports — do not re-implement them elsewhere |
lib/hooks/core/useGridDevWarnings.ts |
Dev-only console.warns (unbounded container, pagination + grouping) |
| Task | Where |
|---|---|
| New filter operator for a column type | lib/utils/filtering/filtering.ts + types/index.ts |
| New feature (aggregation, pivot, etc.) | New hook in lib/hooks/features/, consumed by DataGrid.tsx |
| New core pipeline stage | New hook in lib/hooks/core/, added to DataGrid.tsx |
| New component (slot, overlay, etc.) | lib/components/<Name>/ |
New public prop on DataGrid |
Add to DataGridProps in lib/types/index.ts, destructure in DataGrid.tsx |
| New column type | GridColType union in lib/types/index.ts, operators in lib/utils/filtering/filtering.ts |
| New slot | Add to GridSlots in lib/types/index.ts, wire in DataGrid.tsx via slots?.mySlot |
- CSS classes:
ogx__BEM prefix (e.g.ogx__cell,ogx__cell--focused) - CSS variables:
--ogx-*(e.g.--ogx-row-height,--ogx-color-primary) - Hook names:
use-grid-*for core hooks,use*for feature hooks - Exported types:
Grid*prefix (e.g.GridColDef,GridRowMeta,GridLocaleText) - Never inject fields onto consumer row objects. Hierarchy info goes in
rowMetaMap/params.rowMeta; the old_hasChildren-style runtime fields were removed in v3.0.
Hierarchy metadata (hasChildren, treeDepth, groupingField, etc.) lives in a Map<GridRowId, GridRowMeta> returned by useTreeData/useRowGrouping, not on the row object. Access it in renderCell via params.rowMeta.
The underscore fields (_hasChildren, _treeDepth, …) were injected onto copied rows from v1.1 to v2.x as a backward-compat shim and removed in v3.0 — rows now pass through unchanged, so params.row is the consumer's own object. Migration: docs/migration/v2-to-v3.md.
Full doc: docs/architecture/grid-row-meta.md
- Test files are co-located with the file they test:
useGridRowPipeline.test.tslives inlib/hooks/core/ - Import pattern:
import { describe, it, expect, vi } from 'vitest'+import { renderHook, act } from '@testing-library/react' - Hook tests use
renderHook(() => useMyHook(params))— no DOM needed for pure hooks - Component tests use
renderfrom@testing-library/react - Run unit tests:
npm test(jsdom). Real-browser tests:npm run test:browser. Both:npm run test:all - Run a single file:
npx vitest run --project unit lib/hooks/core/useGridRowPipeline.test.ts - Anything that depends on layout,
ResizeObserver, or scrolling (virtualization, container height, drag) must be a*.browser.test.tsx— jsdom has no layout engine and reports a 0px viewport (the grid falls back to 600px), so such bugs are invisible there - Browser tests run in Chromium, Firefox and WebKit:
npx playwright install chromium firefox webkitonce. One engine only:npx vitest run --project browser --browser.name=firefox - Browser test files run one at a time (
fileParallelism: false): several test pages open at once in one Firefox steal focus and pointer input from each other - Vitest vs Playwright Test: render
<DataGrid>with props and assert → Vitest (*.test.tsx, or*.browser.test.tsxfor layout). Needs the built package, a real app, a dev/preview server or a file download → the e2e package smoke suite (e2e/,npm run test:smoke). Vitest aliases the package tolib/, so only the smoke suite catches packaging bugs (bundledreact/jsx-runtime, missing CSS indist/,.d.tsthat failsskipLibCheck: false) - Smoke suite:
scripts/smoke.mjsbuilds + packs to.pack/,npm cis each fixture ine2e/fixtures/{react19,react18}and unpacks the tarball there, builds them (strict tsc + vite), then runse2e/smoke.spec.tsin Chromium/Firefox/WebKit × both fixtures againstvite preview. Scenarios via?scenario=(basic,flex,grouping,editing; React 19 alsopinned,dark). Any console error/warning fails a test. Fixtures are outside root lint/typecheck/vitest; their lockfiles must not carryintegrityfor the tarball entry. Guide:docs/contributing/testing.md - Keep browser tests engine-neutral: no CDP (
cdp()is Chromium-only; use a Playwright-backed command such ascommands.setColorSchemefromvitest.config.ts), Tab-order anchors are text inputs (WebKit on macOS skips buttons on Tab), compare scroll positions with what the browser actually took (Firefox snaps them to device pixels), and wait for CSS animations to finish before measuring. Skip an engine only withit.skipIf(server.browser === '...')(serverfromvitest/browser) and a comment saying why
| Content | Location |
|---|---|
| Component public API (props, slots, events) | docs/components/<name>.md |
| Internal hook architecture | docs/architecture/<hook-name>.md |
| Feature guides (usage patterns, code examples) | docs/features/<feature>.md |
| Full public API reference | docs/API_REFERENCE.md |
| Roadmap and feature status | docs/roadmap.md |
| Wiki-only pages (Home, Getting Started, FAQ) | wiki/ |
The GitHub wiki is generated from docs/ + wiki/ by scripts/build-wiki.mjs (.github/workflows/wiki.yml, on push to main). Never edit the wiki directly. A new doc page needs an entry in SECTIONS there; npm run wiki:build warns about unlisted docs
npm run lint && npm run buildmust pass after every change- Zero
eslint-disableor@ts-ignore— fix the root cause - Zero
any— useunknownor explicit interfaces - Never add
Co-Authored-ByAI attribution to commit messages
- Content height is capped to the browser's limit. Firefox lays out elements up to about 17.9M px and collapses a taller one (it does not clamp like Chromium/WebKit at about 33.5M px), which made large grids unscrollable there.
useGridMaxContentHeight(hook 32a, beforeuseGridVirtualization) measures the limit once per document with an invisible probe (lib/utils/maxElementHeight.ts:getMaxElementHeight,countReachableRows;undefineduntil measured,Infinitywithout a layout engine), anduseGridVirtualizationreturnsvirtualContainerHeightcapped to it — always render the rows' container with that, nevertotalHeight - pinned heights - The tall-content dev warning waits for the measurement and fires only when rows are cut off, naming the reachable count.
DataGrid.heightCap.browser.test.tsx(500k rows) fails without the cap in Firefox - Benchmark:
npm run bench(bench/, port 4320) measures the packed package at 10k–1M rows in 3 engines; results inbench/results/, tables indocs/performance.md(node bench/report.mjs --write docs/performance.md). Run it on an idle machine; the weekly CI pass is for trends only
Design: docs/superpowers/specs/2026-10-09-ai-assistant-header-filters-fill-design.md.
- AI assistant (
aiAssistant): core holds the panel (useGridAiAssistant, hook 45a, beforeuseGridToolbarProps; UI inlib/components/AiAssistant/),/aiholds model logic (createGridAiPromptHandler,createGridAgentTools). The core bundle must contain no AI code and/aino React (check-bundle;/aibudget 8 kB, ≈ 6.7 kB now)./aistrings must never contain the word "react" or a lowercaseimport/require((check-bundle regexes) - Brand rule:
validateGridAiStatebrands result andstatewith the non-enumerableSymbol.for('opengridx.ai.validated')(lib/utils/aiBrand.ts); the grid applies onlyisGridAiValidated(result.state). Parts the validator emptied are skipped; column visibility is merged; Undo restores a snapshot of touched parts (view undo, separate fromundoRedo) GridAiState/GridAiPart/GridAiValidationErrorand assistant types live inlib/types/index.ts; allGridAi*/GridAgent*types are exported from/aionly (publicApi rule/^Grid(?:Ai|Agent)[A-Z]/)- Agent tools are all-or-nothing on validation errors and never throw;
get_row_summaryexists only withallowRowAccess. They readapiRef.currentat call time — create them in a handler/effect, not during render (react-hooks/refs) rowGroupingModelis a controlled/uncontrolled pair (onRowGroupingModelChange) inuseGridControlledState. NewGridApi:setSortModel,setRowGroupingModel,setAggregationModel,setColumnVisibilityModel,setPivotModel,getGridAiState,openAiAssistant,closeAiAssistant- Two buttons swapped by a ternary inside a
<form>need distinctkeys, or the click on the outgoing one submits the form (the Stop button re-sent the prompt) - Header filters (
headerFilters): each cell owns only the rootfilterModelitemid: 'header:<field>'(lib/utils/headerFilters.ts:getHeaderFilterState,setHeaderFilterItem, which returns the same object for a model it can't show). Custom ='or'root with items, field inside a group, or an untagged/second root item on the field. Off in pivot mode - The filter row renders inside
.ogx__header-wrapafter.ogx__header; column cells of both rows must usegetHeaderColumnStyle(lib/components/Header/headerColumnStyle.ts). Focus:FocusedCell.headerFilter; column header is row -2 when header filters are on, filter row -1.useGridAriaRows({ headerFilterRow }). "Custom filter" opens the panel viaGridToolbarHost.registerFiltersPanel+forceFiltersOpen/onFiltersPanelClose - Fill handle:
useGridFillHandle(hook 36d, afteruseGridUndoRedo) →rowRenderProps.fillHandle→fillHandleColon the handle's row only;RANGE_FILL_HANDLE(32) flag;.ogx__cell-fill-handle(24 px hit area inside the corner cell). Drags go throughcellSelection.startTrackedDrag(pointer events, touch, auto-scroll, Escape cancels). Pure fill logic inlib/utils/editing/fill.ts(no regexes); commits viaapplyEdits(edits, 'fill'); Ctrl/Cmd+D / R always copy - Smoke suites of parallel agents share ports 4318/4319 — never run two at once
- Batch edits: paste, range clear, undo and redo commit through
useGridBatchEdit.applyEdits— oneprocessRowUpdateper row (rows in parallel, a failing row reported throughonProcessRowUpdateErrorwithout blocking others) and oneREPLACE_ROWSstore update for all successful rows.getRowsees rows stored since the last render. Single edits store throughstoreRow. Shared helpers inlib/utils/editing/commit.ts; edit targets (rows × range columns, same indices ascellSelection.getCurrentRange().resolved) inlib/utils/editing/targets.ts - Hook order (see
docs/architecture/datagrid-orchestration.md):useGridEditCommitChannel(12a) anduseGridBatchEdit(12b) beforeuseGridEditing;useGridClipboardPaste(36b) afteruseGridCellSelectionApi, thenuseGridUndoRedo(36c). Commits reach the history through the channel'semit(changes, source); history ignores'undo'/'redo'sources - Paste, Delete/Backspace and Ctrl/Cmd+Z / Shift+Z / Ctrl+Y are native listeners on the grid root, gated by
classifyKeyTarget('grid'/'control') and no open editor, so an editor's own paste and undo keep working - History: an action is recorded only after
processRowUpdatesucceeds, as{ id, field, before, after }withafterread from the stored row viagetCellValue. Undo writesbeforeexpectingafter(redo the reverse); stale cells are skipped'changed', removed rows'missing'; values are compared, not row identity - Parsing:
lib/utils/parsing.ts(parseTsv, per-typeparse*Text,parseValueByType,parseCellTextwithvalueParser) is pure and the single parser for paste and/ai(lib/ai/coerce.tsonly adds typed-JSON handling on top). Keep its regexes linear /aientry point (lib/ai/, no React, no dependencies): ships asdist/ai.es.js,dist/ai.cjs(.cjsbecause the package is"type": "module") anddist/ai.d.ts, built by a secondvite.config.lib.jsrun (--mode ai).check-bundleenforces no imports, no react, ≤ 5 KB gzipped (≈ 4.6 KB now) and that the core bundles don't contain the marker'OpenGridX grid state'. Demo, Vitest and tsconfig alias@opencorestack/opengridx/aibefore the root alias.lib/ai/columns.tsis the single source of which columns each part allows; the validator reads own keys only, never__proto__, caps lists at 200 and errors at 100, and never throws (fuzz test)- Shared inputs:
InputisforwardRefwithvariant: 'field' | 'cell'andinputClassName(classNamestays on the wrapper);errorsetsaria-invalid. Built-in search boxes, the filter value box and text/number/date editors render throughInput; the boolean editor and list-view checkbox throughCheckbox(hidden<input>: tests click.ogx-checkbox__box). The boolean editor needspreventDefaulton mousedown on the whole.ogx__edit-booleandiv. Browser tests that click styled controls must importlib/styles/opengridx.css - Browser paste tests copy from a textarea and press the paste shortcut (
ControlOrMeta+V); Firefox dropsclipboardDataon a syntheticClipboardEvent
- Cell range selection (
cellSelection, design:docs/superpowers/specs/2026-10-01-cell-range-selection-design.md).useGridCellSelectionruns afteruseGridSpanningand beforeuseGridKeyboardNavigation(feeds itcellSelection.keyboard);useGridCellSelectionApiruns afteruseGridPointerFocusHandlers, keeps the anchor in sync withfocusedCelland installs the sixapiRefmethods. Pure helpers inlib/utils/cellSelection.ts - The model stores corner row ids + fields and is resolved each render against
allRenderableRowsand the data columns ofnavigationColumns, so a range follows sorting; a missing corner clears it with reason'dataChange'. Only the first range is used.isRangeColumnFieldexcludes system fields and__group__; synthetic rows are highlighted but skipped by copy andgetSelectedCells RowisReact.memo(areRowPropsEqual,cellRangecompared by value); row renderers passgetRowCellRange(...),nulloutside the range. Handlers passed to rows must keep a stable identity, or every row re-renders- With
cellSelectionon, the Ctrl/Cmd+C shortcut copies the range (or the focused cell) throughuseGridClipboard'sgetShortcutText, no header line;copySelectedRowsis unchanged. A drag clears the page text selection (WebKit otherwise extends it outside the grid and takes over Ctrl+C) - Browser tests drive held-mouse drags with the Playwright-backed
commands.pointerMoveTo/pointerDown/pointerUpfromvitest.config.ts - Wrapped header text:
wrapHeaderText(grid) andGridColDef.wrapHeaderText(column wins);lib/utils/headerWrap.ts(getHeaderLineClamp,HEADER_WRAP_LINE_HEIGHT16 must matchHeader.css). The header never grows; classogx__header-cell--wrap - React Compiler: ESLint runs
react-hooksrecommended-latest(onlyset-state-in-effectoff).npm run check:compilershould report onlyuseGridDataSource(try/finally) plus the two'use no memo'wrappers (InlineToolbar,CellRenderTarget— they call consumer functions that may use hooks).REACT_COMPILER=1 npx vitest run …testslib/compiled. In components and hooks: no?./??/||/ternaries insidetry(useattempt()fromlib/utils/attempt.ts), notry/finally, no callback naming itself (go through a ref), default array/object props as module-level constants - Vitest's unit project excludes
.claude/**(agent worktrees)
- Regexes that run on cell text or consumer input must be linear (CodeQL scans every push): no adjacent overlapping quantifiers such as
\s*(px)?\s*or[\d\s]*\d[\d\s]*; use a lookahead, trim first, or a plain loop. Tests inlib/utils/export/exportShared.test.tstime 50k-char worst cases exceljsis a devDependency as well as an optional peer (likejspdf);uuidis overridden to^11.1.1in the root and both e2e fixtures
- Boolean edit checkbox calls
preventDefault()on mousedown (GridEditInputCell.tsx): Safari does not focus a checkbox on click, so the cell blur committed the old value first - Package smoke suite
npm run test:smoke(e2e/,scripts/smoke.mjs) runs inci.yml(PRs, branches, manual) and beforenpm publish; weeklynightly.yml. Fixture lockfiles must not carry anintegrityfield for the tarball
exportToPdfis time-sliced (runPdfExport+PdfExportTuninginlib/utils/export/exportToPdf.ts; internal, not exported from the package). Yields usesetTimeout(0), notscheduler.yield()(which resumes ahead of queued tasks and starves React's scheduler). The body is drawn as several autoTable calls: continuation chunks start atstartY: MARGINand awillDrawPagehook movesdata.cursor.yto the previousfinalYand setssettings.showHead = 'never'for that page only (bypasses autoTable's fit-below-startY page break, so breaks match a single call). Chunks are even-sized (alternate shading parity); foot only on the last chunk.exportToPdf.browser.test.tschecks drawing ops are identical to a single call- New options
onProgress,signal(DOMException'AbortError', nothing saved),maxRows(defaultPDF_MAX_ROWS_DEFAULT20000, dev warning only)
GridColDef.sortComparator(v1, v2, params1, params2): ascending comparator; sees null/undefined (no built-in nulls-last for that column); negated for desc. Sorting utils (lib/utils/sorting) take it throughGridSortValue.comparator+params— two items use it only when both carry the same comparator, otherwise the built-in key comparison applies (tree auto-parents, group rows sorted by a non-grouping field). Always call it throughcallSortComparator(throw / NaN → 0, warn once). Row grouping passes the grouping column's comparator for group rows; pivot uses the source column's for row-label fields- Column auto-size:
useGridColumnAutosize(hooks/core, afteruseGridScrollToIndexesApi) measures vialib/utils/columnAutosize(.ogx__cell-content/.ogx__header-cell-contentatmax-content+ cell padding/borders, scoped to the grid's own.ogxroot so nested grids are skipped) and writes throughhandleColumnResize. InstallsapiRef.autosizeColumn/autosizeColumns;ColumnResizeHandlecalls it on double-click and Enter. Only rendered rows are measured
- Without a
heightprop the root getsogx--fill(height: 100%; flex: 1 1 auto; min-height: 0) and fills its container; in an auto-height container it still grows to fit. The unbounded warning inuseGridDevWarningsalso requires the viewport to be at least as tall as the rows - The list view does not virtualize;
useGridDevWarningswarns aboveLIST_VIEW_WARN_THRESHOLD(2000) items - The column resize handle lies inside its header cell (the cell clips overflow); right-pinned columns take
margin-left: autoon their first cell and.ogx__contentismax(100%, totalWidth)when right-pinned columns exist GridApi<R>/useGridApiRef<R>()type the row getters;apiRefprop acceptsMutableRefObject<GridApi<R>> | MutableRefObject<GridApi>.ogx-list-view__rowshastabIndex={-1}(Firefox makes scroll containers Tab stops). CI (deploy + publish workflows) installs and runs Chromium, Firefox and WebKit- Demo pages: a
<DataGrid>withoutheightnow stretches to its parent; give demo grids an explicitheight(or a sized wrapper) unless filling is intended
Correctness release; full list in CHANGELOG.md, consumer guide in docs/migration/v2-to-v3.md. Facts future work needs:
- Rows are never modified. No
idor underscore fields are written; the row store maps rows to ids viagetRowIdandidByRow(useGridRowIdOf). Hierarchy info lives only inrowMetaMap - Generics use
GridValidRowModel(object), notGridRowModel, so consumer interfaces work;DataGridand exports have typed and untyped-columns overloads - Read cell values through
getCellValue(lib/utils/values.ts): it appliesvalueGetterand contains throws (valueundefined, dev warning once per column). Don't callvalueGetterdirectly - Consumer callbacks must be contained (throws fall back to defaults); synthetic rows (group, subtotal, auto-parent, pivot Grand Total) are never selectable and never start or join row spans
DataGrid.tsxhook layout is documented indocs/architecture/datagrid-orchestration.mdnpm run typecheck(lib, tests, demo) is a gate alongside lint and build
- Row-grouping aggregation now shares
lib/utils/aggregation(nulls ignored,unique,availableAggregationFunctions). Expansion in bothuseRowGroupinganduseTreeDatais derived state (depth default plus user overrides keyed by config); no effect resets it on row changes. Never call callbacks such asonRowExpansionChangeinside asetStateupdater - Hierarchy renderers injected by
useGridColumnsmust renderparams.formattedValue, notparams.value, orvalueFormatteris lost;CellpassesformattedValueintorenderCellparams slots.footer/noRowsOverlay/loadingOverlayare rendered (they were typed-only).footerreplaces the pagination area and receives grid state- New:
onRowDoubleClick, exportedColumnVisibilityPanel,ExcelAdvancedExportOptions.groupedRows - Row indices:
renderContextandlayout.cumulativeHeightscover center (unpinned) rows only;allRenderableRowsindices include top-pinned rows. Convert withpinnedTopRows.length. Vertical scroll-into-view goes throughlib/utils/scroll(scrollRowIntoView), which accounts for the sticky header and pinned rows - Virtualization needs a bounded container height. In an unbounded container (e.g. flex child without
min-height: 0) the viewport grows to content and every row renders; pagination masks it and grouping disables pagination.useGridDevWarningswarns about both
The following props existed in v1.x but had no runtime implementation. They have been removed from the public TypeScript types. (GridColDef.description was not removed — it renders as the header title tooltip.)
| Removed | Type | Reason |
|---|---|---|
onPinnedRowsChange |
DataGridProps |
No pin/unpin-row UI exists; callback was never called |
onRowGroupingModelChange |
DataGridProps |
No drag-to-group UI; callback was never called |
density?: 'compact' | 'standard' | 'comfortable' now controls effectiveRowHeight (compact=32, standard=rowHeight, comfortable=72) via CSS --ogx-row-height. Previously the prop was accepted but silently discarded.
Both props are now wired in handleRowClick. disableRowSelectionOnClick suppresses click-to-select; disableMultipleRowSelection caps selection to a single row.
When rowGroupingModel is active, passing groupingColDef now creates a dedicated synthetic __group__ column at position 0, auto-pinned left. Previously the prop was accepted but had no runtime effect.
Columns with groupable: false are now skipped when building the grouping tree in useRowGrouping. Previously this flag was silently ignored.
groupingValueFormatter?: (params: { field: string; value: unknown }) => stringCustomizes the label for group-header rows at that level. Falls back to "${field}: ${value}" when omitted. The formatted string flows through GridRowMeta.groupLabel so useGridColumns picks it up for rendering.
Per-column availableAggregationFunctions?: string[] now gates which aggregation functions are computed in useAggregation. If the current aggregationModel entry for a field uses a function not in that list, the aggregation is skipped for that field.
Shift-clicking a column header now appends to (or removes from) sortModel rather than replacing it. The sort priority badge (1, 2, 3…) appears next to the sort arrow when more than one sort key is active. Plain click still replaces with a single-key sort.
GridApi.getGroupedExportRows()— new method returning a flatGridGroupedExportRow[]ordered list by traversing the active row-grouping tree (group-header → children → subtotal → grand-total). Returnsnullwhen row grouping is not active.GridGroupedExportRowtype — public interface for each entry:type(group-header | leaf | group-subtotal | grand-total),depth, optionalgroupField,groupValue,aggregatedValues,row.- Grouped export across all formats —
exportToCsv,exportToExcel,exportToJson,printGrid,exportToPdfall acceptgroupedRows?: GridGroupedExportRow[]; flat behavior unchanged when absent.
hideable: falsecolumns excluded from Columns panel —ColumnVisibilityPanelnow filters out columns withhideable: falseby default, removing the permanently-disabled rows that added visual noise. Columns still render in the grid body and exports unchanged.GridToolbarProps.showNonHideableColumns— new optional boolean prop. Passtrueto restore the old behavior (show non-hideable columns as disabled rows in the panel). Default:false.
CellErrorBoundaryCellRenderTarget pattern —renderFnis now called inside a module-scopeCellRenderTargetchild component so the throw happens in a descendant; boundaries cannot catch errors in their ownrender()resetKey={row}— error boundary resets when the row object reference changes (not just the cell value), so "Restore" correctly clears error state after fresh row objects are providedgetAllFilteredRows()API — newGridApimethod returning all filtered+sorted rows regardless of pagination; use for full-dataset exports- PDF aggregation footer —
PdfExportDemonow computes the aggregation directly from exported rows (bypassesuseEffect-basedgetAggregationResult); footer now reliably appears with correct values - PDF footer text color — aggregation footer row text is now explicitly black (
[0,0,0]) instead of inheriting the default (which could render invisible against the light gray fill) exportToPdfstandalone API — usesautoTable(doc, opts)standalone function instead ofdoc.autoTable(opts)to avoid unreliable ESM prototype patching
exportToPdf— native PDF export via optional peer depsjspdf+jspdf-autotable; lazy-loaded, zero bundle impact for non-PDF consumersPdfExportOptions— 12-field interface: fileName, title, logoUrl, orientation, selectedRows, aggregationResult, aggregationModel, filterModel, alternateRowColor, headerBackgroundColor, headerTextColor, fontSizeCellErrorBoundaryfix — changed torenderFnprop so the boundary callsrenderCellinside its ownrender(); previously the throw occurred in the parent before the boundary could catch it- GitHub Pages SPA routing — deploy workflow now copies
404.htmlso direct URL access to demo routes works
GridRowMeta— hierarchy metadata moved offGridRowModelintoMap<GridRowId, GridRowMeta>; exposed viaparams.rowMetainrenderCellCellErrorBoundary—renderCellerrors now caught per-cell; grid continues renderingGridLocaleText/localeTextprop — all pagination strings are now overrideable for i18n- Duplicate DOM ID fix —
Rowcheckbox andFilterPanelinputs now useuseId()(wasogx-select-row-${row.id}, caused duplicates with two grids on one page)