@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,14 +1,27 @@
|
|
|
1
|
-
import { useEffect, useRef, useCallback } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
GridDataSource,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
GridDataSource,
|
|
4
|
+
GridRowId,
|
|
5
|
+
GridRowModel,
|
|
6
|
+
GridSortItem,
|
|
7
|
+
GridFilterModel,
|
|
7
8
|
GridPaginationModel,
|
|
8
|
-
|
|
9
|
+
GridGetRowsResponse,
|
|
9
10
|
GridAggregationModel,
|
|
10
11
|
GridAggregationResult,
|
|
11
12
|
} from '../../types';
|
|
13
|
+
import { GRID_ALL_ROWS_END_ROW, isServerDrivenDataSource } from '../../utils/dataSource';
|
|
14
|
+
|
|
15
|
+
/** Rapid sort, filter and page changes are collapsed into one request. */
|
|
16
|
+
const FETCH_DEBOUNCE_MS = 300;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* - `page`: server pagination. Each request is one page and replaces the rows.
|
|
20
|
+
* - `infinite`: infinite scroll. Rows are appended, from the end of what is loaded up to the
|
|
21
|
+
* current page, so no range is ever skipped.
|
|
22
|
+
* - `all`: the grid pages on the client, so it loads every row once.
|
|
23
|
+
*/
|
|
24
|
+
type FetchKind = 'page' | 'infinite' | 'all';
|
|
12
25
|
|
|
13
26
|
interface UseGridDataSourceParams<R extends GridRowModel> {
|
|
14
27
|
dataSource?: GridDataSource<R>;
|
|
@@ -19,14 +32,80 @@ interface UseGridDataSourceParams<R extends GridRowModel> {
|
|
|
19
32
|
sortingMode?: 'client' | 'server';
|
|
20
33
|
filterMode?: 'client' | 'server';
|
|
21
34
|
aggregationModel?: GridAggregationModel;
|
|
35
|
+
/**
|
|
36
|
+
* Row grouping is active. It turns pagination off (every group renders in one scrollable view),
|
|
37
|
+
* so a `paginationMode: 'server'` source is asked for every row instead of the first page.
|
|
38
|
+
*/
|
|
39
|
+
rowGroupingActive?: boolean;
|
|
40
|
+
/** Resolves a row's id, to skip fetched rows that are already loaded. Defaults to `row.id`. */
|
|
41
|
+
getRowId?: (row: R) => GridRowId;
|
|
22
42
|
setRows: (rows: R[] | ((prev: R[]) => R[]), preserveRowCount?: boolean) => void;
|
|
23
43
|
setRowCount: (count: number) => void;
|
|
24
44
|
setDataSourceLoading: (loading: boolean) => void;
|
|
25
45
|
setDataSourceError: (error: unknown) => void;
|
|
26
|
-
|
|
46
|
+
onAggregationResults?: (results: GridAggregationResult) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Infinite scroll: called with page 0 when a sort, filter, page size or dataSource change
|
|
49
|
+
* restarts the list from its first row.
|
|
50
|
+
*/
|
|
51
|
+
onPaginationModelChange?: (model: GridPaginationModel) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface UseGridDataSourceReturn {
|
|
55
|
+
/** Requests the current rows now (skipping the debounce), e.g. to retry after an error. */
|
|
56
|
+
fetchRows: () => Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Loads the children of a server-side tree node and appends them. A path that is already
|
|
59
|
+
* loading or loaded for the current rows is not requested again. `onError` runs when the
|
|
60
|
+
* request fails; the grid-level error is left alone because the rows themselves are fine.
|
|
61
|
+
*/
|
|
62
|
+
fetchChildren: (parentId: GridRowId, groupKeys: string[], onError?: (error: unknown) => void) => Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface InfiniteState {
|
|
66
|
+
/** The page the list is loaded up to (inclusive). */
|
|
67
|
+
page: number;
|
|
68
|
+
/** Server offset just past the last loaded row. */
|
|
69
|
+
loadedEnd: number;
|
|
70
|
+
/** `endRow` of the request in flight, or null. */
|
|
71
|
+
inFlightEnd: number | null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const defaultGetRowId = (row: GridRowModel): GridRowId => row.id;
|
|
75
|
+
|
|
76
|
+
function getFetchKind(paginationMode: UseGridDataSourceParams<GridRowModel>['paginationMode'], rowGroupingActive: boolean): FetchKind {
|
|
77
|
+
if (paginationMode === 'infinite') return 'infinite';
|
|
78
|
+
// Row grouping shows no pager, so a page-sized request would leave the other rows unreachable.
|
|
79
|
+
if (paginationMode === 'server') return rowGroupingActive ? 'all' : 'page';
|
|
80
|
+
return 'all';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Drops fetched rows whose id (getRowId) repeats within the response, keeping the first
|
|
85
|
+
* occurrence, and pairs each row with its id. Rows are kept as given: the store keys them by
|
|
86
|
+
* getRowId. Runs outside any state updater, so a getRowId that throws for a fetched row fails the
|
|
87
|
+
* request (error overlay) instead of throwing inside React's update.
|
|
88
|
+
*/
|
|
89
|
+
function identifyRows<R extends GridRowModel>(rows: R[], getRowId: (row: R) => GridRowId): { id: GridRowId; row: R }[] {
|
|
90
|
+
const seen = new Set<GridRowId>();
|
|
91
|
+
const result: { id: GridRowId; row: R }[] = [];
|
|
92
|
+
rows.forEach(row => {
|
|
93
|
+
const id = getRowId(row);
|
|
94
|
+
if (seen.has(id)) return;
|
|
95
|
+
seen.add(id);
|
|
96
|
+
result.push({ id, row });
|
|
97
|
+
});
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The loaded rows followed by the fetched rows whose id is not loaded yet. */
|
|
102
|
+
function appendNewRows<R extends GridRowModel>(loaded: R[], fetched: { id: GridRowId; row: R }[], getRowId: (row: R) => GridRowId): R[] {
|
|
103
|
+
const loadedIds = new Set<GridRowId>(loaded.map(getRowId));
|
|
104
|
+
const added = fetched.filter(entry => !loadedIds.has(entry.id)).map(entry => entry.row);
|
|
105
|
+
return added.length === 0 ? loaded : [...loaded, ...added];
|
|
27
106
|
}
|
|
28
107
|
|
|
29
|
-
export function useGridDataSource<R extends GridRowModel>(params: UseGridDataSourceParams<R>) {
|
|
108
|
+
export function useGridDataSource<R extends GridRowModel>(params: UseGridDataSourceParams<R>): UseGridDataSourceReturn {
|
|
30
109
|
const {
|
|
31
110
|
dataSource,
|
|
32
111
|
sortModel,
|
|
@@ -36,155 +115,302 @@ export function useGridDataSource<R extends GridRowModel>(params: UseGridDataSou
|
|
|
36
115
|
sortingMode,
|
|
37
116
|
filterMode,
|
|
38
117
|
aggregationModel,
|
|
39
|
-
|
|
40
|
-
setRowCount,
|
|
41
|
-
setDataSourceLoading,
|
|
42
|
-
setDataSourceError,
|
|
43
|
-
onAggregationResults,
|
|
118
|
+
rowGroupingActive = false,
|
|
44
119
|
} = params;
|
|
45
120
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
121
|
+
const kind = getFetchKind(paginationMode, rowGroupingActive);
|
|
122
|
+
const loadsAllForGrouping = Boolean(dataSource) && paginationMode === 'server' && rowGroupingActive;
|
|
123
|
+
const warnedGroupingRef = useRef(false);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (process.env.NODE_ENV === 'production' || warnedGroupingRef.current || !loadsAllForGrouping) return;
|
|
126
|
+
warnedGroupingRef.current = true;
|
|
127
|
+
console.warn(
|
|
128
|
+
"[OpenGridX] `rowGroupingModel` turns pagination off, so the dataSource with `paginationMode: 'server'` is " +
|
|
129
|
+
'asked for every row (startRow 0, endRow Number.MAX_SAFE_INTEGER) instead of one page. ' +
|
|
130
|
+
'See docs/features/data-source.md.'
|
|
131
|
+
);
|
|
132
|
+
}, [loadsAllForGrouping]);
|
|
133
|
+
const getRows = dataSource?.getRows;
|
|
134
|
+
const serverDriven = isServerDrivenDataSource({ dataSource, paginationMode, sortingMode, filterMode });
|
|
52
135
|
|
|
53
|
-
|
|
136
|
+
// What the loaded rows depend on, compared by content: an inline filterModel, sortModel or
|
|
137
|
+
// aggregationModel (a new object with the same content on every parent render) does not refetch.
|
|
138
|
+
const datasetKey = JSON.stringify([
|
|
139
|
+
kind,
|
|
140
|
+
kind !== 'all' || sortingMode === 'server' ? sortModel : null,
|
|
141
|
+
kind !== 'all' || filterMode === 'server' ? filterModel : null,
|
|
142
|
+
serverDriven ? aggregationModel ?? null : null,
|
|
143
|
+
kind === 'all' ? null : paginationModel.pageSize,
|
|
144
|
+
]);
|
|
145
|
+
// Client pagination has every row already, so page changes never refetch.
|
|
146
|
+
const requestedPage = kind === 'all' ? 0 : paginationModel.page;
|
|
147
|
+
const requestedPageSize = kind === 'all' ? 0 : paginationModel.pageSize;
|
|
54
148
|
|
|
55
|
-
|
|
56
|
-
|
|
149
|
+
// Requests read the latest params when they start and when they land.
|
|
150
|
+
const latestRef = useRef({ params, kind });
|
|
151
|
+
useLayoutEffect(() => {
|
|
152
|
+
latestRef.current = { params, kind };
|
|
153
|
+
});
|
|
57
154
|
|
|
58
|
-
|
|
155
|
+
/** Bumped to drop the rows request in flight: a response whose token is stale is ignored. */
|
|
156
|
+
const tokenRef = useRef(0);
|
|
157
|
+
/** Bumped when the rows are replaced: children fetched for the old rows are ignored. */
|
|
158
|
+
const rowsGenerationRef = useRef(0);
|
|
159
|
+
const datasetRef = useRef<{ key: string; getRows: GridDataSource<R>['getRows'] } | null>(null);
|
|
160
|
+
const infiniteRef = useRef<InfiniteState>({ page: 0, loadedEnd: 0, inFlightEnd: null });
|
|
161
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
162
|
+
/** A rows request is scheduled or in flight. */
|
|
163
|
+
const rowsPendingRef = useRef(false);
|
|
164
|
+
const childrenPendingRef = useRef(0);
|
|
165
|
+
const loadingRef = useRef(false);
|
|
166
|
+
/** Children paths requested for the current rows (loading or loaded). */
|
|
167
|
+
const childRequestsRef = useRef(new Set<string>());
|
|
59
168
|
|
|
60
|
-
|
|
61
|
-
|
|
169
|
+
const syncLoading = useCallback(() => {
|
|
170
|
+
const loading = rowsPendingRef.current || childrenPendingRef.current > 0;
|
|
171
|
+
if (loading === loadingRef.current) return;
|
|
172
|
+
loadingRef.current = loading;
|
|
173
|
+
latestRef.current.params.setDataSourceLoading(loading);
|
|
174
|
+
}, []);
|
|
62
175
|
|
|
63
|
-
|
|
64
|
-
|
|
176
|
+
const clearTimer = useCallback(() => {
|
|
177
|
+
if (timerRef.current !== null) {
|
|
178
|
+
clearTimeout(timerRef.current);
|
|
179
|
+
timerRef.current = null;
|
|
180
|
+
}
|
|
181
|
+
}, []);
|
|
65
182
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
groupKeys: [],
|
|
72
|
-
aggregationModel,
|
|
73
|
-
};
|
|
183
|
+
const invalidateRowsRequest = useCallback(() => {
|
|
184
|
+
tokenRef.current++;
|
|
185
|
+
infiniteRef.current.inFlightEnd = null;
|
|
186
|
+
rowsPendingRef.current = false;
|
|
187
|
+
}, []);
|
|
74
188
|
|
|
75
|
-
|
|
76
|
-
|
|
189
|
+
const invalidateChildren = useCallback(() => {
|
|
190
|
+
rowsGenerationRef.current++;
|
|
191
|
+
childRequestsRef.current.clear();
|
|
192
|
+
childrenPendingRef.current = 0;
|
|
193
|
+
}, []);
|
|
77
194
|
|
|
78
|
-
|
|
79
|
-
|
|
195
|
+
/** Runs one rows request. Resolves true when an infinite list needs the next range right away. */
|
|
196
|
+
const requestRowsOnce = useCallback(async (): Promise<boolean> => {
|
|
197
|
+
const { params: p, kind: k } = latestRef.current;
|
|
198
|
+
const source = p.dataSource;
|
|
199
|
+
if (!source) return false;
|
|
200
|
+
const infinite = infiniteRef.current;
|
|
201
|
+
const pageSize = p.paginationModel.pageSize;
|
|
202
|
+
|
|
203
|
+
let startRow: number;
|
|
204
|
+
let endRow: number;
|
|
205
|
+
if (k === 'infinite') {
|
|
206
|
+
// One range at a time: the request in flight asks for the rest when it lands.
|
|
207
|
+
if (infinite.inFlightEnd !== null) return false;
|
|
208
|
+
startRow = infinite.loadedEnd;
|
|
209
|
+
endRow = (infinite.page + 1) * pageSize;
|
|
210
|
+
if (endRow <= startRow) {
|
|
211
|
+
rowsPendingRef.current = false;
|
|
212
|
+
syncLoading();
|
|
213
|
+
return false;
|
|
80
214
|
}
|
|
215
|
+
infinite.inFlightEnd = endRow;
|
|
216
|
+
} else if (k === 'page') {
|
|
217
|
+
startRow = p.paginationModel.page * pageSize;
|
|
218
|
+
endRow = startRow + pageSize;
|
|
219
|
+
} else {
|
|
220
|
+
startRow = 0;
|
|
221
|
+
endRow = GRID_ALL_ROWS_END_ROW;
|
|
222
|
+
}
|
|
81
223
|
|
|
82
|
-
|
|
224
|
+
const token = ++tokenRef.current;
|
|
225
|
+
rowsPendingRef.current = true;
|
|
226
|
+
syncLoading();
|
|
227
|
+
p.setDataSourceError(null);
|
|
83
228
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
229
|
+
let response: GridGetRowsResponse<R>;
|
|
230
|
+
try {
|
|
231
|
+
response = await source.getRows({
|
|
232
|
+
startRow,
|
|
233
|
+
endRow,
|
|
234
|
+
sortModel: p.sortModel,
|
|
235
|
+
filterModel: p.filterModel,
|
|
236
|
+
groupKeys: [],
|
|
237
|
+
aggregationModel: p.aggregationModel,
|
|
238
|
+
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (token !== tokenRef.current) return false;
|
|
241
|
+
infinite.inFlightEnd = null;
|
|
242
|
+
rowsPendingRef.current = false;
|
|
243
|
+
latestRef.current.params.setDataSourceError(error);
|
|
244
|
+
console.error('Data Source Error:', error);
|
|
245
|
+
syncLoading();
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (token !== tokenRef.current) return false;
|
|
87
249
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
250
|
+
// A malformed response, or a getRowId that throws for a fetched row, fails the request like a
|
|
251
|
+
// rejected getRows: error overlay, loading cleared, nothing thrown.
|
|
252
|
+
try {
|
|
253
|
+
const { params: current } = latestRef.current;
|
|
254
|
+
const getRowId = current.getRowId ?? (defaultGetRowId as (row: R) => GridRowId);
|
|
255
|
+
const fetched = identifyRows(response.rows, getRowId);
|
|
256
|
+
if (k === 'infinite' && startRow > 0) {
|
|
257
|
+
current.setRows(prev => appendNewRows(prev, fetched, getRowId), true);
|
|
258
|
+
} else {
|
|
259
|
+
current.setRows(fetched.map(entry => entry.row), true);
|
|
260
|
+
invalidateChildren();
|
|
261
|
+
}
|
|
262
|
+
if (response.aggregationResults) current.onAggregationResults?.(response.aggregationResults);
|
|
263
|
+
if (response.rowCount !== undefined) current.setRowCount(response.rowCount);
|
|
91
264
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
265
|
+
if (k === 'infinite') {
|
|
266
|
+
infinite.inFlightEnd = null;
|
|
267
|
+
infinite.loadedEnd = startRow + response.rows.length;
|
|
268
|
+
// A short response is the end of the data; otherwise catch up with a page that moved on.
|
|
269
|
+
const receivedAll = response.rows.length >= endRow - startRow;
|
|
270
|
+
if (receivedAll && (infinite.page + 1) * pageSize > endRow) return true;
|
|
96
271
|
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
infinite.inFlightEnd = null;
|
|
274
|
+
rowsPendingRef.current = false;
|
|
275
|
+
latestRef.current.params.setDataSourceError(error);
|
|
276
|
+
console.error('Data Source Error:', error);
|
|
277
|
+
syncLoading();
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
rowsPendingRef.current = false;
|
|
281
|
+
syncLoading();
|
|
282
|
+
return false;
|
|
283
|
+
}, [syncLoading, invalidateChildren]);
|
|
97
284
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
285
|
+
const requestRows = useCallback(async (): Promise<void> => {
|
|
286
|
+
while (await requestRowsOnce()) {
|
|
287
|
+
// keep extending the infinite list until it reaches the current page
|
|
288
|
+
}
|
|
289
|
+
}, [requestRowsOnce]);
|
|
290
|
+
|
|
291
|
+
// Layout effect: loading is on before the first paint, so the empty state never flashes up
|
|
292
|
+
// while the first request waits for its debounce.
|
|
293
|
+
useLayoutEffect(() => {
|
|
294
|
+
if (!getRows) {
|
|
295
|
+
if (datasetRef.current) {
|
|
296
|
+
// The dataSource was removed: nothing it still returns may reach the rows.
|
|
297
|
+
datasetRef.current = null;
|
|
298
|
+
clearTimer();
|
|
299
|
+
invalidateRowsRequest();
|
|
300
|
+
invalidateChildren();
|
|
301
|
+
latestRef.current.params.setDataSourceError(null);
|
|
302
|
+
syncLoading();
|
|
102
303
|
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
103
306
|
|
|
104
|
-
|
|
105
|
-
|
|
307
|
+
const previous = datasetRef.current;
|
|
308
|
+
const infinite = infiniteRef.current;
|
|
309
|
+
if (!previous || previous.key !== datasetKey || previous.getRows !== getRows) {
|
|
310
|
+
datasetRef.current = { key: datasetKey, getRows };
|
|
311
|
+
invalidateRowsRequest();
|
|
312
|
+
invalidateChildren();
|
|
313
|
+
// A new sort, filter, page size or dataSource restarts an infinite list at its first page.
|
|
314
|
+
const restart = kind === 'infinite' && previous !== null && requestedPage !== 0;
|
|
315
|
+
infinite.page = restart ? 0 : requestedPage;
|
|
316
|
+
infinite.loadedEnd = 0;
|
|
317
|
+
if (restart) {
|
|
318
|
+
const { params: p } = latestRef.current;
|
|
319
|
+
p.onPaginationModelChange?.({ ...p.paginationModel, page: 0 });
|
|
320
|
+
}
|
|
321
|
+
} else if (kind === 'infinite') {
|
|
322
|
+
if (requestedPage < infinite.page) {
|
|
323
|
+
// Going back to an earlier page reloads the list up to that page.
|
|
324
|
+
invalidateRowsRequest();
|
|
325
|
+
infinite.loadedEnd = 0;
|
|
106
326
|
}
|
|
327
|
+
infinite.page = requestedPage;
|
|
328
|
+
} else {
|
|
329
|
+
// A new page supersedes the request in flight.
|
|
330
|
+
invalidateRowsRequest();
|
|
331
|
+
}
|
|
107
332
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
//
|
|
333
|
+
if (kind === 'infinite') {
|
|
334
|
+
const inFlight = infinite.inFlightEnd !== null;
|
|
335
|
+
if (inFlight || (infinite.page + 1) * requestedPageSize <= infinite.loadedEnd) {
|
|
336
|
+
// The request in flight extends the list when it lands, or the rows are already loaded.
|
|
337
|
+
rowsPendingRef.current = inFlight;
|
|
338
|
+
syncLoading();
|
|
339
|
+
return;
|
|
112
340
|
}
|
|
341
|
+
}
|
|
113
342
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
343
|
+
rowsPendingRef.current = true;
|
|
344
|
+
syncLoading();
|
|
345
|
+
timerRef.current = setTimeout(() => {
|
|
346
|
+
timerRef.current = null;
|
|
347
|
+
void requestRows();
|
|
348
|
+
}, FETCH_DEBOUNCE_MS);
|
|
349
|
+
return clearTimer;
|
|
350
|
+
}, [getRows, datasetKey, kind, requestedPage, requestedPageSize, clearTimer, invalidateRowsRequest, invalidateChildren, syncLoading, requestRows]);
|
|
120
351
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
352
|
+
// Nothing still in flight may write to an unmounted grid. (A grid that is shown again, e.g. by
|
|
353
|
+
// StrictMode or <Activity>, reruns the effect above and requests what it still needs.)
|
|
354
|
+
useEffect(() => () => {
|
|
355
|
+
invalidateRowsRequest();
|
|
356
|
+
invalidateChildren();
|
|
357
|
+
}, [invalidateRowsRequest, invalidateChildren]);
|
|
358
|
+
|
|
359
|
+
const fetchRows = useCallback((): Promise<void> => {
|
|
360
|
+
clearTimer();
|
|
361
|
+
return requestRows();
|
|
362
|
+
}, [clearTimer, requestRows]);
|
|
363
|
+
|
|
364
|
+
const fetchChildren = useCallback(async (
|
|
365
|
+
_parentId: GridRowId,
|
|
366
|
+
groupKeys: string[],
|
|
367
|
+
onError?: (error: unknown) => void,
|
|
368
|
+
): Promise<void> => {
|
|
369
|
+
const { params: p } = latestRef.current;
|
|
370
|
+
const source = p.dataSource;
|
|
371
|
+
if (!source) return;
|
|
372
|
+
|
|
373
|
+
const requestKey = JSON.stringify(groupKeys);
|
|
374
|
+
const requests = childRequestsRef.current;
|
|
375
|
+
if (requests.has(requestKey)) return;
|
|
376
|
+
const generation = rowsGenerationRef.current;
|
|
377
|
+
requests.add(requestKey);
|
|
378
|
+
childrenPendingRef.current++;
|
|
379
|
+
syncLoading();
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const response = await source.getRows({
|
|
383
|
+
startRow: 0,
|
|
384
|
+
endRow: GRID_ALL_ROWS_END_ROW,
|
|
385
|
+
sortModel: p.sortModel,
|
|
386
|
+
filterModel: p.filterModel,
|
|
387
|
+
groupKeys,
|
|
388
|
+
aggregationModel: p.aggregationModel,
|
|
389
|
+
});
|
|
390
|
+
// Children of rows that have since been replaced (another page, sort or source) are dropped.
|
|
391
|
+
if (generation !== rowsGenerationRef.current) return;
|
|
392
|
+
if (response.rows.length > 0) {
|
|
393
|
+
const { params: current } = latestRef.current;
|
|
394
|
+
const getRowId = current.getRowId ?? (defaultGetRowId as (row: R) => GridRowId);
|
|
395
|
+
const fetched = identifyRows(response.rows, getRowId);
|
|
396
|
+
current.setRows(prev => appendNewRows(prev, fetched, getRowId), true);
|
|
125
397
|
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (generation !== rowsGenerationRef.current) return;
|
|
400
|
+
// Forget the request so that expanding the node again retries it.
|
|
401
|
+
requests.delete(requestKey);
|
|
402
|
+
console.error('Failed to fetch children:', error);
|
|
403
|
+
onError?.(error);
|
|
126
404
|
} finally {
|
|
127
|
-
if (
|
|
128
|
-
|
|
405
|
+
if (generation === rowsGenerationRef.current) {
|
|
406
|
+
childrenPendingRef.current--;
|
|
407
|
+
syncLoading();
|
|
129
408
|
}
|
|
130
409
|
}
|
|
131
|
-
}, [
|
|
132
|
-
dataSource,
|
|
133
|
-
paginationModel.page,
|
|
134
|
-
paginationModel.pageSize,
|
|
135
|
-
sortModel,
|
|
136
|
-
filterModel,
|
|
137
|
-
paginationMode,
|
|
138
|
-
aggregationModel,
|
|
139
|
-
onAggregationResults,
|
|
140
|
-
setRows,
|
|
141
|
-
setRowCount,
|
|
142
|
-
setDataSourceLoading,
|
|
143
|
-
setDataSourceError
|
|
144
|
-
]);
|
|
145
|
-
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
const isServerSide = paginationMode === 'server' || paginationMode === 'infinite' || sortingMode === 'server' || filterMode === 'server';
|
|
148
|
-
|
|
149
|
-
if (dataSource && isServerSide) {
|
|
150
|
-
const timer = setTimeout(() => {
|
|
151
|
-
fetchRows();
|
|
152
|
-
}, 300);
|
|
153
|
-
return () => clearTimeout(timer);
|
|
154
|
-
}
|
|
155
|
-
}, [fetchRows, dataSource, paginationMode, sortingMode, filterMode]);
|
|
156
|
-
|
|
157
|
-
const fetchChildren = useCallback(async (_parentId: string | number, groupKeys: string[]) => {
|
|
158
|
-
if (!dataSource) return;
|
|
159
|
-
|
|
160
|
-
setDataSourceLoading(true);
|
|
161
|
-
try {
|
|
162
|
-
const response = await dataSource.getRows({
|
|
163
|
-
startRow: 0,
|
|
164
|
-
endRow: -1,
|
|
165
|
-
sortModel,
|
|
166
|
-
filterModel,
|
|
167
|
-
groupKeys
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
if (response.rows.length > 0) {
|
|
171
|
-
setRows((prevRows) => {
|
|
172
|
-
|
|
173
|
-
const existingIds = new Set(prevRows.map(r => r.id));
|
|
174
|
-
const newRows = response.rows.filter(r => !existingIds.has(r.id));
|
|
175
|
-
return [...prevRows, ...newRows];
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
} catch (error) {
|
|
179
|
-
setDataSourceError(error);
|
|
180
|
-
console.error('Failed to fetch children:', error);
|
|
181
|
-
} finally {
|
|
182
|
-
setDataSourceLoading(false);
|
|
183
|
-
}
|
|
184
|
-
}, [dataSource, sortModel, filterModel, setRows, setDataSourceLoading, setDataSourceError]);
|
|
410
|
+
}, [syncLoading]);
|
|
185
411
|
|
|
186
412
|
return {
|
|
187
413
|
fetchRows,
|
|
188
|
-
fetchChildren
|
|
414
|
+
fetchChildren,
|
|
189
415
|
};
|
|
190
416
|
}
|