@opencorestack/opengridx 2.1.0 → 3.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.
- package/CHANGELOG.md +399 -0
- package/README.md +95 -72
- package/dist/index.d.ts +590 -131
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +7891 -5631
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +17 -23
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +348 -166
- package/docs/README.md +6 -1
- package/docs/architecture/datagrid-orchestration.md +81 -0
- package/docs/architecture/grid-row-meta.md +102 -0
- package/docs/architecture/use-grid-columns.md +128 -0
- package/docs/architecture/use-grid-controlled-state.md +157 -0
- package/docs/architecture/use-grid-row-pipeline.md +120 -0
- package/docs/architecture/use-grid-scroll-sync.md +111 -0
- package/docs/architecture/use-grid-state-snapshot.md +90 -0
- package/docs/architecture/use-grid-virtualization.md +115 -0
- package/docs/architecture/use-grid-visible-rows.md +85 -0
- package/docs/assets/banner-alt.png +0 -0
- package/docs/assets/banner.png +0 -0
- package/docs/assets/hero.png +0 -0
- package/docs/assets/logo.png +0 -0
- package/docs/components/aggregation-footer.md +15 -10
- package/docs/components/cell.md +16 -6
- package/docs/components/column-group-header.md +55 -28
- package/docs/components/column-resize.md +17 -13
- package/docs/components/column-visibility.md +6 -7
- package/docs/components/datagrid.md +15 -13
- package/docs/components/empty-state.md +2 -1
- package/docs/components/error-overlay.md +12 -7
- package/docs/components/filter-panel.md +28 -8
- package/docs/components/header.md +28 -9
- package/docs/components/pagination.md +14 -8
- package/docs/components/row.md +21 -5
- package/docs/components/toolbar.md +27 -9
- package/docs/components/tooltip.md +16 -7
- package/docs/customization/slots-api.md +90 -38
- package/docs/customization/theming.md +66 -18
- package/docs/features/aggregation-pivot.md +75 -9
- package/docs/features/cell-spanning.md +80 -125
- package/docs/features/clipboard.md +41 -16
- package/docs/features/custom-pagination.md +38 -18
- package/docs/features/data-source.md +36 -13
- package/docs/features/editing-reordering.md +125 -16
- package/docs/features/export-guide.md +126 -39
- package/docs/features/filtering.md +44 -6
- package/docs/features/infinite-scroll.md +54 -46
- package/docs/features/keyboard-navigation.md +83 -0
- package/docs/features/list-view.md +58 -5
- package/docs/features/loading-states.md +18 -20
- package/docs/features/master-detail.md +18 -5
- package/docs/features/pdf-export.md +72 -19
- package/docs/features/pinning.md +24 -5
- package/docs/features/selection.md +50 -10
- package/docs/features/sorting-pagination.md +64 -14
- package/docs/features/state-persistence.md +78 -10
- package/docs/features/toolbar-customization.md +26 -17
- package/docs/features/tree-data-grouping.md +36 -8
- package/docs/features/virtualization.md +37 -14
- package/docs/migration/v1-to-v2.md +115 -0
- package/docs/migration/v2-to-v3.md +1233 -0
- package/docs/performance.md +124 -0
- package/docs/roadmap.md +5 -5
- package/lib/components/Cell/Cell.css +17 -1
- package/lib/components/Cell/Cell.tsx +129 -57
- package/lib/components/Cell/CellErrorBoundary.tsx +12 -6
- package/lib/components/Cell/GridEditInputCell.tsx +114 -41
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +7 -1
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +15 -2
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +104 -38
- package/lib/components/ColumnResizeHandle/clampResizeWidth.ts +13 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +6 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +21 -13
- package/lib/components/DataGrid/DataGrid.css +85 -12
- package/lib/components/DataGrid/DataGrid.tsx +582 -876
- package/lib/components/DataGrid/GridAggregationFooter.tsx +53 -17
- package/lib/components/DataGrid/GridErrorOverlay.tsx +15 -12
- package/lib/components/DataGrid/GridListView.tsx +165 -24
- package/lib/components/DataGrid/GridLiveRegion.tsx +31 -0
- package/lib/components/DataGrid/GridLoadingOverlay.tsx +27 -0
- package/lib/components/DataGrid/GridPaginationArea.tsx +44 -0
- package/lib/components/DataGrid/GridPinnedRows.tsx +62 -11
- package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +40 -38
- package/lib/components/DataGrid/GridToolbarSlot.tsx +51 -0
- package/lib/components/DataGrid/GridVirtualRows.tsx +40 -24
- package/lib/components/DetailPanel/DetailPanel.css +0 -6
- package/lib/components/DetailPanel/DetailPanel.tsx +34 -5
- package/lib/components/DetailPanel/DetailPanelContent.tsx +17 -0
- package/lib/components/FilterPanel/FilterPanel.css +13 -2
- package/lib/components/FilterPanel/FilterPanel.tsx +272 -99
- package/lib/components/Header/ColumnMenu.tsx +91 -39
- package/lib/components/Header/Header.css +6 -3
- package/lib/components/Header/Header.tsx +181 -160
- package/lib/components/ListView/ListView.css +25 -0
- package/lib/components/ListView/ListViewRow.tsx +76 -10
- package/lib/components/Pagination/Pagination.tsx +15 -7
- package/lib/components/Row/Row.tsx +177 -77
- package/lib/components/Toolbar/GlobalSearch.css +6 -3
- package/lib/components/Toolbar/GlobalSearch.tsx +26 -27
- package/lib/components/Toolbar/GridToolbar.css +12 -11
- package/lib/components/Toolbar/GridToolbar.tsx +133 -89
- package/lib/components/Toolbar/PivotPanel.tsx +89 -49
- package/lib/components/Tooltip/Tooltip.css +20 -0
- package/lib/components/Tooltip/Tooltip.tsx +68 -35
- package/lib/components/ui/Button.tsx +3 -0
- package/lib/components/ui/Checkbox.tsx +2 -1
- package/lib/components/ui/ExpandIcon.css +0 -9
- package/lib/components/ui/ExpandIcon.tsx +4 -1
- package/lib/hooks/core/gridApiPlaceholder.ts +34 -0
- package/lib/hooks/core/gridToolbarHostContext.ts +14 -0
- package/lib/hooks/core/useDataGrid.ts +115 -173
- package/lib/hooks/core/useGridAggregationApi.ts +72 -0
- package/lib/hooks/core/useGridApiMethods.ts +115 -0
- package/lib/hooks/core/useGridApiRef.ts +19 -4
- package/lib/hooks/core/useGridApiRefBinding.ts +18 -0
- package/lib/hooks/core/useGridAriaRows.ts +38 -0
- package/lib/hooks/core/useGridClipboardApi.ts +38 -0
- package/lib/hooks/core/useGridColumnLookup.ts +30 -0
- package/lib/hooks/core/useGridColumns.tsx +279 -76
- package/lib/hooks/core/useGridColumnsPanel.ts +77 -0
- package/lib/hooks/core/useGridControlledState.ts +99 -6
- package/lib/hooks/core/useGridDetailPanel.ts +51 -0
- package/lib/hooks/core/useGridDevWarnings.ts +104 -5
- package/lib/hooks/core/useGridFocusHandlers.ts +50 -0
- package/lib/hooks/core/useGridGroupingColumn.ts +62 -0
- package/lib/hooks/core/useGridHeaderHandlers.ts +92 -0
- package/lib/hooks/core/useGridHierarchy.ts +81 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +447 -243
- package/lib/hooks/core/useGridLiveRowSelection.ts +67 -0
- package/lib/hooks/core/useGridPageCorrection.ts +55 -0
- package/lib/hooks/core/useGridRowIdOf.ts +32 -0
- package/lib/hooks/core/useGridRowInteractions.ts +81 -0
- package/lib/hooks/core/useGridRowPipeline.ts +69 -39
- package/lib/hooks/core/useGridRowSelection.ts +91 -0
- package/lib/hooks/core/useGridScrollSync.ts +80 -32
- package/lib/hooks/core/useGridScrollToIndexesApi.ts +45 -0
- package/lib/hooks/core/useGridStateSnapshot.ts +40 -5
- package/lib/hooks/core/useGridThemeDimensions.ts +49 -0
- package/lib/hooks/core/useGridToolbarProps.ts +97 -0
- package/lib/hooks/core/useGridViewport.ts +38 -0
- package/lib/hooks/core/useGridViewportSize.ts +26 -0
- package/lib/hooks/core/useGridVisibleRows.ts +17 -5
- package/lib/hooks/core/useLayout.ts +215 -195
- package/lib/hooks/core/useRowsScrollEndLatch.ts +41 -0
- package/lib/hooks/core/useStableColumns.ts +34 -0
- package/lib/hooks/features/useAggregation.ts +82 -51
- package/lib/hooks/features/useColumnGroupReorderGuard.ts +34 -0
- package/lib/hooks/features/useDetailPanelHeights.ts +29 -0
- package/lib/hooks/features/useGridClipboard.ts +133 -73
- package/lib/hooks/features/useGridDataSource.ts +354 -128
- package/lib/hooks/features/useGridEditing.ts +211 -51
- package/lib/hooks/features/useGridPivot.ts +92 -0
- package/lib/hooks/features/useGridSpanRenderWindow.ts +104 -0
- package/lib/hooks/features/useGridSpanning.ts +410 -306
- package/lib/hooks/features/usePivot.ts +0 -0
- package/lib/hooks/features/useServerTreeChildren.ts +50 -0
- package/lib/hooks/useColumnReorder.ts +38 -44
- package/lib/hooks/useRowGrouping.ts +298 -160
- package/lib/hooks/useRowReorder.ts +62 -31
- package/lib/hooks/useTreeData.ts +287 -220
- package/lib/index.ts +28 -2
- package/lib/state/index.ts +1 -0
- package/lib/state/types.ts +7 -1
- package/lib/state/useGridStateStorage.ts +88 -29
- package/lib/theme/DataGridThemeProvider.tsx +143 -38
- package/lib/theme/gridThemeContext.ts +5 -0
- package/lib/theme/index.ts +5 -0
- package/lib/theme/presets.ts +5 -4
- package/lib/theme/types.ts +40 -2
- package/lib/types/index.ts +313 -116
- package/lib/utils/aggregation/index.ts +119 -24
- package/lib/utils/aria/index.ts +40 -0
- package/lib/utils/columnGroups.ts +110 -0
- package/lib/utils/columnLookup.ts +33 -0
- package/lib/utils/columnWidth.ts +22 -0
- package/lib/utils/dataSource/index.ts +46 -0
- package/lib/utils/detailPanel.ts +66 -0
- package/lib/utils/dragData.ts +44 -0
- package/lib/utils/editing/index.ts +30 -0
- package/lib/utils/export/exportShared.ts +197 -0
- package/lib/utils/export/exportToExcelAdvanced.ts +278 -139
- package/lib/utils/export/exportToPdf.ts +143 -92
- package/lib/utils/export/groupLabel.ts +14 -0
- package/lib/utils/export/index.ts +278 -272
- package/lib/utils/export/pdfText.ts +50 -0
- package/lib/utils/filtering/index.ts +277 -148
- package/lib/utils/focus/index.ts +115 -0
- package/lib/utils/gridApiRows.ts +35 -0
- package/lib/utils/gridModes.ts +77 -0
- package/lib/utils/grouping/groupedExportRows.ts +91 -0
- package/lib/utils/pagination/index.ts +56 -0
- package/lib/utils/pinning/index.ts +77 -33
- package/lib/utils/pivot/index.ts +368 -0
- package/lib/utils/scroll/index.ts +55 -5
- package/lib/utils/scroll/scrollEnd.ts +18 -0
- package/lib/utils/sorting/index.ts +154 -30
- package/lib/utils/syntheticRows.ts +12 -0
- package/lib/utils/values.ts +206 -0
- package/lib/utils/viewport.ts +14 -0
- package/llms.txt +261 -164
- package/package.json +13 -5
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +0 -220
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.test.tsx +0 -85
- package/lib/components/DataGrid/DataGrid.defects.test.tsx +0 -103
- package/lib/components/DataGrid/DataGrid.regression.test.tsx +0 -132
- package/lib/components/DataGrid/DataGrid.smoke.test.tsx +0 -58
- package/lib/components/DataGrid/DataGrid.v2.test.tsx +0 -127
- package/lib/components/DataGrid/DataGrid.virtualization.browser.test.tsx +0 -183
- package/lib/components/Header/Header.test.tsx +0 -135
- package/lib/components/Pagination/Pagination.test.tsx +0 -64
- package/lib/hooks/core/useDataGrid.regression.test.ts +0 -66
- package/lib/hooks/core/useGridControlledState.test.ts +0 -86
- package/lib/hooks/core/useGridKeyboardNavigation.test.ts +0 -176
- package/lib/hooks/core/useGridRowPipeline.test.ts +0 -117
- package/lib/hooks/features/useAggregation.test.ts +0 -175
- package/lib/hooks/features/usePivot.test.ts +0 -202
- package/lib/hooks/useRowGrouping.test.ts +0 -298
- package/lib/hooks/useTreeData.test.ts +0 -83
- package/lib/utils/aggregation/aggregation.test.ts +0 -16
- package/lib/utils/export/exportToExcelAdvanced.test.ts +0 -109
- package/lib/utils/export/exportToPdf.test.ts +0 -120
- package/lib/utils/export/groupedExport.test.ts +0 -144
- package/lib/utils/filtering/filtering.test.ts +0 -178
- package/lib/utils/sorting/sorting.test.ts +0 -88
|
@@ -1,38 +1,72 @@
|
|
|
1
1
|
|
|
2
|
-
import { useReducer, useCallback, useState, useRef,
|
|
2
|
+
import { useReducer, useCallback, useState, useRef, useLayoutEffect } from 'react';
|
|
3
3
|
import type {
|
|
4
4
|
GridInternalState as GridState,
|
|
5
5
|
GridRowModel,
|
|
6
6
|
GridColDef,
|
|
7
7
|
GridRowId,
|
|
8
|
-
GridSortItem,
|
|
9
|
-
GridFilterModel,
|
|
10
|
-
GridPaginationModel,
|
|
11
8
|
GridColumnVisibilityModel,
|
|
12
9
|
GridApi
|
|
13
10
|
} from '../../types';
|
|
11
|
+
import { createGridApiPlaceholder } from './gridApiPlaceholder';
|
|
12
|
+
|
|
13
|
+
// Sort, filter, pagination and selection deliberately have no slice here: they live in
|
|
14
|
+
// useGridControlledState, which is what the grid renders from. (Copies of them in this
|
|
15
|
+
// reducer used to drift from it, which made the imperative API a no-op.)
|
|
16
|
+
type GetRowId = (row: GridRowModel) => GridRowId;
|
|
14
17
|
|
|
15
18
|
type GridAction =
|
|
16
|
-
| { type: 'SET_ROWS'; payload: GridRowModel[] | ((prev: GridRowModel[]) => GridRowModel[]);
|
|
19
|
+
| { type: 'SET_ROWS'; payload: GridRowModel[] | ((prev: GridRowModel[]) => GridRowModel[]); getRowId: GetRowId }
|
|
20
|
+
| { type: 'REPLACE_ROW'; id: GridRowId; row: GridRowModel }
|
|
17
21
|
| { type: 'SET_COLUMNS'; payload: GridColDef[] }
|
|
18
|
-
| { type: 'SET_SORT_MODEL'; payload: GridSortItem[] }
|
|
19
|
-
| { type: 'SET_FILTER_MODEL'; payload: GridFilterModel }
|
|
20
|
-
| { type: 'SET_PAGINATION_MODEL'; payload: GridPaginationModel }
|
|
21
|
-
| { type: 'SET_SELECTION'; payload: Set<GridRowId> }
|
|
22
22
|
| { type: 'SET_DIMENSIONS'; payload: { viewportWidth: number; viewportHeight: number } }
|
|
23
23
|
| { type: 'SET_DATASOURCE_LOADING'; payload: boolean }
|
|
24
24
|
| { type: 'SET_DATASOURCE_ERROR'; payload: unknown }
|
|
25
25
|
| { type: 'SET_ROW_COUNT'; payload: number };
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
const defaultGetRowId: GetRowId = (row) => row.id;
|
|
28
|
+
|
|
29
|
+
// Rows are re-keyed on every rows change, so warn once rather than on every update.
|
|
30
|
+
let warnedDuplicateIds = false;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Keys rows by getRowId(row). The row objects are stored as given (never copied or given an
|
|
34
|
+
* `id` field), so `idByRow` maps each one back to its key. A row object the store already
|
|
35
|
+
* holds keeps its key (`knownIds`), so a committed edit stored under the edited row's id stays
|
|
36
|
+
* there even when the stored object does not resolve to that id itself. A duplicate id keeps
|
|
37
|
+
* the first row, so a later row never silently replaces it (or renders twice), and warns once
|
|
38
|
+
* in development.
|
|
39
|
+
*/
|
|
40
|
+
function buildRowsState(rows: GridRowModel[], getRowId: GetRowId, knownIds?: Map<GridRowModel, GridRowId>): GridState['rows'] {
|
|
28
41
|
const idRowsLookup = new Map<GridRowId, GridRowModel>();
|
|
42
|
+
const idByRow = new Map<GridRowModel, GridRowId>();
|
|
29
43
|
const allRows: GridRowId[] = [];
|
|
44
|
+
const duplicates: GridRowId[] = [];
|
|
30
45
|
|
|
31
46
|
rows.forEach(row => {
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
const id = knownIds?.get(row) ?? getRowId(row);
|
|
48
|
+
if (idRowsLookup.has(id)) {
|
|
49
|
+
duplicates.push(id);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
idRowsLookup.set(id, row);
|
|
53
|
+
idByRow.set(row, id);
|
|
54
|
+
allRows.push(id);
|
|
34
55
|
});
|
|
35
56
|
|
|
57
|
+
if (duplicates.length > 0 && !warnedDuplicateIds && process.env.NODE_ENV !== 'production') {
|
|
58
|
+
warnedDuplicateIds = true;
|
|
59
|
+
console.warn(
|
|
60
|
+
`[OpenGridX] ${duplicates.length} row(s) reuse the id of an earlier row and were ignored ` +
|
|
61
|
+
`(ids: ${duplicates.slice(0, 5).map(String).join(', ')}${duplicates.length > 5 ? ', …' : ''}). ` +
|
|
62
|
+
'Every row needs a unique id; pass getRowId if the unique key lives in another field.'
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { idRowsLookup, allRows, idByRow };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function createInitialState<R extends GridRowModel>(rows: R[], getRowId: GetRowId, columns: GridColDef<R>[], columnVisibilityModel: GridColumnVisibilityModel = {}): GridState {
|
|
36
70
|
const columnLookup = new Map<string, GridColDef>();
|
|
37
71
|
const orderedFields: string[] = [];
|
|
38
72
|
|
|
@@ -42,41 +76,20 @@ function createInitialState<R extends GridRowModel>(rows: R[], columns: GridColD
|
|
|
42
76
|
});
|
|
43
77
|
|
|
44
78
|
return {
|
|
45
|
-
rows:
|
|
46
|
-
idRowsLookup,
|
|
47
|
-
allRows
|
|
48
|
-
},
|
|
79
|
+
rows: buildRowsState(rows, getRowId),
|
|
49
80
|
columns: {
|
|
50
81
|
all: columns as unknown as GridColDef[],
|
|
51
82
|
lookup: columnLookup,
|
|
52
83
|
orderedFields,
|
|
53
84
|
columnVisibilityModel
|
|
54
85
|
},
|
|
55
|
-
sorting: {
|
|
56
|
-
sortModel: []
|
|
57
|
-
},
|
|
58
|
-
filter: {
|
|
59
|
-
filterModel: { items: [] }
|
|
60
|
-
},
|
|
61
86
|
pagination: {
|
|
62
|
-
|
|
63
|
-
rowCount: rows.length
|
|
64
|
-
},
|
|
65
|
-
selection: {
|
|
66
|
-
selectedRows: new Set()
|
|
87
|
+
rowCount: undefined
|
|
67
88
|
},
|
|
68
89
|
pinning: {
|
|
69
90
|
pinnedColumns: {},
|
|
70
91
|
pinnedRows: {}
|
|
71
92
|
},
|
|
72
|
-
virtualization: {
|
|
73
|
-
renderContext: {
|
|
74
|
-
firstRowIndex: 0,
|
|
75
|
-
lastRowIndex: 0,
|
|
76
|
-
firstColumnIndex: 0,
|
|
77
|
-
lastColumnIndex: 0
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
93
|
dimensions: {
|
|
81
94
|
rowHeight: 52,
|
|
82
95
|
headerHeight: 56,
|
|
@@ -93,28 +106,31 @@ function createInitialState<R extends GridRowModel>(rows: R[], columns: GridColD
|
|
|
93
106
|
function gridReducer(state: GridState, action: GridAction): GridState {
|
|
94
107
|
switch (action.type) {
|
|
95
108
|
case 'SET_ROWS': {
|
|
96
|
-
const currentRows =
|
|
97
|
-
const newRows = typeof action.payload === 'function'
|
|
109
|
+
const currentRows = state.rows.allRows.map(id => state.rows.idRowsLookup.get(id)!);
|
|
110
|
+
const newRows = typeof action.payload === 'function'
|
|
98
111
|
? action.payload(currentRows)
|
|
99
112
|
: action.payload;
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
// pagination.rowCount is the server-reported total only (SET_ROW_COUNT). Client row
|
|
115
|
+
// counts are derived from the rows the grid shows, so replacing rows never touches it.
|
|
116
|
+
return {
|
|
117
|
+
...state,
|
|
118
|
+
rows: buildRowsState(newRows, action.getRowId, state.rows.idByRow),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
108
121
|
|
|
122
|
+
case 'REPLACE_ROW': {
|
|
123
|
+
// Stores a committed edit under the id of the row that was edited, in the current store.
|
|
124
|
+
const previous = state.rows.idRowsLookup.get(action.id);
|
|
125
|
+
if (previous === undefined) return state;
|
|
126
|
+
const idRowsLookup = new Map(state.rows.idRowsLookup);
|
|
127
|
+
idRowsLookup.set(action.id, action.row);
|
|
128
|
+
const idByRow = new Map(state.rows.idByRow);
|
|
129
|
+
idByRow.delete(previous);
|
|
130
|
+
idByRow.set(action.row, action.id);
|
|
109
131
|
return {
|
|
110
132
|
...state,
|
|
111
|
-
rows: { idRowsLookup,
|
|
112
|
-
pagination: {
|
|
113
|
-
...state.pagination,
|
|
114
|
-
// Only override rowCount when we're NOT in server mode (i.e. no preserveRowCount flag)
|
|
115
|
-
// Server-side fetches set rowCount separately via SET_ROW_COUNT.
|
|
116
|
-
rowCount: action.preserveRowCount ? state.pagination.rowCount : newRows.length
|
|
117
|
-
}
|
|
133
|
+
rows: { ...state.rows, idRowsLookup, idByRow },
|
|
118
134
|
};
|
|
119
135
|
}
|
|
120
136
|
|
|
@@ -138,33 +154,6 @@ function gridReducer(state: GridState, action: GridAction): GridState {
|
|
|
138
154
|
};
|
|
139
155
|
}
|
|
140
156
|
|
|
141
|
-
case 'SET_SORT_MODEL':
|
|
142
|
-
return {
|
|
143
|
-
...state,
|
|
144
|
-
sorting: { sortModel: action.payload }
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
case 'SET_FILTER_MODEL':
|
|
148
|
-
return {
|
|
149
|
-
...state,
|
|
150
|
-
filter: { filterModel: action.payload }
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
case 'SET_PAGINATION_MODEL':
|
|
154
|
-
return {
|
|
155
|
-
...state,
|
|
156
|
-
pagination: {
|
|
157
|
-
...state.pagination,
|
|
158
|
-
paginationModel: action.payload
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
case 'SET_SELECTION':
|
|
163
|
-
return {
|
|
164
|
-
...state,
|
|
165
|
-
selection: { selectedRows: action.payload }
|
|
166
|
-
};
|
|
167
|
-
|
|
168
157
|
case 'SET_DIMENSIONS':
|
|
169
158
|
return {
|
|
170
159
|
...state,
|
|
@@ -209,32 +198,47 @@ function gridReducer(state: GridState, action: GridAction): GridState {
|
|
|
209
198
|
|
|
210
199
|
export interface UseDataGridParams<R extends GridRowModel = GridRowModel> {
|
|
211
200
|
rows: R[];
|
|
201
|
+
/** Keys the store. Read when rows are set; defaults to `row.id`. */
|
|
202
|
+
getRowId?: (row: R) => GridRowId;
|
|
212
203
|
columns: GridColDef<R>[];
|
|
213
204
|
rowHeight?: number;
|
|
214
205
|
headerHeight?: number;
|
|
215
|
-
rowCount?: number;
|
|
216
206
|
columnVisibilityModel?: GridColumnVisibilityModel;
|
|
217
207
|
initialState?: import('../../state/types').GridInitialState;
|
|
208
|
+
/**
|
|
209
|
+
* When true (no dataSource owns the rows), the store follows the `rows` param: a new rows
|
|
210
|
+
* array replaces the stored rows in the same render, so rows and the columns rendered
|
|
211
|
+
* against them always change together.
|
|
212
|
+
*/
|
|
213
|
+
syncRows?: boolean;
|
|
218
214
|
}
|
|
219
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The row store, plus dimensions and dataSource status. With `syncRows`, a new `rows` array
|
|
218
|
+
* replaces the stored rows during render (never one commit later, which let new columns run
|
|
219
|
+
* against the previous rows). Without it, `rows` only seeds the store and a dataSource fills
|
|
220
|
+
* it through setRows.
|
|
221
|
+
*/
|
|
220
222
|
export function useDataGrid<R extends GridRowModel = GridRowModel>(params: UseDataGridParams<R>) {
|
|
221
|
-
const { rows, columns, rowHeight = 52, headerHeight = 56, columnVisibilityModel, initialState: propInitialState } = params;
|
|
223
|
+
const { rows, columns, rowHeight = 52, headerHeight = 56, columnVisibilityModel, initialState: propInitialState, syncRows = false } = params;
|
|
224
|
+
const getRowId = (params.getRowId ?? defaultGetRowId) as GetRowId;
|
|
225
|
+
|
|
226
|
+
// Latest getRowId for dispatches from effects and event handlers. An inline getRowId
|
|
227
|
+
// therefore never re-keys (or resets) the store by itself; new rows are keyed with it.
|
|
228
|
+
const getRowIdRef = useRef(getRowId);
|
|
229
|
+
useLayoutEffect(() => { getRowIdRef.current = getRowId; });
|
|
222
230
|
|
|
223
231
|
const [internalInitialState] = useState(() =>
|
|
224
|
-
createInitialState(rows, columns, columnVisibilityModel)
|
|
232
|
+
createInitialState(rows, getRowId, columns, columnVisibilityModel)
|
|
225
233
|
);
|
|
226
234
|
|
|
227
235
|
const [state, dispatch] = useReducer(gridReducer, {
|
|
228
236
|
...internalInitialState,
|
|
229
|
-
sorting: propInitialState?.sorting || internalInitialState.sorting,
|
|
230
|
-
filter: propInitialState?.filter || internalInitialState.filter,
|
|
231
237
|
dataSource: propInitialState?.dataSource
|
|
232
238
|
? { ...internalInitialState.dataSource, ...propInitialState.dataSource }
|
|
233
239
|
: internalInitialState.dataSource,
|
|
234
240
|
pagination: {
|
|
235
|
-
|
|
236
|
-
...(propInitialState?.pagination || {}),
|
|
237
|
-
rowCount: params.rowCount ?? propInitialState?.pagination?.rowCount ?? internalInitialState.pagination.rowCount
|
|
241
|
+
rowCount: propInitialState?.pagination?.rowCount,
|
|
238
242
|
},
|
|
239
243
|
dimensions: {
|
|
240
244
|
...internalInitialState.dimensions,
|
|
@@ -243,42 +247,33 @@ export function useDataGrid<R extends GridRowModel = GridRowModel>(params: UseDa
|
|
|
243
247
|
}
|
|
244
248
|
});
|
|
245
249
|
|
|
250
|
+
// The rows (and sync mode) the store was last filled from. Updating the store while
|
|
251
|
+
// rendering makes React re-render before committing, so no commit pairs the columns of
|
|
252
|
+
// this render with the rows of the previous one.
|
|
253
|
+
const [syncedFrom, setSyncedFrom] = useState({ rows, syncRows });
|
|
254
|
+
if (syncedFrom.rows !== rows || syncedFrom.syncRows !== syncRows) {
|
|
255
|
+
setSyncedFrom({ rows, syncRows });
|
|
256
|
+
if (syncRows) dispatch({ type: 'SET_ROWS', payload: rows, getRowId });
|
|
257
|
+
}
|
|
258
|
+
|
|
246
259
|
const stateRef = useRef(state);
|
|
247
260
|
useLayoutEffect(() => { stateRef.current = state; });
|
|
248
261
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
dispatch({ type: 'SET_ROWS', payload: rows as GridRowModel[] });
|
|
255
|
-
}
|
|
256
|
-
}, [rows]);
|
|
262
|
+
// `_preserveRowCount` is still accepted from callers written against the old signature;
|
|
263
|
+
// replacing rows never changes the server row count any more.
|
|
264
|
+
const setRows = useCallback((rowsOrUpdater: GridRowModel[] | ((prev: GridRowModel[]) => GridRowModel[]), _preserveRowCount?: boolean) => {
|
|
265
|
+
dispatch({ type: 'SET_ROWS', payload: rowsOrUpdater, getRowId: getRowIdRef.current });
|
|
266
|
+
}, []);
|
|
257
267
|
|
|
258
|
-
|
|
259
|
-
|
|
268
|
+
/** Replaces the row stored under `id` (a committed edit); does nothing if that row is gone. */
|
|
269
|
+
const replaceRow = useCallback((id: GridRowId, row: GridRowModel) => {
|
|
270
|
+
dispatch({ type: 'REPLACE_ROW', id, row });
|
|
260
271
|
}, []);
|
|
261
272
|
|
|
262
273
|
const setColumns = useCallback((newColumns: GridColDef[]) => {
|
|
263
274
|
dispatch({ type: 'SET_COLUMNS', payload: newColumns });
|
|
264
275
|
}, []);
|
|
265
276
|
|
|
266
|
-
const setSortModel = useCallback((sortModel: GridSortItem[]) => {
|
|
267
|
-
dispatch({ type: 'SET_SORT_MODEL', payload: sortModel });
|
|
268
|
-
}, []);
|
|
269
|
-
|
|
270
|
-
const setFilterModel = useCallback((filterModel: GridFilterModel) => {
|
|
271
|
-
dispatch({ type: 'SET_FILTER_MODEL', payload: filterModel });
|
|
272
|
-
}, []);
|
|
273
|
-
|
|
274
|
-
const setPaginationModel = useCallback((paginationModel: GridPaginationModel) => {
|
|
275
|
-
dispatch({ type: 'SET_PAGINATION_MODEL', payload: paginationModel });
|
|
276
|
-
}, []);
|
|
277
|
-
|
|
278
|
-
const setSelection = useCallback((selectedRows: Set<GridRowId>) => {
|
|
279
|
-
dispatch({ type: 'SET_SELECTION', payload: selectedRows });
|
|
280
|
-
}, []);
|
|
281
|
-
|
|
282
277
|
const setDimensions = useCallback((viewportWidth: number, viewportHeight: number) => {
|
|
283
278
|
dispatch({ type: 'SET_DIMENSIONS', payload: { viewportWidth, viewportHeight } });
|
|
284
279
|
}, []);
|
|
@@ -295,77 +290,24 @@ export function useDataGrid<R extends GridRowModel = GridRowModel>(params: UseDa
|
|
|
295
290
|
dispatch({ type: 'SET_ROW_COUNT', payload: count });
|
|
296
291
|
}, []);
|
|
297
292
|
|
|
298
|
-
|
|
293
|
+
// Store-backed getters live here. DataGrid installs the rest of the API (sort, filter,
|
|
294
|
+
// pagination, selection, visible rows and columns) from the state it actually renders.
|
|
295
|
+
const [initialApi] = useState<GridApi>(() => ({
|
|
296
|
+
...createGridApiPlaceholder(),
|
|
299
297
|
getRow: (id: GridRowId) => stateRef.current.rows.idRowsLookup.get(id) || null,
|
|
300
|
-
getAllRows: () =>
|
|
301
|
-
getVisibleRows: () => Array.from(stateRef.current.rows.idRowsLookup.values()),
|
|
302
|
-
getAllFilteredRows: () => Array.from(stateRef.current.rows.idRowsLookup.values()),
|
|
303
|
-
getGroupedExportRows: () => null,
|
|
304
|
-
getAggregationResult: () => null,
|
|
305
|
-
getAggregationModel: () => null,
|
|
298
|
+
getAllRows: () => stateRef.current.rows.allRows.map(id => stateRef.current.rows.idRowsLookup.get(id)!),
|
|
306
299
|
getColumn: (field: string) => stateRef.current.columns.lookup.get(field) || null,
|
|
307
300
|
getAllColumns: () => stateRef.current.columns.all,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const newSelection = new Set(stateRef.current.selection.selectedRows);
|
|
311
|
-
if (isSelected) {
|
|
312
|
-
newSelection.add(id);
|
|
313
|
-
} else {
|
|
314
|
-
newSelection.delete(id);
|
|
315
|
-
}
|
|
316
|
-
setSelection(newSelection);
|
|
317
|
-
},
|
|
318
|
-
selectRows: (ids: GridRowId[], isSelected = true) => {
|
|
319
|
-
const newSelection = new Set(stateRef.current.selection.selectedRows);
|
|
320
|
-
ids.forEach(id => {
|
|
321
|
-
if (isSelected) {
|
|
322
|
-
newSelection.add(id);
|
|
323
|
-
} else {
|
|
324
|
-
newSelection.delete(id);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
setSelection(newSelection);
|
|
328
|
-
},
|
|
329
|
-
getSelectedRows: () => Array.from(stateRef.current.selection.selectedRows),
|
|
330
|
-
sortColumn: (field: string, direction) => {
|
|
331
|
-
if (direction === null) {
|
|
332
|
-
setSortModel(stateRef.current.sorting.sortModel.filter(item => item.field !== field));
|
|
333
|
-
} else {
|
|
334
|
-
const existingIndex = stateRef.current.sorting.sortModel.findIndex(item => item.field === field);
|
|
335
|
-
if (existingIndex >= 0) {
|
|
336
|
-
const newModel = [...stateRef.current.sorting.sortModel];
|
|
337
|
-
newModel[existingIndex] = { field, sort: direction };
|
|
338
|
-
setSortModel(newModel);
|
|
339
|
-
} else {
|
|
340
|
-
setSortModel([...stateRef.current.sorting.sortModel, { field, sort: direction }]);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
getSortModel: () => stateRef.current.sorting.sortModel,
|
|
345
|
-
setFilterModel,
|
|
346
|
-
getFilterModel: () => stateRef.current.filter.filterModel,
|
|
347
|
-
setPage: (page: number) => {
|
|
348
|
-
setPaginationModel({ ...stateRef.current.pagination.paginationModel, page });
|
|
349
|
-
},
|
|
350
|
-
setPageSize: (pageSize: number) => {
|
|
351
|
-
setPaginationModel({ ...stateRef.current.pagination.paginationModel, pageSize, page: 0 });
|
|
352
|
-
},
|
|
353
|
-
scrollToIndexes: () => {
|
|
354
|
-
|
|
355
|
-
},
|
|
356
|
-
copySelectedRows: () => Promise.resolve()
|
|
357
|
-
});
|
|
301
|
+
}));
|
|
302
|
+
const apiRef = useRef<GridApi>(initialApi);
|
|
358
303
|
|
|
359
304
|
return {
|
|
360
305
|
state,
|
|
361
306
|
dispatch,
|
|
362
307
|
apiRef,
|
|
363
308
|
setRows,
|
|
309
|
+
replaceRow,
|
|
364
310
|
setColumns,
|
|
365
|
-
setSortModel,
|
|
366
|
-
setFilterModel,
|
|
367
|
-
setPaginationModel,
|
|
368
|
-
setSelection,
|
|
369
311
|
setDimensions,
|
|
370
312
|
setDataSourceLoading,
|
|
371
313
|
setDataSourceError,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
2
|
+
import type { MutableRefObject } from 'react';
|
|
3
|
+
import { buildGroupedExportRows } from '../../utils/grouping/groupedExportRows';
|
|
4
|
+
import type { GridHierarchyVisibleRowsOptions, GridGroupAggregationPosition } from '../useRowGrouping';
|
|
5
|
+
import type {
|
|
6
|
+
GridAggregationModel,
|
|
7
|
+
GridAggregationResult,
|
|
8
|
+
GridApi,
|
|
9
|
+
GridColDef,
|
|
10
|
+
GridGroupedExportRow,
|
|
11
|
+
GridRowId,
|
|
12
|
+
GridRowMeta,
|
|
13
|
+
GridRowModel,
|
|
14
|
+
} from '../../types';
|
|
15
|
+
|
|
16
|
+
export interface UseGridAggregationApiParams<R extends GridRowModel> {
|
|
17
|
+
apiRef: MutableRefObject<GridApi>;
|
|
18
|
+
/** An aggregation model is set and the pivot is off. */
|
|
19
|
+
hasAggregation: boolean;
|
|
20
|
+
aggregationResult: GridAggregationResult;
|
|
21
|
+
aggregationModel: GridAggregationModel;
|
|
22
|
+
isRowGrouping: boolean;
|
|
23
|
+
rowGroupingHandlers: {
|
|
24
|
+
getVisibleRows: (options?: GridHierarchyVisibleRowsOptions) => R[] | null;
|
|
25
|
+
rootAggregationPosition: GridGroupAggregationPosition;
|
|
26
|
+
aggregationPositions: Map<GridRowId, GridGroupAggregationPosition>;
|
|
27
|
+
};
|
|
28
|
+
rowMetaMap: Map<GridRowId, GridRowMeta>;
|
|
29
|
+
columns: GridColDef<R>[];
|
|
30
|
+
getRowId: (row: GridRowModel) => GridRowId;
|
|
31
|
+
/** The column that shows the group labels, so export order matches the screen when sorting by it. */
|
|
32
|
+
hierarchyField?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Installs `getAggregationResult`, `getAggregationModel` and `getGroupedExportRows` on the API.
|
|
37
|
+
* Like useGridApiMethods, the methods are installed in a layout effect and read the latest values
|
|
38
|
+
* from a ref, so they are live in a parent's layout effect on mount and never answer from a
|
|
39
|
+
* previous render.
|
|
40
|
+
*/
|
|
41
|
+
export function useGridAggregationApi<R extends GridRowModel>(params: UseGridAggregationApiParams<R>): void {
|
|
42
|
+
const { apiRef } = params;
|
|
43
|
+
const latestRef = useRef(params);
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
latestRef.current = params;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
useLayoutEffect(() => {
|
|
49
|
+
const latest = () => latestRef.current;
|
|
50
|
+
apiRef.current.getAggregationResult = () => (latest().hasAggregation ? latest().aggregationResult : null);
|
|
51
|
+
apiRef.current.getAggregationModel = () => (latest().hasAggregation ? latest().aggregationModel : null);
|
|
52
|
+
apiRef.current.getGroupedExportRows = (): GridGroupedExportRow[] | null => {
|
|
53
|
+
const {
|
|
54
|
+
hasAggregation, aggregationResult, aggregationModel, isRowGrouping, rowGroupingHandlers,
|
|
55
|
+
rowMetaMap, columns, getRowId, hierarchyField,
|
|
56
|
+
} = latest();
|
|
57
|
+
if (!isRowGrouping) return null;
|
|
58
|
+
// Every group expanded, filtered and sorted like the screen: collapsed groups still
|
|
59
|
+
// export their rows. Subtotals and the grand total hidden by getAggregationPosition
|
|
60
|
+
// (null) are left out, as they are on screen.
|
|
61
|
+
return buildGroupedExportRows<R>({
|
|
62
|
+
rows: rowGroupingHandlers.getVisibleRows({ expandAll: true, labelField: hierarchyField }) ?? [],
|
|
63
|
+
getRowId,
|
|
64
|
+
rowMetaMap,
|
|
65
|
+
columns,
|
|
66
|
+
aggregationModel,
|
|
67
|
+
aggregationResult: hasAggregation && rowGroupingHandlers.rootAggregationPosition !== null ? aggregationResult : null,
|
|
68
|
+
isSubtotalHidden: (groupId) => rowGroupingHandlers.aggregationPositions.get(groupId) === null,
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
}, [apiRef]);
|
|
72
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
GridApi,
|
|
4
|
+
GridColDef,
|
|
5
|
+
GridFilterModel,
|
|
6
|
+
GridPaginationModel,
|
|
7
|
+
GridRowId,
|
|
8
|
+
GridRowModel,
|
|
9
|
+
GridSortItem,
|
|
10
|
+
} from '../../types';
|
|
11
|
+
import { isSameSelection, nextSelectionForRows } from './useGridRowSelection';
|
|
12
|
+
import { nextSortModelForColumn } from './useGridHeaderHandlers';
|
|
13
|
+
|
|
14
|
+
export interface UseGridApiMethodsParams {
|
|
15
|
+
apiRef: React.MutableRefObject<GridApi>;
|
|
16
|
+
|
|
17
|
+
sortModel: GridSortItem[];
|
|
18
|
+
/** Mirrors a header click: multiSort merges into the model, otherwise the model is replaced. */
|
|
19
|
+
multiSort: boolean;
|
|
20
|
+
onSortModelChange: (model: GridSortItem[]) => void;
|
|
21
|
+
|
|
22
|
+
filterModel: GridFilterModel;
|
|
23
|
+
onFilterModelChange: (model: GridFilterModel) => void;
|
|
24
|
+
|
|
25
|
+
paginationModel: GridPaginationModel;
|
|
26
|
+
onPaginationModelChange: (model: GridPaginationModel) => void;
|
|
27
|
+
|
|
28
|
+
selectedRowIds: Set<GridRowId>;
|
|
29
|
+
/**
|
|
30
|
+
* A selection change made since the last commit (see useGridControlledState), or null. It
|
|
31
|
+
* wins over `selectedRowIds`, so the API is current inside onRowSelectionModelChange.
|
|
32
|
+
*/
|
|
33
|
+
getPendingRowSelectionModel?: () => GridRowId[] | null;
|
|
34
|
+
onRowSelectionModelChange: (model: GridRowId[]) => void;
|
|
35
|
+
disableMultipleRowSelection: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Rows the grid made (group headers, subtotals, auto-created parents, the pivot Grand Total):
|
|
38
|
+
* `selectRow` / `selectRows` ignore their ids, as the checkbox, click and keyboard paths do.
|
|
39
|
+
*/
|
|
40
|
+
isSyntheticRowId?: (id: GridRowId) => boolean;
|
|
41
|
+
|
|
42
|
+
/** Rows on screen: pinned top, the current page (or all rows), pinned bottom. */
|
|
43
|
+
getVisibleRows: () => GridRowModel[];
|
|
44
|
+
/** Every row that passes the filter, ignoring pagination and hierarchy expansion. */
|
|
45
|
+
getAllFilteredRows: () => GridRowModel[];
|
|
46
|
+
/** Columns on screen, in display order. */
|
|
47
|
+
visibleColumns: GridColDef[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Installs the parts of the imperative API that read or change the state the grid renders
|
|
52
|
+
* from (sort, filter, pagination, selection, visible rows and columns). Setters go through
|
|
53
|
+
* the same handlers as the UI, so controlled props and change callbacks behave the same way.
|
|
54
|
+
*
|
|
55
|
+
* The methods read the latest values from a ref refreshed in a layout effect. A setter also
|
|
56
|
+
* writes its result into that ref, so consecutive calls in one tick (selectRow(1);
|
|
57
|
+
* selectRow(2)) build on each other instead of on the last render.
|
|
58
|
+
*/
|
|
59
|
+
export function useGridApiMethods(params: UseGridApiMethodsParams): void {
|
|
60
|
+
const { apiRef } = params;
|
|
61
|
+
const latestRef = useRef(params);
|
|
62
|
+
|
|
63
|
+
useLayoutEffect(() => {
|
|
64
|
+
latestRef.current = params;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
useLayoutEffect(() => {
|
|
68
|
+
const api = apiRef.current;
|
|
69
|
+
const latest = () => latestRef.current;
|
|
70
|
+
|
|
71
|
+
api.getSortModel = () => latest().sortModel;
|
|
72
|
+
api.sortColumn = (field, direction) => {
|
|
73
|
+
const { sortModel, multiSort, onSortModelChange } = latest();
|
|
74
|
+
const next = nextSortModelForColumn(sortModel, field, direction, multiSort);
|
|
75
|
+
latestRef.current = { ...latest(), sortModel: next };
|
|
76
|
+
onSortModelChange(next);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
api.getFilterModel = () => latest().filterModel;
|
|
80
|
+
api.setFilterModel = (model) => {
|
|
81
|
+
latestRef.current = { ...latest(), filterModel: model };
|
|
82
|
+
latest().onFilterModelChange(model);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const setPaginationModel = (model: GridPaginationModel) => {
|
|
86
|
+
latestRef.current = { ...latest(), paginationModel: model };
|
|
87
|
+
latest().onPaginationModelChange(model);
|
|
88
|
+
};
|
|
89
|
+
api.setPage = (page) => setPaginationModel({ ...latest().paginationModel, page });
|
|
90
|
+
api.setPageSize = (pageSize) => setPaginationModel({ ...latest().paginationModel, pageSize, page: 0 });
|
|
91
|
+
|
|
92
|
+
const currentSelection = (): Set<GridRowId> => {
|
|
93
|
+
const pending = latest().getPendingRowSelectionModel?.();
|
|
94
|
+
return pending ? new Set(pending) : latest().selectedRowIds;
|
|
95
|
+
};
|
|
96
|
+
const setSelection = (ids: GridRowId[], isSelected: boolean) => {
|
|
97
|
+
const { disableMultipleRowSelection, onRowSelectionModelChange, isSyntheticRowId } = latest();
|
|
98
|
+
const dataIds = isSyntheticRowId ? ids.filter(id => !isSyntheticRowId(id)) : ids;
|
|
99
|
+
if (dataIds.length === 0) return;
|
|
100
|
+
const current = currentSelection();
|
|
101
|
+
const next = nextSelectionForRows(current, dataIds, isSelected, disableMultipleRowSelection);
|
|
102
|
+
// Like the UI paths, a call that changes nothing (selecting a selected row) reports nothing.
|
|
103
|
+
if (isSameSelection(current, next)) return;
|
|
104
|
+
latestRef.current = { ...latest(), selectedRowIds: new Set(next) };
|
|
105
|
+
onRowSelectionModelChange(next);
|
|
106
|
+
};
|
|
107
|
+
api.getSelectedRows = () => Array.from(currentSelection());
|
|
108
|
+
api.selectRow = (id, isSelected = true) => setSelection([id], isSelected);
|
|
109
|
+
api.selectRows = (ids, isSelected = true) => setSelection(ids, isSelected);
|
|
110
|
+
|
|
111
|
+
api.getVisibleRows = () => latest().getVisibleRows();
|
|
112
|
+
api.getAllFilteredRows = () => latest().getAllFilteredRows();
|
|
113
|
+
api.getVisibleColumns = () => latest().visibleColumns;
|
|
114
|
+
}, [apiRef]);
|
|
115
|
+
}
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import { useRef } from 'react';
|
|
2
|
-
import type {
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import type { MutableRefObject } from 'react';
|
|
3
|
+
import type { GridApi, GridRowModel, GridValidRowModel } from '../../types';
|
|
4
|
+
import { createGridApiPlaceholder } from './gridApiPlaceholder';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Hook to create a ref for the DataGrid API.
|
|
6
8
|
* This ref can be passed to the `apiRef` prop of the `DataGrid` component.
|
|
9
|
+
*
|
|
10
|
+
* `apiRef.current` is never null: until the grid mounts it holds an API whose methods are
|
|
11
|
+
* no-ops (getters return empty values), and the grid swaps in the live API in a layout
|
|
12
|
+
* effect, so it is available to the parent's layout effects and effects.
|
|
13
|
+
*
|
|
14
|
+
* The return type is spelled out so the published declaration is `MutableRefObject<GridApi>`,
|
|
15
|
+
* which matches `DataGridProps.apiRef` under both @types/react 18 and 19. Left to inference,
|
|
16
|
+
* React 19's types emit `RefObject<GridApi>`, which React 18's types treat as read-only and
|
|
17
|
+
* nullable, so `apiRef={useGridApiRef()}` failed to type-check on React 18.
|
|
18
|
+
*
|
|
19
|
+
* Pass your row type to have the row getters typed: `useGridApiRef<Invoice>().current.getRow(id)`
|
|
20
|
+
* returns `Invoice | null`.
|
|
7
21
|
*/
|
|
8
|
-
export function useGridApiRef() {
|
|
9
|
-
|
|
22
|
+
export function useGridApiRef<R extends GridValidRowModel = GridRowModel>(): MutableRefObject<GridApi<R>> {
|
|
23
|
+
const [placeholder] = useState(createGridApiPlaceholder<R>);
|
|
24
|
+
return useRef<GridApi<R>>(placeholder);
|
|
10
25
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import type { MutableRefObject } from 'react';
|
|
3
|
+
import type { GridApi } from '../../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Points the consumer's `apiRef` prop at the grid's live API. A layout effect, so the live API
|
|
7
|
+
* is in place before the parent's layout effects run.
|
|
8
|
+
*/
|
|
9
|
+
export function useGridApiRefBinding(
|
|
10
|
+
propApiRef: MutableRefObject<GridApi> | undefined,
|
|
11
|
+
apiRef: MutableRefObject<GridApi>
|
|
12
|
+
): void {
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
if (propApiRef) {
|
|
15
|
+
propApiRef.current = apiRef.current;
|
|
16
|
+
}
|
|
17
|
+
}, [propApiRef, apiRef]);
|
|
18
|
+
}
|