@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,349 +1,453 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { GridRowId, GridColDef, GridRowModel, GridRenderCellParams, GridRowMeta } from '../../types';
|
|
3
|
+
import { isSyntheticRowId } from '../../utils/syntheticRows';
|
|
4
4
|
|
|
5
5
|
type ColumnKey = string;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Column-span information for one cell.
|
|
9
|
+
*
|
|
10
|
+
* Only span origins (colSpan > 1) and the cells they cover have an entry; every other cell has none
|
|
11
|
+
* and renders at its own resolved width. `leftVisibleCellIndex` / `rightVisibleCellIndex` are indices
|
|
12
|
+
* into the rendered data columns (left-pinned, then unpinned, then right-pinned).
|
|
13
|
+
*
|
|
14
|
+
* The hook stores only `colSpan` for an origin. `Row` adds the merged `width` and `flexGrow` from the
|
|
15
|
+
* columns it renders, so resizing a column never recomputes the spans.
|
|
16
|
+
*/
|
|
17
|
+
export type CellColSpanInfo =
|
|
18
|
+
| {
|
|
19
|
+
spannedByColSpan: true;
|
|
20
|
+
rightVisibleCellIndex: number;
|
|
21
|
+
leftVisibleCellIndex: number;
|
|
12
22
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
| {
|
|
24
|
+
spannedByColSpan: false;
|
|
25
|
+
cellProps: {
|
|
26
|
+
colSpan: number;
|
|
27
|
+
width?: number;
|
|
28
|
+
flexGrow?: number;
|
|
29
|
+
};
|
|
19
30
|
};
|
|
20
31
|
|
|
21
|
-
type ColspanMap = Map<GridRowId, Record<ColumnKey, CellColSpanInfo>>;
|
|
32
|
+
export type ColspanMap = Map<GridRowId, Record<ColumnKey, CellColSpanInfo>>;
|
|
22
33
|
|
|
23
34
|
export interface RowSpanningCaches {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
/** Row id → field → number of rows the origin cell spans (only entries > 1). */
|
|
36
|
+
spannedCells: Record<GridRowId, Record<ColumnKey, number>>;
|
|
37
|
+
/** Row id → field → `true` for cells covered by a row span that starts in a row above. */
|
|
38
|
+
hiddenCells: Record<GridRowId, Record<ColumnKey, boolean>>;
|
|
39
|
+
/** Row id → field → id of the row whose span covers the hidden cell. */
|
|
40
|
+
hiddenCellOriginMap: Record<GridRowId, Record<ColumnKey, GridRowId>>;
|
|
27
41
|
}
|
|
28
42
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
/** The top-left cell of a merged area. */
|
|
44
|
+
export interface GridSpanOrigin {
|
|
45
|
+
rowId: GridRowId;
|
|
46
|
+
field: string;
|
|
32
47
|
}
|
|
33
48
|
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const getCellColSpanInfo = useCallback((rowId: GridRowId, field: string): CellColSpanInfo | undefined => {
|
|
56
|
-
return colspanMap.get(rowId)?.[field];
|
|
57
|
-
}, [colspanMap]);
|
|
58
|
-
|
|
59
|
-
const calculateColSpan = useCallback((
|
|
60
|
-
rowId: GridRowId,
|
|
61
|
-
row: R,
|
|
62
|
-
rowIndex: number,
|
|
63
|
-
minFirstColumn: number,
|
|
64
|
-
maxLastColumn: number
|
|
65
|
-
) => {
|
|
66
|
-
const newMap = new Map(colspanMap);
|
|
67
|
-
|
|
68
|
-
for (let i = minFirstColumn; i < maxLastColumn; i++) {
|
|
69
|
-
const colDef = columns[i];
|
|
70
|
-
if (!colDef) continue;
|
|
71
|
-
|
|
72
|
-
let colSpan = 1;
|
|
73
|
-
if (colDef.colSpan) {
|
|
74
|
-
if (typeof colDef.colSpan === 'function') {
|
|
75
|
-
const params: GridRenderCellParams<R> = {
|
|
76
|
-
row,
|
|
77
|
-
value: row[colDef.field as keyof R],
|
|
78
|
-
field: colDef.field,
|
|
79
|
-
colDef,
|
|
80
|
-
rowIndex,
|
|
81
|
-
colIndex: i
|
|
82
|
-
};
|
|
83
|
-
colSpan = colDef.colSpan(params);
|
|
84
|
-
} else {
|
|
85
|
-
colSpan = colDef.colSpan;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (colSpan <= 1) {
|
|
90
|
-
setCellColSpanInfo(newMap, rowId, colDef.field, {
|
|
91
|
-
spannedByColSpan: false,
|
|
92
|
-
cellProps: { colSpan: 1, width: columnWidths[colDef.field] || 100 }
|
|
93
|
-
});
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
let width = columnWidths[colDef.field] || 100;
|
|
98
|
-
for (let j = 1; j < colSpan; j++) {
|
|
99
|
-
if (i + j < maxLastColumn) {
|
|
100
|
-
const nextCol = columns[i + j];
|
|
101
|
-
if (nextCol) {
|
|
102
|
-
width += columnWidths[nextCol.field] || 100;
|
|
103
|
-
|
|
104
|
-
setCellColSpanInfo(newMap, rowId, nextCol.field, {
|
|
105
|
-
spannedByColSpan: true,
|
|
106
|
-
leftVisibleCellIndex: i,
|
|
107
|
-
rightVisibleCellIndex: Math.min(i + colSpan - 1, columns.length - 1)
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
49
|
+
export interface UseGridSpanningParams<R extends GridRowModel> {
|
|
50
|
+
/** Row sections as they are rendered. A span never crosses from one section into another. */
|
|
51
|
+
pinnedTopRows: R[];
|
|
52
|
+
/** The scrolling rows that are rendered: the current page when paginated. */
|
|
53
|
+
centerRows: R[];
|
|
54
|
+
pinnedBottomRows: R[];
|
|
55
|
+
/** Visible data columns; used to look up the column definitions (colSpan, rowSpan, valueGetter). */
|
|
56
|
+
columns: GridColDef<R>[];
|
|
57
|
+
/**
|
|
58
|
+
* Rendered data columns per pinned section, in render order (the layout output). Only their
|
|
59
|
+
* fields and order matter here, so a width change does not recompute the spans.
|
|
60
|
+
*/
|
|
61
|
+
leftPinnedColumns: GridColDef<R>[];
|
|
62
|
+
unpinnedColumns: GridColDef<R>[];
|
|
63
|
+
rightPinnedColumns: GridColDef<R>[];
|
|
64
|
+
/** Rows whose detail panel is expanded. A row span ends at such a row, above its panel. */
|
|
65
|
+
expandedRowIds?: Set<GridRowId>;
|
|
66
|
+
rowMetaMap?: Map<GridRowId, GridRowMeta>;
|
|
67
|
+
/** Resolves a row's id (the grid's getRowId); span caches are keyed by it. Defaults to `row.id`. */
|
|
68
|
+
getRowId?: (row: R) => GridRowId;
|
|
69
|
+
}
|
|
112
70
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
71
|
+
const defaultGetRowId = <R extends GridRowModel>(row: R): GridRowId => row.id;
|
|
72
|
+
|
|
73
|
+
export interface GridSpanningResult {
|
|
74
|
+
hasColSpan: boolean;
|
|
75
|
+
hasRowSpan: boolean;
|
|
76
|
+
colspanMap: ColspanMap;
|
|
77
|
+
rowSpanningCaches: RowSpanningCaches;
|
|
78
|
+
/** For a cell covered by a colSpan or rowSpan, the origin cell of that span; otherwise `null`. */
|
|
79
|
+
getSpanOrigin: (rowId: GridRowId, field: string) => GridSpanOrigin | null;
|
|
80
|
+
/**
|
|
81
|
+
* Unpinned column index ranges merged by colSpan in a row, as flat pairs
|
|
82
|
+
* `[first0, last0, first1, last1, …]` in the unpinned-column index space.
|
|
83
|
+
*/
|
|
84
|
+
getUnpinnedColSpanRanges: (rowId: GridRowId) => readonly number[] | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* For a center (scrolling) row index, the index of the earliest row whose row span reaches it,
|
|
87
|
+
* or the index itself when no span from above reaches it.
|
|
88
|
+
*/
|
|
89
|
+
getCenterRowSpanStart: (centerRowIndex: number) => number;
|
|
90
|
+
}
|
|
117
91
|
|
|
118
|
-
|
|
119
|
-
|
|
92
|
+
interface SpanColumn<R extends GridRowModel> {
|
|
93
|
+
colDef: GridColDef<R>;
|
|
94
|
+
field: string;
|
|
95
|
+
/** Exclusive end of this column's pinned section, as an index into the rendered columns. */
|
|
96
|
+
sectionEnd: number;
|
|
97
|
+
hasColSpan: boolean;
|
|
98
|
+
hasRowSpan: boolean;
|
|
99
|
+
}
|
|
120
100
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const hiddenCells: Record<GridRowId, Record<ColumnKey, boolean>> = {};
|
|
130
|
-
const hiddenCellOriginMap: Record<number, Record<ColumnKey, number>> = {};
|
|
131
|
-
|
|
132
|
-
columns.forEach((colDef, columnIndex) => {
|
|
133
|
-
|
|
134
|
-
if (!colDef.rowSpan) return;
|
|
135
|
-
|
|
136
|
-
for (let index = firstRowIndex; index < lastRowIndex; index++) {
|
|
137
|
-
const row = rows[index];
|
|
138
|
-
if (!row) continue;
|
|
139
|
-
|
|
140
|
-
if (hiddenCells[row.id]?.[colDef.field]) continue;
|
|
141
|
-
|
|
142
|
-
const cellValue = colDef.valueGetter
|
|
143
|
-
? colDef.valueGetter({ row, field: colDef.field, value: row[colDef.field as keyof R] })
|
|
144
|
-
: row[colDef.field as keyof R];
|
|
145
|
-
|
|
146
|
-
let span = 1;
|
|
147
|
-
if (typeof colDef.rowSpan === 'function') {
|
|
148
|
-
const params: GridRenderCellParams<R> = {
|
|
149
|
-
row,
|
|
150
|
-
value: cellValue,
|
|
151
|
-
field: colDef.field,
|
|
152
|
-
colDef,
|
|
153
|
-
rowIndex: index,
|
|
154
|
-
colIndex: columnIndex
|
|
155
|
-
};
|
|
156
|
-
span = colDef.rowSpan(params);
|
|
157
|
-
} else if (typeof colDef.rowSpan === 'number') {
|
|
158
|
-
span = colDef.rowSpan;
|
|
159
|
-
}
|
|
101
|
+
interface SpanComputation {
|
|
102
|
+
colspanMap: ColspanMap;
|
|
103
|
+
rowSpanningCaches: RowSpanningCaches;
|
|
104
|
+
/** For hidden cells whose origin sits in another column (colSpan × rowSpan rectangles). */
|
|
105
|
+
hiddenOriginFields: Map<GridRowId, Record<ColumnKey, string>>;
|
|
106
|
+
unpinnedRanges: Map<GridRowId, number[]>;
|
|
107
|
+
centerRowSpanStart: Map<number, number>;
|
|
108
|
+
}
|
|
160
109
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
110
|
+
const EMPTY_COLSPAN_MAP: ColspanMap = new Map();
|
|
111
|
+
const EMPTY_ROW_SPANNING_CACHES: RowSpanningCaches = { spannedCells: {}, hiddenCells: {}, hiddenCellOriginMap: {} };
|
|
112
|
+
const identityRowStart = (centerRowIndex: number) => centerRowIndex;
|
|
113
|
+
const noOrigin = () => null;
|
|
114
|
+
const noRanges = () => undefined;
|
|
115
|
+
|
|
116
|
+
const EMPTY_RESULT: GridSpanningResult = {
|
|
117
|
+
hasColSpan: false,
|
|
118
|
+
hasRowSpan: false,
|
|
119
|
+
colspanMap: EMPTY_COLSPAN_MAP,
|
|
120
|
+
rowSpanningCaches: EMPTY_ROW_SPANNING_CACHES,
|
|
121
|
+
getSpanOrigin: noOrigin,
|
|
122
|
+
getUnpinnedColSpanRanges: noRanges,
|
|
123
|
+
getCenterRowSpanStart: identityRowStart,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const FIELD_SEPARATOR = '\u0000';
|
|
127
|
+
const SECTION_SEPARATOR = '\u0001';
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Normalises a span value: integers ≥ 1 pass through, fractions are floored, `Infinity` means
|
|
131
|
+
* "to the end of the section" (clamped later), and anything else (NaN, ≤ 0, non-numbers) is 1.
|
|
132
|
+
*/
|
|
133
|
+
export function normalizeSpan(value: unknown): number {
|
|
134
|
+
if (typeof value !== 'number' || Number.isNaN(value)) return 1;
|
|
135
|
+
if (value === Infinity) return Number.MAX_SAFE_INTEGER;
|
|
136
|
+
if (!Number.isFinite(value)) return 1;
|
|
137
|
+
const span = Math.floor(value);
|
|
138
|
+
return span < 1 ? 1 : span;
|
|
139
|
+
}
|
|
164
140
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const nextRow = rows[nextIndex];
|
|
169
|
-
if (!hiddenCells[nextRow.id]) hiddenCells[nextRow.id] = {};
|
|
170
|
-
hiddenCells[nextRow.id][colDef.field] = true;
|
|
141
|
+
function sectionKey<R extends GridRowModel>(columns: GridColDef<R>[]): string {
|
|
142
|
+
return columns.map(c => c.field).join(FIELD_SEPARATOR);
|
|
143
|
+
}
|
|
171
144
|
|
|
172
|
-
|
|
173
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Computes column and row spans for the rendered rows and columns.
|
|
147
|
+
*
|
|
148
|
+
* - Spans are computed over the rendered data columns only (hidden columns are skipped) and stop at a
|
|
149
|
+
* pinned-column section boundary; they are computed per row section (pinned top, center, pinned
|
|
150
|
+
* bottom) and never cross into another section.
|
|
151
|
+
* - An origin with colSpan c and rowSpan r covers the whole c × r rectangle. A cell covered by a
|
|
152
|
+
* colSpan is not evaluated as a rowSpan origin, a colSpan stops before a cell that a row span from
|
|
153
|
+
* above already covers, and a row span ends at a row whose detail panel is expanded.
|
|
154
|
+
* - Span values are normalised (see `normalizeSpan`) and clamped to what exists, so the work is
|
|
155
|
+
* O(rows × columns) at most, and zero when no column declares a span.
|
|
156
|
+
* - A span callback (or the valueGetter feeding it) that throws is treated as span 1, with a
|
|
157
|
+
* development warning, instead of unmounting the grid.
|
|
158
|
+
*
|
|
159
|
+
* The result is derived during render (memoised on rows and column structure), not set in an effect.
|
|
160
|
+
*/
|
|
161
|
+
export function useGridSpanning<R extends GridRowModel>(params: UseGridSpanningParams<R>): GridSpanningResult {
|
|
162
|
+
const {
|
|
163
|
+
pinnedTopRows,
|
|
164
|
+
centerRows,
|
|
165
|
+
pinnedBottomRows,
|
|
166
|
+
columns,
|
|
167
|
+
leftPinnedColumns,
|
|
168
|
+
unpinnedColumns,
|
|
169
|
+
rightPinnedColumns,
|
|
170
|
+
expandedRowIds,
|
|
171
|
+
rowMetaMap,
|
|
172
|
+
getRowId = defaultGetRowId,
|
|
173
|
+
} = params;
|
|
174
|
+
|
|
175
|
+
const hasColSpan = useMemo(() => columns.some(c => c.colSpan !== undefined && c.colSpan !== null), [columns]);
|
|
176
|
+
const hasRowSpan = useMemo(() => columns.some(c => c.rowSpan !== undefined && c.rowSpan !== null), [columns]);
|
|
177
|
+
const hasSpans = hasColSpan || hasRowSpan;
|
|
178
|
+
|
|
179
|
+
// Only the order and pinned section of the rendered columns matter; a string key keeps the span
|
|
180
|
+
// computation from re-running on every column-resize tick (the layout arrays are rebuilt then).
|
|
181
|
+
const structureKey = hasSpans
|
|
182
|
+
? [leftPinnedColumns, unpinnedColumns, rightPinnedColumns].map(sectionKey).join(SECTION_SEPARATOR)
|
|
183
|
+
: '';
|
|
184
|
+
|
|
185
|
+
const spanColumns = useMemo<{ columns: SpanColumn<R>[]; leftCount: number; unpinnedCount: number }>(() => {
|
|
186
|
+
if (!structureKey) return { columns: [], leftCount: 0, unpinnedCount: 0 };
|
|
187
|
+
const byField = new Map(columns.map(c => [c.field, c]));
|
|
188
|
+
const sections = structureKey.split(SECTION_SEPARATOR).map(s => (s ? s.split(FIELD_SEPARATOR) : []));
|
|
189
|
+
const result: SpanColumn<R>[] = [];
|
|
190
|
+
let end = 0;
|
|
191
|
+
for (const fields of sections) {
|
|
192
|
+
end += fields.length;
|
|
193
|
+
for (const field of fields) {
|
|
194
|
+
const colDef = byField.get(field) ?? ({ field } as GridColDef<R>);
|
|
195
|
+
result.push({
|
|
196
|
+
colDef,
|
|
197
|
+
field,
|
|
198
|
+
sectionEnd: end,
|
|
199
|
+
hasColSpan: colDef.colSpan !== undefined && colDef.colSpan !== null,
|
|
200
|
+
hasRowSpan: colDef.rowSpan !== undefined && colDef.rowSpan !== null,
|
|
201
|
+
});
|
|
174
202
|
}
|
|
175
|
-
}
|
|
176
203
|
}
|
|
177
|
-
|
|
178
|
-
});
|
|
204
|
+
return { columns: result, leftCount: sections[0].length, unpinnedCount: sections[1].length };
|
|
205
|
+
}, [structureKey, columns]);
|
|
206
|
+
|
|
207
|
+
const computation = useMemo<SpanComputation | null>(() => {
|
|
208
|
+
if (!hasSpans || spanColumns.columns.length === 0) return null;
|
|
209
|
+
const out: SpanComputation = {
|
|
210
|
+
colspanMap: new Map(),
|
|
211
|
+
rowSpanningCaches: { spannedCells: {}, hiddenCells: {}, hiddenCellOriginMap: {} },
|
|
212
|
+
hiddenOriginFields: new Map(),
|
|
213
|
+
unpinnedRanges: new Map(),
|
|
214
|
+
centerRowSpanStart: new Map(),
|
|
215
|
+
};
|
|
216
|
+
const warned = new Set<string>();
|
|
217
|
+
const context = { spanColumns, expandedRowIds, rowMetaMap, getRowId, warned, out };
|
|
218
|
+
computeSection(pinnedTopRows, 0, false, context);
|
|
219
|
+
computeSection(centerRows, pinnedTopRows.length, true, context);
|
|
220
|
+
computeSection(pinnedBottomRows, pinnedTopRows.length + centerRows.length, false, context);
|
|
221
|
+
return out;
|
|
222
|
+
}, [hasSpans, spanColumns, pinnedTopRows, centerRows, pinnedBottomRows, expandedRowIds, rowMetaMap, getRowId]);
|
|
223
|
+
|
|
224
|
+
return useMemo<GridSpanningResult>(() => {
|
|
225
|
+
if (!computation) return EMPTY_RESULT;
|
|
226
|
+
const { colspanMap, rowSpanningCaches, hiddenOriginFields, unpinnedRanges, centerRowSpanStart } = computation;
|
|
227
|
+
const renderedColumns = spanColumns.columns;
|
|
228
|
+
return {
|
|
229
|
+
hasColSpan,
|
|
230
|
+
hasRowSpan,
|
|
231
|
+
colspanMap,
|
|
232
|
+
rowSpanningCaches,
|
|
233
|
+
getSpanOrigin: (rowId, field) => {
|
|
234
|
+
const colSpanInfo = colspanMap.get(rowId)?.[field];
|
|
235
|
+
if (colSpanInfo?.spannedByColSpan) return { rowId, field: renderedColumns[colSpanInfo.leftVisibleCellIndex].field };
|
|
236
|
+
const originRowId = rowSpanningCaches.hiddenCellOriginMap[rowId]?.[field];
|
|
237
|
+
if (originRowId === undefined) return null;
|
|
238
|
+
return { rowId: originRowId, field: hiddenOriginFields.get(rowId)?.[field] ?? field };
|
|
239
|
+
},
|
|
240
|
+
getUnpinnedColSpanRanges: (rowId) => unpinnedRanges.get(rowId),
|
|
241
|
+
getCenterRowSpanStart: (centerRowIndex) => centerRowSpanStart.get(centerRowIndex) ?? centerRowIndex,
|
|
242
|
+
};
|
|
243
|
+
}, [computation, hasColSpan, hasRowSpan, spanColumns]);
|
|
244
|
+
}
|
|
179
245
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
caches: {
|
|
189
|
-
spannedCells: {},
|
|
190
|
-
hiddenCells: {},
|
|
191
|
-
hiddenCellOriginMap: {}
|
|
192
|
-
},
|
|
193
|
-
processedRange: { firstRowIndex: 0, lastRowIndex: 0 }
|
|
194
|
-
});
|
|
195
|
-
}, []);
|
|
246
|
+
interface SectionContext<R extends GridRowModel> {
|
|
247
|
+
spanColumns: { columns: SpanColumn<R>[]; leftCount: number; unpinnedCount: number };
|
|
248
|
+
expandedRowIds?: Set<GridRowId>;
|
|
249
|
+
rowMetaMap?: Map<GridRowId, GridRowMeta>;
|
|
250
|
+
getRowId: (row: R) => GridRowId;
|
|
251
|
+
warned: Set<string>;
|
|
252
|
+
out: SpanComputation;
|
|
253
|
+
}
|
|
196
254
|
|
|
197
|
-
|
|
255
|
+
function computeSection<R extends GridRowModel>(
|
|
256
|
+
rows: R[],
|
|
257
|
+
rowIndexOffset: number,
|
|
258
|
+
isCenter: boolean,
|
|
259
|
+
context: SectionContext<R>,
|
|
260
|
+
): void {
|
|
261
|
+
const { spanColumns, expandedRowIds, rowMetaMap, getRowId, warned, out } = context;
|
|
262
|
+
const { columns, leftCount, unpinnedCount } = spanColumns;
|
|
263
|
+
const columnCount = columns.length;
|
|
264
|
+
const { colspanMap, rowSpanningCaches, hiddenOriginFields, unpinnedRanges, centerRowSpanStart } = out;
|
|
265
|
+
|
|
266
|
+
// Per column: the (section-local) row index until which a row span from above covers it, and
|
|
267
|
+
// the origin of that span. `coverEnd` is the furthest such row over all columns.
|
|
268
|
+
const coveredUntil = new Int32Array(columnCount);
|
|
269
|
+
const coverOriginRow: GridRowId[] = new Array(columnCount);
|
|
270
|
+
const coverOriginField: string[] = new Array(columnCount);
|
|
271
|
+
const coverOriginIndex = new Int32Array(columnCount);
|
|
272
|
+
let coverEnd = 0;
|
|
273
|
+
|
|
274
|
+
const spanColumnIndices: number[] = [];
|
|
275
|
+
columns.forEach((column, index) => {
|
|
276
|
+
if (column.hasColSpan || column.hasRowSpan) spanColumnIndices.push(index);
|
|
277
|
+
});
|
|
198
278
|
|
|
199
|
-
const
|
|
279
|
+
const markHidden = (rowId: GridRowId, r: number, i: number) => {
|
|
280
|
+
const field = columns[i].field;
|
|
281
|
+
setRecord(rowSpanningCaches.hiddenCells, rowId, field, true);
|
|
282
|
+
setRecord(rowSpanningCaches.hiddenCellOriginMap, rowId, field, coverOriginRow[i]);
|
|
283
|
+
if (coverOriginField[i] !== field) setRecord(hiddenOriginFields, rowId, field, coverOriginField[i]);
|
|
284
|
+
if (isCenter) {
|
|
285
|
+
const start = centerRowSpanStart.get(r);
|
|
286
|
+
if (start === undefined || coverOriginIndex[i] < start) centerRowSpanStart.set(r, coverOriginIndex[i]);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
200
289
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
290
|
+
/** Evaluates the spans of the cell at column `i` (not covered from above); returns the columns it takes. */
|
|
291
|
+
const processCell = (row: R, r: number, i: number): number => {
|
|
292
|
+
const column = columns[i];
|
|
293
|
+
const rowId = getRowId(row);
|
|
294
|
+
const cellParams = buildParams(row, rowId, column.colDef, rowIndexOffset + r, i, rowMetaMap, warned);
|
|
205
295
|
|
|
206
296
|
let colSpan = 1;
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
row,
|
|
211
|
-
value: row[colDef.field as keyof R],
|
|
212
|
-
field: colDef.field,
|
|
213
|
-
colDef,
|
|
214
|
-
rowIndex: index,
|
|
215
|
-
colIndex: i
|
|
216
|
-
};
|
|
217
|
-
colSpan = colDef.colSpan(params);
|
|
218
|
-
} else {
|
|
219
|
-
colSpan = colDef.colSpan;
|
|
220
|
-
}
|
|
297
|
+
if (column.hasColSpan) {
|
|
298
|
+
const limit = Math.min(evaluateSpan(column.colDef.colSpan, cellParams, 'colSpan', column.field, warned), column.sectionEnd - i);
|
|
299
|
+
while (colSpan < limit && coveredUntil[i + colSpan] <= r) colSpan++;
|
|
221
300
|
}
|
|
222
301
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
302
|
+
let rowSpan = 1;
|
|
303
|
+
if (column.hasRowSpan) {
|
|
304
|
+
rowSpan = Math.min(evaluateSpan(column.colDef.rowSpan, cellParams, 'rowSpan', column.field, warned), rows.length - r);
|
|
305
|
+
// A span ends before the next synthetic row (group header, subtotal, auto-created
|
|
306
|
+
// parent, pivot Grand Total): it never merges data across a group boundary.
|
|
307
|
+
for (let k = 1; k < rowSpan; k++) {
|
|
308
|
+
if (isSyntheticRowId(getRowId(rows[r + k]), rowMetaMap)) {
|
|
309
|
+
rowSpan = k;
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (rowSpan > 1 && expandedRowIds && expandedRowIds.size > 0) {
|
|
314
|
+
for (let k = 0; k < rowSpan - 1; k++) {
|
|
315
|
+
if (expandedRowIds.has(getRowId(rows[r + k]))) {
|
|
316
|
+
rowSpan = k + 1;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
230
321
|
}
|
|
231
322
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
323
|
+
if (colSpan > 1) {
|
|
324
|
+
setRecord(colspanMap, rowId, column.field, { spannedByColSpan: false, cellProps: { colSpan } });
|
|
325
|
+
for (let j = 1; j < colSpan; j++) {
|
|
326
|
+
const covered = columns[i + j];
|
|
327
|
+
setRecord(colspanMap, rowId, covered.field, {
|
|
328
|
+
spannedByColSpan: true,
|
|
329
|
+
leftVisibleCellIndex: i,
|
|
330
|
+
rightVisibleCellIndex: i + colSpan - 1,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
if (i >= leftCount && i < leftCount + unpinnedCount) {
|
|
334
|
+
let ranges = unpinnedRanges.get(rowId);
|
|
335
|
+
if (!ranges) {
|
|
336
|
+
ranges = [];
|
|
337
|
+
unpinnedRanges.set(rowId, ranges);
|
|
338
|
+
}
|
|
339
|
+
ranges.push(i - leftCount, i + colSpan - 1 - leftCount);
|
|
245
340
|
}
|
|
246
|
-
}
|
|
247
341
|
}
|
|
248
342
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
setColspanMap(newMap);
|
|
259
|
-
|
|
260
|
-
const spannedCells: Record<GridRowId, Record<ColumnKey, number>> = {};
|
|
261
|
-
const hiddenCells: Record<GridRowId, Record<ColumnKey, boolean>> = {};
|
|
262
|
-
const hiddenCellOriginMap: Record<number, Record<ColumnKey, number>> = {};
|
|
263
|
-
|
|
264
|
-
columns.forEach((colDef, columnIndex) => {
|
|
265
|
-
if (!colDef.rowSpan) return;
|
|
266
|
-
|
|
267
|
-
for (let index = 0; index < rows.length; index++) {
|
|
268
|
-
const row = rows[index];
|
|
269
|
-
if (!row) continue;
|
|
270
|
-
|
|
271
|
-
if (hiddenCells[row.id]?.[colDef.field]) continue;
|
|
272
|
-
|
|
273
|
-
const cellValue = colDef.valueGetter
|
|
274
|
-
? colDef.valueGetter({ row, field: colDef.field, value: row[colDef.field as keyof R] })
|
|
275
|
-
: row[colDef.field as keyof R];
|
|
276
|
-
|
|
277
|
-
let span = 1;
|
|
278
|
-
if (typeof colDef.rowSpan === 'function') {
|
|
279
|
-
const params: GridRenderCellParams<R> = {
|
|
280
|
-
row,
|
|
281
|
-
value: cellValue,
|
|
282
|
-
field: colDef.field,
|
|
283
|
-
colDef,
|
|
284
|
-
rowIndex: index,
|
|
285
|
-
colIndex: columnIndex
|
|
286
|
-
};
|
|
287
|
-
span = colDef.rowSpan(params);
|
|
288
|
-
} else if (typeof colDef.rowSpan === 'number') {
|
|
289
|
-
span = colDef.rowSpan;
|
|
343
|
+
if (rowSpan > 1) {
|
|
344
|
+
setRecord(rowSpanningCaches.spannedCells, rowId, column.field, rowSpan);
|
|
345
|
+
for (let j = i; j < i + colSpan; j++) {
|
|
346
|
+
coveredUntil[j] = r + rowSpan;
|
|
347
|
+
coverOriginRow[j] = rowId;
|
|
348
|
+
coverOriginField[j] = column.field;
|
|
349
|
+
coverOriginIndex[j] = r;
|
|
350
|
+
}
|
|
351
|
+
if (r + rowSpan > coverEnd) coverEnd = r + rowSpan;
|
|
290
352
|
}
|
|
291
353
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
spannedCells[row.id][colDef.field] = span;
|
|
354
|
+
return colSpan;
|
|
355
|
+
};
|
|
295
356
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
357
|
+
for (let r = 0; r < rows.length; r++) {
|
|
358
|
+
const row = rows[r];
|
|
359
|
+
if (!row || row._isSkeleton) continue;
|
|
360
|
+
// Synthetic rows hold no data of their own: they are never a span origin (spans from above
|
|
361
|
+
// already end before them).
|
|
362
|
+
if (isSyntheticRowId(getRowId(row), rowMetaMap)) continue;
|
|
363
|
+
|
|
364
|
+
if (coverEnd <= r) {
|
|
365
|
+
// Nothing is covered from above: only the columns that declare a span need a look.
|
|
366
|
+
let next = 0;
|
|
367
|
+
for (const i of spanColumnIndices) {
|
|
368
|
+
if (i >= next) next = i + processCell(row, r, i);
|
|
369
|
+
}
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
302
372
|
|
|
303
|
-
|
|
304
|
-
|
|
373
|
+
let i = 0;
|
|
374
|
+
while (i < columnCount) {
|
|
375
|
+
if (coveredUntil[i] > r) {
|
|
376
|
+
markHidden(getRowId(row), r, i);
|
|
377
|
+
i++;
|
|
378
|
+
} else if (columns[i].hasColSpan || columns[i].hasRowSpan) {
|
|
379
|
+
i += processCell(row, r, i);
|
|
380
|
+
} else {
|
|
381
|
+
i++;
|
|
305
382
|
}
|
|
306
|
-
}
|
|
307
383
|
}
|
|
308
|
-
|
|
309
|
-
|
|
384
|
+
}
|
|
385
|
+
}
|
|
310
386
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
387
|
+
function buildParams<R extends GridRowModel>(
|
|
388
|
+
row: R,
|
|
389
|
+
rowId: GridRowId,
|
|
390
|
+
colDef: GridColDef<R>,
|
|
391
|
+
rowIndex: number,
|
|
392
|
+
colIndex: number,
|
|
393
|
+
rowMetaMap: Map<GridRowId, GridRowMeta> | undefined,
|
|
394
|
+
warned: Set<string>,
|
|
395
|
+
): GridRenderCellParams<R> {
|
|
396
|
+
const raw = row[colDef.field as keyof R];
|
|
397
|
+
let value: unknown = raw;
|
|
398
|
+
if (colDef.valueGetter) {
|
|
399
|
+
try {
|
|
400
|
+
value = colDef.valueGetter({ row, field: colDef.field, value: raw });
|
|
401
|
+
} catch (error) {
|
|
402
|
+
warnOnce(warned, `valueGetter:${colDef.field}`, `valueGetter for column "${colDef.field}" threw while computing its span`, error);
|
|
403
|
+
value = undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return { row, value, field: colDef.field, colDef, rowIndex, colIndex, rowMeta: rowMetaMap?.get(rowId) };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function evaluateSpan<R extends GridRowModel>(
|
|
410
|
+
spec: GridColDef<R>['colSpan'],
|
|
411
|
+
cellParams: GridRenderCellParams<R>,
|
|
412
|
+
kind: 'colSpan' | 'rowSpan',
|
|
413
|
+
field: string,
|
|
414
|
+
warned: Set<string>,
|
|
415
|
+
): number {
|
|
416
|
+
if (typeof spec === 'number') return normalizeSpan(spec);
|
|
417
|
+
if (typeof spec !== 'function') return 1;
|
|
418
|
+
try {
|
|
419
|
+
return normalizeSpan(spec(cellParams));
|
|
420
|
+
} catch (error) {
|
|
421
|
+
warnOnce(warned, `${kind}:${field}`, `${kind} for column "${field}" threw; the cell is rendered without a span`, error);
|
|
422
|
+
return 1;
|
|
423
|
+
}
|
|
335
424
|
}
|
|
336
425
|
|
|
337
|
-
function
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
426
|
+
function warnOnce(warned: Set<string>, key: string, message: string, error: unknown): void {
|
|
427
|
+
if (process.env.NODE_ENV === 'production' || warned.has(key)) return;
|
|
428
|
+
warned.add(key);
|
|
429
|
+
console.warn(`[OpenGridX] ${message}.`, error);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function setRecord<V>(
|
|
433
|
+
target: Map<GridRowId, Record<ColumnKey, V>> | Record<GridRowId, Record<ColumnKey, V>>,
|
|
434
|
+
rowId: GridRowId,
|
|
435
|
+
field: string,
|
|
436
|
+
value: V,
|
|
437
|
+
): void {
|
|
438
|
+
if (target instanceof Map) {
|
|
439
|
+
let record = target.get(rowId);
|
|
440
|
+
if (!record) {
|
|
441
|
+
record = {};
|
|
442
|
+
target.set(rowId, record);
|
|
443
|
+
}
|
|
444
|
+
record[field] = value;
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
let record = target[rowId];
|
|
448
|
+
if (!record) {
|
|
449
|
+
record = {};
|
|
450
|
+
target[rowId] = record;
|
|
451
|
+
}
|
|
452
|
+
record[field] = value;
|
|
349
453
|
}
|