@opencorestack/opengridx 0.1.9 → 1.0.1

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +162 -19
  3. package/dist/index.d.ts +60 -41
  4. package/dist/opengridx.es.js +5045 -4549
  5. package/dist/opengridx.es.js.map +1 -1
  6. package/dist/opengridx.umd.js +11 -11
  7. package/dist/opengridx.umd.js.map +1 -1
  8. package/docs/API_REFERENCE.md +17 -7
  9. package/docs/README.md +14 -0
  10. package/docs/components/aggregation-footer.md +48 -0
  11. package/docs/components/datagrid.md +8 -3
  12. package/docs/components/empty-state.md +50 -0
  13. package/docs/components/error-overlay.md +40 -0
  14. package/docs/features/editing-reordering.md +11 -7
  15. package/docs/features/filtering.md +10 -8
  16. package/docs/features/selection.md +2 -1
  17. package/docs/features/sorting-pagination.md +3 -1
  18. package/docs/features/state-persistence.md +1 -2
  19. package/docs/roadmap.md +20 -1
  20. package/lib/components/Cell/Cell.tsx +9 -10
  21. package/lib/components/Cell/GridEditInputCell.tsx +4 -4
  22. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
  23. package/lib/components/DataGrid/DataGrid.regression.test.tsx +132 -0
  24. package/lib/components/DataGrid/DataGrid.tsx +336 -1018
  25. package/lib/components/DataGrid/GridAggregationFooter.tsx +68 -0
  26. package/lib/components/DataGrid/GridEmptyState.tsx +16 -0
  27. package/lib/components/DataGrid/GridErrorOverlay.tsx +33 -0
  28. package/lib/components/DataGrid/GridListView.tsx +116 -0
  29. package/lib/components/DataGrid/GridPinnedRows.tsx +98 -0
  30. package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +124 -0
  31. package/lib/components/DataGrid/GridVirtualRows.tsx +202 -0
  32. package/lib/components/FilterPanel/FilterPanel.tsx +18 -10
  33. package/lib/components/Header/ColumnMenu.tsx +2 -2
  34. package/lib/components/Header/Header.tsx +4 -4
  35. package/lib/components/ListView/ListViewRow.tsx +2 -2
  36. package/lib/components/Row/Row.tsx +10 -10
  37. package/lib/components/SkeletonRow/SkeletonRow.tsx +9 -2
  38. package/lib/components/Toolbar/GridToolbar.tsx +6 -0
  39. package/lib/hooks/core/useDataGrid.regression.test.ts +66 -0
  40. package/lib/hooks/core/useDataGrid.ts +14 -34
  41. package/lib/hooks/core/useGridColumns.tsx +228 -0
  42. package/lib/hooks/core/useGridControlledState.ts +192 -0
  43. package/lib/hooks/core/useGridRowPipeline.ts +109 -0
  44. package/lib/hooks/core/useGridScrollSync.ts +52 -0
  45. package/lib/hooks/core/useGridStateSnapshot.ts +63 -0
  46. package/lib/hooks/core/useGridVirtualization.ts +186 -0
  47. package/lib/hooks/core/useGridVisibleRows.ts +66 -0
  48. package/lib/hooks/core/useLayout.ts +0 -6
  49. package/lib/hooks/features/useAggregation.ts +7 -7
  50. package/lib/hooks/features/useGridDataSource.ts +1 -1
  51. package/lib/hooks/features/useGridEditing.ts +31 -32
  52. package/lib/hooks/features/usePivot.test.ts +202 -0
  53. package/lib/hooks/features/usePivot.ts +0 -0
  54. package/lib/hooks/useColumnReorder.ts +1 -1
  55. package/lib/hooks/useRowGrouping.ts +14 -14
  56. package/lib/hooks/useTreeData.ts +1 -1
  57. package/lib/state/types.ts +1 -1
  58. package/lib/state/useGridStateStorage.ts +1 -1
  59. package/lib/types/index.ts +48 -32
  60. package/lib/utils/export/exportToExcelAdvanced.ts +14 -14
  61. package/lib/utils/export/index.ts +22 -17
  62. package/lib/utils/filtering/index.ts +20 -20
  63. package/lib/utils/sorting/index.ts +1 -1
  64. package/package.json +1 -1
@@ -22,11 +22,15 @@ The main component for displaying and interacting with data.
22
22
  | `checkboxSelection` | `boolean` | `false` | Enable row selection via checkboxes. |
23
23
  | `pagination` | `boolean` | `false` | Enable the bottom pagination bar. |
24
24
  | `paginationMode` | `'client' \| 'server' \| 'infinite'` | `'client'` | How to handle paging. |
