@opencorestack/opengridx 0.1.8 → 1.0.0
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 +59 -0
- package/dist/index.d.ts +60 -41
- package/dist/opengridx.es.js +5271 -4660
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +12 -12
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/README.md +14 -0
- package/docs/components/aggregation-footer.md +48 -0
- package/docs/components/datagrid.md +3 -0
- package/docs/components/empty-state.md +50 -0
- package/docs/components/error-overlay.md +40 -0
- package/docs/roadmap.md +20 -1
- package/lib/components/Cell/Cell.tsx +61 -64
- 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.smoke.test.tsx +58 -0
- package/lib/components/DataGrid/DataGrid.tsx +396 -1654
- 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 +46 -45
- package/lib/components/SkeletonRow/SkeletonRow.tsx +9 -2
- package/lib/components/Toolbar/GlobalSearch.tsx +1 -1
- package/lib/components/Toolbar/GridToolbar.tsx +26 -5
- package/lib/components/Toolbar/PivotPanel.tsx +7 -2
- package/lib/components/ui/DragHandleIcon.tsx +1 -1
- package/lib/hooks/core/useDataGrid.regression.test.ts +66 -0
- package/lib/hooks/core/useDataGrid.ts +15 -35
- package/lib/hooks/core/useGridColumns.tsx +228 -0
- package/lib/hooks/core/useGridControlledState.ts +192 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +371 -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 +348 -0
- package/lib/hooks/features/useAggregation.test.ts +135 -0
- package/lib/hooks/features/useAggregation.ts +7 -7
- package/lib/hooks/features/useGridClipboard.ts +1 -1
- package/lib/hooks/features/useGridDataSource.ts +2 -2
- 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 +2 -2
- package/lib/types/index.ts +48 -32
- package/lib/utils/export/exportToExcelAdvanced.ts +14 -14
- package/lib/utils/export/index.ts +23 -18
- package/lib/utils/filtering/filtering.test.ts +178 -0
- package/lib/utils/filtering/index.ts +20 -20
- package/lib/utils/sorting/index.ts +1 -1
- package/lib/utils/sorting/sorting.test.ts +88 -0
- package/package.json +12 -5
- package/lib/utils/virtualization/index.ts +0 -106
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { GridRowModel, GridColDef } from '../../types';
|
|
3
|
+
import type { LayoutResult } from './useLayout';
|
|
4
|
+
|
|
5
|
+
export interface UseGridVirtualizationParams<R extends GridRowModel> {
|
|
6
|
+
layout: LayoutResult<R>;
|
|
7
|
+
scrollTop: number;
|
|
8
|
+
scrollLeft: number;
|
|
9
|
+
viewportWidth: number;
|
|
10
|
+
viewportHeight: number;
|
|
11
|
+
autoHeight: boolean;
|
|
12
|
+
rowReordering: boolean;
|
|
13
|
+
hasDetailPanel: boolean;
|
|
14
|
+
checkboxSelection: boolean;
|
|
15
|
+
pinCheckboxColumn: boolean;
|
|
16
|
+
pinExpandColumn: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GridVirtualizationResult<R extends GridRowModel = GridRowModel> {
|
|
20
|
+
renderContext: {
|
|
21
|
+
firstRowIndex: number;
|
|
22
|
+
lastRowIndex: number;
|
|
23
|
+
firstColumnIndex: number;
|
|
24
|
+
lastColumnIndex: number;
|
|
25
|
+
};
|
|
26
|
+
offsetTop: number;
|
|
27
|
+
offsetLeft: number;
|
|
28
|
+
totalHeight: number;
|
|
29
|
+
pinnedTopHeight: number;
|
|
30
|
+
pinnedBottomHeight: number;
|
|
31
|
+
totalWidth: number;
|
|
32
|
+
rowHeights: number[];
|
|
33
|
+
cumulativeHeights: number[];
|
|
34
|
+
virtualColumns: (GridColDef<R> & { width: number })[];
|
|
35
|
+
columnMetrics: {
|
|
36
|
+
leftPinnedWidth: number;
|
|
37
|
+
rightPinnedWidth: number;
|
|
38
|
+
unpinnedAccWidths: number[];
|
|
39
|
+
unpinnedCols: { field: string; width: number }[];
|
|
40
|
+
totalSpecialsWidth: number;
|
|
41
|
+
pinnedSpecialsWidth: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useGridVirtualization<R extends GridRowModel>(
|
|
46
|
+
params: UseGridVirtualizationParams<R>
|
|
47
|
+
): GridVirtualizationResult<R> {
|
|
48
|
+
const {
|
|
49
|
+
layout,
|
|
50
|
+
scrollTop,
|
|
51
|
+
scrollLeft,
|
|
52
|
+
viewportWidth: rawViewportWidth,
|
|
53
|
+
viewportHeight: rawViewportHeight,
|
|
54
|
+
autoHeight,
|
|
55
|
+
rowReordering,
|
|
56
|
+
hasDetailPanel,
|
|
57
|
+
checkboxSelection,
|
|
58
|
+
pinCheckboxColumn,
|
|
59
|
+
pinExpandColumn,
|
|
60
|
+
} = params;
|
|
61
|
+
|
|
62
|
+
return useMemo<GridVirtualizationResult<R>>(() => {
|
|
63
|
+
const {
|
|
64
|
+
unpinnedAccWidths,
|
|
65
|
+
unpinnedColsWithWidth,
|
|
66
|
+
leftPinnedCols,
|
|
67
|
+
rightPinnedCols,
|
|
68
|
+
totalWidth,
|
|
69
|
+
leftWidth,
|
|
70
|
+
rightWidth,
|
|
71
|
+
unpinnedTotalWidth,
|
|
72
|
+
systemColumnsWidth,
|
|
73
|
+
cumulativeHeights,
|
|
74
|
+
pinnedTopHeight,
|
|
75
|
+
pinnedBottomHeight,
|
|
76
|
+
unpinnedRowsHeight,
|
|
77
|
+
rowHeights,
|
|
78
|
+
unpinnedRowsLength,
|
|
79
|
+
} = layout;
|
|
80
|
+
|
|
81
|
+
const viewportWidth = rawViewportWidth || 1000;
|
|
82
|
+
const viewportHeight = autoHeight
|
|
83
|
+
? pinnedTopHeight + unpinnedRowsHeight + pinnedBottomHeight + 50
|
|
84
|
+
: rawViewportHeight || 600;
|
|
85
|
+
|
|
86
|
+
const overscanCols = 6;
|
|
87
|
+
const visibleLocalStart = Math.max(0, scrollLeft - leftWidth);
|
|
88
|
+
const visibleLocalEnd = scrollLeft + viewportWidth - leftWidth;
|
|
89
|
+
|
|
90
|
+
let firstUnpinnedIndex = 0;
|
|
91
|
+
let lastUnpinnedIndex = unpinnedColsWithWidth.length - 1;
|
|
92
|
+
|
|
93
|
+
let low = 0, high = unpinnedAccWidths.length - 1;
|
|
94
|
+
while (low <= high) {
|
|
95
|
+
const mid = (low + high) >>> 1;
|
|
96
|
+
if (unpinnedAccWidths[mid] < visibleLocalStart) { low = mid + 1; } else { high = mid - 1; }
|
|
97
|
+
}
|
|
98
|
+
firstUnpinnedIndex = Math.max(0, low - overscanCols);
|
|
99
|
+
|
|
100
|
+
for (let i = firstUnpinnedIndex; i < unpinnedAccWidths.length; i++) {
|
|
101
|
+
if ((i === 0 ? 0 : unpinnedAccWidths[i - 1]) > visibleLocalEnd) {
|
|
102
|
+
lastUnpinnedIndex = Math.min(unpinnedColsWithWidth.length - 1, i + overscanCols);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
lastUnpinnedIndex = Math.min(unpinnedColsWithWidth.length - 1, i + overscanCols);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const leftSpacerWidth = firstUnpinnedIndex > 0 ? unpinnedAccWidths[firstUnpinnedIndex - 1] : 0;
|
|
109
|
+
const rightSpacerWidth = unpinnedTotalWidth - unpinnedAccWidths[lastUnpinnedIndex];
|
|
110
|
+
|
|
111
|
+
const virtualColumns: (GridColDef<R> & { width: number })[] = [
|
|
112
|
+
...leftPinnedCols,
|
|
113
|
+
...(leftSpacerWidth > 0 ? [{ field: '__spacer_left__', width: leftSpacerWidth, isSpacer: true }] : []),
|
|
114
|
+
...unpinnedColsWithWidth.slice(firstUnpinnedIndex, lastUnpinnedIndex + 1),
|
|
115
|
+
...(rightSpacerWidth > 0 ? [{ field: '__spacer_right__', width: rightSpacerWidth, isSpacer: true }] : []),
|
|
116
|
+
...rightPinnedCols,
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const hasHorizontalScroll = viewportWidth > 0 && totalWidth > viewportWidth;
|
|
120
|
+
const scrollbarBuffer = hasHorizontalScroll ? 16 : 0;
|
|
121
|
+
const totalHeight = pinnedTopHeight + unpinnedRowsHeight + pinnedBottomHeight + scrollbarBuffer + 2;
|
|
122
|
+
|
|
123
|
+
const overscanRows = 5;
|
|
124
|
+
let firstRowIndex = 0;
|
|
125
|
+
let lastRowIndex = unpinnedRowsLength - 1;
|
|
126
|
+
let offsetTop = 0;
|
|
127
|
+
|
|
128
|
+
let rLow = 0, rHigh = cumulativeHeights.length - 1;
|
|
129
|
+
while (rLow <= rHigh) {
|
|
130
|
+
const mid = (rLow + rHigh) >>> 1;
|
|
131
|
+
if (cumulativeHeights[mid] < scrollTop) { rLow = mid + 1; } else { rHigh = mid - 1; }
|
|
132
|
+
}
|
|
133
|
+
firstRowIndex = Math.max(0, rLow - overscanRows);
|
|
134
|
+
offsetTop = firstRowIndex > 0 ? cumulativeHeights[firstRowIndex - 1] : 0;
|
|
135
|
+
|
|
136
|
+
for (let i = firstRowIndex; i < cumulativeHeights.length; i++) {
|
|
137
|
+
if (cumulativeHeights[i] >= scrollTop + viewportHeight) {
|
|
138
|
+
lastRowIndex = Math.min(unpinnedRowsLength - 1, i + overscanRows);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const pinnedSpecialsWidth =
|
|
144
|
+
(rowReordering ? 48 : 0) +
|
|
145
|
+
(hasDetailPanel && pinExpandColumn ? 48 : 0) +
|
|
146
|
+
(checkboxSelection && pinCheckboxColumn ? 48 : 0);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
renderContext: {
|
|
150
|
+
firstRowIndex,
|
|
151
|
+
lastRowIndex,
|
|
152
|
+
firstColumnIndex: firstUnpinnedIndex,
|
|
153
|
+
lastColumnIndex: lastUnpinnedIndex,
|
|
154
|
+
},
|
|
155
|
+
offsetTop,
|
|
156
|
+
offsetLeft: 0,
|
|
157
|
+
totalHeight,
|
|
158
|
+
pinnedTopHeight,
|
|
159
|
+
pinnedBottomHeight,
|
|
160
|
+
totalWidth,
|
|
161
|
+
rowHeights,
|
|
162
|
+
cumulativeHeights,
|
|
163
|
+
virtualColumns,
|
|
164
|
+
columnMetrics: {
|
|
165
|
+
leftPinnedWidth: leftWidth,
|
|
166
|
+
rightPinnedWidth: rightWidth,
|
|
167
|
+
unpinnedAccWidths,
|
|
168
|
+
unpinnedCols: unpinnedColsWithWidth,
|
|
169
|
+
totalSpecialsWidth: systemColumnsWidth,
|
|
170
|
+
pinnedSpecialsWidth,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}, [
|
|
174
|
+
layout,
|
|
175
|
+
scrollTop,
|
|
176
|
+
scrollLeft,
|
|
177
|
+
rawViewportWidth,
|
|
178
|
+
rawViewportHeight,
|
|
179
|
+
autoHeight,
|
|
180
|
+
rowReordering,
|
|
181
|
+
hasDetailPanel,
|
|
182
|
+
checkboxSelection,
|
|
183
|
+
pinCheckboxColumn,
|
|
184
|
+
pinExpandColumn,
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { GridRowModel, GridRowId } from '../../types';
|
|
3
|
+
|
|
4
|
+
interface RenderContext {
|
|
5
|
+
firstRowIndex: number;
|
|
6
|
+
lastRowIndex: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface UseGridVisibleRowsParams<R extends GridRowModel> {
|
|
10
|
+
renderContext: RenderContext;
|
|
11
|
+
pinnedTopRows: R[];
|
|
12
|
+
pinnedBottomRows: R[];
|
|
13
|
+
paginatedUnpinnedRows: R[];
|
|
14
|
+
sortedUnpinnedRows: R[];
|
|
15
|
+
pagination: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GridVisibleRow<R extends GridRowModel> {
|
|
19
|
+
row: R;
|
|
20
|
+
rowIndex: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useGridVisibleRows<R extends GridRowModel>(
|
|
24
|
+
params: UseGridVisibleRowsParams<R>
|
|
25
|
+
): GridVisibleRow<R>[] {
|
|
26
|
+
const {
|
|
27
|
+
renderContext,
|
|
28
|
+
pinnedTopRows,
|
|
29
|
+
pinnedBottomRows,
|
|
30
|
+
paginatedUnpinnedRows,
|
|
31
|
+
sortedUnpinnedRows,
|
|
32
|
+
pagination,
|
|
33
|
+
} = params;
|
|
34
|
+
|
|
35
|
+
return useMemo<GridVisibleRow<R>[]>(() => {
|
|
36
|
+
const { firstRowIndex, lastRowIndex } = renderContext;
|
|
37
|
+
const centerRows = pagination ? paginatedUnpinnedRows : sortedUnpinnedRows;
|
|
38
|
+
const topPinnedCount = pinnedTopRows.length;
|
|
39
|
+
|
|
40
|
+
const topPinned = pinnedTopRows.map((row, index) => ({ row, rowIndex: index }));
|
|
41
|
+
const bottomPinned = pinnedBottomRows.map((row, index) => ({
|
|
42
|
+
row,
|
|
43
|
+
rowIndex: topPinnedCount + centerRows.length + index,
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const centerStartIndex = Math.max(0, firstRowIndex - topPinnedCount);
|
|
47
|
+
const centerEndIndex = Math.min(centerRows.length, lastRowIndex - topPinnedCount + 1);
|
|
48
|
+
|
|
49
|
+
const centerVisible = centerRows
|
|
50
|
+
.slice(centerStartIndex, centerEndIndex)
|
|
51
|
+
.map((row, index) => ({
|
|
52
|
+
row,
|
|
53
|
+
rowIndex: topPinnedCount + centerStartIndex + index,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const combined = [...topPinned, ...centerVisible, ...bottomPinned];
|
|
57
|
+
|
|
58
|
+
// Deduplication guard: a row ID in both pinnedRows and rows would cause React key collisions
|
|
59
|
+
const seenIds = new Set<GridRowId>();
|
|
60
|
+
return combined.filter(item => {
|
|
61
|
+
if (seenIds.has(item.row.id)) return false;
|
|
62
|
+
seenIds.add(item.row.id);
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
}, [renderContext, pinnedTopRows, pinnedBottomRows, paginatedUnpinnedRows, sortedUnpinnedRows, pagination]);
|
|
66
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { GridColDef, GridRowModel, GridRowId, GridColumnPinning, GridDetailPanelHeight } from '../../types';
|
|
3
|
+
import { isColumnPinned } from '../../utils/pinning';
|
|
4
|
+
|
|
5
|
+
export interface UseLayoutParams<R extends GridRowModel> {
|
|
6
|
+
rowHeight: number;
|
|
7
|
+
pagination: boolean;
|
|
8
|
+
paginatedUnpinnedRows: R[];
|
|
9
|
+
sortedUnpinnedRows: R[];
|
|
10
|
+
expandedRowIds: Set<GridRowId>;
|
|
11
|
+
getDetailPanelHeight?: (params: { row: R; id: GridRowId; rowIndex: number }) => GridDetailPanelHeight;
|
|
12
|
+
pinnedTopRowsLength: number;
|
|
13
|
+
pinnedBottomRowsLength: number;
|
|
14
|
+
visibleOrderedColumns: GridColDef<R>[];
|
|
15
|
+
pinnedColumns?: GridColumnPinning;
|
|
16
|
+
columnWidths: Record<string, number>;
|
|
17
|
+
viewportWidth: number;
|
|
18
|
+
checkboxSelection: boolean;
|
|
19
|
+
hasDetailPanel: boolean;
|
|
20
|
+
rowReordering: boolean;
|
|
21
|
+
pinCheckboxColumn: boolean;
|
|
22
|
+
pinExpandColumn: boolean;
|
|
23
|
+
autoHeight: boolean;
|
|
24
|
+
paginationMode: string;
|
|
25
|
+
isLoading: boolean;
|
|
26
|
+
pageSize: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LayoutResult<R extends GridRowModel> {
|
|
30
|
+
rowHeights: number[];
|
|
31
|
+
cumulativeHeights: number[];
|
|
32
|
+
unpinnedRowsHeight: number;
|
|
33
|
+
pinnedTopHeight: number;
|
|
34
|
+
pinnedBottomHeight: number;
|
|
35
|
+
leftPinnedCols: (GridColDef<R> & { width: number; zIndex: number })[];
|
|
36
|
+
rightPinnedCols: (GridColDef<R> & { width: number; zIndex: number })[];
|
|
37
|
+
unpinnedColsWithWidth: (GridColDef<R> & { width: number })[];
|
|
38
|
+
unpinnedColWidths: number[];
|
|
39
|
+
unpinnedAccWidths: number[];
|
|
40
|
+
totalWidth: number;
|
|
41
|
+
leftWidth: number;
|
|
42
|
+
rightWidth: number;
|
|
43
|
+
unpinnedTotalWidth: number;
|
|
44
|
+
systemColumnsWidth: number;
|
|
45
|
+
pinnedTopRowsLength: number;
|
|
46
|
+
pinnedBottomRowsLength: number;
|
|
47
|
+
unpinnedRowsLength: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseWidth(width: number | string | undefined): { type: 'fixed' | 'percentage' | 'auto'; value: number } {
|
|
51
|
+
if (width === undefined) return { type: 'auto', value: 0 };
|
|
52
|
+
if (typeof width === 'number') return { type: 'fixed', value: width };
|
|
53
|
+
if (typeof width === 'string') {
|
|
54
|
+
if (width.toLowerCase() === 'auto') return { type: 'auto', value: 0 };
|
|
55
|
+
if (width.endsWith('%')) {
|
|
56
|
+
const percentage = parseFloat(width);
|
|
57
|
+
return { type: 'percentage', value: percentage };
|
|
58
|
+
}
|
|
59
|
+
const floatVal = parseFloat(width);
|
|
60
|
+
if (!isNaN(floatVal)) return { type: 'fixed', value: floatVal };
|
|
61
|
+
}
|
|
62
|
+
return { type: 'fixed', value: 100 };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function useLayout<R extends GridRowModel>(params: UseLayoutParams<R>): LayoutResult<R> {
|
|
66
|
+
const {
|
|
67
|
+
rowHeight,
|
|
68
|
+
pagination,
|
|
69
|
+
paginatedUnpinnedRows,
|
|
70
|
+
sortedUnpinnedRows,
|
|
71
|
+
expandedRowIds,
|
|
72
|
+
getDetailPanelHeight,
|
|
73
|
+
pinnedTopRowsLength,
|
|
74
|
+
pinnedBottomRowsLength,
|
|
75
|
+
visibleOrderedColumns,
|
|
76
|
+
pinnedColumns,
|
|
77
|
+
columnWidths,
|
|
78
|
+
viewportWidth,
|
|
79
|
+
checkboxSelection,
|
|
80
|
+
hasDetailPanel,
|
|
81
|
+
rowReordering,
|
|
82
|
+
paginationMode,
|
|
83
|
+
isLoading,
|
|
84
|
+
pageSize,
|
|
85
|
+
} = params;
|
|
86
|
+
|
|
87
|
+
return useMemo<LayoutResult<R>>(() => {
|
|
88
|
+
const unpinnedRows = pagination ? paginatedUnpinnedRows : sortedUnpinnedRows;
|
|
89
|
+
|
|
90
|
+
const rowHeights = unpinnedRows.map((row, index) => {
|
|
91
|
+
let height = rowHeight;
|
|
92
|
+
if (expandedRowIds.has(row.id)) {
|
|
93
|
+
const detailHeight = getDetailPanelHeight?.({ row, id: row.id, rowIndex: pinnedTopRowsLength + index }) ?? 200;
|
|
94
|
+
height += typeof detailHeight === 'number' ? detailHeight : parseInt(String(detailHeight), 10) || 200;
|
|
95
|
+
}
|
|
96
|
+
return height;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const cumulativeHeights = rowHeights.reduce((acc, height, index) => {
|
|
100
|
+
acc.push((acc[index - 1] || 0) + height);
|
|
101
|
+
return acc;
|
|
102
|
+
}, [] as number[]);
|
|
103
|
+
|
|
104
|
+
const skeletonCount = (paginationMode === 'infinite' && isLoading && unpinnedRows.length > 0)
|
|
105
|
+
? Math.min(pageSize, 20)
|
|
106
|
+
: 0;
|
|
107
|
+
const unpinnedRowsHeight = (cumulativeHeights[cumulativeHeights.length - 1] || 0) + skeletonCount * rowHeight;
|
|
108
|
+
const pinnedTopHeight = pinnedTopRowsLength * rowHeight;
|
|
109
|
+
const pinnedBottomHeight = pinnedBottomRowsLength * rowHeight;
|
|
110
|
+
|
|
111
|
+
const rawLeftPinnedCols: GridColDef<R>[] = [];
|
|
112
|
+
const rawRightPinnedCols: GridColDef<R>[] = [];
|
|
113
|
+
const unpinnedCols: GridColDef<R>[] = [];
|
|
114
|
+
const unpinnedColWidths: number[] = [];
|
|
115
|
+
|
|
116
|
+
visibleOrderedColumns.forEach(col => {
|
|
117
|
+
const pinned = isColumnPinned(col.field, pinnedColumns);
|
|
118
|
+
if (pinned === 'left') {
|
|
119
|
+
rawLeftPinnedCols.push(col);
|
|
120
|
+
} else if (pinned === 'right') {
|
|
121
|
+
rawRightPinnedCols.push(col);
|
|
122
|
+
} else {
|
|
123
|
+
unpinnedCols.push(col);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const systemColumnsWidth = (checkboxSelection ? 48 : 0) + (hasDetailPanel ? 48 : 0) + (rowReordering ? 48 : 0);
|
|
128
|
+
|
|
129
|
+
const getColWidth = (c: GridColDef<R>) => columnWidths[c.field] ?? c.width;
|
|
130
|
+
|
|
131
|
+
const leftWidth = rawLeftPinnedCols.reduce((sum, c) => {
|
|
132
|
+
const parsed = parseWidth(getColWidth(c));
|
|
133
|
+
return sum + (parsed.type === 'fixed' ? parsed.value : 100);
|
|
134
|
+
}, 0);
|
|
135
|
+
const rightWidth = rawRightPinnedCols.reduce((sum, c) => {
|
|
136
|
+
const parsed = parseWidth(getColWidth(c));
|
|
137
|
+
return sum + (parsed.type === 'fixed' ? parsed.value : 100);
|
|
138
|
+
}, 0);
|
|
139
|
+
|
|
140
|
+
const naturalFlexWidth = unpinnedCols.reduce((sum, col) => {
|
|
141
|
+
const hasManualWidth = Object.prototype.hasOwnProperty.call(columnWidths, col.field);
|
|
142
|
+
if (!hasManualWidth && col.flex && col.flex > 0) {
|
|
143
|
+
const minWidth = col.minWidth ?? 150;
|
|
144
|
+
const w = typeof col.width === 'number' ? col.width : 150;
|
|
145
|
+
return sum + (minWidth || w);
|
|
146
|
+
}
|
|
147
|
+
const parsed = parseWidth(getColWidth(col));
|
|
148
|
+
if (parsed.type === 'fixed') return sum + parsed.value;
|
|
149
|
+
if (parsed.type === 'percentage') return sum + 100;
|
|
150
|
+
return sum + (col.minWidth ?? 150);
|
|
151
|
+
}, 0);
|
|
152
|
+
|
|
153
|
+
const availableWidth = Math.max(
|
|
154
|
+
viewportWidth - systemColumnsWidth - leftWidth - rightWidth,
|
|
155
|
+
naturalFlexWidth
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const fixedWidthCols: Array<{ col: GridColDef<R>; width: number }> = [];
|
|
159
|
+
const percentageCols: Array<{ col: GridColDef<R>; percentage: number }> = [];
|
|
160
|
+
const flexCols: Array<{ col: GridColDef<R>; flex: number }> = [];
|
|
161
|
+
|
|
162
|
+
unpinnedCols.forEach(col => {
|
|
163
|
+
const hasManualWidth = Object.prototype.hasOwnProperty.call(columnWidths, col.field);
|
|
164
|
+
const parsed = parseWidth(getColWidth(col));
|
|
165
|
+
|
|
166
|
+
if (!hasManualWidth && col.flex && col.flex > 0) {
|
|
167
|
+
flexCols.push({ col, flex: col.flex });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (parsed.type === 'fixed') {
|
|
172
|
+
fixedWidthCols.push({ col, width: parsed.value });
|
|
173
|
+
} else if (parsed.type === 'percentage') {
|
|
174
|
+
percentageCols.push({ col, percentage: parsed.value });
|
|
175
|
+
} else {
|
|
176
|
+
flexCols.push({ col, flex: 1 });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const fixedWidth = fixedWidthCols.reduce((sum, { width }) => sum + width, 0);
|
|
181
|
+
let remainingSpaceForFlex = Math.max(0, availableWidth - fixedWidth);
|
|
182
|
+
|
|
183
|
+
const percentageWidthMap = new Map<string, number>();
|
|
184
|
+
percentageCols.forEach(({ col, percentage }) => {
|
|
185
|
+
const calculatedWidth = (percentage / 100) * remainingSpaceForFlex;
|
|
186
|
+
percentageWidthMap.set(col.field, calculatedWidth);
|
|
187
|
+
remainingSpaceForFlex -= calculatedWidth;
|
|
188
|
+
});
|
|
189
|
+
remainingSpaceForFlex = Math.max(0, remainingSpaceForFlex);
|
|
190
|
+
|
|
191
|
+
const finalFlexWidths = new Map<string, number>();
|
|
192
|
+
const flexItems = flexCols.map(f => ({
|
|
193
|
+
...f,
|
|
194
|
+
minWidth: f.col.minWidth ?? 50,
|
|
195
|
+
maxWidth: f.col.maxWidth ?? Infinity,
|
|
196
|
+
frozen: false,
|
|
197
|
+
computedWidth: 0
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
const solveFlexAllocation = () => {
|
|
201
|
+
let iterations = 0;
|
|
202
|
+
const maxIterations = flexItems.length * 2;
|
|
203
|
+
|
|
204
|
+
while (iterations < maxIterations) {
|
|
205
|
+
iterations++;
|
|
206
|
+
const unfrozen = flexItems.filter(f => !f.frozen);
|
|
207
|
+
if (unfrozen.length === 0) break;
|
|
208
|
+
|
|
209
|
+
const unfrozenFlexTotal = unfrozen.reduce((sum, f) => sum + f.flex, 0);
|
|
210
|
+
const frozenWidthTotal = flexItems.reduce((sum, f) => f.frozen ? sum + f.computedWidth : sum, 0);
|
|
211
|
+
const currentFreeSpace = Math.max(0, remainingSpaceForFlex - frozenWidthTotal);
|
|
212
|
+
|
|
213
|
+
if (unfrozenFlexTotal <= 0) {
|
|
214
|
+
unfrozen.forEach(f => { f.computedWidth = f.minWidth; f.frozen = true; });
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const pixelsPerFlex = currentFreeSpace / unfrozenFlexTotal;
|
|
219
|
+
let totalViolation = 0;
|
|
220
|
+
const minViolators: typeof flexItems = [];
|
|
221
|
+
const maxViolators: typeof flexItems = [];
|
|
222
|
+
|
|
223
|
+
unfrozen.forEach(f => {
|
|
224
|
+
const rawWidth = pixelsPerFlex * f.flex;
|
|
225
|
+
if (rawWidth < f.minWidth) {
|
|
226
|
+
totalViolation += f.minWidth - rawWidth;
|
|
227
|
+
minViolators.push(f);
|
|
228
|
+
} else if (rawWidth > f.maxWidth) {
|
|
229
|
+
totalViolation += f.maxWidth - rawWidth;
|
|
230
|
+
maxViolators.push(f);
|
|
231
|
+
} else {
|
|
232
|
+
f.computedWidth = rawWidth;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (minViolators.length === 0 && maxViolators.length === 0) {
|
|
237
|
+
unfrozen.forEach(f => f.frozen = true);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (totalViolation > 0) {
|
|
242
|
+
minViolators.forEach(f => { f.computedWidth = f.minWidth; f.frozen = true; });
|
|
243
|
+
} else if (totalViolation < 0) {
|
|
244
|
+
maxViolators.forEach(f => { f.computedWidth = f.maxWidth; f.frozen = true; });
|
|
245
|
+
} else {
|
|
246
|
+
minViolators.forEach(f => { f.computedWidth = f.minWidth; f.frozen = true; });
|
|
247
|
+
maxViolators.forEach(f => { f.computedWidth = f.maxWidth; f.frozen = true; });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
solveFlexAllocation();
|
|
253
|
+
flexItems.forEach(f => finalFlexWidths.set(f.col.field, f.computedWidth));
|
|
254
|
+
|
|
255
|
+
const computedWidthMap = new Map<string, number>();
|
|
256
|
+
unpinnedCols.forEach(col => {
|
|
257
|
+
let computedWidth: number;
|
|
258
|
+
if (finalFlexWidths.has(col.field)) {
|
|
259
|
+
computedWidth = finalFlexWidths.get(col.field)!;
|
|
260
|
+
} else {
|
|
261
|
+
const fixedCol = fixedWidthCols.find(f => f.col.field === col.field);
|
|
262
|
+
const pctCol = percentageCols.find(p => p.col.field === col.field);
|
|
263
|
+
if (fixedCol) {
|
|
264
|
+
computedWidth = fixedCol.width;
|
|
265
|
+
} else if (pctCol) {
|
|
266
|
+
const rawPctWidth = percentageWidthMap.get(col.field) || 0;
|
|
267
|
+
computedWidth = Math.max(col.minWidth ?? 50, rawPctWidth || 100);
|
|
268
|
+
} else {
|
|
269
|
+
computedWidth = 100;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
unpinnedColWidths.push(computedWidth);
|
|
273
|
+
computedWidthMap.set(col.field, computedWidth);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const unpinnedColsWithWidth = unpinnedCols.map(col => {
|
|
277
|
+
const flexItem = flexItems.find(f => f.col.field === col.field);
|
|
278
|
+
return {
|
|
279
|
+
...col,
|
|
280
|
+
width: computedWidthMap.get(col.field) ?? (typeof col.width === 'number' ? col.width : 100),
|
|
281
|
+
flex: flexItem ? flexItem.flex : col.flex
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const baseLeftZ = 11;
|
|
286
|
+
const leftPinnedCols = rawLeftPinnedCols.map((col, i) => {
|
|
287
|
+
const parsed = parseWidth(getColWidth(col));
|
|
288
|
+
return { ...col, width: parsed.type === 'fixed' ? parsed.value : 100, zIndex: baseLeftZ + i };
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const baseRightZ = 11;
|
|
292
|
+
const rightPinnedCols = rawRightPinnedCols.map((col, i) => {
|
|
293
|
+
const parsed = parseWidth(getColWidth(col));
|
|
294
|
+
return {
|
|
295
|
+
...col,
|
|
296
|
+
width: parsed.type === 'fixed' ? parsed.value : 100,
|
|
297
|
+
zIndex: baseRightZ + (rawRightPinnedCols.length - 1 - i)
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const unpinnedTotalWidth = unpinnedColWidths.reduce((sum, w) => sum + w, 0);
|
|
302
|
+
const totalWidth = leftWidth + unpinnedTotalWidth + rightWidth + systemColumnsWidth;
|
|
303
|
+
const unpinnedAccWidths = unpinnedColWidths.reduce((acc, w, i) => {
|
|
304
|
+
acc.push((acc[i - 1] || 0) + w);
|
|
305
|
+
return acc;
|
|
306
|
+
}, [] as number[]);
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
rowHeights,
|
|
310
|
+
cumulativeHeights,
|
|
311
|
+
unpinnedRowsHeight,
|
|
312
|
+
pinnedTopHeight,
|
|
313
|
+
pinnedBottomHeight,
|
|
314
|
+
leftPinnedCols,
|
|
315
|
+
rightPinnedCols,
|
|
316
|
+
unpinnedColsWithWidth,
|
|
317
|
+
unpinnedColWidths,
|
|
318
|
+
unpinnedAccWidths,
|
|
319
|
+
totalWidth,
|
|
320
|
+
leftWidth,
|
|
321
|
+
rightWidth,
|
|
322
|
+
unpinnedTotalWidth,
|
|
323
|
+
systemColumnsWidth,
|
|
324
|
+
pinnedTopRowsLength,
|
|
325
|
+
pinnedBottomRowsLength,
|
|
326
|
+
unpinnedRowsLength: unpinnedRows.length,
|
|
327
|
+
};
|
|
328
|
+
}, [
|
|
329
|
+
rowHeight,
|
|
330
|
+
pagination,
|
|
331
|
+
paginatedUnpinnedRows,
|
|
332
|
+
sortedUnpinnedRows,
|
|
333
|
+
expandedRowIds,
|
|
334
|
+
getDetailPanelHeight,
|
|
335
|
+
pinnedTopRowsLength,
|
|
336
|
+
pinnedBottomRowsLength,
|
|
337
|
+
visibleOrderedColumns,
|
|
338
|
+
pinnedColumns,
|
|
339
|
+
columnWidths,
|
|
340
|
+
viewportWidth,
|
|
341
|
+
checkboxSelection,
|
|
342
|
+
hasDetailPanel,
|
|
343
|
+
rowReordering,
|
|
344
|
+
paginationMode,
|
|
345
|
+
isLoading,
|
|
346
|
+
pageSize,
|
|
347
|
+
]);
|
|
348
|
+
}
|