@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.
- package/CHANGELOG.md +51 -0
- package/README.md +162 -19
- package/dist/index.d.ts +60 -41
- package/dist/opengridx.es.js +5045 -4549
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +11 -11
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +17 -7
- package/docs/README.md +14 -0
- package/docs/components/aggregation-footer.md +48 -0
- package/docs/components/datagrid.md +8 -3
- package/docs/components/empty-state.md +50 -0
- package/docs/components/error-overlay.md +40 -0
- package/docs/features/editing-reordering.md +11 -7
- package/docs/features/filtering.md +10 -8
- package/docs/features/selection.md +2 -1
- package/docs/features/sorting-pagination.md +3 -1
- package/docs/features/state-persistence.md +1 -2
- package/docs/roadmap.md +20 -1
- package/lib/components/Cell/Cell.tsx +9 -10
- package/lib/components/Cell/GridEditInputCell.tsx +4 -4
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
- package/lib/components/DataGrid/DataGrid.regression.test.tsx +132 -0
- package/lib/components/DataGrid/DataGrid.tsx +336 -1018
- package/lib/components/DataGrid/GridAggregationFooter.tsx +68 -0
- package/lib/components/DataGrid/GridEmptyState.tsx +16 -0
- package/lib/components/DataGrid/GridErrorOverlay.tsx +33 -0
- package/lib/components/DataGrid/GridListView.tsx +116 -0
- package/lib/components/DataGrid/GridPinnedRows.tsx +98 -0
- package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +124 -0
- package/lib/components/DataGrid/GridVirtualRows.tsx +202 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +18 -10
- package/lib/components/Header/ColumnMenu.tsx +2 -2
- package/lib/components/Header/Header.tsx +4 -4
- package/lib/components/ListView/ListViewRow.tsx +2 -2
- package/lib/components/Row/Row.tsx +10 -10
- package/lib/components/SkeletonRow/SkeletonRow.tsx +9 -2
- package/lib/components/Toolbar/GridToolbar.tsx +6 -0
- package/lib/hooks/core/useDataGrid.regression.test.ts +66 -0
- package/lib/hooks/core/useDataGrid.ts +14 -34
- package/lib/hooks/core/useGridColumns.tsx +228 -0
- package/lib/hooks/core/useGridControlledState.ts +192 -0
- package/lib/hooks/core/useGridRowPipeline.ts +109 -0
- package/lib/hooks/core/useGridScrollSync.ts +52 -0
- package/lib/hooks/core/useGridStateSnapshot.ts +63 -0
- package/lib/hooks/core/useGridVirtualization.ts +186 -0
- package/lib/hooks/core/useGridVisibleRows.ts +66 -0
- package/lib/hooks/core/useLayout.ts +0 -6
- package/lib/hooks/features/useAggregation.ts +7 -7
- package/lib/hooks/features/useGridDataSource.ts +1 -1
- package/lib/hooks/features/useGridEditing.ts +31 -32
- package/lib/hooks/features/usePivot.test.ts +202 -0
- package/lib/hooks/features/usePivot.ts +0 -0
- package/lib/hooks/useColumnReorder.ts +1 -1
- package/lib/hooks/useRowGrouping.ts +14 -14
- package/lib/hooks/useTreeData.ts +1 -1
- package/lib/state/types.ts +1 -1
- package/lib/state/useGridStateStorage.ts +1 -1
- package/lib/types/index.ts +48 -32
- package/lib/utils/export/exportToExcelAdvanced.ts +14 -14
- package/lib/utils/export/index.ts +22 -17
- package/lib/utils/filtering/index.ts +20 -20
- package/lib/utils/sorting/index.ts +1 -1
- package/package.json +1 -1
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
import React, { useRef, useEffect, useCallback, useMemo, useState } from 'react';
|
|
2
2
|
import { useLayout } from '../../hooks/core/useLayout';
|
|
3
3
|
import { useGridKeyboardNavigation } from '../../hooks/core/useGridKeyboardNavigation';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
4
|
+
import { useGridControlledState } from '../../hooks/core/useGridControlledState';
|
|
5
|
+
import { useGridRowPipeline } from '../../hooks/core/useGridRowPipeline';
|
|
6
|
+
import { useGridVirtualization } from '../../hooks/core/useGridVirtualization';
|
|
7
|
+
import { useGridColumns } from '../../hooks/core/useGridColumns';
|
|
8
|
+
import { useGridVisibleRows } from '../../hooks/core/useGridVisibleRows';
|
|
9
|
+
import { useGridScrollSync } from '../../hooks/core/useGridScrollSync';
|
|
10
|
+
import { useGridStateSnapshot } from '../../hooks/core/useGridStateSnapshot';
|
|
11
|
+
import { GridAggregationFooter } from './GridAggregationFooter';
|
|
12
|
+
import { GridEmptyState } from './GridEmptyState';
|
|
13
|
+
import { GridErrorOverlay } from './GridErrorOverlay';
|
|
7
14
|
import { Header } from '../Header/Header';
|
|
8
|
-
import { Row } from '../Row/Row';
|
|
9
|
-
import { SkeletonRow } from '../SkeletonRow';
|
|
10
15
|
import { Pagination } from '../Pagination/Pagination';
|
|
11
16
|
import { useDataGrid } from '../../hooks/core/useDataGrid';
|
|
12
|
-
import { useColumnReorder } from '../../hooks/useColumnReorder';
|
|
13
17
|
import { useRowReorder } from '../../hooks/useRowReorder';
|
|
14
18
|
import { useTreeData } from '../../hooks/useTreeData';
|
|
15
19
|
import { useRowGrouping } from '../../hooks/useRowGrouping';
|
|
16
20
|
import { useGridEditing } from '../../hooks/features/useGridEditing';
|
|
17
21
|
import { useGridSpanning } from '../../hooks/features/useGridSpanning';
|
|
18
22
|
import { useGridDataSource } from '../../hooks/features/useGridDataSource';
|
|
19
|
-
import { useAggregation
|
|
23
|
+
import { useAggregation } from '../../hooks/features/useAggregation';
|
|
20
24
|
import { usePivot } from '../../hooks/features/usePivot';
|
|
21
25
|
import { useGridClipboard } from '../../hooks/features/useGridClipboard';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import type { DataGridProps, GridRowModel, GridRowId, GridSortDirection, GridColumnOrderChangeParams, GridColDef, GridRenderCellParams, GridRowParams, GridCellParams, GridPaginationModel, GridDataSource, GridAggregationResult, GridAggregationModel, GridPivotModel, GridColumnPinning, GridFilterModel } from '../../types';
|
|
26
|
+
import { GridListView } from './GridListView';
|
|
27
|
+
import { GridPinnedRows } from './GridPinnedRows';
|
|
28
|
+
import { GridVirtualRows } from './GridVirtualRows';
|
|
29
|
+
import { GridStandaloneColumnPanel } from './GridStandaloneColumnPanel';
|
|
30
|
+
import type { DataGridProps, GridRowModel, GridRowId, GridSortDirection, GridColDef, GridRowParams, GridCellParams, GridDataSource, GridAggregationResult, GridFilterModel, GridTreeNode, GridSortItem } from '../../types';
|
|
28
31
|
|
|
29
32
|
export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridProps<R>) {
|
|
30
33
|
const {
|
|
@@ -113,15 +116,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
113
116
|
const defaultFilterModel: GridFilterModel = useMemo(() => ({ items: [] }), []);
|
|
114
117
|
const filterModel = (propFilterModel || defaultFilterModel) as GridFilterModel;
|
|
115
118
|
|
|
116
|
-
const [internalSortModel, setInternalSortModel] = useState<{ field: string; sort: 'asc' | 'desc' }[]>(
|
|
117
|
-
() => initialState?.sorting?.sortModel ?? []
|
|
118
|
-
);
|
|
119
|
-
const isSortControlled = propSortModel !== undefined;
|
|
120
|
-
const sortModel = isSortControlled ? propSortModel : internalSortModel;
|
|
121
|
-
|
|
122
119
|
const defaultRowGroupingModel = useMemo(() => [], []);
|
|
123
120
|
const rowGroupingModel = propRowGroupingModel || defaultRowGroupingModel;
|
|
124
|
-
const isAggregationControlled = propAggregationModel !== undefined;
|
|
125
121
|
|
|
126
122
|
// ─── Stabilize toolbar component identity ────────────────────────────────
|
|
127
123
|
// Problem: demos/users often define their toolbar as an inline function
|
|
@@ -148,51 +144,46 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
148
144
|
const Toolbar = latestToolbarRef.current;
|
|
149
145
|
return Toolbar ? (Toolbar as (p: typeof props) => React.ReactElement | null)(props) : null;
|
|
150
146
|
}).current;
|
|
151
|
-
const [internalAggregationModel, setInternalAggregationModel] = useState<GridAggregationModel>(
|
|
152
|
-
() => propAggregationModel ?? {}
|
|
153
|
-
);
|
|
154
|
-
const aggregationModel = isAggregationControlled ? propAggregationModel : internalAggregationModel;
|
|
155
|
-
|
|
156
|
-
const handleAggregationModelChange = useCallback((model: GridAggregationModel) => {
|
|
157
|
-
if (!isAggregationControlled) {
|
|
158
|
-
setInternalAggregationModel(model);
|
|
159
|
-
}
|
|
160
|
-
onAggregationModelChange?.(model);
|
|
161
|
-
}, [isAggregationControlled, onAggregationModelChange]);
|
|
162
147
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
148
|
+
const controlledState = useGridControlledState({
|
|
149
|
+
initialState,
|
|
150
|
+
sortModel: propSortModel,
|
|
151
|
+
onSortModelChange,
|
|
152
|
+
aggregationModel: propAggregationModel,
|
|
153
|
+
onAggregationModelChange,
|
|
154
|
+
columnVisibilityModel: propColumnVisibilityModel,
|
|
155
|
+
onColumnVisibilityModelChange,
|
|
156
|
+
pinnedColumns: propPinnedColumns,
|
|
157
|
+
onPinnedColumnsChange,
|
|
158
|
+
pivotModel: propPivotModel,
|
|
159
|
+
onPivotModelChange,
|
|
160
|
+
paginationModel,
|
|
161
|
+
onPaginationModelChange,
|
|
162
|
+
rowSelectionModel: propRowSelectionModel,
|
|
163
|
+
onRowSelectionModelChange: propOnRowSelectionModelChange,
|
|
164
|
+
});
|
|
168
165
|
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
166
|
+
const {
|
|
167
|
+
sortModel,
|
|
168
|
+
isSortControlled,
|
|
169
|
+
setInternalSortModel,
|
|
170
|
+
aggregationModel,
|
|
171
|
+
handleAggregationModelChange,
|
|
172
|
+
columnVisibilityModel,
|
|
173
|
+
handleColumnVisibilityModelChange,
|
|
174
|
+
pinnedColumns,
|
|
175
|
+
handlePinnedColumnsChange,
|
|
176
|
+
currentPivotModel,
|
|
177
|
+
handlePivotModelChange,
|
|
178
|
+
effectivePaginationModel,
|
|
179
|
+
handlePaginationModelChange,
|
|
180
|
+
selectedRowIds,
|
|
181
|
+
isSelectionControlled,
|
|
182
|
+
setInternalRowSelectionModel,
|
|
183
|
+
} = controlledState;
|
|
175
184
|
|
|
176
|
-
const
|
|
177
|
-
() => initialState?.columns?.pinnedColumns ?? {}
|
|
178
|
-
);
|
|
179
|
-
const isPinnedColumnsControlled = propPinnedColumns !== undefined;
|
|
180
|
-
const pinnedColumns = isPinnedColumnsControlled ? propPinnedColumns : internalPinnedColumns;
|
|
185
|
+
const onRowSelectionModelChange = propOnRowSelectionModelChange;
|
|
181
186
|
|
|
182
|
-
const handlePinnedColumnsChange = useCallback((model: GridColumnPinning) => {
|
|
183
|
-
if (!isPinnedColumnsControlled) {
|
|
184
|
-
setInternalPinnedColumns(model);
|
|
185
|
-
}
|
|
186
|
-
onPinnedColumnsChange?.(model);
|
|
187
|
-
}, [isPinnedColumnsControlled, onPinnedColumnsChange]);
|
|
188
|
-
|
|
189
|
-
const EMPTY_PIVOT_MODEL: GridPivotModel = useMemo(() => ({ rowFields: [], columnFields: [], valueFields: [] }), []);
|
|
190
|
-
const [internalPivotModel, setInternalPivotModel] = useState<GridPivotModel>(propPivotModel ?? EMPTY_PIVOT_MODEL);
|
|
191
|
-
const currentPivotModel = propPivotModel ?? internalPivotModel;
|
|
192
|
-
const handlePivotModelChange = useCallback((model: GridPivotModel) => {
|
|
193
|
-
setInternalPivotModel(model);
|
|
194
|
-
onPivotModelChange?.(model);
|
|
195
|
-
}, [onPivotModelChange]);
|
|
196
187
|
const pivot = usePivot(rows as GridRowModel[], columns as unknown as GridColDef[], currentPivotModel, pivotMode);
|
|
197
188
|
|
|
198
189
|
const activeRows = (pivotMode && pivot.isValid ? pivot.pivotRows : rows) as unknown as R[];
|
|
@@ -204,25 +195,6 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
204
195
|
setServerAggregationResults(null);
|
|
205
196
|
}, [aggregationModel]);
|
|
206
197
|
|
|
207
|
-
const [internalPaginationModel, setInternalPaginationModel] = useState(
|
|
208
|
-
() => initialState?.pagination?.paginationModel ?? paginationModel
|
|
209
|
-
);
|
|
210
|
-
const isPaginationControlled = props.paginationModel !== undefined;
|
|
211
|
-
const effectivePaginationModel = isPaginationControlled ? paginationModel : internalPaginationModel;
|
|
212
|
-
|
|
213
|
-
const handlePaginationModelChange = useCallback((newModel: GridPaginationModel) => {
|
|
214
|
-
if (!isPaginationControlled) {
|
|
215
|
-
setInternalPaginationModel(newModel);
|
|
216
|
-
}
|
|
217
|
-
onPaginationModelChange?.(newModel);
|
|
218
|
-
}, [isPaginationControlled, onPaginationModelChange]);
|
|
219
|
-
|
|
220
|
-
const [internalRowSelectionModel, setInternalRowSelectionModel] = useState<GridRowId[]>([]);
|
|
221
|
-
const isSelectionControlled = propRowSelectionModel !== undefined;
|
|
222
|
-
const rowSelectionModel = isSelectionControlled ? propRowSelectionModel : internalRowSelectionModel;
|
|
223
|
-
const selectedRowIds = useMemo(() => new Set(rowSelectionModel), [rowSelectionModel]);
|
|
224
|
-
const onRowSelectionModelChange = propOnRowSelectionModelChange;
|
|
225
|
-
|
|
226
198
|
const viewportRef = useRef<HTMLDivElement>(null);
|
|
227
199
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
228
200
|
const gridRef = useRef<HTMLDivElement>(null);
|
|
@@ -240,12 +212,12 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
240
212
|
dataSourceRef.current = dataSource;
|
|
241
213
|
|
|
242
214
|
const fetchChildrenRef = useRef<((parentId: GridRowId, groupKeys: string[]) => Promise<void>) | null>(null);
|
|
243
|
-
const treeDataRef = useRef<
|
|
215
|
+
const treeDataRef = useRef<ReturnType<typeof useTreeData> | null>(null);
|
|
244
216
|
|
|
245
|
-
const handleNodeExpansion = useCallback((node:
|
|
217
|
+
const handleNodeExpansion = useCallback((node: GridTreeNode) => {
|
|
246
218
|
if (dataSourceRef.current && treeData) {
|
|
247
219
|
|
|
248
|
-
if (node.serverChildrenCount > 0 && node.children.length === 0) {
|
|
220
|
+
if ((node.serverChildrenCount ?? 0) > 0 && (node.children ?? []).length === 0) {
|
|
249
221
|
|
|
250
222
|
const groupKeys = treeDataRef.current?.getNodePath(node.id) || [node.groupingKey];
|
|
251
223
|
fetchChildrenRef.current?.(node.id, groupKeys);
|
|
@@ -267,13 +239,14 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
267
239
|
apiRef,
|
|
268
240
|
setRows,
|
|
269
241
|
setColumns,
|
|
270
|
-
setScroll,
|
|
271
242
|
setDimensions,
|
|
272
243
|
setDataSourceLoading,
|
|
273
244
|
setDataSourceError,
|
|
274
245
|
setRowCount
|
|
275
246
|
} = gridData;
|
|
276
247
|
|
|
248
|
+
const { scrollTop, scrollLeft, handleScroll } = useGridScrollSync({ onRowsScrollEnd });
|
|
249
|
+
|
|
277
250
|
useEffect(() => {
|
|
278
251
|
if (propApiRef) {
|
|
279
252
|
propApiRef.current = apiRef.current;
|
|
@@ -337,7 +310,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
337
310
|
const { dispatch } = gridData;
|
|
338
311
|
const currentRows = Array.from(gridData.state.rows.idRowsLookup.values());
|
|
339
312
|
const nextRows = currentRows.map(r =>
|
|
340
|
-
|
|
313
|
+
r.id === updatedRow.id ? updatedRow : r
|
|
341
314
|
);
|
|
342
315
|
dispatch({ type: 'SET_ROWS', payload: nextRows });
|
|
343
316
|
},
|
|
@@ -352,9 +325,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
352
325
|
|
|
353
326
|
const handleRowClick = useCallback((params: GridRowParams<R>) => {
|
|
354
327
|
const { row, id } = params;
|
|
355
|
-
const rowData = row as any;
|
|
356
328
|
|
|
357
|
-
if (isHierarchyEnabled &&
|
|
329
|
+
if (isHierarchyEnabled && row._hasChildren) {
|
|
358
330
|
activeHierarchyHandlers?.toggleExpansion(id);
|
|
359
331
|
return;
|
|
360
332
|
}
|
|
@@ -362,120 +334,6 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
362
334
|
onRowClick?.(params);
|
|
363
335
|
}, [isHierarchyEnabled, activeHierarchyHandlers, onRowClick]);
|
|
364
336
|
|
|
365
|
-
const effectiveColumns = useMemo(() => {
|
|
366
|
-
if (!isHierarchyEnabled) return activeColumns;
|
|
367
|
-
|
|
368
|
-
return activeColumns.map((col, index) => {
|
|
369
|
-
|
|
370
|
-
if (index === 0) {
|
|
371
|
-
return {
|
|
372
|
-
...col,
|
|
373
|
-
renderCell: (params: GridRenderCellParams<R>) => {
|
|
374
|
-
const { _treeDepth, _hasChildren, _isExpanded, _groupingField, _groupingValue, _descendantCount } = params.row as any;
|
|
375
|
-
const depth = _treeDepth || 0;
|
|
376
|
-
const paddingLeft = depth * 24;
|
|
377
|
-
|
|
378
|
-
let content = col.renderCell ? col.renderCell(params) : params.value;
|
|
379
|
-
|
|
380
|
-
if (isTreeData && _hasChildren && params.row._isGroupRow) {
|
|
381
|
-
content = (
|
|
382
|
-
<div className="ogx__group-cell-content">
|
|
383
|
-
{params.value}
|
|
384
|
-
{_descendantCount !== undefined && _descendantCount > 0 ? ` (${_descendantCount})` : ''}
|
|
385
|
-
</div>
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// For row grouping, override the first column content to show the group label
|
|
390
|
-
if (isRowGrouping && _hasChildren && _groupingField) {
|
|
391
|
-
content = (
|
|
392
|
-
<div className="ogx__group-cell-content">
|
|
393
|
-
{col.field === _groupingField ? '' : `${_groupingField}: `}
|
|
394
|
-
{String(_groupingValue)}
|
|
395
|
-
{_descendantCount !== undefined ? ` (${_descendantCount})` : ''}
|
|
396
|
-
</div>
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return (
|
|
401
|
-
<div style={{ display: 'flex', alignItems: 'center', paddingLeft, width: '100%', height: '100%' }}>
|
|
402
|
-
<div
|
|
403
|
-
style={{
|
|
404
|
-
marginRight: 4,
|
|
405
|
-
width: 24,
|
|
406
|
-
height: 24,
|
|
407
|
-
display: 'flex',
|
|
408
|
-
alignItems: 'center',
|
|
409
|
-
justifyContent: 'center',
|
|
410
|
-
cursor: _hasChildren ? 'pointer' : 'default',
|
|
411
|
-
flexShrink: 0
|
|
412
|
-
}}
|
|
413
|
-
>
|
|
414
|
-
|
|
415
|
-
{_hasChildren ? (
|
|
416
|
-
<div
|
|
417
|
-
onClick={(e) => {
|
|
418
|
-
e.stopPropagation();
|
|
419
|
-
e.preventDefault();
|
|
420
|
-
activeHierarchyHandlers?.toggleExpansion(params.row.id);
|
|
421
|
-
}}
|
|
422
|
-
onMouseDown={(e) => {
|
|
423
|
-
e.stopPropagation();
|
|
424
|
-
}}
|
|
425
|
-
style={{
|
|
426
|
-
cursor: 'pointer',
|
|
427
|
-
display: 'flex',
|
|
428
|
-
alignItems: 'center',
|
|
429
|
-
justifyContent: 'center',
|
|
430
|
-
pointerEvents: 'auto',
|
|
431
|
-
zIndex: 10,
|
|
432
|
-
position: 'relative'
|
|
433
|
-
}}
|
|
434
|
-
>
|
|
435
|
-
<ExpandIcon
|
|
436
|
-
isExpanded={_isExpanded}
|
|
437
|
-
onClick={(e) => {
|
|
438
|
-
e.stopPropagation();
|
|
439
|
-
e.preventDefault();
|
|
440
|
-
activeHierarchyHandlers?.toggleExpansion(params.row.id);
|
|
441
|
-
}}
|
|
442
|
-
/>
|
|
443
|
-
</div>
|
|
444
|
-
) : null}
|
|
445
|
-
</div>
|
|
446
|
-
<div style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
447
|
-
{content}
|
|
448
|
-
</div>
|
|
449
|
-
</div>
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return {
|
|
456
|
-
...col,
|
|
457
|
-
renderCell: (params: GridRenderCellParams<R>) => {
|
|
458
|
-
const { _hasChildren, _groupingField } = params.row as any;
|
|
459
|
-
|
|
460
|
-
if (isRowGrouping && _hasChildren) {
|
|
461
|
-
|
|
462
|
-
if (col.field === _groupingField) {
|
|
463
|
-
return null;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (params.value !== undefined && params.value !== null) {
|
|
467
|
-
return col.renderCell ? col.renderCell(params) : params.value;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
return col.renderCell ? col.renderCell(params) : params.value;
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
}) as GridColDef<R>[];
|
|
477
|
-
}, [activeColumns, isHierarchyEnabled, isRowGrouping, activeHierarchyHandlers]);
|
|
478
|
-
|
|
479
337
|
const dataSourceHandlers = useGridDataSource({
|
|
480
338
|
dataSource,
|
|
481
339
|
sortModel,
|
|
@@ -498,68 +356,16 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
498
356
|
}
|
|
499
357
|
}, [dataSourceHandlers.fetchChildren]);
|
|
500
358
|
|
|
501
|
-
const [columnWidths, setColumnWidths] = useState<Record<string, number>>(
|
|
502
|
-
() => initialState?.columns?.columnWidths ?? {}
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
const [internalColumnOrder, setInternalColumnOrder] = useState<string[]>(() =>
|
|
506
|
-
initialState?.columns?.columnOrder ?? activeColumns.map(col => col.field)
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
const effectiveColumnOrder = columnOrder || internalColumnOrder;
|
|
510
|
-
|
|
511
359
|
useEffect(() => {
|
|
512
360
|
if (!dataSource) {
|
|
513
361
|
setRows(activeRows);
|
|
514
362
|
}
|
|
515
363
|
}, [activeRows, setRows, dataSource]);
|
|
516
364
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}, [activeColumns, setColumns]);
|
|
520
|
-
|
|
521
|
-
useEffect(() => {
|
|
522
|
-
if (pivotMode) {
|
|
523
|
-
setInternalColumnOrder(activeColumns.map(col => col.field));
|
|
524
|
-
}
|
|
525
|
-
}, [pivotMode, activeColumns]);
|
|
526
|
-
|
|
527
|
-
const orderedColumns = useMemo(() => {
|
|
528
|
-
if (disableColumnReorder) return effectiveColumns as GridColDef<R>[];
|
|
529
|
-
|
|
530
|
-
const orderMap = new Map(effectiveColumnOrder.map((field, index) => [field, index]));
|
|
531
|
-
return [...(effectiveColumns as GridColDef<R>[])].sort((a, b) => {
|
|
532
|
-
const aIndex = orderMap.get(a.field) ?? effectiveColumns.indexOf(a);
|
|
533
|
-
const bIndex = orderMap.get(b.field) ?? effectiveColumns.indexOf(b);
|
|
534
|
-
return aIndex - bIndex;
|
|
535
|
-
});
|
|
536
|
-
}, [effectiveColumns, effectiveColumnOrder, disableColumnReorder]);
|
|
537
|
-
|
|
538
|
-
const visibleOrderedColumns = useMemo(() => {
|
|
539
|
-
return orderedColumns.filter(col => columnVisibilityModel[col.field] !== false);
|
|
540
|
-
}, [orderedColumns, columnVisibilityModel]);
|
|
541
|
-
|
|
542
|
-
const columnReorderHandlers = useColumnReorder({
|
|
543
|
-
columns: orderedColumns,
|
|
544
|
-
onColumnOrderChange: useCallback((params: GridColumnOrderChangeParams) => {
|
|
545
|
-
const { oldIndex, targetIndex } = params;
|
|
546
|
-
|
|
547
|
-
const newOrder = [...effectiveColumnOrder];
|
|
548
|
-
const [movedField] = newOrder.splice(oldIndex, 1);
|
|
549
|
-
newOrder.splice(targetIndex, 0, movedField);
|
|
550
|
-
|
|
551
|
-
if (!columnOrder) {
|
|
552
|
-
setInternalColumnOrder(newOrder);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
onColumnOrderChange?.(params);
|
|
556
|
-
}, [effectiveColumnOrder, columnOrder, onColumnOrderChange]),
|
|
557
|
-
disableColumnReorder,
|
|
558
|
-
});
|
|
559
|
-
|
|
365
|
+
// ── Detail panel (hoisted — hasDetailPanel feeds into useGridColumns) ──────
|
|
366
|
+
const hasDetailPanel = Boolean(getDetailPanelContent);
|
|
560
367
|
const [internalExpandedRowIds, setInternalExpandedRowIds] = useState<Set<GridRowId>>(new Set());
|
|
561
368
|
const expandedRowIds = controlledExpandedRowIds ?? internalExpandedRowIds;
|
|
562
|
-
const hasDetailPanel = Boolean(getDetailPanelContent);
|
|
563
369
|
|
|
564
370
|
const handleDetailPanelToggle = useCallback((rowId: GridRowId) => {
|
|
565
371
|
const newExpandedRowIds = new Set(expandedRowIds);
|
|
@@ -568,45 +374,43 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
568
374
|
} else {
|
|
569
375
|
newExpandedRowIds.add(rowId);
|
|
570
376
|
}
|
|
571
|
-
|
|
572
377
|
if (controlledExpandedRowIds === undefined) {
|
|
573
378
|
setInternalExpandedRowIds(newExpandedRowIds);
|
|
574
379
|
}
|
|
575
380
|
onDetailPanelExpandedRowIdsChange?.(newExpandedRowIds);
|
|
576
381
|
}, [expandedRowIds, controlledExpandedRowIds, onDetailPanelExpandedRowIdsChange]);
|
|
577
382
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
};
|
|
383
|
+
// ── Column management ─────────────────────────────────────────────────────
|
|
384
|
+
const {
|
|
385
|
+
effectiveColumns,
|
|
386
|
+
orderedColumns,
|
|
387
|
+
visibleOrderedColumns,
|
|
388
|
+
navigationColumns,
|
|
389
|
+
columnWidths,
|
|
390
|
+
effectiveColumnOrder,
|
|
391
|
+
setInternalColumnOrder,
|
|
392
|
+
columnReorderHandlers,
|
|
393
|
+
handleColumnResize,
|
|
394
|
+
} = useGridColumns<R>({
|
|
395
|
+
activeColumns,
|
|
396
|
+
isHierarchyEnabled,
|
|
397
|
+
isRowGrouping,
|
|
398
|
+
isTreeData,
|
|
399
|
+
activeHierarchyHandlers,
|
|
400
|
+
columnVisibilityModel,
|
|
401
|
+
columnOrder,
|
|
402
|
+
onColumnOrderChange,
|
|
403
|
+
disableColumnReorder,
|
|
404
|
+
pivotMode,
|
|
405
|
+
checkboxSelection,
|
|
406
|
+
hasDetailPanel,
|
|
407
|
+
rowReordering,
|
|
408
|
+
initialState,
|
|
409
|
+
setColumns,
|
|
410
|
+
});
|
|
607
411
|
|
|
608
|
-
|
|
609
|
-
|
|
412
|
+
useGridStateSnapshot({
|
|
413
|
+
onStateChange,
|
|
610
414
|
sortModel,
|
|
611
415
|
filterModel,
|
|
612
416
|
effectivePaginationModel,
|
|
@@ -614,61 +418,34 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
614
418
|
effectiveColumnOrder,
|
|
615
419
|
columnVisibilityModel,
|
|
616
420
|
pinnedColumns,
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const baseRows = useMemo(() => {
|
|
620
|
-
return effectiveRows;
|
|
621
|
-
}, [effectiveRows]);
|
|
622
|
-
|
|
623
|
-
const filteredRows = useMemo(() => {
|
|
624
|
-
if (activeHierarchyHandlers) {
|
|
625
|
-
|
|
626
|
-
return (activeHierarchyHandlers.getVisibleRows() || []) as R[];
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (filterMode === 'server' && dataSource) {
|
|
630
|
-
return baseRows;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
return filterRows(baseRows, filterModel);
|
|
634
|
-
}, [baseRows, filterModel, activeHierarchyHandlers, filterMode, dataSource]);
|
|
635
|
-
|
|
636
|
-
const { top: pinnedTopRows, center: unpinnedRows, bottom: pinnedBottomRows } = useMemo(() => {
|
|
637
|
-
if (activeHierarchyHandlers) {
|
|
638
|
-
|
|
639
|
-
return { top: [], center: filteredRows, bottom: [] };
|
|
640
|
-
}
|
|
641
|
-
return getPinnedRowGroups(filteredRows, pinnedRows);
|
|
642
|
-
}, [filteredRows, pinnedRows, activeHierarchyHandlers]);
|
|
643
|
-
|
|
644
|
-
const sortedUnpinnedRows = useMemo(() => {
|
|
645
|
-
if (activeHierarchyHandlers) {
|
|
646
|
-
return unpinnedRows;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
if (sortingMode === 'server' && dataSource) {
|
|
650
|
-
return unpinnedRows;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
return sortRows(unpinnedRows, sortModel);
|
|
654
|
-
}, [unpinnedRows, sortModel, activeHierarchyHandlers, sortingMode, dataSource]);
|
|
655
|
-
|
|
656
|
-
const paginatedUnpinnedRows = useMemo(() => {
|
|
657
|
-
if (!pagination) return sortedUnpinnedRows;
|
|
658
|
-
|
|
659
|
-
if (paginationMode === 'server' && dataSource) {
|
|
660
|
-
return sortedUnpinnedRows;
|
|
661
|
-
}
|
|
421
|
+
});
|
|
662
422
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
423
|
+
const rowPipeline = useGridRowPipeline<GridRowModel>({
|
|
424
|
+
effectiveRows: effectiveRows as GridRowModel[],
|
|
425
|
+
activeHierarchyHandlers: activeHierarchyHandlers as { getVisibleRows: () => GridRowModel[] } | null,
|
|
426
|
+
filterMode,
|
|
427
|
+
filterModel,
|
|
428
|
+
dataSource: dataSource as GridDataSource<GridRowModel> | undefined,
|
|
429
|
+
sortModel,
|
|
430
|
+
sortingMode,
|
|
431
|
+
pagination,
|
|
432
|
+
paginationMode,
|
|
433
|
+
effectivePaginationModel,
|
|
434
|
+
pinnedRows,
|
|
435
|
+
isLoading: state.dataSource.loading,
|
|
436
|
+
pageSize: effectivePaginationModel.pageSize,
|
|
437
|
+
});
|
|
438
|
+
const filteredRows = rowPipeline.filteredRows as R[];
|
|
439
|
+
const pinnedTopRows = rowPipeline.pinnedTopRows as R[];
|
|
440
|
+
const pinnedBottomRows = rowPipeline.pinnedBottomRows as R[];
|
|
441
|
+
const sortedUnpinnedRows = rowPipeline.sortedUnpinnedRows as R[];
|
|
442
|
+
const paginatedUnpinnedRows = rowPipeline.paginatedUnpinnedRows as R[];
|
|
443
|
+
const allRenderableRows = rowPipeline.allRenderableRows as R[];
|
|
667
444
|
|
|
668
445
|
useEffect(() => {
|
|
669
|
-
gridData.apiRef.current.getVisibleRows = () => sortedUnpinnedRows;
|
|
670
|
-
gridData.apiRef.current.getVisibleColumns = () => effectiveColumns as
|
|
671
|
-
}, [sortedUnpinnedRows, effectiveColumns, gridData.apiRef]);
|
|
446
|
+
gridData.apiRef.current.getVisibleRows = () => pagination ? paginatedUnpinnedRows : sortedUnpinnedRows;
|
|
447
|
+
gridData.apiRef.current.getVisibleColumns = () => effectiveColumns as unknown as GridColDef[];
|
|
448
|
+
}, [sortedUnpinnedRows, paginatedUnpinnedRows, pagination, effectiveColumns, gridData.apiRef]);
|
|
672
449
|
|
|
673
450
|
const rowReorderHandlers = useRowReorder({
|
|
674
451
|
rows: pagination ? paginatedUnpinnedRows : sortedUnpinnedRows,
|
|
@@ -677,29 +454,6 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
677
454
|
rowReordering
|
|
678
455
|
});
|
|
679
456
|
|
|
680
|
-
const allRenderableRows = useMemo(() => {
|
|
681
|
-
|
|
682
|
-
if (activeHierarchyHandlers) {
|
|
683
|
-
return unpinnedRows;
|
|
684
|
-
}
|
|
685
|
-
const centerRows = pagination ? paginatedUnpinnedRows : sortedUnpinnedRows;
|
|
686
|
-
const base = [...pinnedTopRows, ...centerRows, ...pinnedBottomRows];
|
|
687
|
-
|
|
688
|
-
// Append skeleton rows while an infinite-scroll fetch is in flight
|
|
689
|
-
if (paginationMode === 'infinite' && state.dataSource.loading && base.length > 0) {
|
|
690
|
-
const skeletonCount = Math.min(effectivePaginationModel.pageSize, 20);
|
|
691
|
-
const skeletons = Array.from({ length: skeletonCount }, (_, i) => ({
|
|
692
|
-
id: `__skeleton_${i}__`,
|
|
693
|
-
_isSkeleton: true,
|
|
694
|
-
}));
|
|
695
|
-
return [...base, ...skeletons] as typeof base;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
return base;
|
|
699
|
-
}, [pinnedTopRows, paginatedUnpinnedRows, sortedUnpinnedRows, pinnedBottomRows, pagination,
|
|
700
|
-
activeHierarchyHandlers, unpinnedRows, paginationMode, state.dataSource.loading,
|
|
701
|
-
effectivePaginationModel.pageSize]);
|
|
702
|
-
|
|
703
457
|
const spanning = useGridSpanning(
|
|
704
458
|
allRenderableRows,
|
|
705
459
|
navigationColumns,
|
|
@@ -710,7 +464,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
710
464
|
rows: filteredRows,
|
|
711
465
|
aggregationModel,
|
|
712
466
|
isServerSide: !!(dataSource && (paginationMode === 'server' || sortingMode === 'server')),
|
|
713
|
-
dataSource
|
|
467
|
+
dataSource,
|
|
714
468
|
filterModel,
|
|
715
469
|
sortModel,
|
|
716
470
|
serverAggregationResults,
|
|
@@ -783,148 +537,20 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
783
537
|
};
|
|
784
538
|
}, [layout, viewportRef, gridData.apiRef]);
|
|
785
539
|
|
|
786
|
-
const virtualization =
|
|
787
|
-
const {
|
|
788
|
-
unpinnedAccWidths,
|
|
789
|
-
unpinnedColsWithWidth,
|
|
790
|
-
leftPinnedCols,
|
|
791
|
-
rightPinnedCols,
|
|
792
|
-
totalWidth,
|
|
793
|
-
leftWidth,
|
|
794
|
-
rightWidth,
|
|
795
|
-
unpinnedTotalWidth,
|
|
796
|
-
systemColumnsWidth,
|
|
797
|
-
cumulativeHeights,
|
|
798
|
-
pinnedTopHeight,
|
|
799
|
-
pinnedBottomHeight,
|
|
800
|
-
unpinnedRowsHeight,
|
|
801
|
-
rowHeights,
|
|
802
|
-
unpinnedRowsLength
|
|
803
|
-
} = layout;
|
|
804
|
-
|
|
805
|
-
const scrollLeft = state.virtualization.scrollLeft;
|
|
806
|
-
const scrollTop = state.virtualization.scrollTop;
|
|
807
|
-
const viewportWidth = state.dimensions.viewportWidth || 1000;
|
|
808
|
-
const viewportHeight = autoHeight ? (pinnedTopHeight + unpinnedRowsHeight + pinnedBottomHeight + 50) : (state.dimensions.viewportHeight || 600);
|
|
809
|
-
|
|
810
|
-
const overscanCols = 6;
|
|
811
|
-
|
|
812
|
-
const visibleLocalStart = Math.max(0, scrollLeft - leftWidth);
|
|
813
|
-
const visibleLocalEnd = scrollLeft + viewportWidth - leftWidth;
|
|
814
|
-
|
|
815
|
-
let firstUnpinnedIndex = 0;
|
|
816
|
-
let lastUnpinnedIndex = unpinnedColsWithWidth.length - 1;
|
|
817
|
-
|
|
818
|
-
let low = 0, high = unpinnedAccWidths.length - 1;
|
|
819
|
-
while (low <= high) {
|
|
820
|
-
const mid = (low + high) >>> 1;
|
|
821
|
-
if (unpinnedAccWidths[mid] < visibleLocalStart) {
|
|
822
|
-
low = mid + 1;
|
|
823
|
-
} else {
|
|
824
|
-
high = mid - 1;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
firstUnpinnedIndex = Math.max(0, low - overscanCols);
|
|
828
|
-
|
|
829
|
-
for (let i = firstUnpinnedIndex; i < unpinnedAccWidths.length; i++) {
|
|
830
|
-
if ((i === 0 ? 0 : unpinnedAccWidths[i - 1]) > visibleLocalEnd) {
|
|
831
|
-
lastUnpinnedIndex = Math.min(unpinnedColsWithWidth.length - 1, i + overscanCols);
|
|
832
|
-
break;
|
|
833
|
-
}
|
|
834
|
-
lastUnpinnedIndex = Math.min(unpinnedColsWithWidth.length - 1, i + overscanCols);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
const leftSpacerWidth = firstUnpinnedIndex > 0 ? unpinnedAccWidths[firstUnpinnedIndex - 1] : 0;
|
|
838
|
-
const rightSpacerWidth = unpinnedTotalWidth - unpinnedAccWidths[lastUnpinnedIndex];
|
|
839
|
-
|
|
840
|
-
const virtualColumns: any[] = [
|
|
841
|
-
...leftPinnedCols,
|
|
842
|
-
...(leftSpacerWidth > 0 ? [{ field: '__spacer_left__', width: leftSpacerWidth, isSpacer: true }] : []),
|
|
843
|
-
...unpinnedColsWithWidth.slice(firstUnpinnedIndex, lastUnpinnedIndex + 1),
|
|
844
|
-
...(rightSpacerWidth > 0 ? [{ field: '__spacer_right__', width: rightSpacerWidth, isSpacer: true }] : []),
|
|
845
|
-
...rightPinnedCols
|
|
846
|
-
];
|
|
847
|
-
|
|
848
|
-
const hasHorizontalScroll = viewportWidth > 0 && totalWidth > viewportWidth;
|
|
849
|
-
const scrollbarBuffer = hasHorizontalScroll ? 16 : 0;
|
|
850
|
-
const totalHeight = pinnedTopHeight + unpinnedRowsHeight + pinnedBottomHeight + scrollbarBuffer + 2;
|
|
851
|
-
|
|
852
|
-
const overscanRows = 5;
|
|
853
|
-
let firstRowIndex = 0;
|
|
854
|
-
let lastRowIndex = unpinnedRowsLength - 1;
|
|
855
|
-
let offsetTop = 0;
|
|
856
|
-
|
|
857
|
-
let rLow = 0, rHigh = cumulativeHeights.length - 1;
|
|
858
|
-
while (rLow <= rHigh) {
|
|
859
|
-
const mid = (rLow + rHigh) >>> 1;
|
|
860
|
-
if (cumulativeHeights[mid] < scrollTop) {
|
|
861
|
-
rLow = mid + 1;
|
|
862
|
-
} else {
|
|
863
|
-
rHigh = mid - 1;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
firstRowIndex = Math.max(0, rLow - overscanRows);
|
|
867
|
-
offsetTop = firstRowIndex > 0 ? cumulativeHeights[firstRowIndex - 1] : 0;
|
|
868
|
-
|
|
869
|
-
for (let i = firstRowIndex; i < cumulativeHeights.length; i++) {
|
|
870
|
-
if (cumulativeHeights[i] >= scrollTop + viewportHeight) {
|
|
871
|
-
lastRowIndex = Math.min(unpinnedRowsLength - 1, i + overscanRows);
|
|
872
|
-
break;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
return {
|
|
877
|
-
renderContext: {
|
|
878
|
-
firstRowIndex,
|
|
879
|
-
lastRowIndex,
|
|
880
|
-
firstColumnIndex: firstUnpinnedIndex,
|
|
881
|
-
lastColumnIndex: lastUnpinnedIndex
|
|
882
|
-
},
|
|
883
|
-
offsetTop,
|
|
884
|
-
offsetLeft: 0,
|
|
885
|
-
totalHeight,
|
|
886
|
-
pinnedTopHeight,
|
|
887
|
-
pinnedBottomHeight,
|
|
888
|
-
totalWidth,
|
|
889
|
-
rowHeights,
|
|
890
|
-
cumulativeHeights,
|
|
891
|
-
virtualColumns,
|
|
892
|
-
columnMetrics: {
|
|
893
|
-
leftPinnedWidth: leftWidth,
|
|
894
|
-
rightPinnedWidth: rightWidth,
|
|
895
|
-
unpinnedAccWidths,
|
|
896
|
-
unpinnedCols: unpinnedColsWithWidth,
|
|
897
|
-
totalSpecialsWidth: systemColumnsWidth,
|
|
898
|
-
pinnedSpecialsWidth: (rowReordering ? 48 : 0) + (hasDetailPanel && pinExpandColumn ? 48 : 0) + (checkboxSelection && pinCheckboxColumn ? 48 : 0)
|
|
899
|
-
}
|
|
900
|
-
};
|
|
901
|
-
}, [
|
|
540
|
+
const virtualization = useGridVirtualization({
|
|
902
541
|
layout,
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
state.dimensions.
|
|
906
|
-
state.dimensions.
|
|
907
|
-
autoHeight
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
if (onRowsScrollEnd) {
|
|
915
|
-
const { scrollTop, scrollHeight, clientHeight } = target;
|
|
916
|
-
const scrollThreshold = 100;
|
|
917
|
-
|
|
918
|
-
if (scrollHeight - scrollTop - clientHeight < scrollThreshold) {
|
|
542
|
+
scrollTop,
|
|
543
|
+
scrollLeft,
|
|
544
|
+
viewportWidth: state.dimensions.viewportWidth,
|
|
545
|
+
viewportHeight: state.dimensions.viewportHeight,
|
|
546
|
+
autoHeight,
|
|
547
|
+
rowReordering,
|
|
548
|
+
hasDetailPanel,
|
|
549
|
+
checkboxSelection,
|
|
550
|
+
pinCheckboxColumn,
|
|
551
|
+
pinExpandColumn,
|
|
552
|
+
});
|
|
919
553
|
|
|
920
|
-
onRowsScrollEnd({
|
|
921
|
-
visibleTop: scrollTop,
|
|
922
|
-
visibleBottom: scrollTop + clientHeight,
|
|
923
|
-
viewportHeight: clientHeight
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}, [setScroll, onRowsScrollEnd]);
|
|
928
554
|
|
|
929
555
|
useEffect(() => {
|
|
930
556
|
if (!viewportRef.current) return;
|
|
@@ -956,28 +582,28 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
956
582
|
setInternalRowSelectionModel(newSelectionArray);
|
|
957
583
|
}
|
|
958
584
|
onRowSelectionModelChange?.(newSelectionArray);
|
|
959
|
-
}, [selectedRowIds, isSelectionControlled, onRowSelectionModelChange]);
|
|
585
|
+
}, [selectedRowIds, isSelectionControlled, onRowSelectionModelChange, setInternalRowSelectionModel]);
|
|
960
586
|
|
|
961
587
|
const handleSelectAll = useCallback((isSelected: boolean) => {
|
|
962
588
|
let newSelection: GridRowId[] = [];
|
|
963
589
|
if (isSelected) {
|
|
964
|
-
newSelection =
|
|
590
|
+
newSelection = effectiveRows.map((row: GridRowModel) => row.id);
|
|
965
591
|
}
|
|
966
592
|
|
|
967
593
|
if (!isSelectionControlled) {
|
|
968
594
|
setInternalRowSelectionModel(newSelection);
|
|
969
595
|
}
|
|
970
596
|
onRowSelectionModelChange?.(newSelection);
|
|
971
|
-
}, [
|
|
597
|
+
}, [effectiveRows, isSelectionControlled, onRowSelectionModelChange, setInternalRowSelectionModel]);
|
|
972
598
|
|
|
973
599
|
const handleSort = useCallback((field: string, direction: GridSortDirection) => {
|
|
974
600
|
const newSortModel = direction ? [{ field, sort: direction }] : [];
|
|
975
601
|
|
|
976
602
|
if (!isSortControlled) {
|
|
977
|
-
setInternalSortModel(newSortModel as
|
|
603
|
+
setInternalSortModel(newSortModel as GridSortItem[]);
|
|
978
604
|
}
|
|
979
|
-
onSortModelChange?.(newSortModel as
|
|
980
|
-
}, [isSortControlled, onSortModelChange]);
|
|
605
|
+
onSortModelChange?.(newSortModel as GridSortItem[]);
|
|
606
|
+
}, [isSortControlled, onSortModelChange, setInternalSortModel]);
|
|
981
607
|
|
|
982
608
|
const { focusedCell, setFocusedCell, handleFocus, handleBlur, handleKeyDown } = useGridKeyboardNavigation({
|
|
983
609
|
allRenderableRows,
|
|
@@ -1018,44 +644,14 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
1018
644
|
prevEditingCellRef.current = editingHandlers.editingCell;
|
|
1019
645
|
}, [editingHandlers.editingCell]);
|
|
1020
646
|
|
|
1021
|
-
const visibleRows =
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
rowIndex: topPinnedCount + (pagination ? paginatedUnpinnedRows.length : sortedUnpinnedRows.length) + index
|
|
1030
|
-
}));
|
|
1031
|
-
|
|
1032
|
-
const centerStartIndex = Math.max(0, firstRowIndex - topPinnedCount);
|
|
1033
|
-
const centerEndIndex = Math.min(
|
|
1034
|
-
pagination ? paginatedUnpinnedRows.length : sortedUnpinnedRows.length,
|
|
1035
|
-
lastRowIndex - topPinnedCount + 1
|
|
1036
|
-
);
|
|
1037
|
-
|
|
1038
|
-
const centerRows = (pagination ? paginatedUnpinnedRows : sortedUnpinnedRows)
|
|
1039
|
-
.slice(centerStartIndex, centerEndIndex)
|
|
1040
|
-
.map((row, index) => ({
|
|
1041
|
-
row,
|
|
1042
|
-
rowIndex: topPinnedCount + centerStartIndex + index
|
|
1043
|
-
}));
|
|
1044
|
-
|
|
1045
|
-
const combined = [...topPinned, ...centerRows, ...bottomPinned];
|
|
1046
|
-
|
|
1047
|
-
const seenIds = new Set<GridRowId>();
|
|
1048
|
-
const deduplicated = combined.filter(item => {
|
|
1049
|
-
if (seenIds.has(item.row.id)) {
|
|
1050
|
-
return false;
|
|
1051
|
-
}
|
|
1052
|
-
seenIds.add(item.row.id);
|
|
1053
|
-
return true;
|
|
1054
|
-
});
|
|
1055
|
-
|
|
1056
|
-
return deduplicated;
|
|
1057
|
-
}, [virtualization.renderContext, pinnedTopRows, pinnedBottomRows, paginatedUnpinnedRows, sortedUnpinnedRows, pagination,
|
|
1058
|
-
paginationMode, state.dataSource.loading, effectivePaginationModel.pageSize, rowHeight]);
|
|
647
|
+
const visibleRows = useGridVisibleRows<R>({
|
|
648
|
+
renderContext: virtualization.renderContext,
|
|
649
|
+
pinnedTopRows,
|
|
650
|
+
pinnedBottomRows,
|
|
651
|
+
paginatedUnpinnedRows,
|
|
652
|
+
sortedUnpinnedRows,
|
|
653
|
+
pagination,
|
|
654
|
+
});
|
|
1059
655
|
|
|
1060
656
|
|
|
1061
657
|
const allSelected = rows.length > 0 && selectedRowIds.size === rows.length;
|
|
@@ -1065,6 +661,51 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
1065
661
|
const [columnsPanelOpen, setColumnsPanelOpen] = React.useState(false);
|
|
1066
662
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
1067
663
|
|
|
664
|
+
const toolbarProps = React.useMemo(() => {
|
|
665
|
+
if (!slots?.toolbar) return null;
|
|
666
|
+
const reorderHandler = disableColumnReorder
|
|
667
|
+
? undefined
|
|
668
|
+
: (fromField: string, toField: string) => {
|
|
669
|
+
const currentOrder = [...effectiveColumnOrder];
|
|
670
|
+
const fromIdx = currentOrder.indexOf(fromField);
|
|
671
|
+
const toIdx = currentOrder.indexOf(toField);
|
|
672
|
+
if (fromIdx === -1 || toIdx === -1) return;
|
|
673
|
+
const newOrder = [...currentOrder];
|
|
674
|
+
newOrder.splice(fromIdx, 1);
|
|
675
|
+
newOrder.splice(toIdx, 0, fromField);
|
|
676
|
+
if (!columnOrder) setInternalColumnOrder(newOrder);
|
|
677
|
+
const col = effectiveColumns.find(c => c.field === fromField);
|
|
678
|
+
if (col) onColumnOrderChange?.({ oldIndex: fromIdx, targetIndex: toIdx, column: col as unknown as GridColDef });
|
|
679
|
+
};
|
|
680
|
+
return {
|
|
681
|
+
apiRef: gridData.apiRef,
|
|
682
|
+
columns: orderedColumns as unknown as GridColDef[],
|
|
683
|
+
baseColumns: columns as unknown as GridColDef[],
|
|
684
|
+
aggregationModel,
|
|
685
|
+
onAggregationModelChange: handleAggregationModelChange,
|
|
686
|
+
...(pivotMode || propPivotModel || onPivotModelChange ? {
|
|
687
|
+
pivotModel: currentPivotModel,
|
|
688
|
+
onPivotModelChange: handlePivotModelChange,
|
|
689
|
+
} : {}),
|
|
690
|
+
filterModel,
|
|
691
|
+
onFilterModelChange,
|
|
692
|
+
columnVisibilityModel,
|
|
693
|
+
onColumnVisibilityModelChange: handleColumnVisibilityModelChange,
|
|
694
|
+
onColumnReorder: reorderHandler,
|
|
695
|
+
onColumnOrderReset: disableColumnReorder ? undefined : () => setInternalColumnOrder(columns.map(c => c.field)),
|
|
696
|
+
forceColumnsOpen: columnsPanelOpen,
|
|
697
|
+
onColumnsPanelClose: () => setColumnsPanelOpen(false),
|
|
698
|
+
...slotProps?.toolbar,
|
|
699
|
+
};
|
|
700
|
+
}, [
|
|
701
|
+
slots?.toolbar, disableColumnReorder, effectiveColumnOrder, orderedColumns, effectiveColumns,
|
|
702
|
+
columnOrder, onColumnOrderChange, setInternalColumnOrder, gridData.apiRef,
|
|
703
|
+
columns, aggregationModel, handleAggregationModelChange, pivotMode,
|
|
704
|
+
propPivotModel, onPivotModelChange, currentPivotModel, handlePivotModelChange,
|
|
705
|
+
filterModel, onFilterModelChange, columnVisibilityModel,
|
|
706
|
+
handleColumnVisibilityModelChange, columnsPanelOpen, slotProps?.toolbar,
|
|
707
|
+
]);
|
|
708
|
+
|
|
1068
709
|
// Click-outside handler for standalone column panel
|
|
1069
710
|
useEffect(() => {
|
|
1070
711
|
if (!columnsPanelOpen || slots?.toolbar) return;
|
|
@@ -1087,188 +728,49 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
1087
728
|
height: height ?? style?.height,
|
|
1088
729
|
'--ogx-row-height': `${rowHeight}px`,
|
|
1089
730
|
'--ogx-header-height': `${headerHeight}px`
|
|
1090
|
-
} as
|
|
731
|
+
} as unknown as React.CSSProperties}
|
|
1091
732
|
aria-busy={effectiveLoading}
|
|
1092
733
|
>
|
|
1093
|
-
{ }
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
columnVisibilityModel,
|
|
1110
|
-
onColumnVisibilityModelChange: handleColumnVisibilityModelChange,
|
|
1111
|
-
|
|
1112
|
-
onColumnReorder: disableColumnReorder ? undefined : (fromField: string, toField: string) => {
|
|
1113
|
-
const currentOrder = [...effectiveColumnOrder];
|
|
1114
|
-
const fromIdx = currentOrder.indexOf(fromField);
|
|
1115
|
-
const toIdx = currentOrder.indexOf(toField);
|
|
1116
|
-
if (fromIdx === -1 || toIdx === -1) return;
|
|
1117
|
-
const newOrder = [...currentOrder];
|
|
1118
|
-
newOrder.splice(fromIdx, 1);
|
|
1119
|
-
newOrder.splice(toIdx, 0, fromField);
|
|
1120
|
-
if (!columnOrder) setInternalColumnOrder(newOrder);
|
|
1121
|
-
const col = effectiveColumns.find(c => c.field === fromField); if (col) onColumnOrderChange?.({ oldIndex: fromIdx, targetIndex: toIdx, column: col as any });
|
|
1122
|
-
},
|
|
1123
|
-
|
|
1124
|
-
forceColumnsOpen: columnsPanelOpen,
|
|
1125
|
-
onColumnsPanelClose: () => setColumnsPanelOpen(false),
|
|
1126
|
-
|
|
1127
|
-
...slotProps?.toolbar,
|
|
1128
|
-
};
|
|
1129
|
-
return <StableToolbar {...toolbarProps} />;
|
|
1130
|
-
})()}
|
|
1131
|
-
|
|
1132
|
-
{/* Fallback standalone column panel when no toolbar is present */}
|
|
1133
|
-
{!slots?.toolbar && columnsPanelOpen && ReactDOM.createPortal(
|
|
1134
|
-
<div
|
|
1135
|
-
id="ogx-standalone-col-panel"
|
|
1136
|
-
style={(() => {
|
|
1137
|
-
const rect = containerRef.current?.getBoundingClientRect();
|
|
1138
|
-
return {
|
|
1139
|
-
position: 'fixed',
|
|
1140
|
-
top: rect ? rect.top + 8 : 16,
|
|
1141
|
-
right: rect ? window.innerWidth - rect.right + 8 : 16,
|
|
1142
|
-
zIndex: 9999,
|
|
1143
|
-
display: 'inline-block',
|
|
1144
|
-
};
|
|
1145
|
-
})()}
|
|
1146
|
-
>
|
|
1147
|
-
<button
|
|
1148
|
-
onClick={() => setColumnsPanelOpen(false)}
|
|
1149
|
-
style={{
|
|
1150
|
-
position: 'absolute',
|
|
1151
|
-
top: -12,
|
|
1152
|
-
right: -12,
|
|
1153
|
-
width: 24,
|
|
1154
|
-
height: 24,
|
|
1155
|
-
display: 'flex',
|
|
1156
|
-
alignItems: 'center',
|
|
1157
|
-
justifyContent: 'center',
|
|
1158
|
-
background: '#334155',
|
|
1159
|
-
color: '#fff',
|
|
1160
|
-
border: 'none',
|
|
1161
|
-
borderRadius: '50%',
|
|
1162
|
-
cursor: 'pointer',
|
|
1163
|
-
fontSize: 14,
|
|
1164
|
-
lineHeight: 1,
|
|
1165
|
-
zIndex: 1,
|
|
1166
|
-
boxShadow: '0 1px 4px rgba(0,0,0,0.2)',
|
|
1167
|
-
}}
|
|
1168
|
-
aria-label="Close"
|
|
1169
|
-
>×</button>
|
|
1170
|
-
<ColumnVisibilityPanel
|
|
1171
|
-
columns={effectiveColumns}
|
|
1172
|
-
visibleColumns={new Set(
|
|
1173
|
-
effectiveColumns
|
|
1174
|
-
.filter(col => columnVisibilityModel[col.field] !== false)
|
|
1175
|
-
.map(col => col.field)
|
|
1176
|
-
)}
|
|
1177
|
-
onVisibilityChange={(field, isVisible) => {
|
|
1178
|
-
handleColumnVisibilityModelChange({ ...columnVisibilityModel, [field]: isVisible });
|
|
1179
|
-
}}
|
|
1180
|
-
onShowAll={() => {
|
|
1181
|
-
const next = { ...columnVisibilityModel };
|
|
1182
|
-
effectiveColumns.forEach(col => { if (col.hideable !== false) next[col.field] = true; });
|
|
1183
|
-
handleColumnVisibilityModelChange(next);
|
|
1184
|
-
}}
|
|
1185
|
-
onHideAll={() => {
|
|
1186
|
-
const next = { ...columnVisibilityModel };
|
|
1187
|
-
effectiveColumns.forEach(col => { if (col.hideable !== false) next[col.field] = false; });
|
|
1188
|
-
handleColumnVisibilityModelChange(next);
|
|
1189
|
-
}}
|
|
1190
|
-
onColumnReorder={disableColumnReorder ? undefined : (fromField, toField) => {
|
|
1191
|
-
const currentOrder = [...effectiveColumnOrder];
|
|
1192
|
-
const fromIdx = currentOrder.indexOf(fromField);
|
|
1193
|
-
const toIdx = currentOrder.indexOf(toField);
|
|
1194
|
-
if (fromIdx === -1 || toIdx === -1) return;
|
|
1195
|
-
const newOrder = [...currentOrder];
|
|
1196
|
-
newOrder.splice(fromIdx, 1);
|
|
1197
|
-
newOrder.splice(toIdx, 0, fromField);
|
|
1198
|
-
if (!columnOrder) {
|
|
1199
|
-
setInternalColumnOrder(newOrder);
|
|
1200
|
-
}
|
|
1201
|
-
onColumnOrderChange?.({ oldIndex: fromIdx, targetIndex: toIdx, column: effectiveColumns.find(c => c.field === fromField) as any });
|
|
1202
|
-
}}
|
|
1203
|
-
/>
|
|
1204
|
-
</div>,
|
|
1205
|
-
containerRef.current?.closest('.ogx-theme-provider') || document.body
|
|
734
|
+
{toolbarProps && <StableToolbar {...toolbarProps} />}
|
|
735
|
+
|
|
736
|
+
{!slots?.toolbar && (
|
|
737
|
+
<GridStandaloneColumnPanel<R>
|
|
738
|
+
isOpen={columnsPanelOpen}
|
|
739
|
+
containerRef={containerRef}
|
|
740
|
+
effectiveColumns={effectiveColumns}
|
|
741
|
+
columnVisibilityModel={columnVisibilityModel}
|
|
742
|
+
effectiveColumnOrder={effectiveColumnOrder}
|
|
743
|
+
columnOrder={columnOrder}
|
|
744
|
+
disableColumnReorder={disableColumnReorder}
|
|
745
|
+
onClose={() => setColumnsPanelOpen(false)}
|
|
746
|
+
onColumnVisibilityChange={handleColumnVisibilityModelChange}
|
|
747
|
+
onColumnOrderChange={onColumnOrderChange}
|
|
748
|
+
setInternalColumnOrder={setInternalColumnOrder}
|
|
749
|
+
/>
|
|
1206
750
|
)}
|
|
1207
751
|
|
|
1208
|
-
{/* ═══════════════════════════════════════════════════════════════
|
|
1209
|
-
LIST VIEW — single-column card layout
|
|
1210
|
-
═══════════════════════════════════════════════════════════════ */}
|
|
1211
752
|
{listView && listViewColumn && (
|
|
1212
|
-
<
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
{
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
{
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
<path d="M3 9h18M9 21V9" />
|
|
1234
|
-
</svg>
|
|
1235
|
-
{noRowsLabel}
|
|
1236
|
-
</div>
|
|
1237
|
-
) : (
|
|
1238
|
-
allRenderableRows.map((row, idx) => (
|
|
1239
|
-
<ListViewRow<R>
|
|
1240
|
-
key={row.id}
|
|
1241
|
-
row={row as R}
|
|
1242
|
-
rowIndex={idx}
|
|
1243
|
-
listViewColumn={listViewColumn as any}
|
|
1244
|
-
isSelected={selectedRowIds.has(row.id)}
|
|
1245
|
-
checkboxSelection={checkboxSelection}
|
|
1246
|
-
rowHeight={rowHeight}
|
|
1247
|
-
onRowClick={(r) => handleRowClick({ row: r, id: r.id, rowIndex: idx })}
|
|
1248
|
-
onSelectionChange={handleSelectionChange}
|
|
1249
|
-
/>
|
|
1250
|
-
))
|
|
1251
|
-
)}
|
|
1252
|
-
</div>
|
|
1253
|
-
|
|
1254
|
-
{/* Pagination in list view — use filteredRows.length as total since state.pagination.rowCount is only set for server-side */}
|
|
1255
|
-
{pagination && (() => {
|
|
1256
|
-
const PaginationComponent = slots?.pagination || Pagination;
|
|
1257
|
-
const totalRowCount = (paginationMode === 'server' && dataSource)
|
|
1258
|
-
? (state.pagination.rowCount || 0)
|
|
1259
|
-
: filteredRows.length;
|
|
1260
|
-
const paginationProps = {
|
|
1261
|
-
page: effectivePaginationModel.page,
|
|
1262
|
-
pageSize: effectivePaginationModel.pageSize,
|
|
1263
|
-
rowCount: totalRowCount,
|
|
1264
|
-
pageSizeOptions: pageSizeOptions,
|
|
1265
|
-
onPageChange: (newPage: number) => handlePaginationModelChange({ ...effectivePaginationModel, page: newPage }),
|
|
1266
|
-
onPageSizeChange: (newPageSize: number) => handlePaginationModelChange({ ...effectivePaginationModel, pageSize: newPageSize, page: 0 }),
|
|
1267
|
-
...slotProps?.pagination
|
|
1268
|
-
};
|
|
1269
|
-
return <PaginationComponent {...paginationProps} />;
|
|
1270
|
-
})()}
|
|
1271
|
-
</div>
|
|
753
|
+
<GridListView<R>
|
|
754
|
+
ariaLabel={ariaLabel}
|
|
755
|
+
allRenderableRows={allRenderableRows}
|
|
756
|
+
filteredRows={filteredRows}
|
|
757
|
+
pagination={pagination}
|
|
758
|
+
effectivePaginationModel={effectivePaginationModel}
|
|
759
|
+
pageSizeOptions={pageSizeOptions}
|
|
760
|
+
selectedRowIds={selectedRowIds}
|
|
761
|
+
listViewColumn={listViewColumn}
|
|
762
|
+
noRowsLabel={noRowsLabel}
|
|
763
|
+
rowHeight={rowHeight}
|
|
764
|
+
checkboxSelection={checkboxSelection}
|
|
765
|
+
paginationMode={paginationMode}
|
|
766
|
+
dataSource={dataSource}
|
|
767
|
+
serverRowCount={state.pagination.rowCount || 0}
|
|
768
|
+
paginationSlot={slots?.pagination}
|
|
769
|
+
paginationSlotProps={slotProps?.pagination}
|
|
770
|
+
onRowClick={handleRowClick}
|
|
771
|
+
onSelectionChange={handleSelectionChange}
|
|
772
|
+
onPaginationModelChange={handlePaginationModelChange}
|
|
773
|
+
/>
|
|
1272
774
|
)}
|
|
1273
775
|
|
|
1274
776
|
{/* ══════════════════════════════════════════════════════════════
|
|
@@ -1310,7 +812,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
1310
812
|
{ }
|
|
1311
813
|
<Header
|
|
1312
814
|
columns={virtualization.virtualColumns}
|
|
1313
|
-
allColumns={effectiveColumns
|
|
815
|
+
allColumns={effectiveColumns}
|
|
1314
816
|
columnGroupingModel={columnGroupingModel}
|
|
1315
817
|
checkboxSelection={checkboxSelection}
|
|
1316
818
|
allSelected={allSelected}
|
|
@@ -1365,299 +867,135 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
1365
867
|
|
|
1366
868
|
{/* Empty State Overlay (Standard View) — showing after header */}
|
|
1367
869
|
{!effectiveLoading && !state.dataSource.error && filteredRows.length === 0 && (
|
|
1368
|
-
<
|
|
1369
|
-
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
1370
|
-
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
1371
|
-
<path d="M3 9h18M9 21V9" />
|
|
1372
|
-
</svg>
|
|
1373
|
-
<span>{noRowsLabel}</span>
|
|
1374
|
-
</div>
|
|
870
|
+
<GridEmptyState noRowsLabel={noRowsLabel} width={virtualization.totalWidth} />
|
|
1375
871
|
)}
|
|
1376
872
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
pinExpandColumn={pinExpandColumn}
|
|
1402
|
-
|
|
1403
|
-
focusedCellField={focusedCell?.id === row.id ? focusedCell.field : null}
|
|
1404
|
-
|
|
1405
|
-
colspanMap={spanning.colspanMap}
|
|
1406
|
-
rowSpanningCaches={spanning.rowSpanningState.caches}
|
|
1407
|
-
rowHeight={rowHeight}
|
|
1408
|
-
/>
|
|
1409
|
-
))}
|
|
1410
|
-
</div>
|
|
1411
|
-
)}
|
|
873
|
+
<GridPinnedRows<R>
|
|
874
|
+
rows={pinnedTopRows}
|
|
875
|
+
position="top"
|
|
876
|
+
columns={virtualization.virtualColumns}
|
|
877
|
+
selectedRowIds={selectedRowIds}
|
|
878
|
+
checkboxSelection={checkboxSelection}
|
|
879
|
+
onRowClick={handleRowClick}
|
|
880
|
+
onCellClick={handleCellClick}
|
|
881
|
+
onSelectionChange={handleSelectionChange}
|
|
882
|
+
columnWidths={columnWidths}
|
|
883
|
+
pinnedColumns={pinnedColumns}
|
|
884
|
+
pinnedRows={pinnedRows}
|
|
885
|
+
hasDetailPanel={hasDetailPanel}
|
|
886
|
+
expandedRowIds={expandedRowIds}
|
|
887
|
+
getDetailPanelContent={getDetailPanelContent}
|
|
888
|
+
getDetailPanelHeight={getDetailPanelHeight}
|
|
889
|
+
onDetailPanelToggle={handleDetailPanelToggle}
|
|
890
|
+
pinCheckboxColumn={pinCheckboxColumn}
|
|
891
|
+
pinExpandColumn={pinExpandColumn}
|
|
892
|
+
focusedCell={focusedCell}
|
|
893
|
+
colspanMap={spanning.colspanMap}
|
|
894
|
+
rowSpanningCaches={spanning.rowSpanningState.caches}
|
|
895
|
+
rowHeight={rowHeight}
|
|
896
|
+
/>
|
|
1412
897
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
{
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
row={row}
|
|
1475
|
-
columns={virtualization.virtualColumns}
|
|
1476
|
-
rowIndex={actualIndex}
|
|
1477
|
-
isSelected={selectedRowIds.has(row.id)}
|
|
1478
|
-
checkboxSelection={checkboxSelection}
|
|
1479
|
-
onRowClick={handleRowClick}
|
|
1480
|
-
onCellClick={handleCellClick}
|
|
1481
|
-
onSelectionChange={handleSelectionChange}
|
|
1482
|
-
columnWidths={columnWidths}
|
|
1483
|
-
pinnedColumns={pinnedColumns}
|
|
1484
|
-
pinnedRows={pinnedRows}
|
|
1485
|
-
// Detail Panel
|
|
1486
|
-
hasDetailPanel={hasDetailPanel}
|
|
1487
|
-
isDetailPanelExpanded={expandedRowIds.has(row.id)}
|
|
1488
|
-
detailPanelContent={getDetailPanelContent ? getDetailPanelContent({ row, id: row.id, rowIndex: actualIndex }) : null}
|
|
1489
|
-
detailPanelHeight={getDetailPanelHeight?.({ row, id: row.id, rowIndex: actualIndex }) || 200}
|
|
1490
|
-
onDetailPanelToggle={handleDetailPanelToggle}
|
|
1491
|
-
pinCheckboxColumn={pinCheckboxColumn}
|
|
1492
|
-
pinExpandColumn={pinExpandColumn}
|
|
1493
|
-
// Row Reordering
|
|
1494
|
-
rowReordering={rowReordering}
|
|
1495
|
-
onDragStart={rowReorderHandlers.onDragStart}
|
|
1496
|
-
onDragOver={rowReorderHandlers.onDragOver}
|
|
1497
|
-
onDragEnd={rowReorderHandlers.onDragEnd}
|
|
1498
|
-
onDrop={rowReorderHandlers.onDrop}
|
|
1499
|
-
isDragging={rowReorderHandlers.draggedRowId === row.id}
|
|
1500
|
-
isDragOver={rowReorderHandlers.dragOverRowId === row.id}
|
|
1501
|
-
// Editing
|
|
1502
|
-
editingCell={editingHandlers.editingCell}
|
|
1503
|
-
onEditStart={editingHandlers.startCellEdit}
|
|
1504
|
-
onEditStop={editingHandlers.stopCellEdit}
|
|
1505
|
-
onEditCellValueChange={editingHandlers.setEditCellValue}
|
|
1506
|
-
// Focus
|
|
1507
|
-
focusedCellField={focusedCell != null && focusedCell.id === row.id ? focusedCell.field : null}
|
|
1508
|
-
// Spanning
|
|
1509
|
-
colspanMap={spanning.colspanMap}
|
|
1510
|
-
rowSpanningCaches={spanning.rowSpanningState.caches}
|
|
1511
|
-
rowHeight={rowHeight}
|
|
1512
|
-
/>
|
|
1513
|
-
);
|
|
1514
|
-
})
|
|
1515
|
-
)}
|
|
1516
|
-
|
|
1517
|
-
{/* Infinite Scroll Skeletons (Inline with flow, inside translateY container) */}
|
|
1518
|
-
{paginationMode === 'infinite' && state.dataSource.loading && sortedUnpinnedRows.length > 0 && (() => {
|
|
1519
|
-
const skeletonCount = Math.min(effectivePaginationModel.pageSize, 20);
|
|
1520
|
-
// The virtual container translates its children down by `firstRowIndex * rowHeight`.
|
|
1521
|
-
// Since these skeletons are at the VERY BOTTOM of all rows, their actual array index
|
|
1522
|
-
// is precisely after the last real row.
|
|
1523
|
-
const startIdx = layout.unpinnedRowsLength;
|
|
1524
|
-
|
|
1525
|
-
// However, they are rendered INSIDE the translateY block. The real rows handled this nicely
|
|
1526
|
-
// because they just layout one after another. Skeletons can do exactly the same, EXCEPT we might
|
|
1527
|
-
// need to offset them so they sit at the bottom.
|
|
1528
|
-
return (
|
|
1529
|
-
<div className="ogx__skeleton-group">
|
|
1530
|
-
{Array.from({ length: skeletonCount }).map((_, i) => (
|
|
1531
|
-
<Row
|
|
1532
|
-
key={`__skeleton_${i}__`}
|
|
1533
|
-
row={{ id: `__skeleton_${i}__`, _isSkeleton: true } as any}
|
|
1534
|
-
columns={virtualization.virtualColumns}
|
|
1535
|
-
rowIndex={startIdx + i}
|
|
1536
|
-
rowHeight={rowHeight}
|
|
1537
|
-
/>
|
|
1538
|
-
))}
|
|
1539
|
-
</div>
|
|
1540
|
-
);
|
|
1541
|
-
})()}
|
|
1542
|
-
</div>
|
|
1543
|
-
|
|
1544
|
-
</div>
|
|
1545
|
-
|
|
1546
|
-
{/* Pinned Bottom Rows */}
|
|
1547
|
-
{pinnedBottomRows.length > 0 && (
|
|
1548
|
-
<div className="ogx__pinned-rows ogx__pinned-rows--bottom" role="rowgroup">
|
|
1549
|
-
{pinnedBottomRows.map((row, index) => (
|
|
1550
|
-
<Row
|
|
1551
|
-
key={row.id}
|
|
1552
|
-
row={row}
|
|
1553
|
-
columns={virtualization.virtualColumns}
|
|
1554
|
-
rowIndex={index}
|
|
1555
|
-
isSelected={selectedRowIds.has(row.id)}
|
|
1556
|
-
checkboxSelection={checkboxSelection}
|
|
1557
|
-
onRowClick={handleRowClick}
|
|
1558
|
-
onCellClick={handleCellClick}
|
|
1559
|
-
onSelectionChange={handleSelectionChange}
|
|
1560
|
-
columnWidths={columnWidths}
|
|
1561
|
-
pinnedColumns={pinnedColumns}
|
|
1562
|
-
pinnedRows={pinnedRows}
|
|
1563
|
-
// Detail Panel
|
|
1564
|
-
hasDetailPanel={hasDetailPanel}
|
|
1565
|
-
isDetailPanelExpanded={expandedRowIds.has(row.id)}
|
|
1566
|
-
detailPanelContent={getDetailPanelContent ? getDetailPanelContent({ row, id: row.id, rowIndex: index }) : null}
|
|
1567
|
-
detailPanelHeight={getDetailPanelHeight?.({ row, id: row.id, rowIndex: index }) || 200}
|
|
1568
|
-
onDetailPanelToggle={handleDetailPanelToggle}
|
|
1569
|
-
pinCheckboxColumn={pinCheckboxColumn}
|
|
1570
|
-
pinExpandColumn={pinExpandColumn}
|
|
1571
|
-
// Focus
|
|
1572
|
-
focusedCellField={focusedCell?.id === row.id ? focusedCell.field : null}
|
|
1573
|
-
// Spanning
|
|
1574
|
-
colspanMap={spanning.colspanMap}
|
|
1575
|
-
rowSpanningCaches={spanning.rowSpanningState.caches}
|
|
1576
|
-
rowHeight={rowHeight}
|
|
1577
|
-
/>
|
|
1578
|
-
))}
|
|
1579
|
-
</div>
|
|
1580
|
-
)}
|
|
898
|
+
<GridVirtualRows<R>
|
|
899
|
+
virtualContainerHeight={virtualization.totalHeight - virtualization.pinnedTopHeight - virtualization.pinnedBottomHeight}
|
|
900
|
+
offsetTop={virtualization.offsetTop}
|
|
901
|
+
effectiveLoading={effectiveLoading}
|
|
902
|
+
visibleRows={visibleRows}
|
|
903
|
+
baseColumns={columns as unknown as GridColDef<R>[]}
|
|
904
|
+
virtualColumns={virtualization.virtualColumns}
|
|
905
|
+
viewportWidth={state.dimensions.viewportWidth}
|
|
906
|
+
checkboxSelection={checkboxSelection}
|
|
907
|
+
hasDetailPanel={hasDetailPanel}
|
|
908
|
+
rowReordering={rowReordering}
|
|
909
|
+
rowHeight={rowHeight}
|
|
910
|
+
pinnedRows={pinnedRows}
|
|
911
|
+
selectedRowIds={selectedRowIds}
|
|
912
|
+
onRowClick={handleRowClick}
|
|
913
|
+
onCellClick={handleCellClick}
|
|
914
|
+
onSelectionChange={handleSelectionChange}
|
|
915
|
+
columnWidths={columnWidths}
|
|
916
|
+
pinnedColumns={pinnedColumns}
|
|
917
|
+
expandedRowIds={expandedRowIds}
|
|
918
|
+
getDetailPanelContent={getDetailPanelContent}
|
|
919
|
+
getDetailPanelHeight={getDetailPanelHeight}
|
|
920
|
+
onDetailPanelToggle={handleDetailPanelToggle}
|
|
921
|
+
pinCheckboxColumn={pinCheckboxColumn}
|
|
922
|
+
pinExpandColumn={pinExpandColumn}
|
|
923
|
+
rowReorderHandlers={rowReorderHandlers}
|
|
924
|
+
editingHandlers={editingHandlers}
|
|
925
|
+
focusedCell={focusedCell}
|
|
926
|
+
colspanMap={spanning.colspanMap}
|
|
927
|
+
rowSpanningCaches={spanning.rowSpanningState.caches}
|
|
928
|
+
paginationMode={paginationMode}
|
|
929
|
+
dataSourceLoading={state.dataSource.loading}
|
|
930
|
+
sortedUnpinnedRowCount={sortedUnpinnedRows.length}
|
|
931
|
+
infiniteScrollSkeletonCount={Math.min(effectivePaginationModel.pageSize, 20)}
|
|
932
|
+
unpinnedRowsLength={layout.unpinnedRowsLength}
|
|
933
|
+
/>
|
|
934
|
+
|
|
935
|
+
<GridPinnedRows<R>
|
|
936
|
+
rows={pinnedBottomRows}
|
|
937
|
+
position="bottom"
|
|
938
|
+
columns={virtualization.virtualColumns}
|
|
939
|
+
selectedRowIds={selectedRowIds}
|
|
940
|
+
checkboxSelection={checkboxSelection}
|
|
941
|
+
onRowClick={handleRowClick}
|
|
942
|
+
onCellClick={handleCellClick}
|
|
943
|
+
onSelectionChange={handleSelectionChange}
|
|
944
|
+
columnWidths={columnWidths}
|
|
945
|
+
pinnedColumns={pinnedColumns}
|
|
946
|
+
pinnedRows={pinnedRows}
|
|
947
|
+
hasDetailPanel={hasDetailPanel}
|
|
948
|
+
expandedRowIds={expandedRowIds}
|
|
949
|
+
getDetailPanelContent={getDetailPanelContent}
|
|
950
|
+
getDetailPanelHeight={getDetailPanelHeight}
|
|
951
|
+
onDetailPanelToggle={handleDetailPanelToggle}
|
|
952
|
+
pinCheckboxColumn={pinCheckboxColumn}
|
|
953
|
+
pinExpandColumn={pinExpandColumn}
|
|
954
|
+
focusedCell={focusedCell}
|
|
955
|
+
colspanMap={spanning.colspanMap}
|
|
956
|
+
rowSpanningCaches={spanning.rowSpanningState.caches}
|
|
957
|
+
rowHeight={rowHeight}
|
|
958
|
+
/>
|
|
1581
959
|
|
|
1582
960
|
{/* Aggregation Footer Row — suppressed in pivot mode because pivot rows
|
|
1583
961
|
already contain pre-aggregated values with synthetic field keys
|
|
1584
962
|
(e.g. 'Q1\u001frevenue\u001fsum') that don't match aggregationModel keys. */}
|
|
1585
963
|
{hasAggregation && !pivotMode && (
|
|
1586
|
-
<
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
<div style={{ width: 48, flexShrink: 0 }} />
|
|
1597
|
-
)}
|
|
1598
|
-
{hasDetailPanel && (
|
|
1599
|
-
<div style={{ width: 48, flexShrink: 0 }} />
|
|
1600
|
-
)}
|
|
1601
|
-
{rowReordering && (
|
|
1602
|
-
<div style={{ width: 48, flexShrink: 0 }} />
|
|
1603
|
-
)}
|
|
1604
|
-
{orderedColumns.map((col) => {
|
|
1605
|
-
const fnName = (aggregationModel as Record<string, string>)[col.field];
|
|
1606
|
-
const rawValue = aggregationResult[col.field];
|
|
1607
|
-
const colWidth = columnWidths[col.field] ?? (typeof col.width === 'number' ? col.width : 120);
|
|
1608
|
-
|
|
1609
|
-
return (
|
|
1610
|
-
<div
|
|
1611
|
-
key={col.field}
|
|
1612
|
-
className="ogx__aggregation-cell"
|
|
1613
|
-
role="gridcell"
|
|
1614
|
-
style={{
|
|
1615
|
-
width: colWidth,
|
|
1616
|
-
minWidth: colWidth,
|
|
1617
|
-
maxWidth: colWidth,
|
|
1618
|
-
textAlign: (col.align as React.CSSProperties['textAlign']) || 'left',
|
|
1619
|
-
}}
|
|
1620
|
-
>
|
|
1621
|
-
{fnName ? (
|
|
1622
|
-
<>
|
|
1623
|
-
<span className="ogx__aggregation-label">
|
|
1624
|
-
{fnName}
|
|
1625
|
-
</span>
|
|
1626
|
-
<span className="ogx__aggregation-value">
|
|
1627
|
-
{formatAggregationValue(rawValue, fnName)}
|
|
1628
|
-
</span>
|
|
1629
|
-
</>
|
|
1630
|
-
) : null}
|
|
1631
|
-
</div>
|
|
1632
|
-
);
|
|
1633
|
-
})}
|
|
1634
|
-
</div>
|
|
964
|
+
<GridAggregationFooter
|
|
965
|
+
columns={orderedColumns as unknown as GridColDef[]}
|
|
966
|
+
aggregationModel={aggregationModel}
|
|
967
|
+
aggregationResult={aggregationResult}
|
|
968
|
+
columnWidths={columnWidths}
|
|
969
|
+
rowHeight={rowHeight}
|
|
970
|
+
checkboxSelection={checkboxSelection}
|
|
971
|
+
hasDetailPanel={hasDetailPanel}
|
|
972
|
+
rowReordering={rowReordering}
|
|
973
|
+
/>
|
|
1635
974
|
)}
|
|
1636
975
|
|
|
1637
976
|
</div>
|
|
1638
977
|
</div>
|
|
1639
978
|
)}
|
|
1640
979
|
|
|
1641
|
-
{/* Pagination — shared between grid and list view when not inside list view block */}
|
|
1642
980
|
{!listView && pagination && (() => {
|
|
1643
981
|
const PaginationComponent = slots?.pagination || Pagination;
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
982
|
+
return (
|
|
983
|
+
<PaginationComponent
|
|
984
|
+
page={effectivePaginationModel.page}
|
|
985
|
+
pageSize={effectivePaginationModel.pageSize}
|
|
986
|
+
rowCount={state.pagination.rowCount}
|
|
987
|
+
pageSizeOptions={pageSizeOptions}
|
|
988
|
+
onPageChange={(newPage: number) => handlePaginationModelChange({ ...effectivePaginationModel, page: newPage })}
|
|
989
|
+
onPageSizeChange={(newPageSize: number) => handlePaginationModelChange({ ...effectivePaginationModel, pageSize: newPageSize, page: 0 })}
|
|
990
|
+
{...slotProps?.pagination}
|
|
991
|
+
/>
|
|
992
|
+
);
|
|
1655
993
|
})()}
|
|
1656
994
|
|
|
1657
995
|
{/* Accessibility Live Region */}
|
|
1658
996
|
<div className="ogx-aria-live-status" role="status" aria-live="polite">
|
|
1659
997
|
{effectiveLoading ? 'Loading data...' : ''}
|
|
1660
|
-
{state.dataSource.error ? `Error: ${state.dataSource.error.message
|
|
998
|
+
{state.dataSource.error ? `Error: ${state.dataSource.error instanceof Error ? state.dataSource.error.message : 'Unknown error'}` : ''}
|
|
1661
999
|
{!loading && !state.dataSource.error && (
|
|
1662
1000
|
filteredRows.length === 0
|
|
1663
1001
|
? noRowsLabel
|
|
@@ -1667,27 +1005,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
1667
1005
|
)}
|
|
1668
1006
|
</div>
|
|
1669
1007
|
|
|
1670
|
-
{state.dataSource.error
|
|
1671
|
-
<div className="ogx-error-overlay" aria-live="assertive" role="alert">
|
|
1672
|
-
<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="var(--ogx-color-error)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
1673
|
-
<circle cx="12" cy="12" r="10" />
|
|
1674
|
-
<line x1="12" y1="8" x2="12" y2="12" />
|
|
1675
|
-
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
1676
|
-
</svg>
|
|
1677
|
-
<div className="ogx-error-overlay__title">
|
|
1678
|
-
Oops! Something went wrong
|
|
1679
|
-
</div>
|
|
1680
|
-
<div className="ogx-error-overlay__message">
|
|
1681
|
-
{state.dataSource.error.message || 'An unexpected error occurred while loading the data.'}
|
|
1682
|
-
</div>
|
|
1683
|
-
<button
|
|
1684
|
-
onClick={() => window.location.reload()}
|
|
1685
|
-
className="ogx-button ogx-button--primary"
|
|
1686
|
-
>
|
|
1687
|
-
Retry
|
|
1688
|
-
</button>
|
|
1689
|
-
</div>
|
|
1690
|
-
)}
|
|
1008
|
+
<GridErrorOverlay error={state.dataSource.error} />
|
|
1691
1009
|
</div>
|
|
1692
1010
|
);
|
|
1693
1011
|
}
|