25
- | `paginationModel` | `GridPaginationModel` | `{page:0, pageSize:25}` | Current page and page size. |
25
+ | `paginationModel` | `GridPaginationModel` | — | Controlled pagination state (`{ page, pageSize }`). |
26
+ | `onPaginationModelChange` | `(model: GridPaginationModel) => void` | — | Fired when page or page size changes. |
27
+ | `pageSizeOptions` | `number[]` | `[10, 25, 50]` | Available page size options. |
26
28
  | `rowCount` | `number` | — | Total rows (required for server-side paging). |
29
+ | `height` | `number \| string` | `undefined` | Total height of the grid container. |
30
+ | `density` | `'compact' \| 'standard' \| 'comfortable'` | `'standard'` | Visual row density. |
27
31
  | `initialState` | `GridInitialState` | `undefined` | Starting state for sorting, filters, etc. |
28
32
  | `slots` | `GridSlots` | `{}` | Custom component overrides. |
29
- | `slotProps` | `Record<string, any>` | `{}` | Props passed to custom slots. |
33
+ | `slotProps` | `Record<string, unknown>` | `{}` | Props passed to custom slots. |
30
34
  | `filterModel` | `GridFilterModel` | `undefined` | Active filters. |
31
35
  | `sortModel` | `GridSortItem[]` | `undefined` | Active sorting. |
32
36
  | `onRowClick` | `(params: GridRowParams) => void` | — | Fired when a row is clicked. |
@@ -54,8 +58,14 @@ Access these methods via the `apiRef` prop.
54
58
  | `sortColumn(field, dir)` | `void` | Programmatically sort a column. |
55
59
  | `getSortModel()` | `GridSortItem[]` | Get active sorting state. |
56
60
  | `setFilterModel(model)` | `void` | Programmatically set filters. |
61
+ | `getFilterModel()` | `GridFilterModel` | Get the current filter model. |
57
62
  | `setPage(page)` | `void` | Change current page (0-indexed). |
63
+ | `setPageSize(pageSize)` | `void` | Change the current page size. |
58
64
  | `scrollToIndexes(params)` | `void` | Scroll to specific row/column index. |
65
+ | `getAllColumns()` | `GridColDef[]` | Get all defined columns. |
66
+ | `getAggregationResult()` | `Record<string, unknown> \| null` | Get current aggregation results. |
67
+ | `getAggregationModel()` | `GridAggregationModel \| null` | Get the active aggregation configuration. |
68
+ | `copySelectedRows()` | `Promise<void>` | Copy selected rows to clipboard as TSV. |
59
69
 
60
70
  ---
61
71
 
@@ -71,7 +81,7 @@ Used in Tree Data and Row Grouping hierarchies.
71
81
  | `depth` | `number` | Nesting level (0 for root). |
72
82
  | `isExpanded` | `boolean` | Current expansion state. |
73
83
  | `children` | `GridRowId[]` | IDs of child nodes. |
74
- | `groupingValue` | `any` | The value this node is grouping by. |
84
+ | `groupingValue` | `unknown` | The value this node is grouping by. |
75
85
  | `aggregatedValues` | `object` | Sums/Avgs computed for this group. |
76
86
 
77
87
  ---
@@ -85,13 +95,13 @@ Defines the behavior and appearance of a single column.
85
95
  | :--- | :--- | :--- | :--- |
86
96
  | `field` | `string` | — | Unique identifier (matches row property). |
87
97
  | `headerName` | `string` | — | Text displayed in the header. |
88
- | `width` | `number` | `100` | Width in pixels. |
89
- | `type` | `'string' \| 'number' \| 'date' \| 'dateTime' \| 'boolean'` | `'string'` | Data type for formatting and filtering. |
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. |
90
100
  | `sortable` | `boolean` | `true` | Enable sorting for this column. |
91
101
  | `filterable` | `boolean` | `true` | Enable filtering for this column. |
92
102
  | `resizable` | `boolean` | `true` | Allow user to drag resize. |
93
103
  | `hideable` | `boolean` | `true` | Allow user to hide the column. |
94
- | `pinned` | `'left' \| 'right' \| null` | `null` | Sticky positioning. |
104
+ | `pinnable` | `boolean` | `true` | Allow column to be pinned via the UI. |
95
105
  | `exportable` | `boolean` | `true` | If `false`, column is excluded from all exports. |
96
106
  | `valueFormatter` | `(params: GridValueFormatterParams) => string` | — | Format value for display. |
97
107
  | `valueGetter` | `(params: GridValueGetterParams) => any` | — | Compute value from row data. |
