@opencorestack/opengridx 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.0.3] — July 30, 2026 📚
9
+
10
+ ### Fixed
11
+ - **API reference expanded to full surface coverage**: `docs/API_REFERENCE.md` grew 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, `GridFilterGroup` nesting), grid state and initial state slice reference, aggregation reference (all 6 built-in functions, `getAggregationPosition` semantics), 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.
12
+
13
+ ---
14
+
15
+ ## [1.0.2] — July 30, 2026 📚
16
+
17
+ ### Fixed
18
+ - **`GridColDef` documentation**: Added all previously undocumented column properties — `flex`, `minWidth`, `maxWidth`, `align`, `headerAlign`, `description`, `editable`, `renderHeader`, `renderEditCell`, `cellClassName`, `headerClassName`, `disableColumnMenu`, `groupable`, `aggregable`, `availableAggregationFunctions`, `valueOptions` (for `singleSelect`), `colSpan`, `rowSpan`. Both `README.md` and `docs/API_REFERENCE.md` now carry the complete table.
19
+ - **Hooks documentation**: Expanded the stubs in `docs/API_REFERENCE.md` into 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).
20
+
21
+ ---
22
+
8
23
  ## [1.0.1] — July 30, 2026 📚
9
24
 
10
25
  ### Fixed
package/README.md CHANGED
@@ -118,6 +118,42 @@ This means that **Cursor**, **GitHub Copilot**, **Windsurf**, and other AI agent
118
118
  | `className` | `string` | — | Custom CSS class on the grid container. |
119
119
  | `style` | `React.CSSProperties` | — | Custom inline styles on the grid container. |
120
120
 
121
+ ### Column Definitions (`GridColDef`)
122
+
123
+ | Property | Type | Default | Description |
124
+ | :--- | :--- | :--- | :--- |
125
+ | `field` | `string` | — | **Required.** Must match the row object key. |
126
+ | `headerName` | `string` | — | Header label text. |
127
+ | `description` | `string` | — | Tooltip on header hover. |
128
+ | `width` | `number \| string` | `100` | Fixed width in px. |
129
+ | `flex` | `number` | — | Flex-grow weight — fills remaining space. Cannot be combined with `width`. |
130
+ | `minWidth` | `number` | — | Minimum width in px, enforced during resize. |
131
+ | `maxWidth` | `number` | — | Maximum width in px, enforced during resize. |
132
+ | `align` | `'left' \| 'center' \| 'right'` | `'left'` | Cell content alignment. |
133
+ | `headerAlign` | `'left' \| 'center' \| 'right'` | `'left'` | Header content alignment. |
134
+ | `type` | `'string' \| 'number' \| 'date' \| 'boolean' \| 'singleSelect' \| 'image'` | `'string'` | Drives filter operators and default formatting. |
135
+ | `valueOptions` | `Array<string \| number \| { value, label }>` | — | Options list for `type: 'singleSelect'`. |
136
+ | `editable` | `boolean` | `false` | Enables inline cell editing. Pair with `processRowUpdate`. |
137
+ | `valueGetter` | `(params) => unknown` | — | Derive a computed cell value from the row. |
138
+ | `valueFormatter` | `(params) => string` | — | Format the display string (does not affect edit or sort). |
139
+ | `renderCell` | `(params) => ReactNode` | — | Fully custom cell renderer. |
140
+ | `renderHeader` | `(params) => ReactNode` | — | Custom header cell renderer. |
141
+ | `renderEditCell` | `(params) => ReactNode` | — | Custom editor rendered in edit mode. |
142
+ | `cellClassName` | `string \| ((params) => string)` | — | CSS class on every cell; use a function for conditional per-row styling. |
143
+ | `headerClassName` | `string` | — | CSS class on the header cell. |
144
+ | `colSpan` | `number \| ((params) => number)` | — | Merge cells horizontally. |
145
+ | `rowSpan` | `number \| ((params) => number)` | — | Merge cells vertically. |
146
+ | `sortable` | `boolean` | `true` | Enable/disable column sorting. |
147
+ | `filterable` | `boolean` | `true` | Enable/disable column filtering. |
148
+ | `resizable` | `boolean` | `true` | Allow drag-resize. |
149
+ | `hideable` | `boolean` | `true` | Allow hiding via the column panel. |
150
+ | `pinnable` | `boolean` | `true` | Allow pinning via the UI. |
151
+ | `disableColumnMenu` | `boolean` | `false` | Hide the column header context menu. |
152
+ | `exportable` | `boolean` | `true` | Set to `false` to exclude from all exports. |
153
+ | `groupable` | `boolean` | `true` | Allow as a row grouping dimension. |
154
+ | `aggregable` | `boolean` | `true` | Allow aggregation on this column. |
155
+ | `availableAggregationFunctions` | `string[]` | all | Restrict which aggregation functions are offered. |
156
+
121
157
  ### Selection
122
158
 
123
159
  | Prop | Type | Description |
@@ -39,6 +39,129 @@ The main component for displaying and interacting with data.
39
39
  | `processRowUpdate` | `(new, old) => R \| Promise<R>` | — | Fired after a cell edit is committed. |
40
40
  | `dataSource` | `GridDataSource` | — | Remote data provider interface. |
41
41
 
