@opencorestack/opengridx 0.1.5 → 0.1.7

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.
Files changed (130) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +9 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/opengridx.css +1 -1
  5. package/dist/opengridx.es.js +319 -319
  6. package/dist/opengridx.es.js.map +1 -1
  7. package/dist/opengridx.umd.js +7 -7
  8. package/dist/opengridx.umd.js.map +1 -1
  9. package/docs/API_REFERENCE.md +177 -0
  10. package/docs/README.md +60 -0
  11. package/docs/components/cell.md +44 -0
  12. package/docs/components/column-group-header.md +57 -0
  13. package/docs/components/column-resize.md +63 -0
  14. package/docs/components/column-visibility.md +61 -0
  15. package/docs/components/datagrid.md +70 -0
  16. package/docs/components/filter-panel.md +26 -0
  17. package/docs/components/header.md +39 -0
  18. package/docs/components/pagination.md +34 -0
  19. package/docs/components/row.md +31 -0
  20. package/docs/components/toolbar.md +40 -0
  21. package/docs/components/tooltip.md +68 -0
  22. package/docs/customization/slots-api.md +227 -0
  23. package/docs/customization/theming.md +293 -0
  24. package/docs/features/aggregation-pivot.md +79 -0
  25. package/docs/features/cell-spanning.md +520 -0
  26. package/docs/features/clipboard.md +76 -0
  27. package/docs/features/custom-pagination.md +143 -0
  28. package/docs/features/data-source.md +72 -0
  29. package/docs/features/editing-reordering.md +99 -0
  30. package/docs/features/export-guide.md +434 -0
  31. package/docs/features/filtering.md +83 -0
  32. package/docs/features/infinite-scroll.md +87 -0
  33. package/docs/features/list-view.md +65 -0
  34. package/docs/features/loading-states.md +68 -0
  35. package/docs/features/master-detail.md +46 -0
  36. package/docs/features/pinning.md +67 -0
  37. package/docs/features/selection.md +70 -0
  38. package/docs/features/sorting-pagination.md +66 -0
  39. package/docs/features/state-persistence.md +67 -0
  40. package/docs/features/tree-data-grouping.md +67 -0
  41. package/docs/features/virtualization.md +188 -0
  42. package/docs/roadmap.md +71 -0
  43. package/lib/components/Cell/Cell.css +119 -0
  44. package/lib/components/Cell/Cell.tsx +264 -0
  45. package/lib/components/Cell/GridEditInputCell.css +62 -0
  46. package/lib/components/Cell/GridEditInputCell.tsx +152 -0
  47. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +67 -0
  48. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +220 -0
  49. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +31 -0
  50. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +85 -0
  51. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +137 -0
  52. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +190 -0
  53. package/lib/components/DataGrid/DataGrid.css +224 -0
  54. package/lib/components/DataGrid/DataGrid.tsx +2253 -0
  55. package/lib/components/DetailPanel/DetailPanel.css +36 -0
  56. package/lib/components/DetailPanel/DetailPanel.tsx +34 -0
  57. package/lib/components/FilterPanel/FilterPanel.css +196 -0
  58. package/lib/components/FilterPanel/FilterPanel.tsx +183 -0
  59. package/lib/components/FilterPanel/index.ts +1 -0
  60. package/lib/components/Header/ColumnMenu.css +68 -0
  61. package/lib/components/Header/ColumnMenu.tsx +249 -0
  62. package/lib/components/Header/Header.css +224 -0
  63. package/lib/components/Header/Header.tsx +516 -0
  64. package/lib/components/ListView/ListView.css +112 -0
  65. package/lib/components/ListView/ListViewRow.tsx +63 -0
  66. package/lib/components/Pagination/Pagination.css +91 -0
  67. package/lib/components/Pagination/Pagination.tsx +151 -0
  68. package/lib/components/QuickFilter/QuickFilter.css +23 -0
  69. package/lib/components/QuickFilter/QuickFilter.tsx +79 -0
  70. package/lib/components/Row/Row.css +126 -0
  71. package/lib/components/Row/Row.tsx +377 -0
  72. package/lib/components/Skeleton/Skeleton.css +55 -0
  73. package/lib/components/Skeleton/Skeleton.tsx +22 -0
  74. package/lib/components/SkeletonRow/SkeletonRow.css +32 -0
  75. package/lib/components/SkeletonRow/SkeletonRow.tsx +89 -0
  76. package/lib/components/SkeletonRow/index.ts +1 -0
  77. package/lib/components/Toolbar/GlobalSearch.css +113 -0
  78. package/lib/components/Toolbar/GlobalSearch.tsx +120 -0
  79. package/lib/components/Toolbar/GridToolbar.css +204 -0
  80. package/lib/components/Toolbar/GridToolbar.tsx +647 -0
  81. package/lib/components/Toolbar/PivotPanel.css +212 -0
  82. package/lib/components/Toolbar/PivotPanel.tsx +239 -0
  83. package/lib/components/Tooltip/Tooltip.css +59 -0
  84. package/lib/components/Tooltip/Tooltip.tsx +106 -0
  85. package/lib/components/ui/Button.css +89 -0
  86. package/lib/components/ui/Button.tsx +39 -0
  87. package/lib/components/ui/Checkbox.css +71 -0
  88. package/lib/components/ui/Checkbox.tsx +66 -0
  89. package/lib/components/ui/DragHandleIcon.css +19 -0
  90. package/lib/components/ui/DragHandleIcon.tsx +18 -0
  91. package/lib/components/ui/ExpandIcon.css +38 -0
  92. package/lib/components/ui/ExpandIcon.tsx +75 -0
  93. package/lib/components/ui/Input.css +59 -0
  94. package/lib/components/ui/Input.tsx +45 -0
  95. package/lib/components/ui/index.ts +15 -0
  96. package/lib/hooks/core/useDataGrid.ts +392 -0
  97. package/lib/hooks/core/useGridApiRef.ts +10 -0
  98. package/lib/hooks/features/useAggregation.ts +143 -0
  99. package/lib/hooks/features/useGridClipboard.ts +133 -0
  100. package/lib/hooks/features/useGridDataSource.ts +190 -0
  101. package/lib/hooks/features/useGridEditing.ts +100 -0
  102. package/lib/hooks/features/useGridSpanning.ts +349 -0
  103. package/lib/hooks/features/usePivot.ts +149 -0
  104. package/lib/hooks/useColumnReorder.ts +114 -0
  105. package/lib/hooks/useRowGrouping.ts +302 -0
  106. package/lib/hooks/useRowReorder.ts +83 -0
  107. package/lib/hooks/useTreeData.ts +323 -0
  108. package/lib/index.ts +106 -0
  109. package/lib/state/index.ts +17 -0
  110. package/lib/state/types.ts +48 -0
  111. package/lib/state/useGridStateStorage.ts +113 -0
  112. package/lib/styles/base/reset.css +21 -0
  113. package/lib/styles/base/variables.css +161 -0
  114. package/lib/styles/opengridx.css +27 -0
  115. package/lib/styles/opengridx.css.d.ts +4 -0
  116. package/lib/theme/DataGridThemeProvider.tsx +276 -0
  117. package/lib/theme/defaultStyles.ts +0 -0
  118. package/lib/theme/index.ts +21 -0
  119. package/lib/theme/presets.ts +148 -0
  120. package/lib/theme/types.ts +155 -0
  121. package/lib/types/index.ts +764 -0
  122. package/lib/utils/export/exportToExcelAdvanced.ts +516 -0
  123. package/lib/utils/export/index.ts +550 -0
  124. package/lib/utils/filtering/index.ts +240 -0
  125. package/lib/utils/pinning/index.ts +282 -0
  126. package/lib/utils/sorting/index.ts +55 -0
  127. package/lib/utils/virtualization/index.ts +106 -0
  128. package/lib/vite-env.d.ts +1 -0
  129. package/llms.txt +12 -2
  130. package/package.json +13 -4