@@ -153,7 +163,7 @@ interface GridSortItem {
153
163
  ### `GridFilterModel`
154
164
  ```typescript
155
165
  interface GridFilterModel {
156
- items: GridFilterItem[];
166
+ items?: (GridFilterItem | GridFilterGroup)[];
157
167
  logicOperator?: 'and' | 'or';
158
168
  quickFilterValues?: string[];
159
169
  }
package/docs/README.md CHANGED
@@ -22,6 +22,9 @@ Detailed reference for the core structural components.
22
22
  - **[Column Visibility](components/column-visibility.md)** - Managing visible fields
23
23
  - **[Column Grouping](components/column-group-header.md)** - Multi-level nested headers
24
24
  - **[Column Resizing](components/column-resize.md)** - Dynamic width controls
25
+ - **[Empty State](components/empty-state.md)** - No-rows overlay and `noRowsOverlay` slot
26
+ - **[Error Overlay](components/error-overlay.md)** - Data source error display
27
+ - **[Aggregation Footer](components/aggregation-footer.md)** - Sticky totals row
25
28
  ---
26
29
 
27
30
  ## 🚀 Features
@@ -55,6 +58,17 @@ Make the grid your own.
55
58
 
56
59
  ---
57
60
 
61
+ ## 🏛️ Architecture — Internal Hooks
62
+ Reference for core hooks extracted from `DataGrid.tsx`. Intended for contributors.
63
+
64
+ - **[useGridControlledState](architecture/use-grid-controlled-state.md)** - Controlled/uncontrolled state for all 7 state pairs
65
+ - **[useGridRowPipeline](architecture/use-grid-row-pipeline.md)** - Filter → pin → sort → paginate row pipeline
66
+ - **[useGridVirtualization](architecture/use-grid-virtualization.md)** - Render context and scroll-position math
67
+ - **[useGridColumns](architecture/use-grid-columns.md)** - Column management: effective, ordered, visible, reorder handlers
68
+ - **[useGridVisibleRows](architecture/use-grid-visible-rows.md)** - Render window: pinned + virtual center merge
69
+ - **[useGridScrollSync](architecture/use-grid-scroll-sync.md)** - RAF-batched scroll tick, scrollPosRef, onRowsScrollEnd threshold
70
+ - **[useGridStateSnapshot](architecture/use-grid-state-snapshot.md)** - onStateChange callback delivery on sort/filter/pagination/column changes
71
+
58
72
  ## 🛠️ Project Meta
59
73
  - **[Development Roadmap](roadmap.md)** - What's built and what's next
60
74
  - **[Research Notes](research/FEATURE_CATALOG.md)** - Comparative analysis and design decisions
@@ -0,0 +1,48 @@
1
+ # `<GridAggregationFooter />`
2
+
3
+ Internal component that renders the sticky aggregation totals row pinned to the bottom of the grid. Displays function name labels (e.g. `sum`, `avg`) alongside their computed values for each configured column.
4
+
5
+ ## ⚙️ Props
6
+
7
+ | Prop | Type | Description |
8
+ | :--- | :--- | :--- |
9
+ | `columns` | `GridColDef[]` | Ordered list of visible column definitions used to lay out cells. |
10
+ | `aggregationModel` | `GridAggregationModel` | Maps field names to function names (`'sum' \| 'avg' \| 'count' \| 'min' \| 'max'`). |
11
+ | `aggregationResult` | `GridAggregationResult` | Computed values keyed by field name, produced by `useAggregation`. |
12
+ | `columnWidths` | `Record<string, number>` | Runtime pixel widths for each field, kept in sync with user resize actions. |
13
+ | `rowHeight` | `number` | Sets `min-height` on the footer row, matching the grid's configured row height. |
14
+ | `checkboxSelection` | `boolean` | When `true`, inserts a 48px spacer at the start to align with the checkbox column. |
15
+ | `hasDetailPanel` | `boolean` | When `true`, inserts a 48px spacer to align with the expand/collapse column. |
16
+ | `rowReordering` | `boolean` | When `true`, inserts a 48px spacer to align with the drag handle column. |
17
+
18
+ ## 🔄 When it renders
19
+
20
+ `GridAggregationFooter` renders when `aggregationModel` has at least one entry and `aggregationResult` contains the corresponding computed values. The footer is sticky — it remains visible when the user scrolls vertically.
21
+
22
+ ## 🔢 Value formatting
23
+
24
+ Values are formatted by `formatAggregationValue` from `useAggregation`:
25
+ - `count` → integer (e.g. `42`)
26
+ - `sum` / `avg` / `min` / `max` → `toLocaleString()` with up to 2 decimal places
27
+
28
+ ## 🎨 Usage via DataGrid prop
29
+
30
+ ```tsx
31
+ <DataGrid
32
+ aggregationModel={{
33
+ salary: 'sum',
34
+ age: 'avg',
35
+ department: 'count'
36
+ }}
37
+ />
38
+ ```
39
+
40
+ The footer renders automatically. No additional configuration required.
41
+
42
+ ## ♿ Accessibility
43
+
44
+ The footer row uses `role="row"` and `aria-label="Aggregation totals"`. Each cell uses `role="gridcell"`. The container uses `aria-live="polite"` so screen readers announce value updates when the data changes.
45
+
46
+ ## 🔗 Related
47
+ - [DataGrid](datagrid.md)
48
+ - [Aggregation & Pivot](../features/aggregation-pivot.md)
@@ -29,8 +29,9 @@ function MyGrid() {
29
29
  | `loading` | `boolean` | `false` | Displays a loading skeleton/shimmer. |
30
30
  | `checkboxSelection` | `boolean` | `false` | Enable row checkboxes. |
31
31
  | `pagination` | `boolean` | `false` | Enable/Disable bottom pagination bar. |
32
- | `paginationModel` | `GridPaginationModel` | `{page:0, pageSize:25}` | Controlled pagination state. |
33
- | `initialState` | `GridState` | `undefined` | Initial configuration (sorting, columns, etc). |
32
+ | `paginationModel` | `GridPaginationModel` | — | Controlled pagination state (`{ page, pageSize }`). |
33
+ | `pageSizeOptions` | `number[]` | `[10, 25, 50]` | Available page size options. |
34
+ | `initialState` | `GridInitialState` | `undefined` | Initial configuration (sorting, columns, etc). |
34
35
 
35
36
  ## 🖱️ Interaction & Events
36
37
 
@@ -38,8 +39,9 @@ function MyGrid() {
38
39
  | :--- | :--- | :--- |
39
40
  | `onRowClick` | `(params) => void` | Fired when clicking a row body. |
40
41
  | `onCellClick` | `(params) => void` | Fired when clicking a specific cell. |
42
+ | `onPaginationModelChange` | `(model: GridPaginationModel) => void` | Fired when page or page size changes. |
41
43
  | `onStateChange` | `(state) => void` | Fired on any internal state update. |
42
- | `processRowUpdate` | `(new, old) => R` | Fired after a cell edit is committed. |
44
+ | `processRowUpdate` | `(new, old) => R \| Promise<R>` | Fired after a cell edit is committed. |
43
45
 
44
46
  ## 📦 Slots
45
47
 
@@ -68,3 +70,6 @@ Customize internal components using the `slots` prop.
68
70
  - [Column Visibility](column-visibility.md)
69
71
  - [Column Grouping](column-group-header.md)
70
72
  - [Column Resizing](column-resize.md)
73
+ - [Empty State](empty-state.md)
74
+ - [Error Overlay](error-overlay.md)
75
+ - [Aggregation Footer](aggregation-footer.md)
@@ -0,0 +1,50 @@
1
+ # `<GridEmptyState />`
2
+
3
+ Internal component rendered when the grid has no rows to display. Replaces the row viewport with a centered icon and message.
4
+
5
+ ## ⚙️ Props
6
+
7
+ | Prop | Type | Description |
8
+ | :--- | :--- | :--- |
9
+ | `noRowsLabel` | `string` | Text shown beneath the empty-state icon. Controlled by the `noRowsLabel` DataGrid prop. |
10
+ | `width` | `number` | Pixel width of the empty-state container, matched to the grid's total column width. |
11
+
12
+ ## 🔄 When it renders
13
+
14
+ `GridEmptyState` renders when `allRenderableRows.length === 0` and the grid is not in a loading state. The loading skeleton takes precedence — if `loading={true}` the skeleton rows are shown instead.
15
+
16
+ ## 🎨 Customizing via slot
17
+
18
+ Replace the default empty state with any React component using the `noRowsOverlay` slot:
19
+
20
+ ```tsx
21
+ function MyEmptyState() {
22
+ return (
23
+ <div style={{ textAlign: 'center', padding: '40px' }}>
24
+ <p>No results match your filter.</p>
25
+ <button onClick={clearFilters}>Clear filters</button>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ <DataGrid
31
+ slots={{ noRowsOverlay: MyEmptyState }}
32
+ slotProps={{ noRowsOverlay: { /* custom props if needed */ } }}
33
+ />
34
+ ```
35
+
36
+ ## 💬 Custom label (without a full slot replacement)
37
+
38
+ To only change the message text without replacing the whole component, use `noRowsLabel`:
39
+
40
+ ```tsx
41
+ <DataGrid
42
+ noRowsLabel="No employees found for this department."
43
+ />
44
+ ```
45
+
46
+ Default value: `"No rows"`
47
+
48
+ ## 🔗 Related
49
+ - [DataGrid](datagrid.md)
50
+ - [Loading States](../features/loading-states.md)
@@ -0,0 +1,40 @@
1
+ # `<GridErrorOverlay />`
2
+
3
+ Internal component rendered when a `GridDataSource` fetch fails. Displays an error icon, a human-readable message, and a **Retry** button that calls `window.location.reload()`.
4
+
5
+ ## ⚙️ Props
6
+
7
+ | Prop | Type | Description |
8
+ | :--- | :--- | :--- |
9
+ | `error` | `{ message?: string } \| null` | The error object from `state.dataSource.error`. When `null` the component renders nothing (`null` return). |
10
+
11
+ ## 🔄 When it renders
12
+
13
+ `GridErrorOverlay` only appears when using a server-side `dataSource` prop. When a fetch throws, DataGrid catches the error and stores it in internal state. The overlay replaces the row viewport until the user retries.
14
+
15
+ ```tsx
16
+ <DataGrid
17
+ dataSource={{
18
+ getRows: async ({ filterModel, sortModel, paginationModel }) => {
19
+ const res = await fetch('/api/employees');
20
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
21
+ return res.json();
22
+ }
23
+ }}
24
+ />
25
+ ```
26
+
27
+ If `getRows` throws, the overlay renders with the thrown error's `message`.
28
+
29
+ ## ♿ Accessibility
30
+
31
+ The overlay container uses `role="alert"` and `aria-live="assertive"` so screen readers announce the error immediately when it appears.
32
+
33
+ ## 🎨 Customizing
34
+
35
+ `GridErrorOverlay` is not currently exposed as a configurable slot. To implement custom error handling, catch errors in your `dataSource.getRows` and manage the error display in your own component wrapping the grid.
36
+
37
+ ## 🔗 Related
38
+ - [DataGrid](datagrid.md)
39
+ - [Data Source](../features/data-source.md)
40
+ - [Loading States](../features/loading-states.md)
@@ -82,18 +82,22 @@ const [colOrder, setColOrder] = useState(['id', 'name', 'status']);
82
82
  <DataGrid
83
83
  columnOrder={colOrder}
84
84
  onColumnOrderChange={(params) => {
85
- // params.newOrder is the updated array of field strings
86
- setColOrder(params.newOrder);
85
+ // params: { column: GridColDef, oldIndex: number, targetIndex: number }
86
+ const newOrder = [...colOrder];
87
+ const [moved] = newOrder.splice(params.oldIndex, 1);
88
+ newOrder.splice(params.targetIndex, 0, moved);
89
+ setColOrder(newOrder);
87
90
  }}
88
91
  />
89
92
  ```
90
93
 
91
94
  ### Non-Reorderable Columns
92
- To prevent a specific column from being moved, set `reorderable: false` in its definition.
95
+ To prevent all columns from being reordered, use `disableColumnReorder` on the grid. For individual column control, pin the column using `pinnedColumns` at the grid level — pinned columns do not participate in drag-reorder.
93
96
 
94
97
  ```tsx
95
- const columns = [
96
- { field: 'id', reorderable: false }, // Stay pinned at start
97
- { field: 'name' }
98
- ];
98
+ // Disable reordering globally
99
+ <DataGrid disableColumnReorder />
100
+
101
+ // Keep a column fixed by pinning it
102
+ <DataGrid pinnedColumns={{ left: ['id'] }} />
99
103
  ```
@@ -49,7 +49,7 @@ The `GridFilterModel` describes the current state of filters.
49
49
 
50
50
  ```typescript
51
51
  interface GridFilterModel {
52
- items: GridFilterItem[]; // Active filter items
52
+ items?: (GridFilterItem | GridFilterGroup)[]; // Active filter items or groups
53
53
  logicOperator?: 'and' | 'or'; // How to combine items
54
54
  quickFilterValues?: string[]; // Values for the global search
55
55
  }
@@ -57,27 +57,29 @@ interface GridFilterModel {
57
57
  interface GridFilterItem {
58
58
  field: string; // Column to filter
59
59
  operator: GridFilterOperator;
60
- value?: any; // Comparison value
60
+ value?: unknown; // Comparison value
61
61
  }
62
62
  ```
63
63
 
64
64
  ### Supported Operators
65
65
  | Type | Operators |
66
66
  | :--- | :--- |
67
- | **String** | `contains`, `equals`, `startsWith`, `endsWith`, `isEmpty`, `isNotEmpty` |
68
- | **Number** | `=`, `!=`, `>`, `>=`, `<`, `<=`, `isEmpty`, `isNotEmpty` |
69
- | **Date** | `is`, `not`, `after`, `before`, `isEmpty`, `isNotEmpty` |
67
+ | **String** | `contains`, `equals`, `startsWith`, `endsWith`, `isEmpty`, `isNotEmpty`, `isAnyOf` |
68
+ | **Number** | `equals`, `!=`, `>`, `>=`, `<`, `<=`, `isEmpty`, `isNotEmpty` |
69
+ | **Date** | `is`, `not`, `isEmpty`, `isNotEmpty` |
70
70
  | **Boolean** | `is` |
71
71
 
72
72
  ---
73
73
 
74
- ## 🎨 Customizing Filter Components
75
- You can replace the default filter panel via the `slots` API:
74
+ ## 🎨 Customizing the Toolbar
75
+ The filter panel is accessible through the built-in `GridToolbar`. To use your own toolbar that includes a filter entry point, pass a custom component to the `toolbar` slot:
76
76
 
77
77
  ```tsx
78
78
  <DataGrid
79
79
  slots={{
80
- filterPanel: MyCustomFilterPanel
80
+ toolbar: MyCustomToolbar
81
81
  }}
82
82
  />
83
83
  ```
84
+
85
+ The `FilterPanel` component is also exported from `@opencorestack/opengridx` if you need to embed it inside a custom layout.
@@ -47,7 +47,8 @@ const [selection, setSelection] = useState<GridRowId[]>([]);
47
47
  | `disableRowSelectionOnClick` | `boolean` | `false` | If true, clicking a cell won't select the row. |
48
48
  | `disableMultipleRowSelection` | `boolean` | `false` | Restricts selection to a single row. |
49
49
  | `rowSelectionModel` | `GridRowId[]` | `[]` | Controlled array of selected IDs. |
50
- | `isRowSelectable` | `(params) => boolean` | `undefined` | Predicate to disable selection for specific rows. |
50
+ | `onRowSelectionModelChange` | `(model: GridRowSelectionModel) => void` | — | Fired when the selection changes. |
51
+ | `pinCheckboxColumn` | `boolean` | `false` | Keep the checkbox column visible during horizontal scroll. |
51
52
 
52
53
  ---
53
54
 
@@ -52,7 +52,9 @@ When using `paginationMode="server"`, you must provide the `rowCount` and handle
52
52
  | Prop | Type | Default | Description |
53
53
  | :--- | :--- | :--- | :--- |
54
54
  | `pagination` | `boolean` | `false` | Enable/disable the pagination footer. |
55
- | `paginationMode` | `'client' \| 'server'` | `'client'` | Location of the pagination logic. |
55
+ | `paginationMode` | `'client' \| 'server' \| 'infinite'` | `'client'` | Location of the pagination logic. |
56
+ | `paginationModel` | `GridPaginationModel` | — | Controlled pagination state (`{ page, pageSize }`). |
57
+ | `onPaginationModelChange` | `(model: GridPaginationModel) => void` | — | Fired when page or page size changes. |
56
58
  | `pageSizeOptions` | `number[]` | `[10, 25, 50]` | Options for the rows-per-page selector. |
57
59
 
58
60
  ---
@@ -63,5 +63,4 @@ The following features support state persistence:
63
63
  - **Filtering**: `filterModel`
64
64
  - **Pagination**: `paginationModel`
65
65
  - **Columns**: `columnVisibilityModel`, `columnOrder`, `pinnedColumns`, `columnWidths`
66
- - **Selection**: `rowSelectionModel`
67
- - **Grouping**: `rowGroupingModel`
66
+ - **Density**: `density`
package/docs/roadmap.md CHANGED
@@ -54,6 +54,20 @@ This document tracks the current status of features in `OpenGridX` and outlines
54
54
  * **Demo**: `AdvancedExcelExportDemo` in demo app (`Data Management` category).
55
55
  * **Docs**: `docs/features/export-guide.md` fully updated with feature comparison table and all options.
56
56
 
57
+ ### Code Quality & Architecture *(completed 2026-07-29 → 2026-07-30)*
58
+ * **DataGrid decomposition**: Extracted `useGridControlledState`, `useGridRowPipeline`, `useGridVirtualization`, `useGridScrollSync`, `useGridVisibleRows`, `useGridColumns` hooks, plus `GridEmptyState`, `GridErrorOverlay`, `GridAggregationFooter`, `GridVirtualRows`, `GridPinnedRows`, `GridStandaloneColumnPanel`, `GridListView` components from `DataGrid.tsx`. Each module has a single clear responsibility with typed params and return interfaces.
59
+ * **Full `any` elimination**: Every `any` across `lib/` replaced with proper types or `unknown`. `GridRowModel` index signature, all cell/value/error params, aggregation functions, slot types, and export utilities are now fully typed. Zero `eslint-disable` suppressions remain — every case fixed at the root.
60
+ * **Regression test suite**: 3 test files covering column-visibility-model persistence, pivot aggregation correctness including grand-total avg and null-value exclusion, and pagination rendering/row key stability.
61
+ * **Pivot null-value fix**: `Number(null) === 0` caused null numeric fields to be silently included as `0` in avg/min/max pivot aggregation. Both the per-row bucket loop and the grand-total bucket loop in `usePivot.ts` now guard with `raw != null` before pushing values.
62
+
63
+ ### Bug Fixes *(completed 2026-07-30)*
64
+ * **`onRowClick` never fired when `onCellClick` was registered**: `Cell.tsx` called `e.stopPropagation()` on every cell click, silently preventing events from reaching the row's `onClick` handler. Removed — both callbacks now fire in natural bubble order.
65
+ * **Column visibility panel order not updating after reorder**: Panel received `effectiveColumns` (pre-ordering, definition order) instead of `orderedColumns`. Fixed — panel list now reflects the live reordered sequence.
66
+ * **Reset button ignoring column sequence**: Reset only restored visibility; column order was left wherever the user dragged it. Added `onColumnOrderReset` callback that restores `internalColumnOrder` to original definition order.
67
+ * **`FilterPanel` debounce stale-closure**: Debounce effect read `item`/`col.field`/`currentOperator` from stale closure, previously suppressed with `eslint-disable`. Replaced with `useLayoutEffect` refs-sync pattern.
68
+ * **`FilterPanelDemo` inaccessible**: Missing `slots={{ toolbar: GridToolbar }}` meant the filter panel had no UI entry point. Fixed; demo also migrated to `DocsLayout`.
69
+ * **`EventsDemo` dead handlers**: Fixed broken `onRowClick`, dead filter/column-order callbacks, and three `any`-typed handler signatures.
70
+
57
71
  ---
58
72
 
59
73
  ## 📅 Upcoming Features
@@ -66,6 +80,11 @@ This document tracks the current status of features in `OpenGridX` and outlines
66
80
  ### DX & Publishing
67
81
  * **npm publish**: Finalize `package.json` metadata, `README.md`, and publish to npm registry.
68
82
 
83
+ ### Demo Site
84
+ * **Full source viewer coverage**: Migrate all 32 examples to `DocsLayout` using Vite `?raw` auto-loading (eliminates string-literal source maintenance). See `docs/superpowers/plans/2026-07-29-demo-app-refactor.md`.
85
+ * **Syntax highlighting**: Prism.js token-based highlighting in the source viewer.
86
+ * **GitHub Pages deployment**: CI/CD via GitHub Actions (`base: '/OpenGridX/'` already configured in `vite.config.js`).
87
+
69
88
  ## ✅ Implemented Features (Recent)
70
- * **Storybook / Docs site**: Interactive component documentation with source code viewer and categorized navigation (implemented via `demo` app).
89
+ * **Demo app (partial)**: 32 interactive examples across 6 categories (Main features, Advanced features, Components, Customization, Tutorials, Resources) with categorized sidebar navigation and per-page Table of Contents. Source code viewer present on 6 of 32 examples. Syntax highlighting and public deployment are in the upcoming Demo Site section above.
71
90
  * **Quickstart & Installation Guides**: Comprehensive onboarding documentation for developers.
@@ -7,9 +7,9 @@ import { GridEditInputCell } from './GridEditInputCell';
7
7
 
8
8
  export interface CellProps<R extends GridRowModel = GridRowModel> {
9
9
  onCellClick?: (params: GridCellParams<R>) => void;
10
- onCellEditStart?: (field: string, value: any) => void;
11
- onCellValueChange?: (field: string, newValue: any) => void;
12
- value: any;
10
+ onCellEditStart?: (field: string, value: unknown) => void;
11
+ onCellValueChange?: (field: string, newValue: unknown) => void;
12
+ value: unknown;
13
13
  row: R;
14
14
  colDef: GridColDef<R>;
15
15
  rowIndex: number;
@@ -27,7 +27,7 @@ export interface CellProps<R extends GridRowModel = GridRowModel> {
27
27
  isEditing?: boolean;
28
28
  onEditStart?: () => void;
29
29
  onEditStop?: (cancel?: boolean) => void;
30
- onValueChange?: (newValue: any) => void;
30
+ onValueChange?: (newValue: unknown) => void;
31
31
 
32
32
  colSpanInfo?: CellColSpanInfo;
33
33
  rowSpan?: number;
@@ -63,7 +63,7 @@ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
63
63
  } = props;
64
64
 
65
65
  // All hooks must come before any early returns (Rules of Hooks)
66
- const handleValueChange = React.useCallback((newValue: any) => {
66
+ const handleValueChange = React.useCallback((newValue: unknown) => {
67
67
  if (onCellValueChange) {
68
68
  onCellValueChange(colDef.field, newValue);
69
69
  } else {
@@ -94,7 +94,7 @@ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
94
94
  value,
95
95
  row,
96
96
  field: colDef.field,
97
- colDef: colDef as any,
97
+ colDef,
98
98
  rowIndex,
99
99
  colIndex
100
100
  });
@@ -104,7 +104,7 @@ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
104
104
  value={value}
105
105
  row={row}
106
106
  field={colDef.field}
107
- colDef={colDef as any}
107
+ colDef={colDef as unknown as GridColDef}
108
108
  rowIndex={rowIndex}
109
109
  colIndex={colIndex}
110
110
  onValueChange={handleValueChange}
@@ -119,7 +119,7 @@ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
119
119
  value,
120
120
  row,
121
121
  field: colDef.field,
122
- colDef: colDef as any,
122
+ colDef,
123
123
  rowIndex,
124
124
  colIndex
125
125
  });
@@ -131,7 +131,7 @@ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
131
131
  const resolvedCellClassName = React.useMemo(() => {
132
132
  if (!colDef.cellClassName) return '';
133
133
  if (typeof colDef.cellClassName === 'function') {
134
- return colDef.cellClassName({ value, row, field: colDef.field, colDef: colDef as any, rowIndex, colIndex }) || '';
134
+ return colDef.cellClassName({ value, row, field: colDef.field, colDef, rowIndex, colIndex }) || '';
135
135
  }
136
136
  return colDef.cellClassName;
137
137
  }, [colDef, value, row, rowIndex, colIndex]);
@@ -162,7 +162,6 @@ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
162
162
 
163
163
  const handleClick = (e: React.MouseEvent) => {
164
164
  if (onCellClick) {
165
- e.stopPropagation();
166
165
  onCellClick({
167
166
  row,
168
167
  field: colDef.field,
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
2
2
  import type { GridRenderCellParams } from '../../types';
3
3
 
4
4
  export interface GridEditInputCellProps extends GridRenderCellParams {
5
- onValueChange: (value: any) => void;
5
+ onValueChange: (value: unknown) => void;
6
6
  onCommit: () => void;
7
7
  onCancel: () => void;
8
8
  }
@@ -31,7 +31,7 @@ function TextEditor({ value, onValueChange, onCommit, onCancel }: GridEditInputC
31
31
 
32
32
  // ─── Number ───────────────────────────────────────────────────────────────────
33
33
  function NumberEditor({ value, onValueChange, onCommit, onCancel }: GridEditInputCellProps) {
34
- const [local, setLocal] = useState(value ?? '');
34
+ const [local, setLocal] = useState<string | number>(typeof value === 'number' ? value : '');
35
35
  const ref = useRef<HTMLInputElement>(null);
36
36
  useEffect(() => { ref.current?.focus(); ref.current?.select(); }, []);
37
37
 
@@ -94,7 +94,7 @@ function SelectEditor({ value, colDef, onValueChange, onCommit, onCancel }: Grid
94
94
  <select
95
95
  ref={ref}
96
96
  className="ogx__edit-select"
97
- value={value ?? ''}
97
+ value={String(value ?? '')}
98
98
  onChange={e => { onValueChange(e.target.value); }}
99
99
  onBlur={onCommit}
100
100
  onKeyDown={e => {
@@ -113,7 +113,7 @@ function SelectEditor({ value, colDef, onValueChange, onCommit, onCancel }: Grid
113
113
 
114
114
  // ─── Date ─────────────────────────────────────────────────────────────────────
115
115
  function DateEditor({ value, onValueChange, onCommit, onCancel }: GridEditInputCellProps) {
116
- const [local, setLocal] = useState(value ?? '');
116
+ const [local, setLocal] = useState<string>(typeof value === 'string' ? value : String(value ?? ''));
117
117
  const ref = useRef<HTMLInputElement>(null);
118
118
  useEffect(() => { ref.current?.focus(); }, []);
119
119
 
@@ -10,6 +10,7 @@ export interface ColumnVisibilityPanelProps<R extends GridRowModel = GridRowMode
10
10
  onShowAll: () => void;
11
11
  onHideAll: () => void;
12
12
  onColumnReorder?: (fromField: string, toField: string) => void;
13
+ onColumnOrderReset?: () => void;
13
14
  }
14
15
 
15
16
  function SearchIcon() {
@@ -43,7 +44,7 @@ function DragHandleIcon() {
43
44
  export function ColumnVisibilityPanel<R extends GridRowModel = GridRowModel>(
44
45
  props: ColumnVisibilityPanelProps<R>
45
46
  ) {
46
- const { columns, visibleColumns, onVisibilityChange, onShowAll, onHideAll, onColumnReorder } = props;
47
+ const { columns, visibleColumns, onVisibilityChange, onShowAll, onHideAll, onColumnReorder, onColumnOrderReset } = props;
47
48
  const [searchQuery, setSearchQuery] = useState('');
48
49
  const [dragOverField, setDragOverField] = useState<string | null>(null);
49
50
  const dragFieldRef = useRef<string | null>(null);
@@ -180,7 +181,7 @@ export function ColumnVisibilityPanel<R extends GridRowModel = GridRowModel>(
180
181
  </label>
181
182
  <button
182
183
  className="ogx-column-visibility-panel__reset-btn"
183
- onClick={onShowAll}
184
+ onClick={() => { onShowAll(); onColumnOrderReset?.(); }}
184
185
  >
185
186
  Reset
186
187
  </button>