42
+ #### Sorting, Filtering & Pagination
43
+
44
+ | Prop | Type | Default | Description |
45
+ | :--- | :--- | :--- | :--- |
46
+ | `sortingMode` | `'client' \| 'server'` | `'client'` | Set to `'server'` when your backend handles sorting. The grid calls `onSortModelChange` but does not re-sort rows locally. |
47
+ | `filterMode` | `'client' \| 'server'` | `'client'` | Set to `'server'` when your backend handles filtering. The grid calls `onFilterModelChange` but does not re-filter rows locally. |
48
+ | `onSortModelChange` | `(model: GridSortItem[]) => void` | — | Fired when the active sort model changes. |
49
+ | `onFilterModelChange` | `(model: GridFilterModel) => void` | — | Fired when the active filter model changes. |
50
+
51
+ #### Row Selection
52
+
53
+ | Prop | Type | Default | Description |
54
+ | :--- | :--- | :--- | :--- |
55
+ | `rowSelectionModel` | `GridRowSelectionModel` | `[]` | Controlled selection state — array of selected row IDs. |
56
+ | `onRowSelectionModelChange` | `(model: GridRowSelectionModel) => void` | — | Fired when the selection changes. |
57
+ | `disableRowSelectionOnClick` | `boolean` | `false` | When `true`, clicking a row does not toggle its selection. |
58
+ | `disableMultipleRowSelection` | `boolean` | `false` | When `true`, at most one row can be selected at a time. |
59
+
60
+ #### Column Visibility
61
+
62
+ | Prop | Type | Default | Description |
63
+ | :--- | :--- | :--- | :--- |
64
+ | `columnVisibilityModel` | `GridColumnVisibilityModel` | — | Map of `field → boolean` controlling which columns are visible (`false` = hidden). |
65
+ | `onColumnVisibilityModelChange` | `(model: GridColumnVisibilityModel) => void` | — | Fired when the visibility of any column changes. |
66
+
67
+ #### Column & Row Reordering
68
+
69
+ | Prop | Type | Default | Description |
70
+ | :--- | :--- | :--- | :--- |
71
+ | `disableColumnReorder` | `boolean` | `false` | Disables drag-and-drop column reordering. |
72
+ | `columnOrder` | `GridColumnOrder` | — | Controlled ordered array of column field names. |
73
+ | `onColumnOrderChange` | `(params: GridColumnOrderChangeParams) => void` | — | Fired after a column is dragged to a new position. |
74
+ | `rowReordering` | `boolean` | `false` | Enables drag-and-drop row reordering. |
75
+ | `onRowOrderChange` | `(params: GridRowOrderChangeParams) => void` | — | Fired after a row is dragged to a new position. |
76
+
77
+ #### Pinning
78
+
79
+ | Prop | Type | Default | Description |
80
+ | :--- | :--- | :--- | :--- |
81
+ | `pinnedColumns` | `GridColumnPinning` | — | Columns pinned to the left or right viewport edges. See [Column & Row Pinning](#-column--row-pinning). |
82
+ | `onPinnedColumnsChange` | `(model: GridColumnPinning) => void` | — | Fired when column pinning changes. |
83
+ | `pinnedRows` | `GridRowPinning` | — | Row IDs pinned to the top or bottom of the viewport. |
84
+ | `onPinnedRowsChange` | `(model: GridRowPinning) => void` | — | Fired when row pinning changes. |
85
+ | `pinCheckboxColumn` | `boolean` | `false` | Keeps the checkbox column visible during horizontal scrolling. |
86
+ | `pinExpandColumn` | `boolean` | `false` | Keeps the Master-Detail expansion column visible during horizontal scrolling. |
87
+
88
+ #### Inline Editing
89
+
90
+ | Prop | Type | Default | Description |
91
+ | :--- | :--- | :--- | :--- |
92
+ | `isCellEditable` | `(params: GridCellParams) => boolean` | — | Per-cell predicate. Return `false` to make a specific cell read-only even when the column has `editable: true`. |
93
+ | `onProcessRowUpdateError` | `(error: unknown) => void` | — | Fired if `processRowUpdate` throws or returns a rejected Promise. Use to display validation errors or restore the previous row value. |
94
+
95
+ #### Master-Detail
96
+
97
+ | Prop | Type | Default | Description |
98
+ | :--- | :--- | :--- | :--- |
99
+ | `getDetailPanelContent` | `(params: GridDetailPanelParams) => ReactNode` | — | Returns the JSX content rendered inside the expandable detail panel. |
100
+ | `getDetailPanelHeight` | `(params: GridDetailPanelParams) => number \| 'auto'` | `'auto'` | Controls the panel height in pixels, or `'auto'` to fit content. |
101
+ | `detailPanelExpandedRowIds` | `Set<GridRowId>` | — | Controlled set of currently-expanded detail panel row IDs. |
102
+ | `onDetailPanelExpandedRowIdsChange` | `(ids: Set<GridRowId>) => void` | — | Fired when detail panels expand or collapse. |
103
+
104
+ #### Tree Data
105
+
106
+ | Prop | Type | Default | Description |
107
+ | :--- | :--- | :--- | :--- |
108
+ | `treeData` | `boolean` | `false` | Enables hierarchical tree data display. |
109
+ | `getTreeDataPath` | `(row: R) => string[]` | — | Returns the hierarchy path for a row (e.g. `['Engineering', 'Frontend']`). |
110
+ | `groupingColDef` | `GridColDef` | — | Overrides the auto-generated tree expand/collapse column definition. |
111
+ | `defaultGroupingExpansionDepth` | `number` | `0` | Number of tree levels expanded on initial render (`-1` = all). |
112
+
113
+ #### Row Grouping & Aggregation
114
+
115
+ | Prop | Type | Default | Description |
116
+ | :--- | :--- | :--- | :--- |
117
+ | `rowGroupingModel` | `GridRowGroupingModel` | `[]` | Array of field names to group rows by (e.g. `['department', 'team']`). See [Row Grouping](#️-row-grouping). |
118
+ | `onRowGroupingModelChange` | `(model: GridRowGroupingModel) => void` | — | Fired when the grouping model changes. |
119
+ | `aggregationModel` | `GridAggregationModel` | — | Map of `field → aggFn` (e.g. `{ salary: 'sum', age: 'avg' }`). See [Aggregation Reference](#-aggregation-reference). |
120
+ | `onAggregationModelChange` | `(model: GridAggregationModel) => void` | — | Fired when the aggregation model changes. |
121
+ | `getAggregationPosition` | `(groupNode: GridTreeNode \| null) => 'inline' \| 'footer' \| null` | — | Controls where aggregation results appear. `'inline'` = inside the group row, `'footer'` = a dedicated row below the group, `null` = hidden. Pass `null` groupNode = grand-total (root) position. |
122
+
123
+ #### Pivot
124
+
125
+ | Prop | Type | Default | Description |
126
+ | :--- | :--- | :--- | :--- |
127
+ | `pivotMode` | `boolean` | `false` | Switches the grid to multidimensional Pivot Mode. |
128
+ | `pivotModel` | `GridPivotModel` | — | Controlled pivot configuration (row fields, column fields, value fields). |
129
+ | `onPivotModelChange` | `(model: GridPivotModel) => void` | — | Fired when the pivot model changes. |
130
+
131
+ #### Scroll & Viewport
132
+
133
+ | Prop | Type | Default | Description |
134
+ | :--- | :--- | :--- | :--- |
135
+ | `onRowsScrollEnd` | `(params: GridRowScrollEndParams) => void` | — | Fired when the user scrolls to the bottom of the grid viewport. Use this to trigger the next page in infinite-scroll mode. |
136
+
137
+ #### Accessibility & Appearance
138
+
139
+ | Prop | Type | Default | Description |
140
+ | :--- | :--- | :--- | :--- |
141
+ | `ariaLabel` | `string` | — | ARIA label for the grid container element. |
142
+ | `noRowsLabel` | `string` | `'No rows'` | Message shown in the empty-state overlay when there are no rows. |
143
+ | `className` | `string` | — | Additional CSS class applied to the outermost grid wrapper element. |
144
+ | `style` | `React.CSSProperties` | — | Inline styles applied to the grid wrapper element. |
145
+
146
+ #### Imperative Ref
147
+
148
+ | Prop | Type | Default | Description |
149
+ | :--- | :--- | :--- | :--- |
150
+ | `apiRef` | `React.MutableRefObject<GridApi>` | — | Reactive ref to the imperative API. Create with `useGridApiRef()`. |
151
+
152
+ #### List View
153
+
154
+ | Prop | Type | Default | Description |
155
+ | :--- | :--- | :--- | :--- |
156
+ | `listView` | `boolean` | `false` | Renders the grid as a single-column list of cards. Designed for mobile and responsive layouts. |
157
+ | `listViewColumn` | `GridListViewColDef` | — | Column definition for list view mode. Must provide a `renderCell` function. The `field` value is used as a key. |
158
+
159
+ #### Column Group Headers
160
+
161
+ | Prop | Type | Default | Description |
162
+ | :--- | :--- | :--- | :--- |
163
+ | `columnGroupingModel` | `GridColumnGroupingModel` | — | Defines spanning header rows above the regular column headers. See [Column Group Headers](#️-column-group-headers). |
164
+
42
165
  ---
43
166
 
44
167
  ## 🕹️ Imperative API (`GridApi`)
@@ -91,21 +214,71 @@ Used in Tree Data and Row Grouping hierarchies.
91
214
  ### `GridColDef`
92
215
  Defines the behavior and appearance of a single column.
93
216
 
217
+ #### Sizing & Layout
218
+
94
219
  | Property | Type | Default | Description |
95
220
  | :--- | :--- | :--- | :--- |
96
- | `field` | `string` | — | Unique identifier (matches row property). |
97
- | `headerName` | `string` | — | Text displayed in the header. |
98
- | `width` | `number \| string` | `100` | Width in pixels or percentage string. |
99
- | `type` | `'string' \| 'number' \| 'date' \| 'boolean' \| 'singleSelect' \| 'image'` | `'string'` | Data type for formatting and filtering. |
100
- | `sortable` | `boolean` | `true` | Enable sorting for this column. |
101
- | `filterable` | `boolean` | `true` | Enable filtering for this column. |
102
- | `resizable` | `boolean` | `true` | Allow user to drag resize. |
103
- | `hideable` | `boolean` | `true` | Allow user to hide the column. |
104
- | `pinnable` | `boolean` | `true` | Allow column to be pinned via the UI. |
105
- | `exportable` | `boolean` | `true` | If `false`, column is excluded from all exports. |
106
- | `valueFormatter` | `(params: GridValueFormatterParams) => string` | — | Format value for display. |
107
- | `valueGetter` | `(params: GridValueGetterParams) => any` | — | Compute value from row data. |
108
- | `renderCell` | `(params: GridRenderCellParams) => ReactNode` | — | Custom cell renderer. |
221
+ | `field` | `string` | — | **Required.** Unique identifier matching the row object key. |
222
+ | `headerName` | `string` | — | Text shown in the column header cell. |
223
+ | `description` | `string` | — | Tooltip shown on header hover (accessibility label). |
224
+ | `width` | `number \| string` | `100` | Fixed width in pixels or a percentage string. |
225
+ | `minWidth` | `number` | — | Minimum width in pixels (enforced during resize). |
226
+ | `maxWidth` | `number` | — | Maximum width in pixels (enforced during resize). |
227
+ | `flex` | `number` | — | Flex grow factor — distributes remaining space proportionally. Mutually exclusive with a fixed `width`. |
228
+ | `align` | `'left' \| 'center' \| 'right'` | `'left'` | Horizontal alignment of cell content. |
229
+ | `headerAlign` | `'left' \| 'center' \| 'right'` | `'left'` | Horizontal alignment of the header cell content. |
230
+
231
+ #### Data & Type
232
+
233
+ | Property | Type | Default | Description |
234
+ | :--- | :--- | :--- | :--- |
235
+ | `type` | `'string' \| 'number' \| 'date' \| 'boolean' \| 'singleSelect' \| 'image'` | `'string'` | Data type — determines default filter operators and cell formatting. |
236
+ | `valueOptions` | `Array<string \| number \| { value: unknown; label: string }>` | — | Allowed values for `type: 'singleSelect'` — used in the filter dropdown and edit cell. |
237
+ | `valueGetter` | `(params: GridValueGetterParams) => unknown` | — | Derive a computed value from the row object. Runs before `valueFormatter` and `renderCell`. |
238
+ | `valueFormatter` | `(params: GridValueFormatterParams) => string` | — | Format the value into a display string (e.g. currency, dates). Does not affect editing or sorting. |
239
+
240
+ #### Rendering
241
+
242
+ | Property | Type | Description |
243
+ | :--- | :--- | :--- |
244
+ | `renderCell` | `(params: GridRenderCellParams) => ReactNode` | Fully custom cell renderer. Receives `value`, `row`, `field`, `rowIndex`, `colIndex`. |
245
+ | `renderHeader` | `(params: GridRenderHeaderParams) => ReactNode` | Custom header cell renderer. Use for icons, sort indicators, or rich headers. |
246
+ | `renderEditCell` | `(params: GridRenderCellParams) => ReactNode` | Custom editor rendered when the cell enters edit mode. Requires `editable: true`. |
247
+
248
+ #### Editing
249
+
250
+ | Property | Type | Default | Description |
251
+ | :--- | :--- | :--- | :--- |
252
+ | `editable` | `boolean` | `false` | Enables inline cell editing. Commit is handled by `DataGrid.processRowUpdate`. |
253
+
254
+ #### Spanning
255
+
256
+ | Property | Type | Description |
257
+ | :--- | :--- | :--- |
258
+ | `colSpan` | `number \| ((params: GridRenderCellParams) => number)` | Number of columns this cell merges horizontally. |
259
+ | `rowSpan` | `number \| ((params: GridRenderCellParams) => number)` | Number of rows this cell merges vertically. |
260
+
261
+ #### Styling
262
+
263
+ | Property | Type | Description |
264
+ | :--- | :--- | :--- |
265
+ | `cellClassName` | `string \| ((params: GridRenderCellParams) => string)` | CSS class applied to every cell in this column. Use a function for conditional per-row styling. |
266
+ | `headerClassName` | `string` | CSS class applied to the header cell. |
267
+
268
+ #### Feature Flags
269
+
270
+ | Property | Type | Default | Description |
271
+ | :--- | :--- | :--- | :--- |
272
+ | `sortable` | `boolean` | `true` | Enable/disable sorting for this column. |
273
+ | `filterable` | `boolean` | `true` | Enable/disable filtering for this column. |
274
+ | `resizable` | `boolean` | `true` | Allow the user to drag-resize this column. |
275
+ | `hideable` | `boolean` | `true` | Allow the user to hide this column via the panel. |
276
+ | `pinnable` | `boolean` | `true` | Allow this column to be pinned via the UI. |
277
+ | `disableColumnMenu` | `boolean` | `false` | Hide the column header kebab/context menu. |
278
+ | `exportable` | `boolean` | `true` | Set to `false` to exclude from CSV, Excel, JSON, and Print exports. |
279
+ | `groupable` | `boolean` | `true` | Allow this column to be used as a row grouping dimension. |
280
+ | `aggregable` | `boolean` | `true` | Allow this column to be aggregated. |
281
+ | `availableAggregationFunctions` | `string[]` | all built-ins | Restrict which aggregation functions are available for this column (e.g. `['sum', 'avg']`). |
109
282
 
110
283
  ---
111
284
 
@@ -136,17 +309,169 @@ Context provider for overriding the grid's visual system.
136
309
  ## 🛠️ Hooks
137
310
 
138
311
  ### `useGridApiRef()`
139
- Returns a ref to interact with the grid programmatically.
140
- - `state`: Access full internal state.
141
- - `getVisibleRows()`: Returns filtered/sorted rows.
142
- - `getVisibleColumns()`: Returns currently visible columns.
312
+
313
+ Creates a typed ref to pass to the `apiRef` prop. Gives you imperative access to the grid after mount.
314
+
315
+ ```tsx
316
+ import { useGridApiRef } from '@opencorestack/opengridx';
317
+
318
+ const apiRef = useGridApiRef();
319
+
320
+ // Pass to the grid
321
+ <DataGrid apiRef={apiRef} rows={rows} columns={columns} />
322
+
323
+ // Then call methods imperiously
324
+ apiRef.current.scrollToIndexes({ rowIndex: 0 });
325
+ apiRef.current.setFilterModel({ items: [] });
326
+ ```
327
+
328
+ See the [Imperative API (`GridApi`)](#%EF%B8%8F-imperative-api-gridapi) table above for the full method list.
329
+
330
+ ---
143
331
 
144
332
  ### `useAggregation(params)`
145
- Compute summary values for datasets.
146
- - **Built-in Functions**: `sum`, `avg`, `min`, `max`, `count`, `unique`.
333
+
334
+ Headless hook for computing column summaries outside of the built-in `aggregationModel` prop. Useful when you need aggregation results for a custom footer or external display.
335
+
336
+ ```tsx
337
+ import { useAggregation } from '@opencorestack/opengridx';
338
+
339
+ const { aggregationResult, isLoading } = useAggregation({
340
+ rows,
341
+ aggregationModel: { salary: 'sum', age: 'avg' },
342
+ isServerSide: false,
343
+ });
344
+ ```
345
+
346
+ #### Params (`UseAggregationParams`)
347
+
348
+ | Param | Type | Description |
349
+ | :--- | :--- | :--- |
350
+ | `rows` | `GridRowModel[]` | The rows to aggregate. |
351
+ | `aggregationModel` | `GridAggregationModel` | Map of `field → aggFn` (e.g. `{ salary: 'sum' }`). |
352
+ | `isServerSide` | `boolean` | If `true`, skips client computation and uses `serverAggregationResults`. |
353
+ | `filterModel` | `GridFilterModel` | Optional — restricts aggregation to filtered rows. |
354
+ | `sortModel` | `GridSortItem[]` | Optional — used when `dataSource` is provided. |
355
+ | `dataSource` | `GridDataSource` | Optional — server-side data adapter for async aggregation. |
356
+ | `serverAggregationResults` | `GridAggregationResult \| null` | Pre-fetched results when `isServerSide: true`. |
357
+
358
+ #### Return (`UseAggregationReturn`)
359
+
360
+ | Field | Type | Description |
361
+ | :--- | :--- | :--- |
362
+ | `aggregationResult` | `GridAggregationResult` | Map of `field → computed value`. |
363
+ | `isLoading` | `boolean` | `true` while a server-side fetch is in progress. |
364
+ | `error` | `unknown` | Set if the server-side fetch threw. |
365
+
366
+ **Built-in aggregation functions:** `sum`, `avg`, `min`, `max`, `count`, `unique`
367
+
368
+ You can also use `formatAggregationValue(value, fnName)` to produce a display string from a raw result.
369
+
370
+ ---
371
+
372
+ ### `usePivot(rawRows, rawCols, model, enabled)`
373
+
374
+ Headless hook that transforms a flat dataset into pivot rows and pivot column definitions. Pass the output directly to `<DataGrid rows={} columns={} />` when `pivotMode` is active.
375
+
376
+ ```tsx
377
+ import { usePivot } from '@opencorestack/opengridx';
378
+
379
+ const { pivotRows, pivotColumns, isValid } = usePivot(
380
+ rows,
381
+ columns,
382
+ {
383
+ rowFields: ['department'],
384
+ columnFields: ['year'],
385
+ valueFields: [{ field: 'revenue', aggFn: 'sum' }],
386
+ },
387
+ isPivotEnabled,
388
+ );
389
+
390
+ <DataGrid
391
+ rows={isPivotEnabled ? pivotRows : rows}
392
+ columns={isPivotEnabled ? pivotColumns : columns}
393
+ />
394
+ ```
395
+
396
+ #### Parameters
397
+
398
+ | Param | Type | Description |
399
+ | :--- | :--- | :--- |
400
+ | `rawRows` | `GridRowModel[]` | Original flat dataset. |
401
+ | `rawCols` | `GridColDef[]` | Original column definitions. |
402
+ | `model` | `GridPivotModel` | Pivot configuration — `rowFields`, `columnFields`, `valueFields`. |
403
+ | `enabled` | `boolean` | When `false`, returns empty arrays immediately (no computation). |
404
+
405
+ #### Return (`UsePivotReturn`)
406
+
407
+ | Field | Type | Description |
408
+ | :--- | :--- | :--- |
409
+ | `pivotRows` | `GridRowModel[]` | Transformed rows ready for the grid. |
410
+ | `pivotColumns` | `GridColDef[]` | Generated column definitions for each pivot key. |
411
+ | `colKeys` | `string[]` | The distinct column pivot values used. |
412
+ | `isValid` | `boolean` | `false` if the model is incomplete (e.g. no `rowFields` or `valueFields`). |
413
+
414
+ #### `GridPivotModel`
415
+
416
+ ```ts
417
+ interface GridPivotModel {
418
+ rowFields: string[]; // fields to group rows by
419
+ columnFields: string[]; // fields to spread as columns
420
+ valueFields: Array<{
421
+ field: string;
422
+ aggFn: 'sum' | 'avg' | 'count' | 'min' | 'max';
423
+ headerName?: string;
424
+ }>;
425
+ }
426
+ ```
427
+
428
+ ---
147
429
 
148
430
  ### `useGridStateStorage(options)`
149
- Persist and restore grid configurations to LocalStorage or a backend.
431
+
432
+ Persists grid state (sort, filters, pagination, column visibility, etc.) to `localStorage` and restores it on mount. Pass the returned values directly to `DataGrid`.
433
+
434
+ ```tsx
435
+ import { useGridStateStorage } from '@opencorestack/opengridx';
436
+
437
+ // Simple — just a storage key
438
+ const { initialState, onStateChange } = useGridStateStorage('my-grid');
439
+
440
+ // Advanced — with options
441
+ const { initialState, onStateChange, clearState } = useGridStateStorage({
442
+ key: 'my-grid',
443
+ debounceMs: 500, // default: 300
444
+ include: ['sorting', 'filter', 'pagination'], // persist only these slices
445
+ storage: sessionStorage, // default: localStorage
446
+ });
447
+
448
+ <DataGrid
449
+ rows={rows}
450
+ columns={columns}
451
+ initialState={initialState}
452
+ onStateChange={onStateChange}
453
+ />
454
+
455
+ // Clear saved state (e.g. on a "Reset" button)
456
+ <button onClick={clearState}>Reset Grid</button>
457
+ ```
458
+
459
+ #### Options (`UseGridStateStorageOptions`)
460
+
461
+ | Option | Type | Default | Description |
462
+ | :--- | :--- | :--- | :--- |
463
+ | `key` | `string` | — | **Required.** Storage key — use a unique value per grid instance. |
464
+ | `debounceMs` | `number` | `300` | Debounce delay in ms before writing to storage. |
465
+ | `include` | `(keyof GridState)[]` | all | Restrict which state slices are persisted. |
466
+ | `storage` | `Storage` | `localStorage` | Any object implementing `getItem`/`setItem`/`removeItem` (e.g. `sessionStorage` or a custom backend adapter). |
467
+
468
+ #### Return (`UseGridStateStorageReturn`)
469
+
470
+ | Field | Type | Description |
471
+ | :--- | :--- | :--- |
472
+ | `initialState` | `GridState \| undefined` | Restored state from storage — pass to `DataGrid.initialState`. |
473
+ | `onStateChange` | `(state: GridState) => void` | Callback to pass to `DataGrid.onStateChange`. |
474
+ | `clearState` | `() => void` | Removes the saved state from storage. |
150
475
 
151
476
  ---
152
477
 
@@ -185,3 +510,623 @@ interface GridPivotModel {
185
510
  valueFields: { field: string; aggFn: string; headerName?: string }[];
186
511
  }
187
512
  ```
513
+
514
+ ---
515
+
516
+ ## 📡 Event Callback Types
517
+
518
+ These interfaces describe the parameter objects passed to every event callback prop.
519
+
520
+ ### `GridRowParams<R>`
521
+ Passed to `onRowClick`.
522
+
523
+ | Property | Type | Description |
524
+ | :--- | :--- | :--- |
525
+ | `row` | `R` | The full row data object. |
526
+ | `id` | `GridRowId` | Unique identifier of the row. |
527
+ | `rowIndex` | `number` | Zero-based index of the row in the visible dataset. |
528
+
529
+ ### `GridCellParams<R>`
530
+ Passed to `onCellClick` and `isCellEditable`.
531
+
532
+ | Property | Type | Description |
533
+ | :--- | :--- | :--- |
534
+ | `row` | `R` | The full row data object. |
535
+ | `field` | `string` | The column field name. |
536
+ | `value` | `unknown` | The cell value (after `valueGetter`, before `valueFormatter`). |
537
+ | `colDef` | `GridColDef<R>` | The column definition. |
538
+ | `rowIndex` | `number` | Zero-based row index in the visible dataset. |
539
+ | `colIndex` | `number` | Zero-based column index. |
540
+
541
+ ### `GridColumnOrderChangeParams`
542
+ Passed to `onColumnOrderChange`.
543
+
544
+ | Property | Type | Description |
545
+ | :--- | :--- | :--- |
546
+ | `column` | `GridColDef` | The column definition that was moved. |
547
+ | `oldIndex` | `number` | Previous column index. |
548
+ | `targetIndex` | `number` | New column index after the move. |
549
+
550
+ ### `GridRowOrderChangeParams<R>`
551
+ Passed to `onRowOrderChange`.
552
+
553
+ | Property | Type | Description |
554
+ | :--- | :--- | :--- |
555
+ | `row` | `R` | The row data object that was moved. |
556
+ | `oldIndex` | `number` | Previous row index. |
557
+ | `targetIndex` | `number` | New row index after the move. |
558
+
559
+ ### `GridRowScrollEndParams`
560
+ Passed to `onRowsScrollEnd`.
561
+
562
+ | Property | Type | Description |
563
+ | :--- | :--- | :--- |
564
+ | `visibleTop` | `number` | Pixel offset of the topmost visible row. |
565
+ | `visibleBottom` | `number` | Pixel offset of the bottommost visible row. |
566
+ | `viewportHeight` | `number` | Current height of the scroll viewport in pixels. |
567
+
568
+ ### `GridDetailPanelParams<R>`
569
+ Passed to `getDetailPanelContent` and `getDetailPanelHeight`.
570
+
571
+ | Property | Type | Description |
572
+ | :--- | :--- | :--- |
573
+ | `row` | `R` | The full row data object. |
574
+ | `id` | `GridRowId` | Unique identifier of the row. |
575
+ | `rowIndex` | `number` | Zero-based index of the row in the visible dataset. |
576
+
577
+ ---
578
+
579
+ ## 🌐 Server-Side Data Source (`GridDataSource`)
580
+
581
+ The `dataSource` prop is the primary integration point for connecting the grid to a remote API. When set, the grid delegates data fetching — including pagination, sorting, filtering, and optionally aggregation — to your `getRows` function instead of processing the data locally.
582
+
583
+ ### `GridDataSource<R>`
584
+
585
+ | Property | Type | Required | Description |
586
+ | :--- | :--- | :--- | :--- |
587
+ | `getRows` | `(params: GridGetRowsParams) => Promise<GridGetRowsResponse<R>>` | Yes | Called whenever the grid needs data. The promise must resolve with rows and optionally a total row count. |
588
+ | `getAggregations` | `(params: Omit<GridGetRowsParams, 'startRow' \| 'endRow'>) => Promise<Record<string, unknown>>` | No | Called to fetch aggregate values for the full dataset (e.g. column totals). If omitted, aggregation results from `getRows.aggregationResults` are used instead. |
589
+
590
+ ### `GridGetRowsParams`
591
+ The object passed to `getRows` by the grid on every data fetch.
592
+
593
+ | Property | Type | Description |
594
+ | :--- | :--- | :--- |
595
+ | `startRow` | `number` | Zero-based index of the first row to fetch (based on `page * pageSize`). |
596
+ | `endRow` | `number` | Zero-based index of the last row to fetch (exclusive). |
597
+ | `sortModel` | `GridSortItem[]` | Active sort configuration. Empty array when no sort is applied. |
598
+ | `filterModel` | `GridFilterModel` | Active filter state. Has empty `items` when no filters are applied. |
599
+ | `groupKeys` | `string[]` | Path of grouping key values for the current group level (used in lazy-loaded tree/grouping). Empty array for the root level. |
600
+ | `aggregationModel` | `GridAggregationModel \| undefined` | Active aggregation configuration, forwarded so the server can compute column summaries. |
601
+
602
+ ### `GridGetRowsResponse<R>`
603
+ The object your `getRows` function must resolve with.
604
+
605
+ | Property | Type | Required | Description |
606
+ | :--- | :--- | :--- | :--- |
607
+ | `rows` | `R[]` | Yes | The rows for the requested page/range. |
608
+ | `rowCount` | `number` | No | Total number of rows in the full dataset. Required for server-side pagination; optional for infinite scroll. |
609
+ | `aggregationResults` | `Record<string, unknown>` | No | Column aggregation values for this response (e.g. `{ salary: 84500, age: 34.2 }`). |
610
+
611
+ ### Complete Server-Side Usage Example
612
+
613
+ ```tsx
614
+ import {
615
+ DataGrid,
616
+ GridDataSource,
617
+ GridGetRowsParams,
618
+ GridGetRowsResponse,
619
+ GridRowModel,
620
+ GridColDef,
621
+ } from '@opencorestack/opengridx';
622
+
623
+ interface Employee extends GridRowModel {
624
+ id: number;
625
+ name: string;
626
+ department: string;
627
+ salary: number;
628
+ }
629
+
630
+ const columns: GridColDef<Employee>[] = [
631
+ { field: 'name', headerName: 'Name', flex: 1 },
632
+ { field: 'department', headerName: 'Department', width: 160 },
633
+ { field: 'salary', headerName: 'Salary', type: 'number', width: 130 },
634
+ ];
635
+
636
+ const dataSource: GridDataSource<Employee> = {
637
+ getRows: async (params: GridGetRowsParams): Promise<GridGetRowsResponse<Employee>> => {
638
+ const response = await fetch('/api/employees', {
639
+ method: 'POST',
640
+ headers: { 'Content-Type': 'application/json' },
641
+ body: JSON.stringify(params),
642
+ });
643
+ const data = await response.json();
644
+ // data must have shape { rows: Employee[], total: number }
645
+ return { rows: data.rows, rowCount: data.total };
646
+ },
647
+
648
+ getAggregations: async (params) => {
649
+ const response = await fetch('/api/employees/aggregations', {
650
+ method: 'POST',
651
+ headers: { 'Content-Type': 'application/json' },
652
+ body: JSON.stringify(params),
653
+ });
654
+ return response.json(); // e.g. { salary: 4250000 }
655
+ },
656
+ };
657
+
658
+ export default function EmployeeGrid() {
659
+ return (
660
+ <DataGrid<Employee>
661
+ rows={[]}
662
+ columns={columns}
663
+ dataSource={dataSource}
664
+ paginationMode="server"
665
+ sortingMode="server"
666
+ filterMode="server"
667
+ pagination
668
+ pageSizeOptions={[25, 50, 100]}
669
+ height={600}
670
+ />
671
+ );
672
+ }
673
+ ```
674
+
675
+ **How the grid decides when to call `getRows`:** The hook fires (with a 300 ms debounce) whenever `paginationMode`, `sortingMode`, or `filterMode` is set to `'server'` AND a `dataSource` is provided. Any change to `sortModel`, `filterModel`, or `paginationModel` re-triggers the fetch. Stale responses from superseded requests are silently discarded — only the latest request updates the grid.
676
+
677
+ ---
678
+
679
+ ## 📌 Column & Row Pinning
680
+
681
+ ### `GridColumnPinning`
682
+
683
+ Pinned columns stay visible at the left or right edge of the grid during horizontal scrolling.
684
+
685
+ ```typescript
686
+ interface GridColumnPinning {
687
+ left?: string[]; // field names pinned to the left edge
688
+ right?: string[]; // field names pinned to the right edge
689
+ }
690
+ ```
691
+
692
+ ### `GridRowPinning`
693
+
694
+ Pinned rows stay visible at the top or bottom of the viewport during vertical scrolling. The values are **row IDs**, not full row objects.
695
+
696
+ ```typescript
697
+ interface GridRowPinning {
698
+ top?: GridRowId[]; // IDs of rows pinned to the top
699
+ bottom?: GridRowId[]; // IDs of rows pinned to the bottom
700
+ }
701
+ ```
702
+
703
+ ### Column-level pinning control
704
+
705
+ Set `pinnable: false` on a `GridColDef` to prevent that column from being pinned via the UI menu.
706
+
707
+ ### Usage Example
708
+
709
+ ```tsx
710
+ import { DataGrid, GridColumnPinning, GridRowPinning } from '@opencorestack/opengridx';
711
+
712
+ <DataGrid
713
+ rows={rows}
714
+ columns={columns}
715
+ // Pin 'name' to the left and 'actions' to the right
716
+ pinnedColumns={{ left: ['name'], right: ['actions'] }}
717
+ // Pin rows with id=1 to the top, id=99 to the bottom
718
+ pinnedRows={{ top: [1], bottom: [99] }}
719
+ // Checkbox and expand columns stay visible while scrolling horizontally
720
+ checkboxSelection
721
+ pinCheckboxColumn
722
+ pinExpandColumn
723
+ />
724
+ ```
725
+
726
+ ---
727
+
728
+ ## 🔍 Filter Model Deep Reference
729
+
730
+ ### `GridFilterModel`
731
+
732
+ The top-level filter state passed to `filterModel` and `onFilterModelChange`.
733
+
734
+ ```typescript
735
+ interface GridFilterModel {
736
+ items?: (GridFilterItem | GridFilterGroup)[];
737
+ logicOperator?: 'and' | 'or'; // default: 'and'
738
+ quickFilterValues?: string[]; // global search terms
739
+ }
740
+ ```
741
+
742
+ | Property | Type | Default | Description |
743
+ | :--- | :--- | :--- | :--- |
744
+ | `items` | `(GridFilterItem \| GridFilterGroup)[]` | `[]` | Root-level filter conditions or nested groups. |
745
+ | `logicOperator` | `'and' \| 'or'` | `'and'` | How to combine the root `items`. |
746
+ | `quickFilterValues` | `string[]` | `[]` | Each string must match at least one field in the row (all strings must match). Applied before `items`. |
747
+
748
+ ### `GridFilterItem`
749
+
750
+ A single column filter condition.
751
+
752
+ | Property | Type | Required | Description |
753
+ | :--- | :--- | :--- | :--- |
754
+ | `id` | `string \| number` | No | Optional stable identifier for this item (useful for controlled updates). |
755
+ | `field` | `string` | Yes | The column field to filter on. |
756
+ | `operator` | `GridFilterOperator` | Yes | The comparison operator to apply. |
757
+ | `value` | `unknown` | No | The value to compare against. Not required for `isEmpty` / `isNotEmpty`. |
758
+
759
+ ### `GridFilterGroup`
760
+
761
+ A nested group of filter conditions combined with a logical operator. Groups can be nested arbitrarily.
762
+
763
+ | Property | Type | Required | Description |
764
+ | :--- | :--- | :--- | :--- |
765
+ | `id` | `string \| number` | No | Optional stable identifier. |
766
+ | `logicOperator` | `'and' \| 'or'` | Yes | How to combine the child `items` in this group. |
767
+ | `items` | `(GridFilterItem \| GridFilterGroup)[]` | Yes | Child conditions or nested groups. |
768
+
769
+ ### `GridFilterOperator`
770
+
771
+ ```typescript
772
+ type GridFilterOperator =
773
+ | 'contains' | 'equals' | 'startsWith' | 'endsWith'
774
+ | 'isEmpty' | 'isNotEmpty' | 'isAnyOf'
775
+ | '>' | '>=' | '<' | '<=' | '!='
776
+ | 'is' | 'not';
777
+ ```
778
+
779
+ ### Default Operators per Column Type
780
+
781
+ The filter panel shows only the operators relevant to each column's `type`. These are also the operators used when the grid applies client-side filtering.
782
+
783
+ | Column `type` | Default operator | Available operators |
784
+ | :--- | :--- | :--- |
785
+ | `string` (default) | `contains` | `contains`, `equals`, `startsWith`, `endsWith`, `isEmpty`, `isNotEmpty` |
786
+ | `number` | `=` (numeric eq.) | `=`, `!=`, `>`, `>=`, `<`, `<=`, `isEmpty`, `isNotEmpty` |
787
+ | `date` | `is` | `is`, `not`, `after`, `onOrAfter`, `before`, `onOrBefore`, `isEmpty`, `isNotEmpty` |
788
+ | `boolean` | `is` | `is` |
789
+ | `singleSelect` | `isAnyOf` | `isAnyOf`, `is`, `not` |
790
+
791
+ ### Nested Group Example
792
+
793
+ ```tsx
794
+ import { DataGrid, GridFilterModel } from '@opencorestack/opengridx';
795
+
796
+ const filterModel: GridFilterModel = {
797
+ logicOperator: 'and',
798
+ items: [
799
+ // Simple item: department equals Engineering
800
+ { id: 1, field: 'department', operator: 'equals', value: 'Engineering' },
801
+ // Nested group: salary > 80000 OR title contains 'Lead'
802
+ {
803
+ id: 2,
804
+ logicOperator: 'or',
805
+ items: [
806
+ { id: 3, field: 'salary', operator: '>', value: 80000 },
807
+ { id: 4, field: 'title', operator: 'contains', value: 'Lead' },
808
+ ],
809
+ },
810
+ ],
811
+ };
812
+
813
+ <DataGrid rows={rows} columns={columns} filterModel={filterModel} filterMode="client" />
814
+ ```
815
+
816
+ ---
817
+
818
+ ## 💾 `GridInitialState` and `GridState`
819
+
820
+ `GridInitialState` is a type alias for `GridState`. Pass it to `initialState` to restore persisted grid state on mount (e.g. from `localStorage`). Each key corresponds to a feature slice — all are optional.
821
+
822
+ ```typescript
823
+ type GridInitialState = GridState;
824
+
825
+ interface GridState {
826
+ sorting?: GridSortingState;
827
+ filter?: GridFilterState;
828
+ pagination?: GridPaginationState;
829
+ columns?: GridColumnsState;
830
+ density?: GridDensityState;
831
+ dataSource?: GridDataSourceState;
832
+ }
833
+ ```
834
+
835
+ ### State Slices
836
+
837
+ | Slice key | Interface | Shape |
838
+ | :--- | :--- | :--- |
839
+ | `sorting` | `GridSortingState` | `{ sortModel: GridSortItem[] }` |
840
+ | `filter` | `GridFilterState` | `{ filterModel: GridFilterModel }` |
841
+ | `pagination` | `GridPaginationState` | `{ paginationModel: GridPaginationModel; rowCount?: number }` |
842
+ | `columns` | `GridColumnsState` | `{ columnWidths: Record<string, number>; columnOrder: string[]; pinnedColumns?: GridColumnPinning; columnVisibilityModel?: Record<string, boolean> }` |
843
+ | `density` | `GridDensityState` | `{ density: 'compact' \| 'standard' \| 'comfortable' }` |
844
+ | `dataSource` | `GridDataSourceState` | `{ loading: boolean; error?: unknown }` (read-only — not meaningful to restore) |
845
+
846
+ ### Restoring State from `localStorage`
847
+
848
+ The recommended way is `useGridStateStorage` (see Hooks section), but you can also drive it manually:
849
+
850
+ ```tsx
851
+ import { DataGrid, GridState, GridInitialState } from '@opencorestack/opengridx';
852
+ import { useState, useCallback } from 'react';
853
+
854
+ const STORAGE_KEY = 'my-grid-state';
855
+
856
+ function loadState(): GridInitialState | undefined {
857
+ try {
858
+ const raw = localStorage.getItem(STORAGE_KEY);
859
+ return raw ? (JSON.parse(raw) as GridInitialState) : undefined;
860
+ } catch {
861
+ return undefined;
862
+ }
863
+ }
864
+
865
+ export default function PersistentGrid() {
866
+ const [initialState] = useState<GridInitialState | undefined>(loadState);
867
+
868
+ const handleStateChange = useCallback((state: GridState) => {
869
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
870
+ }, []);
871
+
872
+ return (
873
+ <DataGrid
874
+ rows={rows}
875
+ columns={columns}
876
+ initialState={initialState}
877
+ onStateChange={handleStateChange}
878
+ />
879
+ );
880
+ }
881
+ ```
882
+
883
+ ---
884
+
885
+ ## ∑ Aggregation Reference
886
+
887
+ ### `GridAggregationModel`
888
+
889
+ A plain object mapping column field names to aggregation function names.
890
+
891
+ ```typescript
892
+ interface GridAggregationModel {
893
+ [field: string]: string; // e.g. { salary: 'sum', age: 'avg', name: 'count' }
894
+ }
895
+ ```
896
+
897
+ ### `GridAggregationResult`
898
+
899
+ The computed output, also a plain object mapping field names to their computed values.
900
+
901
+ ```typescript
902
+ type GridAggregationResult = Record<string, unknown>;
903
+ // e.g. { salary: 420000, age: 34.2, name: 12 }
904
+ ```
905
+
906
+ ### Built-In Aggregation Functions
907
+
908
+ | Function | Input | Output | Description |
909
+ | :--- | :--- | :--- | :--- |
910
+ | `sum` | numeric values | `number` | Sum of all non-null numeric values in the group. |
911
+ | `avg` | numeric values | `number \| null` | Arithmetic mean. Returns `null` if there are no numeric values. |
912
+ | `min` | numeric values | `number \| null` | Smallest numeric value. Returns `null` if there are no numeric values. |
913
+ | `max` | numeric values | `number \| null` | Largest numeric value. Returns `null` if there are no numeric values. |
914
+ | `count` | any values | `number` | Count of non-null values. |
915
+ | `unique` | any values | `number` | Count of distinct non-null values (uses a `Set`). |
916
+
917
+ All built-in functions are exported as the `BuiltInAggFn` type: `'sum' | 'avg' | 'count' | 'min' | 'max' | 'unique'`.
918
+
919
+ Use `formatAggregationValue(value, fnName)` to turn a raw result into a display string. `avg` is formatted to 2 decimal places; all other numbers use `toLocaleString()`.
920
+
921
+ ### `getAggregationPosition` Callback
922
+
923
+ ```typescript
924
+ getAggregationPosition?: (groupNode: GridTreeNode | null) => 'inline' | 'footer' | null
925
+ ```
926
+
927
+ Called once per group node (and once with `null` for the grand-total / root level) to decide where the aggregated row appears:
928
+
929
+ | Return value | Effect |
930
+ | :--- | :--- |
931
+ | `'inline'` | Aggregation values appear inside the group header row itself. |
932
+ | `'footer'` | A separate aggregation row is rendered below the group's last row. |
933
+ | `null` | Aggregation result is hidden for this group. |
934
+
935
+ ### Aggregation + Row Grouping Example
936
+
937
+ ```tsx
938
+ import {
939
+ DataGrid,
940
+ GridColDef,
941
+ GridRowModel,
942
+ GridTreeNode,
943
+ GridAggregationModel,
944
+ GridRowGroupingModel,
945
+ } from '@opencorestack/opengridx';
946
+
947
+ interface Employee extends GridRowModel {
948
+ id: number;
949
+ name: string;
950
+ department: string;
951
+ salary: number;
952
+ }
953
+
954
+ const columns: GridColDef<Employee>[] = [
955
+ { field: 'name', headerName: 'Name', flex: 1 },
956
+ { field: 'department', headerName: 'Department', width: 160, groupable: true },
957
+ { field: 'salary', headerName: 'Salary', type: 'number', width: 130, aggregable: true },
958
+ ];
959
+
960
+ const aggregationModel: GridAggregationModel = { salary: 'sum' };
961
+ const rowGroupingModel: GridRowGroupingModel = ['department'];
962
+
963
+ export default function GroupedGrid() {
964
+ return (
965
+ <DataGrid<Employee>
966
+ rows={rows}
967
+ columns={columns}
968
+ rowGroupingModel={rowGroupingModel}
969
+ aggregationModel={aggregationModel}
970
+ getAggregationPosition={(groupNode: GridTreeNode | null) => {
971
+ // Show grand total at the footer, group totals inline
972
+ return groupNode === null ? 'footer' : 'inline';
973
+ }}
974
+ />
975
+ );
976
+ }
977
+ ```
978
+
979
+ ---
980
+
981
+ ## 🗂️ Row Grouping
982
+
983
+ ### `GridRowGroupingModel`
984
+
985
+ A simple array of field names that defines which columns the grid groups rows by, in order.
986
+
987
+ ```typescript
988
+ type GridRowGroupingModel = string[];
989
+ // e.g. ['department'] → one level of grouping
990
+ // e.g. ['department', 'team'] → two levels (department → team → rows)
991
+ ```
992
+
993
+ The referenced fields must have `groupable: true` (the default) on their `GridColDef`.
994
+
995
+ ### Usage Example
996
+
997
+ ```tsx
998
+ import { DataGrid, GridRowGroupingModel, GridAggregationModel } from '@opencorestack/opengridx';
999
+ import { useState } from 'react';
1000
+
1001
+ export default function GroupingExample() {
1002
+ const [rowGroupingModel, setRowGroupingModel] = useState<GridRowGroupingModel>(['department']);
1003
+ const [aggregationModel, setAggregationModel] = useState<GridAggregationModel>({
1004
+ salary: 'sum',
1005
+ age: 'avg',
1006
+ });
1007
+
1008
+ return (
1009
+ <DataGrid
1010
+ rows={rows}
1011
+ columns={columns}
1012
+ rowGroupingModel={rowGroupingModel}
1013
+ onRowGroupingModelChange={setRowGroupingModel}
1014
+ aggregationModel={aggregationModel}
1015
+ onAggregationModelChange={setAggregationModel}
1016
+ />
1017
+ );
1018
+ }
1019
+ ```
1020
+
1021
+ ### Interaction with `aggregationModel`
1022
+
1023
+ When `rowGroupingModel` is set, the grid creates group nodes (see `GridTreeNode`). The `aggregationModel` controls which columns show summaries on those nodes. The `getAggregationPosition` callback gives per-group control over whether the summary appears inline (in the group header row) or as a footer row below the group. For server-side grouping, return aggregation values in `GridGetRowsResponse.aggregationResults`.
1024
+
1025
+ ---
1026
+
1027
+ ## 🗃️ Column Group Headers
1028
+
1029
+ Column groups render spanning header cells above the normal column header row, allowing multi-level column organisation.
1030
+
1031
+ ### `GridColumnGroup`
1032
+
1033
+ ```typescript
1034
+ interface GridColumnGroup {
1035
+ groupId: string; // unique identifier for this group
1036
+ headerName: string; // text shown in the spanning header cell
1037
+ headerClassName?: string; // optional CSS class for custom styling
1038
+ children: Array<string | GridColumnGroup>; // field names (leaf) or nested groups
1039
+ }
1040
+
1041
+ type GridColumnGroupingModel = GridColumnGroup[];
1042
+ ```
1043
+
1044
+ The `children` array can contain:
1045
+ - **`string`** — a column `field` name, making this a leaf group that spans exactly those columns.
1046
+ - **`GridColumnGroup`** — a nested sub-group, enabling multi-level spanning headers.
1047
+
1048
+ ### Usage Example
1049
+
1050
+ ```tsx
1051
+ import { DataGrid, GridColumnGroup, GridColumnGroupingModel } from '@opencorestack/opengridx';
1052
+
1053
+ const columnGroupingModel: GridColumnGroupingModel = [
1054
+ {
1055
+ groupId: 'personal',
1056
+ headerName: 'Personal Info',
1057
+ children: ['firstName', 'lastName', 'age'],
1058
+ },
1059
+ {
1060
+ groupId: 'compensation',
1061
+ headerName: 'Compensation',
1062
+ children: [
1063
+ { groupId: 'base', headerName: 'Base', children: ['salary', 'bonus'] },
1064
+ { groupId: 'equity', headerName: 'Equity', children: ['options', 'rsu'] },
1065
+ ],
1066
+ },
1067
+ ];
1068
+
1069
+ export default function GroupedHeadersGrid() {
1070
+ return (
1071
+ <DataGrid
1072
+ rows={rows}
1073
+ columns={columns}
1074
+ columnGroupingModel={columnGroupingModel}
1075
+ />
1076
+ );
1077
+ }
1078
+ ```
1079
+
1080
+ The grid renders two header rows: the spanning group row on top, and the regular per-column header row below. Groups that are not referenced in `columnGroupingModel` are rendered without a spanning header.
1081
+
1082
+ ---
1083
+
1084
+ ## 📋 List View
1085
+
1086
+ List view renders the grid as a single-column list of cards, replacing all normal column layout. It is designed for narrow viewports and mobile layouts.
1087
+
1088
+ ### `GridListViewColDef<R>`
1089
+
1090
+ ```typescript
1091
+ interface GridListViewColDef<R extends GridRowModel = GridRowModel> {
1092
+ field: string;
1093
+ renderCell: (params: GridRenderCellParams<R>) => React.ReactNode;
1094
+ }
1095
+ ```
1096
+
1097
+ The `renderCell` function receives the full `GridRenderCellParams` (including `row`), so you have access to every field of the row to build a rich card layout.
1098
+
1099
+ ### Usage Example
1100
+
1101
+ ```tsx
1102
+ import { DataGrid, GridListViewColDef, GridRenderCellParams } from '@opencorestack/opengridx';
1103
+
1104
+ interface Employee extends GridRowModel {
1105
+ id: number;
1106
+ name: string;
1107
+ department: string;
1108
+ salary: number;
1109
+ }
1110
+
1111
+ const listViewColumn: GridListViewColDef<Employee> = {
1112
+ field: 'card',
1113
+ renderCell: (params: GridRenderCellParams<Employee>) => (
1114
+ <div style={{ padding: '12px 16px' }}>
1115
+ <strong>{params.row.name}</strong>
1116
+ <span> — {params.row.department}</span>
1117
+ <div>${params.row.salary.toLocaleString()}</div>
1118
+ </div>
1119
+ ),
1120
+ };
1121
+
1122
+ export default function MobileGrid() {
1123
+ return (
1124
+ <DataGrid<Employee>
1125
+ rows={rows}
1126
+ columns={columns}
1127
+ listView
1128
+ listViewColumn={listViewColumn}
1129
+ />
1130
+ );
1131
+ }
1132
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencorestack/opengridx",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.",
5
5
  "type": "module",
6
6
  "main": "./dist/opengridx.umd.js",