@@ -0,0 +1,190 @@
1
+ import { useEffect, useRef, useCallback } from 'react';
2
+ import {
3
+ GridDataSource,
4
+ GridRowModel,
5
+ GridSortItem,
6
+ GridFilterModel,
7
+ GridPaginationModel,
8
+ GridGetRowsParams,
9
+ GridAggregationModel,
10
+ GridAggregationResult,
11
+ } from '../../types';
12
+
13
+ interface UseGridDataSourceParams<R extends GridRowModel> {
14
+ dataSource?: GridDataSource<R>;
15
+ sortModel: GridSortItem[];
16
+ filterModel: GridFilterModel;
17
+ paginationModel: GridPaginationModel;
18
+ paginationMode?: 'client' | 'server' | 'infinite';
19
+ sortingMode?: 'client' | 'server';
20
+ filterMode?: 'client' | 'server';
21
+ aggregationModel?: GridAggregationModel;
22
+ setRows: (rows: R[] | ((prev: R[]) => R[]), preserveRowCount?: boolean) => void;
23
+ setRowCount: (count: number) => void;
24
+ setDataSourceLoading: (loading: boolean) => void;
25
+ setDataSourceError: (error: any) => void;
26
+ onAggregationResults?: (results: GridAggregationResult) => void;
27
+ }
28
+
29
+ export function useGridDataSource<R extends GridRowModel>(params: UseGridDataSourceParams<R>) {
30
+ const {
31
+ dataSource,
32
+ sortModel,
33
+ filterModel,
34
+ paginationModel,
35
+ paginationMode,
36
+ sortingMode,
37
+ filterMode,
38
+ aggregationModel,
39
+ setRows,
40
+ setRowCount,
41
+ setDataSourceLoading,
42
+ setDataSourceError,
43
+ onAggregationResults,
44
+ } = params;
45
+
46
+ const prevParamsRef = useRef<{
47
+ sortModel: GridSortItem[];
48
+ filterModel: GridFilterModel;
49
+ page: number;
50
+ pageSize: number;
51
+ } | null>(null);
52
+
53
+ const activeRequestRef = useRef<number>(0);
54
+
55
+ const fetchRows = useCallback(async () => {
56
+ if (!dataSource) return;
57
+
58
+ const requestId = ++activeRequestRef.current;
59
+
60
+ setDataSourceLoading(true);
61
+ setDataSourceError(null);
62
+
63
+ const startRow = paginationModel.page * paginationModel.pageSize;
64
+ const endRow = startRow + paginationModel.pageSize;
65
+
66
+ const requestParams: GridGetRowsParams = {
67
+ startRow,
68
+ endRow,
69
+ sortModel,
70
+ filterModel,
71
+ groupKeys: [],
72
+ aggregationModel,
73
+ };
74
+
75
+ try {
76
+ const response = await dataSource.getRows(requestParams);
77
+
78
+ if (requestId !== activeRequestRef.current) {
79
+ return;
80
+ }
81
+
82
+ let shouldAppend = false;
83
+
84
+ if (paginationMode === 'infinite') {
85
+ const prev = prevParamsRef.current;
86
+ if (prev) {
87
+
88
+ const sortChanged = JSON.stringify(prev.sortModel) !== JSON.stringify(sortModel);
89
+ const filterChanged = JSON.stringify(prev.filterModel) !== JSON.stringify(filterModel);
90
+ const pageSizeChanged = prev.pageSize !== paginationModel.pageSize;
91
+
92
+ if (!sortChanged && !filterChanged && !pageSizeChanged && paginationModel.page > prev.page) {
93
+ shouldAppend = true;
94
+ }
95
+ }
96
+ }
97
+
98
+ if (shouldAppend) {
99
+ setRows((prevRows) => [...prevRows, ...response.rows], true);
100
+ } else {
101
+ setRows(response.rows, true);
102
+ }
103
+
104
+ if (response.aggregationResults && onAggregationResults) {
105
+ onAggregationResults(response.aggregationResults);
106
+ }
107
+
108
+ if (response.rowCount !== undefined) {
109
+ setRowCount(response.rowCount);
110
+ } else if (paginationMode === 'infinite') {
111
+
112
+ }
113
+
114
+ prevParamsRef.current = {
115
+ sortModel,
116
+ filterModel,
117
+ page: paginationModel.page,
118
+ pageSize: paginationModel.pageSize
119
+ };
120
+
121
+ } catch (error) {
122
+ if (requestId === activeRequestRef.current) {
123
+ setDataSourceError(error);
124
+ console.error('Data Source Error:', error);
125
+ }
126
+ } finally {
127
+ if (requestId === activeRequestRef.current) {
128
+ setDataSourceLoading(false);
129
+ }
130
+ }
131
+ }, [
132
+ dataSource,
133
+ paginationModel.page,
134
+ paginationModel.pageSize,
135
+ sortModel,
136
+ filterModel,
137
+ paginationMode,
138
+ aggregationModel,
139
+ onAggregationResults,
140
+ setRows,
141
+ setRowCount,
142
+ setDataSourceLoading,
143
+ setDataSourceError
144
+ ]);
145
+
146
+ useEffect(() => {
147
+ const isServerSide = paginationMode === 'server' || paginationMode === 'infinite' || sortingMode === 'server' || filterMode === 'server';
148
+
149
+ if (dataSource && isServerSide) {
150
+ const timer = setTimeout(() => {
151
+ fetchRows();
152
+ }, 300);
153
+ return () => clearTimeout(timer);
154
+ }
155
+ }, [fetchRows, dataSource, paginationMode, sortingMode, filterMode]);
156
+
157
+ const fetchChildren = useCallback(async (_parentId: string | number, groupKeys: string[]) => {
158
+ if (!dataSource) return;
159
+
160
+ setDataSourceLoading(true);
161
+ try {
162
+ const response = await dataSource.getRows({
163
+ startRow: 0,
164
+ endRow: -1,
165
+ sortModel,
166
+ filterModel,
167
+ groupKeys
168
+ });
169
+
170
+ if (response.rows.length > 0) {
171
+ setRows((prevRows) => {
172
+
173
+ const existingIds = new Set(prevRows.map(r => r.id));
174
+ const newRows = response.rows.filter(r => !existingIds.has(r.id));
175
+ return [...prevRows, ...newRows];
176
+ });
177
+ }
178
+ } catch (error) {
179
+ setDataSourceError(error);
180
+ console.error('Failed to fetch children:', error);
181
+ } finally {
182
+ setDataSourceLoading(false);
183
+ }
184
+ }, [dataSource, sortModel, filterModel, setRows, setDataSourceLoading, setDataSourceError]);
185
+
186
+ return {
187
+ fetchRows,
188
+ fetchChildren
189
+ };
190
+ }
@@ -0,0 +1,100 @@
1
+ import { useState, useCallback, useRef } from 'react';
2
+ import type { GridRowId, GridRowModel } from '../../types';
3
+
4
+ export interface GridEditingState {
5
+
6
+ editingCell: {
7
+ id: GridRowId;
8
+ field: string;
9
+ value: any;
10
+ originalValue: any;
11
+ } | null;
12
+ }
13
+
14
+ export interface UseGridEditingParams<R extends GridRowModel> {
15
+ rows: R[];
16
+ getRowId: (row: R) => GridRowId;
17
+ processRowUpdate?: (newRow: R, oldRow: R) => R | Promise<R>;
18
+ onProcessRowUpdateError?: (error: any) => void;
19
+ onRowChange?: (newRow: R) => void;
20
+ }
21
+
22
+ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingParams<R>) {
23
+ const {
24
+ rows,
25
+ getRowId,
26
+ processRowUpdate,
27
+ onProcessRowUpdateError,
28
+ onRowChange
29
+ } = params;
30
+
31
+ const [editingCell, setEditingCellState] = useState<GridEditingState['editingCell']>(null);
32
+ const editingCellRef = useRef<GridEditingState['editingCell']>(null);
33
+
34
+ const setEditingCell = useCallback((val: any) => {
35
+ setEditingCellState(val);
36
+ editingCellRef.current = typeof val === 'function' ? val(editingCellRef.current) : val;
37
+ }, []);
38
+
39
+ const startCellEdit = useCallback((params: { id: GridRowId, field: string, value: any }) => {
40
+ setEditingCell({
41
+ id: params.id,
42
+ field: params.field,
43
+ value: params.value,
44
+ originalValue: params.value
45
+ });
46
+ }, []);
47
+
48
+ const stopCellEdit = useCallback(async (params?: { cancel?: boolean }) => {
49
+ const currentCell = editingCellRef.current;
50
+ if (!currentCell) return;
51
+
52
+ if (params?.cancel) {
53
+ setEditingCell(null);
54
+ return;
55
+ }
56
+
57
+ const { id, field, value, originalValue } = currentCell;
58
+
59
+ if (value === originalValue) {
60
+ setEditingCell(null);
61
+ return;
62
+ }
63
+
64
+ const existingRow = rows.find(r => getRowId(r) === id);
65
+ if (!existingRow) {
66
+ setEditingCell(null);
67
+ return;
68
+ }
69
+
70
+ const newRow = { ...existingRow, [field]: value };
71
+
72
+ try {
73
+ if (processRowUpdate) {
74
+ const processedRow = await processRowUpdate(newRow, existingRow);
75
+ onRowChange?.(processedRow);
76
+ } else {
77
+ onRowChange?.(newRow);
78
+ }
79
+ setEditingCell(null);
80
+ } catch (error) {
81
+ if (onProcessRowUpdateError) {
82
+ onProcessRowUpdateError(error);
83
+ }
84
+ }
85
+
86
+ }, [editingCell, rows, getRowId, processRowUpdate, onRowChange, onProcessRowUpdateError]);
87
+
88
+ const setEditCellValue = useCallback((params: { id: GridRowId, field: string, value: any }) => {
89
+ if (editingCell && editingCell.id === params.id && editingCell.field === params.field) {
90
+ setEditingCell((prev: any) => prev ? { ...prev, value: params.value } : null);
91
+ }
92
+ }, [editingCell]);
93
+
94
+ return {
95
+ editingCell,
96
+ startCellEdit,
97
+ stopCellEdit,
98
+ setEditCellValue
99
+ };
100
+ }
@@ -0,0 +1,349 @@
1
+
2
+ import { useState, useCallback, useEffect } from 'react';
3
+ import type { GridRowId, GridColDef, GridRowModel, GridRenderCellParams } from '../../types';
4
+
5
+ type ColumnKey = string;
6
+
7
+ export type CellColSpanInfo =
8
+ | {
9
+ spannedByColSpan: true;
10
+ rightVisibleCellIndex: number;
11
+ leftVisibleCellIndex: number;
12
+ }
13
+ | {
14
+ spannedByColSpan: false;
15
+ cellProps: {
16
+ colSpan: number;
17
+ width: number;
18
+ };
19
+ };
20
+
21
+ type ColspanMap = Map<GridRowId, Record<ColumnKey, CellColSpanInfo>>;
22
+
23
+ export interface RowSpanningCaches {
24
+ spannedCells: Record<GridRowId, Record<ColumnKey, number>>;
25
+ hiddenCells: Record<GridRowId, Record<ColumnKey, boolean>>;
26
+ hiddenCellOriginMap: Record<number, Record<ColumnKey, number>>;
27
+ }
28
+
29
+ export interface RowSpanningState {
30
+ caches: RowSpanningCaches;
31
+ processedRange: { firstRowIndex: number; lastRowIndex: number; };
32
+ }
33
+
34
+ export function useGridSpanning<R extends GridRowModel>(
35
+ rows: R[],
36
+ columns: GridColDef<R>[],
37
+ columnWidths: Record<string, number>
38
+ ) {
39
+
40
+ const [colspanMap, setColspanMap] = useState<ColspanMap>(new Map());
41
+
42
+ const [rowSpanningState, setRowSpanningState] = useState<RowSpanningState>({
43
+ caches: {
44
+ spannedCells: {},
45
+ hiddenCells: {},
46
+ hiddenCellOriginMap: {}
47
+ },
48
+ processedRange: { firstRowIndex: 0, lastRowIndex: 0 }
49
+ });
50
+
51
+ const resetColSpan = useCallback(() => {
52
+ setColspanMap(new Map());
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
+ }
112
+
113
+ setCellColSpanInfo(newMap, rowId, colDef.field, {
114
+ spannedByColSpan: false,
115
+ cellProps: { colSpan, width }
116
+ });
117
+
118
+ i += colSpan - 1;
119
+ }
120
+
121
+ setColspanMap(newMap);
122
+ }, [colspanMap, columns, columnWidths]);
123
+
124
+ const calculateRowSpan = useCallback((
125
+ firstRowIndex: number,
126
+ lastRowIndex: number
127
+ ) => {
128
+ const spannedCells: Record<GridRowId, Record<ColumnKey, number>> = {};
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
+ }
160
+
161
+ if (span > 1) {
162
+ if (!spannedCells[row.id]) spannedCells[row.id] = {};
163
+ spannedCells[row.id][colDef.field] = span;
164
+
165
+ for (let k = 1; k < span; k++) {
166
+ const nextIndex = index + k;
167
+ if (nextIndex < lastRowIndex && rows[nextIndex]) {
168
+ const nextRow = rows[nextIndex];
169
+ if (!hiddenCells[nextRow.id]) hiddenCells[nextRow.id] = {};
170
+ hiddenCells[nextRow.id][colDef.field] = true;
171
+
172
+ if (!hiddenCellOriginMap[nextIndex]) hiddenCellOriginMap[nextIndex] = {};
173
+ hiddenCellOriginMap[nextIndex][colDef.field] = index;
174
+ }
175
+ }
176
+ }
177
+ }
178
+ });
179
+
180
+ setRowSpanningState({
181
+ caches: { spannedCells, hiddenCells, hiddenCellOriginMap },
182
+ processedRange: { firstRowIndex, lastRowIndex }
183
+ });
184
+ }, [rows, columns]);
185
+
186
+ const resetRowSpan = useCallback(() => {
187
+ setRowSpanningState({
188
+ caches: {
189
+ spannedCells: {},
190
+ hiddenCells: {},
191
+ hiddenCellOriginMap: {}
192
+ },
193
+ processedRange: { firstRowIndex: 0, lastRowIndex: 0 }
194
+ });
195
+ }, []);
196
+
197
+ useEffect(() => {
198
+
199
+ const newMap = new Map<GridRowId, Record<ColumnKey, CellColSpanInfo>>();
200
+
201
+ rows.forEach((row, index) => {
202
+ for (let i = 0; i < columns.length; i++) {
203
+ const colDef = columns[i];
204
+ if (!colDef) continue;
205
+
206
+ let colSpan = 1;
207
+ if (colDef.colSpan) {
208
+ if (typeof colDef.colSpan === 'function') {
209
+ const params: GridRenderCellParams<R> = {
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
+ }
221
+ }
222
+
223
+ if (colSpan <= 1) {
224
+ const colWidth = columnWidths[colDef.field] || (typeof colDef.width === 'number' ? colDef.width : 100);
225
+ setCellColSpanInfo(newMap, row.id, colDef.field, {
226
+ spannedByColSpan: false,
227
+ cellProps: { colSpan: 1, width: colWidth }
228
+ });
229
+ continue;
230
+ }
231
+
232
+ let width = columnWidths[colDef.field] || (typeof colDef.width === 'number' ? colDef.width : 100);
233
+ for (let j = 1; j < colSpan; j++) {
234
+ if (i + j < columns.length) {
235
+ const nextCol = columns[i + j];
236
+ if (nextCol) {
237
+ const nextColWidth = columnWidths[nextCol.field] || (typeof nextCol.width === 'number' ? nextCol.width : 100);
238
+ width += nextColWidth;
239
+
240
+ setCellColSpanInfo(newMap, row.id, nextCol.field, {
241
+ spannedByColSpan: true,
242
+ leftVisibleCellIndex: i,
243
+ rightVisibleCellIndex: Math.min(i + colSpan - 1, columns.length - 1)
244
+ });
245
+ }
246
+ }
247
+ }
248
+
249
+ setCellColSpanInfo(newMap, row.id, colDef.field, {
250
+ spannedByColSpan: false,
251
+ cellProps: { colSpan, width }
252
+ });
253
+
254
+ i += colSpan - 1;
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;
290
+ }
291
+
292
+ if (span > 1) {
293
+ if (!spannedCells[row.id]) spannedCells[row.id] = {};
294
+ spannedCells[row.id][colDef.field] = span;
295
+
296
+ for (let k = 1; k < span; k++) {
297
+ const nextIndex = index + k;
298
+ if (nextIndex < rows.length && rows[nextIndex]) {
299
+ const nextRow = rows[nextIndex];
300
+ if (!hiddenCells[nextRow.id]) hiddenCells[nextRow.id] = {};
301
+ hiddenCells[nextRow.id][colDef.field] = true;
302
+
303
+ if (!hiddenCellOriginMap[nextIndex]) hiddenCellOriginMap[nextIndex] = {};
304
+ hiddenCellOriginMap[nextIndex][colDef.field] = index;
305
+ }
306
+ }
307
+ }
308
+ }
309
+ });
310
+
311
+ setRowSpanningState({
312
+ caches: { spannedCells, hiddenCells, hiddenCellOriginMap },
313
+ processedRange: { firstRowIndex: 0, lastRowIndex: rows.length }
314
+ });
315
+ }, [rows, columns, columnWidths]);
316
+
317
+ const getSpannedCells = useCallback(() => rowSpanningState.caches.spannedCells, [rowSpanningState]);
318
+ const getHiddenCells = useCallback(() => rowSpanningState.caches.hiddenCells, [rowSpanningState]);
319
+ const getHiddenCellOriginMap = useCallback(() => rowSpanningState.caches.hiddenCellOriginMap, [rowSpanningState]);
320
+
321
+ return {
322
+
323
+ colspanMap,
324
+ resetColSpan,
325
+ getCellColSpanInfo,
326
+ calculateColSpan,
327
+
328
+ rowSpanningState,
329
+ calculateRowSpan,
330
+ resetRowSpan,
331
+ getSpannedCells,
332
+ getHiddenCells,
333
+ getHiddenCellOriginMap
334
+ };
335
+ }
336
+
337
+ function setCellColSpanInfo(
338
+ map: ColspanMap,
339
+ rowId: GridRowId,
340
+ field: string,
341
+ info: CellColSpanInfo
342
+ ) {
343
+ let columnInfo = map.get(rowId);
344
+ if (!columnInfo) {
345
+ columnInfo = {};
346
+ map.set(rowId, columnInfo);
347
+ }
348
+ columnInfo[field] = info;
349
+ }