-
Notifications
You must be signed in to change notification settings - Fork 0
Changelog
📝 Generated from
CHANGELOG.md. Edit it there; changes made in the wiki are overwritten.
Package: @opencorestack/opengridx
License: MIT © 2026 Open Core Stack
-
GitHub wiki generated from
docs/—scripts/build-wiki.mjs(npm run wiki:build) turns the docs, CHANGELOG and CONTRIBUTING into wiki pages with a sidebar, rewrites relative links to wiki pages (or GitHub URLs for source files and images), and adds hand-written Home, Getting Started and FAQ pages fromwiki/..github/workflows/wiki.ymlpublishes it on every push tomainthat touches them.
- Exports no longer slow down on crafted cell text — the check that tells plain signed numbers ("-12.50") from spreadsheet formulas (CSV/Excel formula-injection guard) could backtrack quadratically on long runs of digits, spaces and separators that end in a non-number character, freezing CSV or Excel export of such a value. It now runs in linear time; results are unchanged. Found by CodeQL.
-
Sheet names and theme heights — stripping apostrophes from Excel sheet names and parsing
'36px'-style theme heights used patterns with the same quadratic worst case; both are linear now, results unchanged. -
Column auto-size selector fallback — where
CSS.escapeis unavailable, field names are now escaped for backslashes as well as quotes.
-
exceljsis a devDependency (it stays an optional peer for consumers), so a regenerated lockfile keeps it. - Dev-only dependency alerts:
undici7.30.0,dompurify3.4.16, anduuidoverridden to^11.1.1(pulled in byexceljs4.4.0) in the repo and smoke fixtures. Consumers who installexceljsthemselves still get itsuuid8 untilexceljsupdates it.
- Boolean editor in Safari — clicking the edit checkbox closed the editor without toggling the value. Safari does not focus a checkbox on click, so mousedown moved focus to the cell and the blur committed the unchanged value before the click arrived. The checkbox now keeps focus on mousedown. Found by the new package smoke suite (WebKit).
-
Community files —
CODE_OF_CONDUCT.md(Contributor Covenant 2.1),CONTRIBUTING.md, GitHub issue forms (bug report, feature request) and a pull request template. -
Package smoke suite (
npm run test:smoke) — packs the library and installs the tarball into strict-TypeScript React 19 and React 18 apps (e2e/fixtures/), builds them and runs Playwright Test in Chromium, Firefox and WebKit: rendering, sorting, quick filter, selection, pagination, a 50,000-row grid filling a flex app shell without aheightprop, row grouping with aggregation, CSV / XLSX / PDF downloads, editors, pinning, column groups, detail panels and the dark theme. Runs in the newci.ymlworkflow (pull requests), beforenpm publish, and weekly against the newest dependency versions. Seedocs/contributing/testing.md.
-
Non-blocking
exportToPdf— the export now works in ~30 ms slices and yields to the event loop between them, both while formatting rows (including grouped rows and WinAnsi conversion) and while drawing the table: the body is drawn by a series ofjspdf-autotablecalls that continue on the same page with fixed column widths, the header repeated on every new page and the footer only at the end. Pages, row positions, shading and page breaks are unchanged (verified against a single call in Chromium, Firefox and WebKit). Measured with 50,000 grouped rows in Chromium: longest main-thread block 6.3 s → ~0.4–0.6 s (jsPDF's final save, which cannot be split), total time about the same (~6–7 s), file size unchanged. -
PdfExportOptions.onProgress—({ phase: 'prepare' | 'render' | 'save', done, total }) => void; new exported typePdfExportProgress. -
PdfExportOptions.signal— anAbortSignal; aborting rejects the promise with aDOMExceptionnamed'AbortError'and saves no file. -
PdfExportOptions.maxRows(default 20,000) — above it a development-modeconsole.warnrecommendsexportToCsv/exportToExcelAdvanced. The export still runs; nothing is thrown. - Demo: the PDF export page has a row-count selector, a progress bar and a Cancel button.
-
GridColDef.sortComparator(v1, v2, params1, params2)— a per-column ascending comparator for client-side sorting, used instead of the built-in type-aware comparison.v1/v2are the cell values aftervalueGetter, includingnull/undefined(the comparator decides where empty values go);paramsare the new exportedGridSortCellParams{ id, field, row, value }. The grid negates the result fordesc. It is used for flat sorting, as one key in a multi-sort chain, to order row-grouping groups by their grouping value (the grouping column's comparator), for tree-data siblings and for pivot row-label columns, sogetAllFilteredRows(), exports and clipboard follow the same order. A comparator that throws or returnsNaNreads as0with a one-time dev warning. Ignored withsortingMode="server". -
Auto-size a column to its content — double-click a column's resize handle (or press Enter with the handle focused) to fit the column to its header and the cells in the current render window (pinned rows included), measured in the DOM and clamped to
minWidth(default 50px) /maxWidth. A flex column gets a fixed width, like after a drag;resizable: falsecolumns are never auto-sized; the double-click never sorts. Works for left/right-pinned columns and under column groups. The width goes tocolumnWidthsstate, soonStateChangereports it. -
apiRef.current.autosizeColumn(field)andapiRef.current.autosizeColumns(fields?)— the same measurement, programmatically (every column whenfieldsis omitted).
- Double-clicking a resize handle used to do nothing; it now auto-sizes the column.
Fixes from a smoke test in a real application (Chromium, Firefox and WebKit).
-
A grid without a
heightprop now fills its container. Its root was auto-height, so inside a bounded flex layout (a wrapper withflex: 1; min-height: 0, with or withoutDataGridThemeProvider) it grew to fit every row and rendered all of them; 50,000 rows froze the tab. The root now getsheight: 100%and, as a flex item,flex: 1 1 auto; min-height: 0(classogx--fill) when neitherheight,style.heightnorautoHeightis set. In an auto-height container it still grows to fit, as before. An explicitheightis never stretched. -
The unbounded-container development warning is only logged when the viewport is at least as tall as the rows it renders, and names the fixes: a definite container height,
min-height: 0on every flex or grid ancestor up to the sized one, aheightprop, orautoHeight. - The list view warns in development when it renders more than 2,000 items. It does not virtualize; README and the list-view guide now say so and recommend pagination.
-
The whole column resize handle can be grabbed. The 8px handle straddled the header cell border and the cell's
overflow: hiddenclipped half of it, leaving 3–4px in every engine. It now lies inside its column against the resizing edge (also in the column-group header layout and for right-pinned columns). - Right-pinned columns sit at the right edge when the columns are narrower than the grid, in the header, column-group rows, body, pinned rows and aggregation footer. They used to follow the last unpinned column, leaving a gap on their right.
-
List view, Firefox: the rows container was a Tab stop of its own (Firefox makes scroll containers focusable), so Tab landed on it instead of the focused row. It now has
tabIndex={-1}.
-
Typed
apiRefrow getters.GridApi<R>(defaultGridRowModel) anduseGridApiRef<R>()typegetRow,getAllRows,getVisibleRowsandgetAllFilteredRowsas your row type.DataGridProps.apiRefaccepts a typed or an untyped ref, so existing code compiles unchanged.
- New root class
ogx--fill(height: 100%; flex: 1 1 auto; min-height: 0) when noheight,style.heightorautoHeightis set. CSS that sets the grid's height on.ogxstill wins only if it is more specific or passed asstyle.height/height. -
.ogx-column-resize-handleisright: 0(was-4px) withjustify-content: flex-end;.ogx-column-resize-handle--startisleft: 0(was-4px) withflex-start. The visible line stays on the column border. -
.ogx__header-cell--pinned-right-first,.ogx__cell--pinned-right-first,.ogx__aggregation-cell--pinned-right-firstand the first right-pinned column-group cell getmargin-left: auto; with right-pinned columns,.ogx__contentismax(100%, <total>px)wide instead of the column total. -
.ogx-list-view__rowshastabIndex="-1".
- The browser test suite and CI (deploy and publish workflows) run in Chromium, Firefox and WebKit.
- README
heightrow, Getting Started, virtualization, theming, datagrid and pinning docs describe how the grid fills its container. - PDF export: the
fontoption is shown up front with a₹example, and the size and time of very large exports are documented (50,000 grouped rows ≈ 120 MB, 10–16 s on the main thread); use CSV or.xlsxfor large exports. - README: a rejected
processRowUpdatekeeps the cell in edit mode with the typed value and callsonProcessRowUpdateError.
A correctness release: every feature area was audited against its documentation and the grid's own behaviour. Most changes are bug fixes, but many of them change observable output (formatted text, exported files, callback indices, request ranges), which is why this is a major version.
Upgrading from 2.x: read docs/migration/v2-to-v3.md. It starts with a table that maps what your app uses to the sections you need.
React 18 users: 2.1.0 crashed on React 18 (the build bundled React 19's JSX runtime). 3.0.0 works on React 18 and 19 again; see Fixed → Build & SSR.
-
The underscore hierarchy fields are no longer added to rows.
_hasChildren,_treeDepth,_isExpanded,_groupingField,_groupingValue,_descendantCountand_isGroupRow(deprecated since 1.1) are gone at runtime; useparams.rowMeta. TypeScript does not flag reads of them, becauseGridRowModelhas an index signature. -
params.rowunder row grouping and tree data is now the consumer's own row object, not a per-render copy. Code that mutatedparams.rowin a render callback now mutates the source data. As a side effect, rows are no longer re-created on every render. -
getRowIdno longer writes anidonto your rows. Rows are stored untouched everywhere (grid, callbacks,processRowUpdate,apiRef), sorow.id === getRowId(row)no longer holds. PassgetRowIdto export functions when exportingselectedRows. -
Grouped-export default labels in
exportToExcelAdvancednow match the grid. Without agroupingValueFormatter, group headers read"field: value"(the grid's documented default) instead of"Header: value". SetgroupingValueFormatteron the grouping column to control the label in the grid and in every export format. -
Cells are formatted by column
typewhen there is novalueFormatter:dateshowstoLocaleDateString(),booleanshows Yes/No,singleSelectshows the option label andimagerenders an<img>. This applies to cells,params.formattedValue, list view and text exports. - Empty filter values no longer filter, the quick filter searches only visible filterable columns, and the toolbar search box splits its text into words that must all match.
-
Aggregation ignores blank strings, booleans and arrays in
sum/avg/min/max, andmin/maxover dates return aDate(was epoch milliseconds). -
CSV files start with a UTF-8 BOM and text that looks like a formula gets a leading
'(CSV and basic Excel). Opt out withbom: false/escapeFormulas: false. -
A non-empty
selectedRowsnow wins overgroupedRowsin every export: the selection is exported flat and totals are recomputed over it. -
exportToExcelalways writes.xls; a.xlsxfile name is renamed with a warning (the file was always HTML, which Excel rejects under.xlsx). - Tab leaves the grid in one press outside edit mode (it used to step through cells). While editing, Tab still moves to the next editable cell.
-
Index values changed meaning:
colIndexis absolute among visible data columns (was window-relative), bottom-pinned rows getrowIndexafter the page rows (were 0..n),onRowOrderChangeindices are positions in therowsprop (were page-local and sorted), andonColumnOrderChangeindices are positions in the full column order. -
isCellEditablenow governs every way of starting an edit (double-click, Enter, Tab) andaria-readonly, not only Tab stops. -
processRowUpdateis called when an edit is lost (the cell scrolls out, is filtered or paged away, or another cell starts editing). Those edits used to be dropped. -
Built-in editors commit the column's value type:
singleSelectcommits the option value (not its string form),datecommits aDateforDatecells andnullwhen cleared. -
Pinned columns render in
pinnedColumns.left/.rightorder, not column order. -
Header and top-pinned rows are wrapped in
div.ogx__sticky-top, bottom-pinned rows and the aggregation footer indiv.ogx__sticky-bottom..ogx__pinned-rows--top/--bottomare no longer sticky themselves; custom CSS that targets them must move to the wrappers. - Group and pivot row ids changed format. Row-grouping ids for non-string values, tree-data auto-parent ids and pivot row ids use new, collision-free formats; do not parse them.
-
DataGridThemeProviderpins a complete light or dark palette chosen by the newGridTheme.mode, whatever the operating-system colour scheme. A provider-wrapped grid no longer switches with the OS. -
Grid buttons have
type="button"(including the exportedButton), so they never submit an enclosing<form>. Passtype="submit"explicitly where you want a submit button. -
apiRef.current.copySelectedRows()rejects when the clipboard write fails (it resolved and logged). -
dataSourcerequests changed:getRowsis now called with all-client modes, infinite scroll requests the gap after the loaded rows, and tree children are requested withendRow: Number.MAX_SAFE_INTEGER(was-1). -
Column resizing uses pointer events (tests that simulate
mousedown/mousemovemust use pointer events) and no longer caps widths at 1000px. -
Removed
GridThemeSkeleton.darkBaseColoranddarkHighlightColor, which had no effect.
-
printGridescapes the title, cell values, image URLs and alt text, and error messages in the same-origin print window, and only renders images fromhttp(s),data:image,blob:and relative URLs. -
exportToCsvandexportToExcelneutralise spreadsheet formula injection: text starting with=,+,-,@, tab or carriage return gets a leading'. New optionescapeFormulas(defaulttrue). -
exportToExcelAdvancedwrites object values as text, so{ formula },{ hyperlink },{ richText }or{ error }objects in row data can no longer become live cells or links. - Grouped
exportToJsonno longer leaksexportable: falsecolumns through subtotals and the grand total.
- Grouped CSV, basic Excel, JSON, print and PDF exports ignored
groupingValueFormatterfor group-header labels; all exporters now share one label rule, andgetGroupedExportRows()entries carry the grid's label asgroupLabel. -
exportToExcelAdvanceddates were shifted by the UTC offset (a day early east of UTC). - NaN, Infinity and Invalid Date values produced a corrupt xlsx file.
-
selectedRowswas ignored whengroupedRowswas also passed; totals are now recomputed over the selection. - Exporting a large selection froze the tab.
- CSV did not quote values containing the configured delimiter or a bare carriage return.
-
count/uniquetotals were formatted as currency or dates. - A
valueFormatterthat reads other row fields or expects aDateaborted exports with totals. - The first column's aggregate was replaced by the Subtotal / Grand Total / TOTAL label; the label is now prefixed to the value.
- Spacer columns (
isSpacer) and grid system columns (__checkbox_col__,__expand_col__,__reorder_col__,__group__) passed incolumnswere exported; they are always left out. - A
valueFormatterplaceholder for missing values is exported, as the grid shows it. - Basic Excel dropped leading zeros and mangled long numeric ids, and wrote invalid sheet names.
-
exportToExcelAdvanced:rows: 'selected'sheets contain only the selection; invalid or duplicate sheet names no longer throw;embedImagefollowsvalueGetterand falls back to the URL for SVG, WebP and AVIF; ISO date strings and numeric strings become native cells; integers have no trailing "."; totals and the summary sheet are numeric; striped rows have no gaps. - PDF: non-Latin-1 text was garbled wholesale;
#fffshorthand colours work; the row count is correct whengroupedRowsis[]; the filter summary reflects nested groups, OR and quick search, and wraps. - JSON aggregation values are numbers, as documented.
- Print keeps group-row highlights on even rows.
- Date operators
after,onOrAfter,beforeandonOrBeforedid not filter (every row passed, with a warning per row). - Date
is/notcompared raw strings; they now match the local calendar day forDatevalues, ISO datetimes and epoch numbers. - A filter item with no value filtered anyway (
>meant> 0,=hid every row). - Numeric filters treated blank cells as 0;
!=treats empty cells consistently. - A
singleSelectfilter set from the panel emptied the grid; the panel now offers the column'svalueOptions. - Opening the filter panel rewrote values (an
isAnyOfarray became"a,b") and firedonFilterModelChange. - Editing a panel row deleted filter groups and other conditions on the same column.
- Changing the operator right after typing lost the typed value, and a pending keystroke undid Clear all.
-
valueGettercolumns could not be sorted, filtered or quick-searched (including under tree data and row grouping). - The quick filter matched the row id, hidden columns, non-column fields,
[object Object]andDate.toString()text; it now matches what cells show. - A multi-word toolbar search now matches words across columns.
- The row-grouping "Group" column appeared in the filter panel.
- Number columns holding numeric strings sort numerically; NaN and Invalid Date no longer break sorting; accented strings sort next to their base letter.
- Shift-click on a sorted column kept moving it to the end of the sort; column-menu Unsort / Asc / Desc cleared the other sort keys.
- The toolbar search box stole focus back on re-render.
- The boolean filter could not select "true" directly; the panel shows the real operator of programmatic items.
-
initialState.filterwas ignored; the toolbar search and Filters button did nothing without afilterModelprop. - A
valueGetterorvalueFormatterthat throws no longer crashes the grid once the column is sorted, filtered, quick-filtered, aggregated, grouped or pivoted, or shown in list view: the value reads asundefinedand a dev warning is logged once per column. - Text typed into the FilterPanel or the toolbar search box just before the panel closes is no longer lost.
-
isCellEditabledid not block double-click and Enter editing; rejected cells now carryaria-readonly="true". -
processRowUpdateran twice for one commit (Tab, or Enter then blur). - A slow
processRowUpdateclosed an edit on another cell or discarded text typed meanwhile. - Clicking inside an open editor committed and closed it; the dropdown editor was unusable with the mouse; clicks inside editors fired
onCellClick/onRowClickand toggled selection. - An edit was lost when its row scrolled out, was filtered or paged away, or another cell started editing; it is now committed.
- Committing one edit reverted other changes to the row and reset the server row count.
- Tree-data parent rows could not be edited by double-click; Enter opened a useless editor on row-grouping group rows.
- Committing by clicking a control outside the grid stole focus back.
- The
singleSelecteditor committed typed option values as strings and showed no empty choice for an empty or unknown value. - The date editor was blank for
Dateand ISO datetime values and committed a different kind of value; clearing it commitsnull. - With a custom
getRowId, aprocessRowUpdateresult without the grid's id was not applied. - An inline
getRowIdfunction reverted committed edits on every parent re-render. -
onRowDoubleClickdid not fire on editable cells. - The boolean editor did not close on blur; IME composition Enter committed the edit; built-in editors had no accessible name.
- Double-clicking inside an editor discarded the typed value.
- A
processRowUpdatethat returns nothing is now reported clearly and the editor stays open. - Enter-to-edit starts from the
valueGettervalue. - Every cell re-rendered on every grid render.
-
apiRefmethodssortColumn,setFilterModel,setPage,setPageSize,selectRowandselectRowsdid not change the grid or fire callbacks;getSortModel,getFilterModelandgetSelectedRowsreturned stale values. -
apiRef.current.getSelectedRows()returned the previous selection insideonRowSelectionModelChange. -
getVisibleColumnsincluded hidden columns and ignored display order;getVisibleRowsandgetAllFilteredRowsomitted pinned rows;getAllFilteredRowsmissed rows under tree data and row grouping. -
getGroupedExportRows()dropped the rows of collapsed groups and ignored sort and filter. - Select-all selected filtered-out rows, could not be undone with a
dataSource, and the header checkbox showed the wrong state. -
disableMultipleRowSelectionwas ignored by checkboxes, Space and select-all. - Selection kept the ids of rows that were removed from
rows. -
getRowIdoverwrote the row's ownidfield (grid,dataSourcerows and tree children). - Duplicate row ids rendered one row twice.
-
onStateChangelooped with inline models; its payload now carriesdensityand never contains__group__;initialState.densityis honoured. -
useGridStateStoragecrashed when storage is blocked,clearStatewas undone, and storage was written on every re-render. -
useGridApiRef()did not type-check against theapiRefprop under@types/react18. - Changing
rowsandcolumnstogether (or leaving pivot mode) ran the new columns against the old rows, crashingvalueGetters. - A controlled
rowSelectionModelholding an id that is no longer inrowsfiredonRowSelectionModelChangeon every render and could loop; the pruned selection (and a corrected page) is now reported once, also under StrictMode. -
getAggregationResult,getAggregationModel,getGroupedExportRowsandscrollToIndexesare available in a parent'suseLayoutEffect. -
getVisibleColumnsreturns columns in render order (left-pinned, unpinned, right-pinned). -
apiRef.selectRow/selectRowsignore synthetic ids (group, subtotal, auto-parent and pivot Grand Total rows), and a call that does not change the selection fires nothing. The pivot Grand Total row cannot be selected.
- The pager total ignored the filter and counted pinned rows.
- Server pagination, sorting or filtering without a
dataSourcere-sliced, re-sorted or re-filtered the rows. - The
rowCountprop was read only at mount. - The default page size could be missing from
pageSizeOptions. -
slots.footerreceived the page length instead of the server total. - Shrinking data left the grid on an empty page past the end; it now shows the last page and reports it through
onPaginationModelChange. - The rows-per-page select did not show a page size missing from
pageSizeOptions, had a hard-coded accessible name, and showed "Infinity" for page size 0. - Infinite scroll skipped rows when the page advanced quickly or while loading; it now requests the missing range without duplicates.
- Infinite scroll: changing sort, filter,
pageSizeordataSourcerestarts from the first row and reports page 0. - Server sort or filter with client pagination showed only the first page.
- A
dataSourcewith the default (client) modes never loaded its rows. - The error overlay's Retry reloaded the whole page; it now re-requests rows, and shows the message of any rejected object with a
message. - Stale responses overwrote newer ones.
- Inline
filterModel,sortModel,aggregationModelordataSourceobjects refetched on every render and wiped loaded infinite pages. - An inline
rows={[]}next to adataSourcewiped the fetched rows. - "No Data" flashed before the first response, and the live region announced "Loading data... No Data".
- Server tree data: children now load with every row, keep the pager total, are not re-filtered or re-sorted under server modes, reload for expanded nodes after a refetch, and a failed children request collapses its node instead of covering the grid.
-
paginationMode="infinite"combined withpaginationsliced the rows. -
loadingwith rows present showed nothing; it now shows a progress bar (orslots.loadingOverlay) over the rows. - A
dataSourceresponse that cannot be processed shows the error overlay instead of loading forever. - Row grouping with a
paginationMode="server"dataSourceloads every row in one request (with a dev warning) instead of grouping only the first page.
- The aggregation footer double-counted expanded groups. Under row grouping it aggregated the visible rows: group rows, which already carry their subtotals in the same fields, plus the expanded leaves. Expanding a group inflated
sum, andcount/avg/uniquewere wrong even when every group was collapsed. The footer,getAggregationResult(), theaggregationResult/rowCountpassed toslots.footer, and grouped-export grand totals now use the filtered data rows, independent of expansion. - Group rows lost their label and expand toggle when the first column was hidden, and showed them mid-row when that column was reordered or another column was pinned left; the hierarchy UI now goes on the leftmost column on screen.
- Subtotals, the "(n)" count and
descendantCountcounted filtered-out rows; groups left empty by the filter are hidden. -
getAggregationPositionwas ignored; it is now called for every group and once withnullfor the grand total. -
groupingColDefhad no effect under tree data. -
pinnedRowsdisappeared under tree data and row grouping. - Tree paths containing
/collided with deeper paths. - Grouping values
nulland'null',1and'1'fell into the same group. - Detail-panel callbacks ran for every rendered row, including group rows; they now run only for expanded data rows, and a throwing
getDetailPanelContentis contained to its panel. - A throwing
getDetailPanelHeight,groupingValueFormatterorgetAggregationPositionfalls back to the default instead of crashing. -
getGroupedExportRowsandgetAllFilteredRowsfollow screen order when sorting by the hierarchy column. -
defaultGroupingExpansionDepthexpands and loads lazy server tree nodes.
- Footer totals now use the column's
valueFormatterforsum/avg/min/max;count/uniquestay plain numbers, and datemin/maxshow as dates. - Group rows formatted counts with the column's currency or unit formatter.
- Blank cells, booleans and arrays counted as 0 in
sum/avg/min/max;count/uniquecounted blank strings. - Aggregates of
valueGettercolumns summedrow[field]instead of the computed values. -
aggregable: falsecolumns were aggregated whenaggregationModelorpivotModelnamed them. - Footer totals now come from the server whenever a
dataSourcedrives the rows, including infinite scroll and server-only filtering. - An inline
aggregationModelrefetchedgetRowsand cleared server totals on every render; stale server totals no longer show under a new function, filter or sort, while pending, or after a failure. - Pivot mode crashed with
RangeErroron datasets over ~110k rows with aminormaxvalue field, andcountcounted numeric values only. - Pivot: filters and the quick filter apply to source rows, filters on generated value columns apply to pivot rows, and the Grand Total matches the rows shown, stays last when sorting and is not selected by select-all.
- Pivot:
getRowIdcollapsed the pivot into one row;treeDataandrowGroupingModelcrashed or regrouped it; adataSourceproduced broken output (pivot is now ignored with a warning). - Pivot: the same field twice (for example
sumandcount) doubled values; a sourcevalueFormatterthat reads row data crashed the grid. - Pivot: row-label columns keep the source column's formatter, renderer, type and alignment; column-field headers are formatted; numeric column keys sort numerically.
- Pivot: an empty pivot keeps its value columns and shows the no-rows overlay; a row field named
idno longer overwrites row ids; pivot rows no longer share ids with source rows. - Pivot: leaving pivot mode restores the column order; a controlled
columnOrderno longer puts value columns before the label column, and the Columns panel can reorder generated columns. - Pivot:
getAggregationResult()andslots.footerno longer report totals over pivot rows, and the Summaries panel no longer lists generated columns. - PivotPanel: editing or removing one of two chips on the same field affected both; it now follows
aggregable: false,availableAggregationFunctionsandgroupable: false, and its controls have descriptive names.
- Columns pinned out of column order overlapped or left gaps; header, body, footer and keyboard navigation now agree on
pinnedColumnsorder. - Column widths ignored
minWidth/maxWidthfor fixed, resized and pinned columns; percentage widths cascaded; pinned%,flexandautocolumns were 100px wide. - Top-pinned rows covered the header when column groups were used; the aggregation footer was hidden behind bottom-pinned rows.
- Keyboard navigation and
scrollToIndexesleft the target row under the footer or bottom-pinned rows;scrollToIndexes({ colIndex })left the column under system or right-pinned columns. -
'auto'detail panels are laid out at their real height;getDetailPanelHeightreturning 0 renders 0px (was 200px). -
onRowsScrollEndfired on every scroll event near the bottom (including horizontal scrolling) and never for a list shorter than the viewport; it now fires once per arrival, in grid and list view. - The grid re-measures its viewport and restores the scroll position after list view is switched off.
- Infinite-scroll placeholder rows appeared before the last loaded row.
- Row-spanned cells painted over the sticky drag and expand columns.
- A new
overscanRowCountapplied only after scrolling. - Pinned-section edge classes and shadows were not applied.
-
autoHeightfiltered visible rows in quadratic time.
- Body cells lost
flex, percentage andautowidths (misaligned with headers) as soon as any column usedcolSpan. -
colSpan/rowSpanrecomputed on every resize tick (a ~2 s freeze per rows change at 100k rows) and cost time even when unused. - Spans counted hidden columns, crossed into another pinned section, and crossed between pinned and scrolling rows.
- A span broke when its origin row or column scrolled out of the render window.
- Huge, Infinity, NaN or fractional span values hung or corrupted the grid;
aria-colspan/aria-rowspanreport the clamped value. - A throwing
colSpan/rowSpancallback unmounted the grid; it now falls back to no span with a dev warning. Infinite-scroll placeholder rows are never passed to span callbacks. -
colSpanreceived the raw field value instead of thevalueGetterresult, and acolIndexdifferent fromrenderCell's. -
colSpanplusrowSpancovered only the origin column in following rows. -
rowSpanpainted over an expanded detail panel; a merged cell was capped by its origin column'smaxWidth. - Keyboard navigation lost focus on covered cells; arrows now cross a merged cell in one step.
- Column group header rows did not follow member columns under
flex/%/autowidths, hiding, reordering and pinning; pinned groups scrolled away; non-contiguous groups caused duplicate-key errors. -
GridColumnGroup.headerClassNamewas ignored. - Group header cells expose
aria-colspan/aria-colindex, filler cells are hidden from assistive technology, andaria-rowcountcounts group header rows. -
rowSpannever starts on or crosses group, subtotal, tree auto-parent or pivot Grand Total rows.
-
onRowOrderChangeindices depended on page, sort, filter and pinned rows; rows with id0and columns with field''could not be reordered. - With
rowReordering, pinned rows had no handle cell and misaligned; pinned and group rows could be dragged and dropped onto. - Row and column drags did not start in Firefox; drags from outside the grid were treated as reorders; a drag broke after its source row scrolled away.
- Header drag-reorder moved the wrong column after columns were added, when
__group__appeared, or with a partial controlledcolumnOrder; dropping on a pinned column sent the dragged column to the far end. -
pinnable: falsealso blocked drag-reorder. - Controlled or
initialStatecolumnOrderwas ignored whendisableColumnReorderwas set. - The Manage-columns panel listed columns in definition order, and its Reset did nothing under a controlled
columnOrder. - Resize clamped to a hidden 1000px maximum and snapped narrow columns to 50px; a click on the handle resized, sorted or turned a flex column fixed; ending a resize over the header sorted the column; right-pinned columns resized from the wrong edge; a resize kept running after unmount.
-
rowReorderinghad no effect but showed handles in pivot mode.
- Keys typed into inputs inside cells and detail panels were taken by the grid; clicking an input rendered by
renderCelllost focus. - Focus was lost when the focused cell scrolled out, on window switch, when tabbing out and back (the last cell is now restored), and when rows or columns were removed, filtered or hidden.
- Tab could not reach detail-panel content.
- Arrow keys stopped on hidden columns and ignored the pinned visual order; Space and arrows at the edges scrolled the viewport.
- ArrowDown onto a row with a tall detail panel scrolled the row out of view; Home onto an unpinned checkbox column did not scroll it into view.
- Enter / Space on the select-all header sorted by
__checkbox_col__instead of selecting all. - Keyboard sorting from a header ignored
multiSortand Shift. - Reorder, expand and checkbox columns were not real focus stops; pinned rows could not be edited.
- DOM focus did not return to the cell after a re-click or an edit.
- A row with id
"HEADER"was treated as the header row. -
aria-rowindex,aria-rowcount,aria-colindex,aria-colcountandaria-sortwere wrong;colIndexinonCellClick,renderCell,cellClassNameandrenderHeaderwas relative to the render window. - The detail panel had an invalid row/gridcell structure.
- A cell whose
renderCellthrew stayed broken; it now recovers when the renderer or value changes. -
GridTooltipplacementleft/rightdid not work; it now opens on focus, usesrole="tooltip"witharia-describedby, closes on Escape, uses theme colours and cleans up its timers. - Columns-panel checkboxes are announced with their column name.
- Copy (Ctrl/Cmd+C and
apiRef.copySelectedRows()) included hidden andexportable: falsecolumns in definition order; it now copies the visible columns in screen order. - Copy read
row[field]instead of thevalueGettervalue before formatting. - Values containing tabs, line breaks or quotes broke the pasted table; they are now quoted.
- Only selected rows on the current page and in expanded groups were copied; now every selected row that passes the filter is copied, including pinned rows and collapsed groups.
- Copying right after
selectRowsor fromonRowSelectionModelChangecopied the old selection. - Focus moved to
<body>after copying. - Ctrl+C did not copy with Caps Lock on or with non-Latin keyboard layouts, and it copied while focus was outside the grid or a text selection was active.
- Fields starting with
__(for example__typename) were not copied. - The Ctrl+C listener was registered more than once.
-
slots.toolbarrejectedmemo,forwardRef, class andlazycomponents; switching the toolbar did not remount cleanly; an inline toolbar lost its state. -
slotProps.toolbarrender props had implicitly-anyparameters understrict;slots.paginationtyped withPaginationPropsdid not type-check;groupingColDefrequired afield. - The column menu offered Hide for
hideable: falseand pin actions forpinnable: falsecolumns (including the grouping column). - Manage columns worked only once (after the toolbar button or Escape closed the panel) and did nothing with a custom toolbar; it now opens a standalone panel. The Columns panel closes on Escape and
onColumnsPanelClosefires on every close. - The Summaries panel offered functions outside
availableAggregationFunctionsand omittedunique. - Toolbar panels misaligned with a classic (non-overlay) scrollbar.
- List view passed loading placeholders to
renderCell, paged incorrectly under tree data, ignored server totals, never firedonRowsScrollEnd, and gaverenderCellnovalue,formattedValue,colDeforrowMeta;aria-rowindexrestarted every page. - List view ignored
loading,slots.loadingOverlay,slots.noRowsOverlayandslots.footer, and showed "No Data" while loading;listViewwithoutlistViewColumnrendered nothing (it now falls back to the grid with a dev warning). - List view had no expand chevron or depth indent for tree-data and group parents.
- List view:
renderCellerrors are contained, group rows get no checkbox and fall back to their label, and "N items" counts data rows. - Two column-menu hides or pins in the same tick both apply.
-
DataGridThemeProviderleft part of the palette to the OS colour scheme, sodarkThemein a light browser (or light presets in a dark browser) was unreadable. - Brand presets and
colors.primarydid not recolour the toolbar, filter panel, column menu, selection and focus ring. -
grid.rowHeight*andgrid.headerHeightdid not size rows and the header (compactThemenow gives 36px rows and a 40px header). -
toolbar.*,scrollbar.*,overlays.itemDanger*andgrid.cellFocusBorderhad no effect.
-
The package works on React 18 again. The 2.1.0 build bundled React 19's JSX runtime, which React 18 cannot render (
recentlyCreatedOwnerStackserror on first render). A build check now fails if the JSX runtime is bundled. -
DataGridcrashed under server rendering (renderToString, Next.js, Remix) because a panel readdocumentduring render. -
docs/migration/now ships in the npm package; the v2 → v3 guide was missing fromnode_modules.
- The README,
llms.txtand the AI context file no longer claim the stylesheet loads automatically:import '@opencorestack/opengridx/styles'is required. - The README props tables list only real props, with correct defaults;
llms.txtexamples compile and use real APIs. - Server-side guides describe the real requests; "millions of rows" is replaced by the browser's height limit (about 645k rows at 52px in Chromium).
- The theming docs no longer reference a non-existent
themesexport; the API reference and component pages were corrected against the code.
- Inline
columns(a new array each render with the same definitions) no longer re-runs client filtering and sorting. - The quick filter no longer re-reads every cell on each keystroke.
- Faster date formatting.
-
GridColDef.valueSetter(GridValueSetterParams) maps an edited value back onto the row for editablevalueGettercolumns. -
renderEditCellreceivesonValueChange,onCommitandonCancel(GridRenderEditCellParams); errors in custom editors are contained to their cell. -
formattedValueandrowMetaare passed torenderEditCelland to the function form ofcellClassName, as they already were torenderCell. - Default cell formatting by
GridColDef.typewhen there is novalueFormatter(see Breaking). -
GridFilterOperatorvalues'=','after','onOrAfter','before'and'onOrBefore'; a date input andsingleSelect/ multi-select value controls in the filter panel; the panel notes conditions it cannot show. -
onColumnOrderModelChange(columnOrder)fires with the whole new order after every change, including the Columns panel's Reset. -
disableClipboardCopyprop; Ctrl+C events already handled (defaultPrevented) are left alone. - Export options:
CsvExportOptions.escapeFormulas,CsvExportOptions.bom,ExcelExportOptions.escapeFormulas,PdfExportOptions.font(a Unicode TrueType font), andgetRowIdon every export function. -
GridGroupedExportRow.groupLabel;GridRowMeta.isGroupFooterand theogx__row--group-footerclass for subtotal rows shown in'footer'position. - Keyboard: Shift+Space selects the focused row, Ctrl/Cmd+A selects all, Alt+ArrowRight / Alt+ArrowLeft expand and collapse, Alt+ArrowDown or Ctrl+Enter opens the column menu, Alt+ArrowLeft / Alt+ArrowRight on a focused header resizes it (Shift for 50px steps).
- Enter on a non-editable cell: on a row with children (group rows, tree-data parents) it only toggles expansion, without firing
onRowClickor selecting; on any other row it acts like a click (onRowClick, click-to-select). - Keyboard navigation between rows in list view.
- Column resizing by touch and pen; resize separators expose
aria-valuenow/aria-valuemin/aria-valuemax. -
aria-level/aria-expandedon hierarchy rows andaria-multiselectableon the grid. -
GridTheme.mode('light' | 'dark'),colors.white,colors.black,colors.gray(50–900),grid.cellFontSizeandgrid.headerFontSize. - Type exports:
GridRenderEditCellParams,GridValueSetterParams,GridThemeToolbar,GridThemeOverlays,GridThemeScrollbar,GridThemeSkeleton,GridThemeGrayScale,GridGroupedExportRow,GridRowScrollEndParams,GridColumnOrder,GridDataSourceState,GridDetailPanelHeight,GridAggregationPosition,GridSlots,GridSlotProps, and the props typesCellProps,RowProps,HeaderProps,SkeletonProps,FilterPanelProps,PaginationProps,GridTooltipProps,ButtonProps,InputProps,CheckboxProps. -
Paginationcomponent export. - Optional
rowIdprop onRow, and optionalariaRowIndex/ariaColIndex/columnIndexMapprops on the exportedRow,CellandHeadercomponents. - Optional props on the exported components:
Cell.isPinnedEdge(adds the pinned-section edge class),Cell.valueError(shows avalueGettererror as a cell error),Row.onDetailPanelHeightChange(rowId, height)(measured height of an'auto'detail panel) andRow.isCellEditable. - Dev warnings: the grid stylesheet is not loaded,
pinnedRowsignored under hierarchy, content taller than browsers can scroll, duplicate row ids, empty or duplicate tree paths,valueGetter+editablewithoutvalueSetter, pivot combined withdataSource/ tree data / row grouping,listViewwithoutlistViewColumn. - CSS hooks:
ogx__sticky-top,ogx__sticky-bottom,ogx__header-cell--pinned-left-last,ogx__header-cell--pinned-right-first,ogx__cell--pinned-left-last,ogx__cell--pinned-right-first,ogx__aggregation-spacer,ogx__aggregation-spacer--pinned,ogx__aggregation-footer--loading,ogx__loading-bar,ogx__loading-overlay--over-rows,ogx__loading-overlay--custom,ogx__cell-image,ogx-list-view__expand,ogx-list-view__loading,ogx-filter__hidden-note,ogx-filter__value-multiselect,ogx-col-group-cell--pinned,ogx-col-group-cell--pinned-left/--pinned-right,ogx-column-resize-handle--start,ogx-tooltip--left/--right.
- Sorting is type- and language-aware (
Intl.Collatorwith numeric collation, dates parsed, mixed kinds ordered by kind). - The quick filter searches only visible, filterable columns, using
valueGetterandvalueFormattertext. - Pinned columns render in
pinnedColumns.left/.rightorder; the column menu appends, so the most recently pinned column sits next to the scrolling area. -
onColumnOrderChangeindices are positions in the full current column order (including hidden columns and__group__). - Header drag-reorder works inside a column group when
columnGroupingModelis set (it was disabled for every column); the toolbar and Columns panel can no longer move a column out of its group. - A synchronous
processRowUpdateresult is applied in the same event. -
apiRef.current.copySelectedRows()rejects when the clipboard write fails. -
min/maxof dates return aDate; the defaultavgformat in pivot cells is locale-grouped (56,666.67); the footer exposesaria-busywhile server totals load. -
GridToolbarshows the Summaries button only whenonAggregationModelChangeis passed; toolbar triggers havearia-haspopup="dialog"andaria-expanded. -
GridTooltiprenders into the theme provider (.ogx-theme-provider) withposition: fixedinstead ofdocument.body. -
useGridApiRef()returns aMutableRefObject<GridApi>that is nevernullbefore mount;GridInitialStateaccepts partialcolumns. - The exported
Headerreports a focused header cell asfocusedCell.id === null(was'HEADER'). - The exported
Cellno longer stops propagation of the double-click that starts an edit, so it reaches the row (onRowDoubleClick). -
onStateChangefires on value changes only (it fired for every new prop identity). WithuseGridStateStorage, remount the grid with<DataGrid key={storageKey}>when the key changes; storage is read during the first render, so SSR apps with saved state should render the grid on the client only (see the migration guide, §26). -
GridAggregationPositionis'inline' | 'footer' | null(it was an unused'footer' | 'inline' | 'both'). - Accent colours use CSS
color-mix()(Chrome 111+, Safari 16.2+, Firefox 113+);darkThemegives the toolbar a solid#1e293bbackground.
- Rows can be typed with your own interfaces and type aliases: every public generic is constrained by the new
GridValidRowModel(object) instead ofGridRowModel, and an untypedGridColDef[]can be passed next to typed rows. The README example now compiles understrict. -
DataGridand the export functions,usePivotanduseAggregationhave a typed and an untyped-columns overload. UseDataGridProps<R>rather thanReact.ComponentProps<typeof DataGrid>. -
GridColDefrow callbacks are declared as methods, soGridColDef<Row>is assignable toGridColDefwhenRowis a type alias or extendsGridRowModel. For an interface without an index signature it is not: type the array asGridColDef<Row>[], or leave the columns untyped and use the untyped-columns overload. - Calling
col.renderEditCell(params)yourself with aGridRenderCellParamsis now a type error: its parameter isGridRenderEditCellParams(the old params plusonValueChange,onCommit,onCancel). -
usePivotis declared to returnPivotResult;UsePivotReturnis now a type alias of it (same fields, no declaration merging). - Exported
Header:focusedCell.idisGridRowId | null. ExportedCell:onEditStop(cancel?, field?). ExportedRow:onEditStopparams gain optionalid/field;rowSpanningCaches.hiddenCellOriginMapisRecord<GridRowId, Record<string, GridRowId>>(wasRecord<number, Record<string, number>>). - Slots are typed with the props the grid passes: new
GridToolbarSlotProps,GridPaginationSlotProps,GridOverlaySlotProps,GridFooterSlotProps. KnownslotPropskeys are type-checked. - Export option types are generic (
CsvExportOptions<R>, …), sogetRowIdreceives your row type. - New root exports:
GridValidRowModel,DataGridUntypedColumnsPropsand the slot props types. - Removed unused, never-exported internal types (
GridEditCellProps,GridRowModes,GridRowModesModel,GridDetailPanelContent,GridDetailPanelState,GridVirtualizationState,GridRenderContext,GridAggregationFunction). - The repository is type-checked in CI (
npm run typecheck: library, tests and demo).
-
GridThemeSkeleton.darkBaseColorandGridThemeSkeleton.darkHighlightColor(they had no effect). - The runtime underscore hierarchy fields on rows (see Breaking).
- The OS dark-mode (
prefers-color-scheme) rules for the expand icon and detail panel; the theme palette colours them.
Fixes from a consumer defect report (migration of an ERP report writer from ag-grid) plus issues found while verifying it.
-
Row-grouping subtotals were wrong when data contained nulls —
useRowGroupinghad its own copy of the aggregation functions that coercednullto0(Number(v) || 0). For[10, null, 20]a group showedmin0,avg10 andcount3. It also ignoreduniqueandavailableAggregationFunctions. Group rows now use the same shared functions as the footer (lib/utils/aggregation), giving 10 / 15 / 2. Grouped exports (getGroupedExportRows) inherit the fix. -
min/maxcould throwRangeErroron very large datasets —Math.min(...values)exceeds the engine's argument limit at roughly 120k values. Replaced with loops. -
Expanded groups collapsed on every data change — any new
rowsarray (an inline edit, a live refresh) or a new-but-equalrowGroupingModelarray reset expansion to the default. User choices are now overrides keyed by the grouping config, and are discarded only whenrowGroupingModelordefaultGroupingExpansionDepthchanges value. -
getVisibleRowssorted the memoized tree's child arrays in place under row grouping; it now sorts a copy. -
Tree data reset user expansion whenever rows changed (when
defaultGroupingExpansionDepthwas non-zero). With server-side tree data, a lazily expanded node could collapse as soon as its children arrived. Tree data now uses the same override model as row grouping, and the default expansion applies on the first render instead of after an effect. -
onRowExpansionChange(server-side child fetch) was called inside a React state updater, which React may run twice (always in StrictMode). It is now called exactly once per expand. -
useTreeData().getVisibleRows()crashed whenfilterModelwas omitted, although the param is optional. -
Rows were drawn in the wrong place when rows were pinned to the top.
useGridVisibleRowssubtracted the pinned count from render-window indices that already exclude pinned rows. With N top-pinned rows, every row past the first screen was drawn N rows too low, and the bottom N rows of the viewport were left blank. -
Keyboard navigation and
apiRef.scrollToIndexesleft the target row hidden below the fold. The scroll math ignored the sticky header, so the row ended up about one header height below the visible area. Keyboard navigation also mapped pinned rows onto the wrong layout index. Both now sharescrollRowIntoViewand never scroll to pinned rows, which are always visible. -
valueFormatterwas dropped for every column once row grouping was on — the injected hierarchy renderers fell back to the raw value. They now render the formatted value. (Report D3) -
slots.footer,slots.noRowsOverlayandslots.loadingOverlaywere typed and documented but never rendered. All three are now wired.footerreplaces the pagination area, as documented, and also receivesaggregationResult,rowCount,paginationModel,onPaginationModelChangeandapiRef. (Report D2)
-
onRowDoubleClickonDataGridProps, in grid and list view. (Report D6) -
GridRenderCellParams.formattedValue— thevalueFormatteroutput, now passed torenderCell. -
ColumnVisibilityPanelandColumnVisibilityPanelPropsexported, as the docs already claimed. (Report D5) -
ExcelAdvancedExportOptions.groupedRows— grouped reports inexportToExcelAdvanced, with Excel row outlining, numeric subtotals that keepnumFmt, andgroupHeaderFillColor/groupSubtotalFillColor. (Report D7) -
Development-mode warnings (skipped when
NODE_ENV === 'production'):- when the grid renders every row of a dataset larger than 200 rows because its container has no bounded height (root cause of report D1);
- when
paginationis passed with an activerowGroupingModel(report D4).
-
Real-browser test project — Vitest browser mode with Playwright/Chromium (
npm run test:browser;npm teststays on jsdom).@vitest/browser-playwrightandplaywrightare now declared devDependencies. -
CI:
.github/workflows/npm-publish.ymlruns unit and browser tests before publishing. It previously ran only lint and build, so a failing test could not block a release.
-
features/virtualization.md— new "The grid needs a bounded height" section. It explains the flexmin-height: 0trap and replaces the claim that grouping "works seamlessly" with virtualization. Report D1 turned out to be this: grouped rows are virtualized (a 25,000-row group renders ~20 DOM rows in a bounded container, verified in Chromium), but an unbounded container renders everything, and pagination had been hiding it. Also corrects the claims that columns are not virtualized and that the default overscan is 5. -
features/tree-data-grouping.md,features/sorting-pagination.md— pagination is ignored under row grouping (report D4); expansion and aggregation semantics. -
features/export-guide.md— CSV and basic Excel append two aggregation rows, labels then values (report D8); grouped advanced-Excel export. -
customization/slots-api.md—footerprops.components/column-visibility.md— the examples showed<ColumnVisibilityPanel />with no props, which cannot work; replaced with real usage. -
roadmap.md— removed "upcoming" items that had already shipped (npm publishing, native PDF export, GitHub Pages deployment).
-
Adaptive overscan based on scroll velocity —
useGridScrollSyncnow tracks scroll velocity (px/ms) on every scroll event and maps it to a dynamic overscan tier (3 / 5 / 12 / 20 / 30 rows). All three values (scrollTop,scrollLeft,overscanRows) are bundled into a single state update per RAF frame souseGridVirtualizationrecomputes exactly once per frame. 200 ms after scrolling stops, the overscan decays back to the floor set byoverscanRowCount. -
overscanRowCountprop — newDataGridPropsfield (number, default3). Sets the minimum overscan floor; the adaptive algorithm always produces a value ≥ this prop. Increasing it pre-renders more rows at rest; decreasing it saves idle memory.
Comprehensive accuracy audit — 32 issues corrected across 19 files:
-
Wrong defaults corrected:
pageSizeOptions([10,25,50]→[10,25,50,100]),pinCheckboxColumn/pinExpandColumn(false→true),noRowsLabel('No rows'→'No Data') — inAPI_REFERENCE.md,components/datagrid.md,features/sorting-pagination.md,features/selection.md,components/empty-state.md -
Missing API surface added:
getAllFilteredRows()andgetGroupedExportRows()added toGridApitable;groupLabelfield added toGridRowMeta;groupedRowsoption added toPdfExportOptions;'unique'added to aggregation function type incomponents/aggregation-footer.md -
Non-existent API removed or corrected:
'both'is not a validgetAggregationPositionreturn (valid:'inline' | 'footer' | null);disableColumnResizeDataGrid prop,columnResizeHandle/columnVisibilityPanel/columnGroupHeader/tooltipslots,onColumnWidthChangecallback,disableReorderonGridColDef,showQuickFilterinGridToolbarProps,toolbar/toolbarPropsDataGrid props — all documented as real but never existed; corrected throughout -
Stale content updated:
use-grid-scroll-sync.mdanduse-grid-virtualization.mdparams/returns updated from oldscrollPosRef/scrollTickpattern to currentscrollTop/scrollLeft;grid-row-meta.mdupdated from "will be removed in v2" to "was removed in v2" (correction, 2026-09-16: this specific change was itself inaccurate — the shim was never actually removed in v2.0; reverted to reflect that it is still present and deprecated);roadmap.mdcolumn virtualization claim corrected;virtualization.md"Aui DataGrid" placeholder fixed anddensityprop section added;upgrade-guideversion and slots API corrected
-
Infinite re-render loop with
rowGroupingModel+groupingColDef— when both were active,activeColumnsandeffectivePinnedColumnswere rebuilt as new object/array references on every render. The new references cascaded throughuseRowGrouping's memoized values and auseEffectthat calledsetExpandedGroupIds, triggering a render → new references → effect →setStatecycle that never settled (Maximum update depth exceeded). Both values are now wrapped inuseMemoso their references only change when their actual inputs change.
-
uniqueaggregation function documented in changelog —unique(count of distinct non-null values viaSet) has been a built-in aggregation function since v1. The v2.0.0 changelog entry foravailableAggregationFunctionsnow explicitly lists all six built-in names:sum,avg,count,min,max,unique.
-
Removed dead public API:
onPinnedRowsChangeandonRowGroupingModelChangeare removed fromDataGridProps. Both props accepted callbacks that were never invoked — no pin/unpin-row UI and no drag-to-group UI exist. Callers that passed these callbacks should simply remove them; grid behavior is unchanged.
-
densitywired —density?: 'compact' | 'standard' | 'comfortable'now sets the--ogx-row-heightCSS variable (compact = 32 px, standard =rowHeight, comfortable = 72 px). Previously accepted but silently discarded. -
disableRowSelectionOnClick— clicking a row no longer triggers selection when this prop istrue. Previously wired to internal state only;onRowSelectionModelChangeis now also fired. -
disableMultipleRowSelection— clicking a row while this prop istruecaps selection to that single row (deselects others). Clicking an already-selected row deselects it. -
groupingColDefimplemented — passinggroupingColDefwhenrowGroupingModelis active now creates a dedicated__group__column at position 0, auto-pinned left. Previously the prop was accepted but had no runtime effect. -
groupable: falsehonored — columns withgroupable: falseare now skipped when building the grouping tree inuseRowGrouping. Previously this flag was silently ignored. -
groupingValueFormatteronGridColDef— new optional fieldgroupingValueFormatter?: (params: { field: string; value: unknown }) => stringcustomizes the label shown for group-header rows at that level. The formatted string flows throughGridRowMeta.groupLabel. Falls back to"${field}: ${value}"when omitted. -
availableAggregationFunctionshonored — per-columnavailableAggregationFunctions?: string[]now gates which aggregation functions are computed inuseAggregation. Functions not in the allowed list are skipped for that field. Built-in function names (all available since v1):sum,avg,count,min,max,unique(count of distinct non-null values). -
multiSortprop — newmultiSort?: booleanonDataGrid. Whentrue, every click on a sortable column header appends/cycles that column in the sort model instead of replacing it — no Shift key required. Shift+click continues to work as an append gesture regardless of this prop. -
Multi-sort shift-click — shift-clicking a column header appends the column to
sortModelrather than replacing it. A numbered priority badge appears next to the sort arrow when more than one sort key is active. Plain click still replaces with a single-key sort (unlessmultiSortis set). -
descriptiontooltip —description?: stringonGridColDefnow renders as the nativetitleattribute on the header cell, providing a browser tooltip on hover.
-
Grouped export — all five export functions (
exportToCsv,exportToExcel,exportToJson,printGrid,exportToPdf) now accept an optionalgroupedRows?: GridGroupedExportRow[]option. When provided, the export preserves the row-grouping structure: group-header rows, indented leaf rows, per-group subtotals, and a grand total footer. Flat behavior is unchanged when the option is absent, making this a fully backward-compatible addition. -
GridApi.getGroupedExportRows()— new method that returns a flat ordered list ofGridGroupedExportRowentries (types:group-header | leaf | group-subtotal | grand-total) by traversing the active row-grouping tree. Returnsnullwhen row grouping is not active. -
GridGroupedExportRowtype — new public interface describing each entry in the grouped export list (type,depth,groupField?,groupValue?,aggregatedValues?,row?).
-
GridToolbarColumns panel — columns withhideable: falseare now excluded from the panel list by default. Previously they appeared as permanently disabled rows, adding visual noise for no benefit (users can never toggle them). The column still renders normally in the grid body and in exports — only the Columns panel list changes.
-
GridToolbarProps.showNonHideableColumns?: boolean— opt-out prop. Set totrueto restore the old behavior and showhideable: falsecolumns in the panel as disabled rows. Default:false. No other API surface changed; this is a backward-compatible addition.
-
CellErrorBoundary— movedrenderFn()call into a module-scopeCellRenderTargetchild component so the throw happens in a descendant; a boundary cannot catch errors thrown inside its ownrender(). -
CellErrorBoundary— changedresetKeyfromvaluetorowobject reference so the boundary resets whenever fresh row objects are provided, not just when the cell value changes. Enables "Restore" to clear error state correctly. -
exportToPdf— aggregation footer now computed directly from the exported rows in the demo, bypassing theuseEffect-basedgetAggregationResult()path that could silently returnnulldue to stale closures. -
exportToPdf— aggregation footer text color is now explicitly[0, 0, 0](black) to prevent text from appearing invisible against the light gray footer background. -
exportToPdf— switched to standaloneautoTable(doc, opts)function to avoid unreliable ESM prototype patching in Vite dynamic imports.
-
GridApi.getAllFilteredRows()— returns all filtered and sorted rows regardless of pagination. Use this in export handlers to include every row, not just the current page. -
CellErrorBoundaryDemo— 100 rows, 10 specific rows throw on demand (rows 10, 20 … 100), with correct Restore behaviour.
-
CellErrorBoundarynow accepts arenderFn: () => React.ReactNodeprop and calls it inside its ownrender(), so React correctly catches thrown errors fromrenderCell. Previously the call happened in the parent's render phase — before the boundary — causing the entire app to crash instead of showing a per-cell error indicator. -
exportToPdfdouble-cast throughunknownto satisfy the installedjspdfTypeScript types (the library's class does not exposeautoTable/lastAutoTablein its official type definitions). - Added
jspdfandjspdf-autotabletodevDependenciesso local development and demo builds work without a manualnpm install. - Deploy workflow now copies
index.html→404.htmlso GitHub Pages serves the SPA for direct URL access to any demo route.
-
exportToPdf(rows, columns, options?)— generate a styled PDF report from grid data. Optional peer deps required:npm install jspdf jspdf-autotable. Features: optional branded header (logo, title, filter summary, row count), multi-page data table with repeating column headers, alternating row shading, aggregation footer row. Seedocs/features/pdf-export.md. - New exported type:
PdfExportOptions
-
GridRowMetainterface — hierarchy metadata (hasChildren,treeDepth,isExpanded, etc.) now available viaparams.rowMetainrenderCell. Seedocs/architecture/grid-row-meta.md. -
GridLocaleTextinterface andlocaleTextprop onDataGrid— override all pagination strings for i18n. -
CellErrorBoundary—renderCellerrors are now caught per-cell; the grid continues rendering. Fallback shows⚠in the affected cell. - 25 new unit tests for
useGridRowPipeline,useGridControlledState, anduseGridKeyboardNavigation. -
CLAUDE.mdat project root — AI coding assistant context file. -
docs/architecture/grid-row-meta.md— architecture doc forGridRowMetaandrowMetaMapdata flow.
-
GridRowModelno longer declares_hasChildren,_treeDepth,_isExpanded,_groupingField,_groupingValue,_descendantCountas typed properties. These fields remain on the row object at runtime (backward-compat shim; scheduled for removal in v2.0 at the time of this release — see correction below). Access hierarchy metadata viaparams.rowMetainstead.
-
params.row._hasChildrenetc. — useparams.rowMeta?.hasChildren. Runtime shim scheduled for removal in v2.0 at the time of this release.
Correction (added retroactively): the runtime shim removal described above did not ship in v2.0.0–v2.0.4. The underscore fields are still injected at runtime as of v2.0.4. Removal is deferred to a future major version. See
docs/architecture/grid-row-meta.md.
-
Toolbar render prop slots:
GridToolbarnow accepts render props to replace individual toolbar controls without replacing the entire toolbar. All panels (columns, filters, aggregation) continue to open and close normally — only the trigger element is swapped.-
renderColumnsButton(props)— replace the Columns icon button -
renderFilterButton(props)— replace the Filters icon button; receivesactiveCount -
renderAggregationButton(props)— replace the Summaries icon button; receivesactiveCount -
renderExportButton()— inject an Export button after the Aggregation button (no built-in exists) -
renderQuickFilter(props)— replace the built-inGlobalSearchinput; receivesvalueandonChange
-
-
GridToolbar.className: Accepts an additional CSS class on the toolbar root<div>for full visual override without replacing the component. -
Exported types:
ToolbarButtonRenderPropsandToolbarQuickFilterRenderPropsare now exported from the package for TypeScript consumers. -
Toolbar Customization demo: new demo page at
/toolbar-customizationshowing a branded dark toolbar and a plain light toolbar, each using all five render props.
-
Aggregation footer width misalignment with
flexcolumns: When aGridColDefusedflex(with or without awidthfallback), the aggregation footer cells usedcolumnWidthsto resolve rendered widths. That map is a user-resize override cache — it contains no entry for columns that have not been manually resized, so flex-columns fell back to their rawcol.widthprop value, producing cells that were narrower than the actual column. Fixed by computingresolvedColumnWidthsinDataGridthat seeds from the layout-computed flex widths (unpinnedColsWithWidth,leftPinnedCols,rightPinnedCols) and then overlays any user-resize overrides.GridAggregationFooternow receivesresolvedColumnWidthsinstead ofcolumnWidths.
-
getRowIdnot applied to internal row store:DataGridderivedeffectiveGetRowIdcorrectly but never used it before rows entered the internal state.createInitialStateand theSET_ROWSreducer both indexed byrow.iddirectly, so any consumer passing rows without a nativeidfield would silently collide all rows onundefinedin the lookup map, produceundefinedReact keys, and trigger a "Each child in a list should have a unique key prop" warning. Fixed by normalizingactiveRowsthrougheffectiveGetRowIdintonormalizedRows(viauseMemo) immediately aftereffectiveGetRowIdis derived. The normalization is a no-op when the default(row) => row.idis used, so there is no overhead for the common case.
-
API reference expanded to full surface coverage:
docs/API_REFERENCE.mdgrew from 188 to 1,132 lines. Added 8 new top-level sections — server-side data source (GridDataSource,GridGetRowsParams,GridGetRowsResponse), all event callback param types (GridRowParams,GridCellParams,GridColumnOrderChangeParams,GridRowOrderChangeParams,GridDetailPanelParams), column and row pinning types, full filter model deep reference (per-type operator table,GridFilterGroupnesting), grid state and initial state slice reference, aggregation reference (all 6 built-in functions,getAggregationPositionsemantics), row grouping, column group headers, and list view. DataGrid props section restructured into 13 feature-area sub-tables covering every previously undocumented prop. Developers building server-side sorting + aggregation + pinned columns no longer need to read TypeScript source.
-
GridColDefdocumentation: Added all previously undocumented column properties —flex,minWidth,maxWidth,align,headerAlign,description,editable,renderHeader,renderEditCell,cellClassName,headerClassName,disableColumnMenu,groupable,aggregable,availableAggregationFunctions,valueOptions(forsingleSelect),colSpan,rowSpan. BothREADME.mdanddocs/API_REFERENCE.mdnow carry the complete table. -
Hooks documentation: Expanded the stubs in
docs/API_REFERENCE.mdinto full reference entries.usePivot(was entirely missing),useGridStateStorage(was one sentence — now has a full options + return table and code example),useAggregation(now has params/return tables and built-in function list),useGridApiRef(corrected description and added usage example).
-
Bundled documentation accuracy: All doc files shipped inside the npm package (
docs/) have been corrected to match the actual v1.0.0 API. Key fixes: removed non-existentpageSizestandalone prop (correct API ispaginationModel+pageSizeOptions), fixedheighttype tonumber | string, correctedonColumnOrderChangeparams shape, removed non-existentisRowSelectableandreorderableprops, fixedGridFilterModel.itemstype, added missingGridApimethods (getFilterModel,getAllColumns,setPageSize,getAggregationResult,getAggregationModel,copySelectedRows), and correctedGridInitialStatepersisted state fields. AI agents (Cursor, Copilot, Windsurf) reading bundled docs will now generate accurate code. -
README API reference: Same prop and type corrections applied — complete
apiRefmethod list, new prop tables for Events, Columns, Pinning, Inline Editing, Row Reordering, and updated comparison table.
First stable public release. All 32 demo pages ship with a live source viewer. npm publish workflow is live.
-
npm publish workflow: GitHub Actions workflow (
.github/workflows/npm-publish.yml) triggers on anyv*.*.*tag push — runs lint, build, thennpm publish. The package is now publicly available as@opencorestack/opengridxon the npm registry. -
Source viewer on all 32 demos: Every demo page now uses
DocsLayoutwith a collapsible "View Source" tab showing the full component source code. Previously only 7 of 32 demos had this; the remaining 25 have been migrated.
-
Version:
0.1.xpre-release series →1.0.0stable. The public API (DataGridProps,GridColDef,GridApi, all hooks and types) is now considered stable. -
Demo consistency: All 32 demos share the same
DocsLayoutshell — consistent title, description, live preview, and source viewer. Inline<h1>/<h2>+<p>manual headers removed from every migrated demo.
-
PivotModeDemo—apiRef: any,fallbackRows: any[],props: anytoolbar → fully typed -
InfiniteScrollDemo— introducedPersonRowinterface, allanyin data source and sort params replaced -
ServerSideAggregationDemo—(a as any)[field]field access →keyof Employeekeyed access -
AggregationFooter+ServerSideAggregationDemo—valueFormatter: { value: any }→unknownwith narrowing -
SlotsDemo— addedEmployeeRowinterface,renderCell: (params: any)× 2 replaced -
CRUDTutorial—renderCell: (params: any)→GridRenderCellParams<User> -
RealEstatePortfolio—useState<any>for pinnedColumns →useState<GridColumnPinning> -
ExportDemo—apiRef: any→ReturnType<typeof useGridApiRef>,rowsToPrint: any[]→ typed -
CustomPagination—(props: any)component signature → explicit typed interface -
Editing—handleProcessRowUpdate = (newRow: any)→MockRowinferred from data
-
onRowClicknever fired whenonCellClickwas registered:Cell.tsxwas callinge.stopPropagation()inside its click handler wheneveronCellClickwas provided. This silently ate the event before it could bubble to the row'sonClickhandler, makingonRowClickpermanently unreachable from cell clicks. Removed the stopPropagation — both callbacks now fire in the natural bubble order (cell first, then row), matching standard data grid behavior. -
Column visibility panel list not updating after column reorder: The toolbar was receiving
effectiveColumns(the pre-ordering array, in original definition order) instead oforderedColumns(the reordered array). After a drag-reorder in the panel, the grid columns reordered correctly but the panel list stayed frozen in definition order. FixedtoolbarProps.columnsto useorderedColumns. -
Reset button ignoring column sequence: The Reset button in the column visibility panel called
onShowAllonly (restoring visibility), leaving any user-reordered sequence in place. AddedonColumnOrderResetprop threaded fromColumnVisibilityPanel→ColumnsPanelWrapper→GridToolbar→DataGrid. DataGrid provides() => setInternalColumnOrder(columns.map(c => c.field))to restore original definition order on reset. -
FilterPanelDemofilter panel inaccessible: The demo had noslots={{ toolbar: GridToolbar }}, so the toolbar never rendered and the filter icon never appeared. Added the toolbar slot and rewrote the demo to useDocsLayout(consistent with all other demos). -
EventsDemoevent handlers never fired:onRowClickwas broken by the stopPropagation bug above.onFilterModelChangeandonColumnOrderChangewere dead — no toolbar existed to trigger them. Fixed by addingslots={{ toolbar: GridToolbar }}. Also replaced threeany-typed handler signatures withGridSortItem[],GridFilterModel, andGridColumnOrderChangeParams. -
FilterPaneldebounce stale-closure bug: The 300 ms debounceuseEffectreaditem,col.field, andcurrentOperatordirectly from closure (stale values after operator or field changes), suppressed witheslint-disable-next-line. Replaced with a refs-sync pattern (useLayoutEffectwritingitemRef,colFieldRef,operatorRefeach render) so the effect reads current values without the lint suppression and without stale closures.
-
Type safety — full
anyelimination: Everyanyacrosslib/replaced with explicit types orunknown. Key changes:GridRowModelindex signatureany → unknown(with explicit internal row fields added to the type), all cell/value/error params typed asunknown, aggregation functions typed as(values: unknown[]) => unknown,GridAggregationResulttyped asRecord<string, unknown>, slot component types useRecord<string, unknown>, export utilities narrowed withinstanceof Errorguards. -
No more lint suppressions: Removed all
eslint-disable-next-line react-hooks/exhaustive-depscomments — every case fixed at the root cause rather than suppressed. Methods include: state refactors (scrollTickanti-pattern →scrollTop/scrollLeftstate), ref patterns for stable callbacks, and correct dep arrays. -
DataGrid.tsx continued decomposition: Further hooks and components extracted —
useGridScrollSync(RAF-batched scroll state),useGridVirtualization,useGridVisibleRows,useGridColumns,GridAggregationFooter,GridEmptyState,GridErrorOverlay,GridVirtualRows,GridPinnedRows,GridStandaloneColumnPanel,GridListView. Each module has a single clear responsibility and typed params/return interface.
-
GridApi.scrollToIndexes: New imperative API methodapiRef.current.scrollToIndexes({ rowIndex?, colIndex? })scrolls the viewport to bring any row and/or column into view. Column index addresses all data columns (left-pinned + unpinned + right-pinned); pinned columns are always visible so they are silently skipped. Row scrolling accounts for variable-height rows (expanded detail panels, grouped rows). -
useGridKeyboardNavigationhook: Extracted ~370 lines of keyboard navigation state and handlers fromDataGrid.tsxinto a standaloneuseGridKeyboardNavigationhook. Fixes a previously dead code path where pressing Enter/Space on a header cell never triggered column sort (the edit handler ran first). The hook is part of the publiclib/source. -
useLayouthook: Extracted all layout-computation logic fromDataGrid.tsxinto a standaloneuseLayouthook, reducing the main component by ~350 lines. -
Test suite: Added Vitest +
@testing-library/reactinfrastructure with 65 unit tests coveringfilterRows,sortRows,useAggregation, and a DataGrid smoke test. -
ScrollToIndexes demo: New
/scroll-todemo page showcasing thescrollToIndexesAPI with live row/column index controls.
-
Excel export file format error:
exportToExcelgenerates an HTML-table file withapplication/vnd.ms-excelMIME type (the legacy XLS trick). All demo call-sites were passing explicit.xlsxfilenames, causing Excel 2007+ to reject the download with "file format or file extension is not valid". Changed allexportToExcelusages to.xls.exportToExcelAdvanced(ExcelJS, real OOXML) is unaffected and correctly keeps.xlsx. - Keyboard sort on column headers: Enter/Space on a focused header cell now correctly triggers sort. Previously the generic Enter-edit handler ran first, making header sort unreachable via keyboard.
-
ESLint errors: Resolved all lint errors across the library — hooks called after conditional early returns (
Cell.tsx,Row.tsx), ref mutations in the render phase moved touseLayoutEffect, and portal targets readingref.currentduring render moved touseState + useLayoutEffect.
-
Toolbar Component Identity: Fixed a major bug where defining the
GridToolbarwithin a component's render body produced a new React component reference on every render, causing the toolbar to constantly unmount and remount (destroying all internal states like open panels or typed search text). ReplacedReact.createElementwith direct function invocation in theStableWrapperto bypass React's component-identity check and persist internal DOM state. -
Global Search Focus Preservation: Refactored
GlobalSearchinto an uncontrolled component to prevent continuous data re-renders from stealing focus. Added auseLayoutEffectto automatically restore browser focus to the input field if a React virtual DOM diff incidentally drops it mid-keystroke. - Filter Panel Auto-Dismiss: The Advanced Filter panel no longer collapses indiscriminately when clicking into numeric filter fields or during parent re-renders. Implemented a stable callback ref that prevents the underlying event listeners from rehooking during typing. Click-outside auto-close has been structurally disabled in favor of an explicit "Close" button.
-
Pivot Mode Aggregation: Addressed a critical bug where
aggregationModelwas trying to read base columns (e.g.revenue) on pivot rows that use synthetic column keys (e.g.Q1\u001frevenue\u001fsum), resulting in broken totals.- The aggregation footer now renders synthetic pivot totals correctly.
- The
GridToolbarnow actively provisionseffectiveColumnsto theAggregationPanelto allow users to build summaries on pivot dimensions. - Added a built-in "Grand Total" row appended directly to the
usePivotoutput to generate automatic baseline column totals.
-
Exporting Selected Rows: Corrected data omission in the Demo files where print exports were grabbing the entire dataset instead of respecting active row selection. Used
apiRef.current.getSelectedRows()to extract standard export data without requiring explicit prop-threading.
-
exportableProperty: Addedexportable?: booleantoGridColDef. This allows excluding specific columns (like action buttons, menus, or images) from all export formats (CSV, Excel, JSON, and Print). -
AI-Native Integration: The published npm package now includes raw source code (
lib/) and full documentation (docs/). This allows AI agents (Cursor, Windsurf, Copilot) to "see" the implementation patterns and documentation insidenode_modules, leading to significantly better code generation for downstream users.
- Fixed an issue where the main wrapper
classNamewould erroneously include extra whitespace (e.g.ogx) when no optional classes were active. - Fixed an issue where
onRowOrderChangedrag-and-drop visuals didn't actually update in theEventsDemocomponent examples because it was referencing a static array instead of React State. - Corrected a TypeScript regression where
headerClassNamecomment structure was accidentally broken during the previous update.
- Refined the npm package publication files:
docs/researchanddocs/assets(large binary images) are now excluded to keep the package size lean while retaining all high-value documentation for humans and AI.
- Complete theming support for all advanced dropdown panels (Column Visibiity, Filter Editor, Pivot Mode, Global Search) so they correctly adapt to custom themes via
<DataGridThemeProvider>. - Aggregation, Pivot, filtering, and export capability options now appear directly in the
ThemingDemoexample.
- Replaced the hardcoded portal mounting (
document.body) on popovers to instead intelligently hunt for.ogx-theme-providerto organically inherit user themes in overlay panels. - Fixed GlobalSearch input focus shadow not fully respecting CSS variables.
- Exported missing public types (
GridSortItem,GridApi,GridRowSelectionModel,GridColumnVisibilityModel, etc.) inlib/index.tsto prevent developers from having to derive them manually usingNonNullable.
-
Column Visibility Reorder: Added a drag handle to the
ColumnVisibilityPanelletting users seamlessly reorder columns directly via the Visibility Panel dropdown checkbox list. Uses native HTML Drag and Drop API with no external dependencies. - Added
onColumnReordersupport toColumnVisibilityPanelandGridToolbar.
-
import '@opencorestack/opengridx/styles'now resolves correctly in TypeScript projects. The./stylessubpath export inpackage.jsonnow includes atypespointer todist/opengridx.css.d.ts, eliminating the "Cannot find module" TS error. -
build:libscript now copiesopengridx.css.d.tsintodist/automatically so it's always included in published packages.
-
llms.txtbundled inside the npm package — a machine-readable AI agent API context file with complete props reference, type definitions, and usage examples. Located atnode_modules/@opencorestack/opengridx/llms.txtafter installation.
- CSS now explicitly imported at the barrel entry (
lib/index.ts), ensuring styles are never silently dropped by bundlers (Vite, Webpack, Next.js App Router) that don't auto-resolve side-effect CSS from library packages. - Column resize:
ColumnResizeHandlenow uses the logical stored width (currentWidthprop) instead of reading DOMgetBoundingClientRect(), fixing resize jitter and incorrect delta calculations on second+ drag. - Pinned column resize: Resizing a pinned (sticky) column no longer corrupts its displayed width — the DOM measurement was previously offset by the sticky
left/rightposition, causing an erroneous width jump on first drag.
- Updated
README.mdto accurately describe CSS handling and provide a clear fallback import instruction for all environments.
- Cell editing state now correctly pushes to internal state (
baseRows) instead of being overridden by rigidprops.rowsbindings, preventing data loss on successive edits.
- ExcelJS correctly marked as external in Vite build config (consistent with
peerDependencies) - Clipboard programmatic copy button now correctly reads live selection state via
apiRef.getSelectedRows() -
Ctrl+Ckeyboard shortcut now works when grid checkboxes are focused
- Package size reduced from 8.4 MB → 1.8 MB unpacked (ExcelJS no longer bundled)
- README rewritten with Getting Started first, basic example, and full API reference table
- Cleaned devDependencies (removed unused
strip-comment,strip-comments)
Status: ✅ RELEASE READY — 100% feature-complete for v0.1.0 scope
This is the initial public release of OpenGridX. All planned v0.1.0 features are implemented, tested, and included in the production bundle.
- High-Performance Virtualization — Custom row + column virtual scrolling engine, 60fps at 100k+ rows
- Multi-Column Sorting — Client-side and server-side; stable multi-field sort
- Advanced Filtering — 11+ operators (contains, equals, startsWith, etc.) with AND/OR filter builder UI
- Pagination — Client-side and server-side modes with configurable page sizes
-
Row Selection — Single and multi-row checkbox selection with
rowSelectionModelcontrolled/uncontrolled API
- Column Pinning — Left and right sticky columns with correct z-index layering
- Row Pinning — Top and bottom pinned rows with visual separation
- Column Resizing — Throttled drag-to-resize at 60fps with minimum width enforcement
- Column Reordering — Drag-and-drop column reorder
-
Row Reordering — Drag-and-drop row reorder with
onRowOrderChangecallback -
Detail Panels — Expandable master-detail rows via
getDetailPanelContent -
Cell & Row Spanning —
colSpanandrowSpansupport for merged-cell layouts -
List View Mode — Card-based responsive layout via
listView/listViewColumn -
Column Grouping — Multi-level column header groups via
columnGroupingModel -
Toolbar — Built-in toolbar with column visibility, filter, and density controls; fully replaceable via
slots
-
Inline Cell Editing — Double-click or Enter to edit;
editableper column;processRowUpdatecallback -
Tree Data — Client-side hierarchical rows via
treeData+getTreeDataPath - Row Grouping — Group rows by column value with collapsible groups and aggregation summaries
- Aggregation — SUM, AVG, COUNT, MIN, MAX in group footers and global sticky footer
-
Pivot Mode — Multidimensional data pivoting via
pivotMode+pivotModel
-
Data Source API —
useGridDataSourcehook for unified server-side fetching - Server-Side Sorting, Filtering & Pagination — All offloaded cleanly to the backend
-
Infinite Scroll — Viewport-triggered batch-loading (
paginationMode="infinite") -
Server-Side Tree Data — Lazy children loading via
dataSource.getChildren - Server-Side Aggregation — Fetch summary totals directly from API responses
-
CSV Export —
exportToCsv()utility, respectsvalueFormatter -
Excel Export — Basic
.xlsxviaexportToExcel(); advanced pixel-perfect image-embedded export viaexportToExcelAdvanced()(lazy-loads ExcelJS) -
JSON Export —
exportToJson() -
Print —
printGrid()with print-optimised CSS
-
Keyboard Copy —
Ctrl+C/Cmd+Ccopies selected rows as TSV (tab-separated values) -
Programmatic Copy —
apiRef.current.copySelectedRows()for button-triggered copying - Excel/Sheets Compatible — TSV output pastes cleanly into any spreadsheet app
-
Smart Focus Handling — Does not intercept
Ctrl+Cin text inputs; correctly handles checkbox-focused grid cells
-
DataGridThemeProvider— React context-based global theming -
5 Built-in Themes —
darkTheme,roseTheme,emeraldTheme,amberTheme,compactTheme -
CSS Variables — Full
--ogx-*token system; Shadow DOM compatible -
cellClassName/headerClassName— Per-column custom class injection
- Semantic ARIA roles:
grid,row,gridcell,columnheader -
aria-sort,aria-selected,aria-expanded,aria-readonly,aria-labelthroughout - Full keyboard navigation: Arrow keys, Tab, Enter, Escape, Home/End, PageUp/PageDown
- Visible focus ring in keyboard mode (CSS classname-toggled, zero React state overhead)
-
initialStateprop — Restore column widths, visibility, sort, and filter on mount -
useGridStateStorage(key)hook — Auto-saves tolocalStorage; pluggable storage backend
-
apiRef— Full imperative API:getSelectedRows,copySelectedRows,selectRow,sortColumn,setFilterModel,getVisibleRows,scrollToIndexes, and more -
slotsSystem — Replace Toolbar, Pagination, NoRowsOverlay, LoadingOverlay, Footer -
slotProps— Pass custom props to slot components -
TypeScript — 100% typed; full
index.d.tsoutput viavite-plugin-dts - Zero UI Dependencies — No Ant Design, MUI, or Radix. Pure React + vanilla CSS (BEM)
| Artifact | Minified | Gzipped |
|---|---|---|
opengridx.es.js (ES Module) |
226 KB | 52 KB |
opengridx.umd.js (UMD) |
1,089 KB | 315 KB |
opengridx.css |
59 KB | 10 KB |
exceljs (lazy, Excel export only) |
1,385 KB | 302 KB |
- Rich Excel Styling — Bold headers, background fill, border styles natively via ExcelJS (no post-processing)
- Cell Range Clipboard — Select a rectangular cell region (mouse drag), copy to clipboard, paste from Excel back into editable cells
Last Updated: March 18, 2026
OpenGridX 3.2.2 · MIT · This wiki is generated from docs/ on every push to main. To fix a page, open a PR against the source file.
Start here
Components
- DataGrid
- Header
- Row
- Cell
- Toolbar
- Pagination
- Filter Panel
- Tooltip
- Column Visibility
- Column Grouping
- Column Resizing
- Empty State
- Error Overlay
- Aggregation Footer
Features
- Virtualization
- Filtering & Search
- Sorting & Pagination
- Custom Pagination
- Editing & Reordering
- Row Selection
- Clipboard
- Pinning
- State Persistence
- Aggregation & Pivot
- Tree Data & Grouping
- Cell Spanning
- Master-Detail
- Keyboard & Accessibility
- List View
- Infinite Scroll
- Data Source
- Loading States
- Toolbar Customization
- Export (CSV, Excel, JSON, Print)
- PDF Export
Customization
Upgrading
Contributing
- Contributing
- Testing
- Roadmap
- DataGrid orchestration
- GridRowMeta
- useGridControlledState
- useGridRowPipeline
- useGridColumns
- useGridVirtualization
- useGridVisibleRows
- useGridScrollSync
- useGridStateSnapshot