@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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { RefObject } from 'react';
|
|
3
|
+
import type { GridToolbarHost } from './gridToolbarHostContext';
|
|
4
|
+
|
|
5
|
+
export interface UseGridColumnsPanelResult {
|
|
6
|
+
/** Provide through `GridToolbarHostContext` around the toolbar slot. */
|
|
7
|
+
toolbarHost: GridToolbarHost;
|
|
8
|
+
/** Passed to the toolbar as `forceColumnsOpen`. */
|
|
9
|
+
toolbarPanelRequested: boolean;
|
|
10
|
+
/** Passed to the toolbar as `onColumnsPanelClose`. */
|
|
11
|
+
closeToolbarPanel: () => void;
|
|
12
|
+
/** Whether the standalone Columns panel (used when no toolbar can show one) is open. */
|
|
13
|
+
standalonePanelOpen: boolean;
|
|
14
|
+
closeStandalonePanel: () => void;
|
|
15
|
+
/** Attach to the standalone panel; a mousedown outside it closes it. */
|
|
16
|
+
standalonePanelRef: RefObject<HTMLDivElement | null>;
|
|
17
|
+
/** The column menu's **Manage columns** action. */
|
|
18
|
+
openColumnsPanel: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Opens the Columns panel for the column menu: in the toolbar when a `GridToolbar` has registered
|
|
23
|
+
* through `GridToolbarHostContext`, otherwise as the standalone panel, which closes on Escape or a
|
|
24
|
+
* mousedown outside it.
|
|
25
|
+
*/
|
|
26
|
+
export function useGridColumnsPanel(): UseGridColumnsPanelResult {
|
|
27
|
+
const [toolbarPanelRequested, setToolbarPanelRequested] = useState(false);
|
|
28
|
+
const [standalonePanelOpen, setStandalonePanelOpen] = useState(false);
|
|
29
|
+
const standalonePanelRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
const toolbarHostCountRef = useRef(0);
|
|
31
|
+
|
|
32
|
+
const toolbarHost = useMemo<GridToolbarHost>(() => ({
|
|
33
|
+
registerColumnsPanel: () => {
|
|
34
|
+
toolbarHostCountRef.current += 1;
|
|
35
|
+
return () => { toolbarHostCountRef.current -= 1; };
|
|
36
|
+
},
|
|
37
|
+
}), []);
|
|
38
|
+
|
|
39
|
+
const openColumnsPanel = useCallback(() => {
|
|
40
|
+
if (toolbarHostCountRef.current > 0) {
|
|
41
|
+
setToolbarPanelRequested(true);
|
|
42
|
+
} else {
|
|
43
|
+
setStandalonePanelOpen(true);
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const closeToolbarPanel = useCallback(() => setToolbarPanelRequested(false), []);
|
|
48
|
+
const closeStandalonePanel = useCallback(() => setStandalonePanelOpen(false), []);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!standalonePanelOpen) return;
|
|
52
|
+
function handleMouseDown(e: MouseEvent) {
|
|
53
|
+
if (standalonePanelRef.current && !standalonePanelRef.current.contains(e.target as Node)) {
|
|
54
|
+
setStandalonePanelOpen(false);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
58
|
+
if (e.key === 'Escape') setStandalonePanelOpen(false);
|
|
59
|
+
}
|
|
60
|
+
document.addEventListener('mousedown', handleMouseDown);
|
|
61
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
62
|
+
return () => {
|
|
63
|
+
document.removeEventListener('mousedown', handleMouseDown);
|
|
64
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
65
|
+
};
|
|
66
|
+
}, [standalonePanelOpen]);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
toolbarHost,
|
|
70
|
+
toolbarPanelRequested,
|
|
71
|
+
closeToolbarPanel,
|
|
72
|
+
standalonePanelOpen,
|
|
73
|
+
closeStandalonePanel,
|
|
74
|
+
standalonePanelRef,
|
|
75
|
+
openColumnsPanel,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo } from 'react';
|
|
1
|
+
import { useState, useCallback, useMemo, useLayoutEffect, useRef } from 'react';
|
|
2
2
|
import type {
|
|
3
3
|
GridRowId,
|
|
4
4
|
GridSortItem,
|
|
5
|
+
GridFilterModel,
|
|
5
6
|
GridColumnPinning,
|
|
6
7
|
GridPaginationModel,
|
|
7
8
|
GridAggregationModel,
|
|
8
9
|
GridPivotModel,
|
|
9
10
|
} from '../../types';
|
|
10
|
-
import type { GridInitialState } from '../../state/types';
|
|
11
|
+
import type { GridInitialState, GridDensityState } from '../../state/types';
|
|
12
|
+
|
|
13
|
+
export type GridDensity = GridDensityState['density'];
|
|
11
14
|
|
|
12
15
|
export interface UseGridControlledStateParams {
|
|
13
16
|
initialState?: GridInitialState;
|
|
@@ -15,6 +18,9 @@ export interface UseGridControlledStateParams {
|
|
|
15
18
|
sortModel?: GridSortItem[];
|
|
16
19
|
onSortModelChange?: (model: GridSortItem[]) => void;
|
|
17
20
|
|
|
21
|
+
filterModel?: GridFilterModel;
|
|
22
|
+
onFilterModelChange?: (model: GridFilterModel) => void;
|
|
23
|
+
|
|
18
24
|
aggregationModel?: GridAggregationModel;
|
|
19
25
|
onAggregationModelChange?: (model: GridAggregationModel) => void;
|
|
20
26
|
|
|
@@ -29,9 +35,13 @@ export interface UseGridControlledStateParams {
|
|
|
29
35
|
|
|
30
36
|
paginationModel?: GridPaginationModel;
|
|
31
37
|
onPaginationModelChange?: (model: GridPaginationModel) => void;
|
|
38
|
+
/** Used to pick the uncontrolled default page size: 100 when offered, else the first option. */
|
|
39
|
+
pageSizeOptions?: number[];
|
|
32
40
|
|
|
33
41
|
rowSelectionModel?: GridRowId[];
|
|
34
42
|
onRowSelectionModelChange?: (model: GridRowId[]) => void;
|
|
43
|
+
|
|
44
|
+
density?: GridDensity;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
export interface UseGridControlledStateReturn {
|
|
@@ -39,6 +49,14 @@ export interface UseGridControlledStateReturn {
|
|
|
39
49
|
sortModel: GridSortItem[];
|
|
40
50
|
isSortControlled: boolean;
|
|
41
51
|
setInternalSortModel: React.Dispatch<React.SetStateAction<GridSortItem[]>>;
|
|
52
|
+
/** Sets the internal model when uncontrolled and fires onSortModelChange. */
|
|
53
|
+
handleSortModelChange: (model: GridSortItem[]) => void;
|
|
54
|
+
|
|
55
|
+
// filter
|
|
56
|
+
filterModel: GridFilterModel;
|
|
57
|
+
isFilterControlled: boolean;
|
|
58
|
+
/** Sets the internal model when uncontrolled and fires onFilterModelChange. */
|
|
59
|
+
handleFilterModelChange: (model: GridFilterModel) => void;
|
|
42
60
|
|
|
43
61
|
// aggregation
|
|
44
62
|
aggregationModel: GridAggregationModel;
|
|
@@ -65,14 +83,37 @@ export interface UseGridControlledStateReturn {
|
|
|
65
83
|
selectedRowIds: Set<GridRowId>;
|
|
66
84
|
isSelectionControlled: boolean;
|
|
67
85
|
setInternalRowSelectionModel: React.Dispatch<React.SetStateAction<GridRowId[]>>;
|
|
86
|
+
/** Sets the internal model when uncontrolled and fires onRowSelectionModelChange. */
|
|
87
|
+
handleRowSelectionModelChange: (model: GridRowId[]) => void;
|
|
88
|
+
/**
|
|
89
|
+
* The model passed to `handleRowSelectionModelChange` since the last commit, or null when
|
|
90
|
+
* there is none: a change made in this tick that has not rendered yet (read from
|
|
91
|
+
* onRowSelectionModelChange or right after apiRef.selectRows). Stable identity.
|
|
92
|
+
*/
|
|
93
|
+
getPendingRowSelectionModel: () => GridRowId[] | null;
|
|
94
|
+
|
|
95
|
+
// density (prop, else initialState.density, else 'standard')
|
|
96
|
+
density: GridDensity;
|
|
68
97
|
}
|
|
69
98
|
|
|
70
99
|
const EMPTY_PIVOT_MODEL: GridPivotModel = { rowFields: [], columnFields: [], valueFields: [] };
|
|
100
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
101
|
+
|
|
102
|
+
/** The uncontrolled page size must be one the page-size select can show. */
|
|
103
|
+
export function getDefaultPageSize(pageSizeOptions?: number[]): number {
|
|
104
|
+
if (!pageSizeOptions || pageSizeOptions.length === 0 || pageSizeOptions.includes(DEFAULT_PAGE_SIZE)) {
|
|
105
|
+
return DEFAULT_PAGE_SIZE;
|
|
106
|
+
}
|
|
107
|
+
return pageSizeOptions[0];
|
|
108
|
+
}
|
|
71
109
|
|
|
72
110
|
export function useGridControlledState(params: UseGridControlledStateParams): UseGridControlledStateReturn {
|
|
73
111
|
const {
|
|
74
112
|
initialState,
|
|
75
113
|
sortModel: propSortModel,
|
|
114
|
+
onSortModelChange,
|
|
115
|
+
filterModel: propFilterModel,
|
|
116
|
+
onFilterModelChange,
|
|
76
117
|
aggregationModel: propAggregationModel,
|
|
77
118
|
onAggregationModelChange,
|
|
78
119
|
columnVisibilityModel: propColumnVisibilityModel,
|
|
@@ -83,8 +124,10 @@ export function useGridControlledState(params: UseGridControlledStateParams): Us
|
|
|
83
124
|
onPivotModelChange,
|
|
84
125
|
paginationModel: propPaginationModel,
|
|
85
126
|
onPaginationModelChange,
|
|
127
|
+
pageSizeOptions,
|
|
86
128
|
rowSelectionModel: propRowSelectionModel,
|
|
87
129
|
onRowSelectionModelChange,
|
|
130
|
+
density: propDensity,
|
|
88
131
|
} = params;
|
|
89
132
|
|
|
90
133
|
// ── Sort ─────────────────────────────────────────────────────────────────
|
|
@@ -94,12 +137,36 @@ export function useGridControlledState(params: UseGridControlledStateParams): Us
|
|
|
94
137
|
const isSortControlled = propSortModel !== undefined;
|
|
95
138
|
const sortModel = isSortControlled ? propSortModel! : internalSortModel;
|
|
96
139
|
|
|
140
|
+
const handleSortModelChange = useCallback((model: GridSortItem[]) => {
|
|
141
|
+
if (!isSortControlled) setInternalSortModel(model);
|
|
142
|
+
onSortModelChange?.(model);
|
|
143
|
+
}, [isSortControlled, onSortModelChange]);
|
|
144
|
+
|
|
145
|
+
// ── Filter ───────────────────────────────────────────────────────────────
|
|
146
|
+
const [internalFilterModel, setInternalFilterModel] = useState<GridFilterModel>(
|
|
147
|
+
() => initialState?.filter?.filterModel ?? { items: [] }
|
|
148
|
+
);
|
|
149
|
+
// `!= null` so a JS consumer's `filterModel={null}` behaves like "not passed", as before.
|
|
150
|
+
const isFilterControlled = propFilterModel != null;
|
|
151
|
+
const filterModel = isFilterControlled ? propFilterModel : internalFilterModel;
|
|
152
|
+
|
|
153
|
+
const handleFilterModelChange = useCallback((model: GridFilterModel) => {
|
|
154
|
+
if (!isFilterControlled) setInternalFilterModel(model);
|
|
155
|
+
onFilterModelChange?.(model);
|
|
156
|
+
}, [isFilterControlled, onFilterModelChange]);
|
|
157
|
+
|
|
97
158
|
// ── Aggregation ──────────────────────────────────────────────────────────
|
|
98
159
|
const isAggregationControlled = propAggregationModel !== undefined;
|
|
99
160
|
const [internalAggregationModel, setInternalAggregationModel] = useState<GridAggregationModel>(
|
|
100
161
|
() => propAggregationModel ?? {}
|
|
101
162
|
);
|
|
102
|
-
|
|
163
|
+
// Identity follows content: `aggregationModel={{ salary: 'sum' }}` is a new object on every parent
|
|
164
|
+
// render, and the fetches and memos keyed on the model must not rerun for an unchanged model.
|
|
165
|
+
const aggregationModelKey = JSON.stringify(isAggregationControlled ? propAggregationModel! : internalAggregationModel);
|
|
166
|
+
const aggregationModel = useMemo<GridAggregationModel>(
|
|
167
|
+
() => JSON.parse(aggregationModelKey) as GridAggregationModel,
|
|
168
|
+
[aggregationModelKey],
|
|
169
|
+
);
|
|
103
170
|
|
|
104
171
|
const handleAggregationModelChange = useCallback((model: GridAggregationModel) => {
|
|
105
172
|
if (!isAggregationControlled) setInternalAggregationModel(model);
|
|
@@ -146,7 +213,7 @@ export function useGridControlledState(params: UseGridControlledStateParams): Us
|
|
|
146
213
|
// ── Pagination ───────────────────────────────────────────────────────────
|
|
147
214
|
const isPaginationControlled = propPaginationModel !== undefined;
|
|
148
215
|
const [internalPaginationModel, setInternalPaginationModel] = useState<GridPaginationModel>(
|
|
149
|
-
() => initialState?.pagination?.paginationModel ?? propPaginationModel ?? { page: 0, pageSize:
|
|
216
|
+
() => initialState?.pagination?.paginationModel ?? propPaginationModel ?? { page: 0, pageSize: getDefaultPageSize(pageSizeOptions) }
|
|
150
217
|
);
|
|
151
218
|
const effectivePaginationModel = isPaginationControlled ? propPaginationModel : internalPaginationModel;
|
|
152
219
|
|
|
@@ -161,13 +228,35 @@ export function useGridControlledState(params: UseGridControlledStateParams): Us
|
|
|
161
228
|
const rowSelectionModel = isSelectionControlled ? propRowSelectionModel! : internalRowSelectionModel;
|
|
162
229
|
const selectedRowIds = useMemo(() => new Set(rowSelectionModel), [rowSelectionModel]);
|
|
163
230
|
|
|
164
|
-
//
|
|
165
|
-
|
|
231
|
+
// Cleared on every commit: the rendered model is current again (a controlled parent may
|
|
232
|
+
// also have rejected the change).
|
|
233
|
+
const pendingRowSelectionRef = useRef<GridRowId[] | null>(null);
|
|
234
|
+
useLayoutEffect(() => {
|
|
235
|
+
pendingRowSelectionRef.current = null;
|
|
236
|
+
});
|
|
237
|
+
const getPendingRowSelectionModel = useCallback(() => pendingRowSelectionRef.current, []);
|
|
238
|
+
|
|
239
|
+
const handleRowSelectionModelChange = useCallback((model: GridRowId[]) => {
|
|
240
|
+
pendingRowSelectionRef.current = model;
|
|
241
|
+
if (!isSelectionControlled) setInternalRowSelectionModel(model);
|
|
242
|
+
onRowSelectionModelChange?.(model);
|
|
243
|
+
}, [isSelectionControlled, onRowSelectionModelChange]);
|
|
244
|
+
|
|
245
|
+
// ── Density ──────────────────────────────────────────────────────────────
|
|
246
|
+
// No built-in density UI exists, so there is no internal setter: the prop wins,
|
|
247
|
+
// otherwise the persisted/initial density applies.
|
|
248
|
+
const [initialDensity] = useState<GridDensity>(() => initialState?.density?.density ?? 'standard');
|
|
249
|
+
const density = propDensity ?? initialDensity;
|
|
166
250
|
|
|
167
251
|
return {
|
|
168
252
|
sortModel: sortModel as GridSortItem[],
|
|
169
253
|
isSortControlled,
|
|
170
254
|
setInternalSortModel: setInternalSortModel as React.Dispatch<React.SetStateAction<GridSortItem[]>>,
|
|
255
|
+
handleSortModelChange,
|
|
256
|
+
|
|
257
|
+
filterModel,
|
|
258
|
+
isFilterControlled,
|
|
259
|
+
handleFilterModelChange,
|
|
171
260
|
|
|
172
261
|
aggregationModel,
|
|
173
262
|
handleAggregationModelChange,
|
|
@@ -188,5 +277,9 @@ export function useGridControlledState(params: UseGridControlledStateParams): Us
|
|
|
188
277
|
selectedRowIds,
|
|
189
278
|
isSelectionControlled,
|
|
190
279
|
setInternalRowSelectionModel,
|
|
280
|
+
handleRowSelectionModelChange,
|
|
281
|
+
getPendingRowSelectionModel,
|
|
282
|
+
|
|
283
|
+
density,
|
|
191
284
|
};
|
|
192
285
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { useDetailPanelHeights } from '../features/useDetailPanelHeights';
|
|
3
|
+
import { getDetailPanelRowIds } from '../../utils/detailPanel';
|
|
4
|
+
import type { GridRowId, GridRowMeta } from '../../types';
|
|
5
|
+
|
|
6
|
+
export interface UseGridDetailPanelParams {
|
|
7
|
+
/** Whether `getDetailPanelContent` was passed: the grid then has an expand column. */
|
|
8
|
+
hasDetailPanel: boolean;
|
|
9
|
+
/** `detailPanelExpandedRowIds` prop (controlled when defined). */
|
|
10
|
+
controlledExpandedRowIds: Set<GridRowId> | undefined;
|
|
11
|
+
onDetailPanelExpandedRowIdsChange: ((expandedRowIds: Set<GridRowId>) => void) | undefined;
|
|
12
|
+
rowMetaMap: Map<GridRowId, GridRowMeta>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseGridDetailPanelResult {
|
|
16
|
+
hasDetailPanel: boolean;
|
|
17
|
+
/** What renders and is laid out: synthetic group rows never show a detail panel. */
|
|
18
|
+
expandedRowIds: Set<GridRowId>;
|
|
19
|
+
handleDetailPanelToggle: (rowId: GridRowId) => void;
|
|
20
|
+
/** Measured heights of `'auto'` panels. */
|
|
21
|
+
detailPanelHeights: Map<GridRowId, number>;
|
|
22
|
+
reportDetailPanelHeight: (rowId: GridRowId, height: number) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Detail-panel expansion (controlled or uncontrolled) and the measured heights of `'auto'` panels. */
|
|
26
|
+
export function useGridDetailPanel(params: UseGridDetailPanelParams): UseGridDetailPanelResult {
|
|
27
|
+
const { hasDetailPanel, controlledExpandedRowIds, onDetailPanelExpandedRowIdsChange, rowMetaMap } = params;
|
|
28
|
+
|
|
29
|
+
const [internalExpandedRowIds, setInternalExpandedRowIds] = useState<Set<GridRowId>>(new Set());
|
|
30
|
+
const requestedExpandedRowIds = controlledExpandedRowIds ?? internalExpandedRowIds;
|
|
31
|
+
const expandedRowIds = useMemo(() => getDetailPanelRowIds(requestedExpandedRowIds, rowMetaMap), [requestedExpandedRowIds, rowMetaMap]);
|
|
32
|
+
|
|
33
|
+
const handleDetailPanelToggle = useCallback((rowId: GridRowId) => {
|
|
34
|
+
// Synthetic group / subtotal rows have no detail panel (keyboard Space on the expand cell lands here too).
|
|
35
|
+
if (rowMetaMap.get(rowId)?.isGroupRow) return;
|
|
36
|
+
const newExpandedRowIds = new Set(requestedExpandedRowIds);
|
|
37
|
+
if (newExpandedRowIds.has(rowId)) {
|
|
38
|
+
newExpandedRowIds.delete(rowId);
|
|
39
|
+
} else {
|
|
40
|
+
newExpandedRowIds.add(rowId);
|
|
41
|
+
}
|
|
42
|
+
if (controlledExpandedRowIds === undefined) {
|
|
43
|
+
setInternalExpandedRowIds(newExpandedRowIds);
|
|
44
|
+
}
|
|
45
|
+
onDetailPanelExpandedRowIdsChange?.(newExpandedRowIds);
|
|
46
|
+
}, [requestedExpandedRowIds, rowMetaMap, controlledExpandedRowIds, onDetailPanelExpandedRowIdsChange]);
|
|
47
|
+
|
|
48
|
+
const { detailPanelHeights, reportDetailPanelHeight } = useDetailPanelHeights();
|
|
49
|
+
|
|
50
|
+
return { hasDetailPanel, expandedRowIds, handleDetailPanelToggle, detailPanelHeights, reportDetailPanelHeight };
|
|
51
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
|
+
import type { RefObject } from 'react';
|
|
2
3
|
|
|
3
4
|
export interface UseGridDevWarningsParams {
|
|
4
5
|
paginationRequested: boolean;
|
|
@@ -7,15 +8,73 @@ export interface UseGridDevWarningsParams {
|
|
|
7
8
|
viewportHeight: number;
|
|
8
9
|
renderedRowCount: number;
|
|
9
10
|
totalRowCount: number;
|
|
11
|
+
/** `pinnedRows` is set while tree data or row grouping is active, where it has no effect. */
|
|
12
|
+
pinnedRowsIgnored?: boolean;
|
|
13
|
+
/** Pixel height of the scrollable content. */
|
|
14
|
+
scrollHeight?: number;
|
|
15
|
+
/** `listView` is set but `listViewColumn` is not, so the grid view is shown. */
|
|
16
|
+
listViewWithoutColumn?: boolean;
|
|
17
|
+
/** Number of items the list view renders (0 when the list view is not shown). It does not virtualize. */
|
|
18
|
+
listViewRowCount?: number;
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
// Below this many rows, rendering everything is harmless, so an unbounded container is not worth a warning.
|
|
13
22
|
const UNBOUNDED_WARN_THRESHOLD = 200;
|
|
14
23
|
|
|
24
|
+
/** The list view renders every item; above this many the page slows down noticeably. */
|
|
25
|
+
export const LIST_VIEW_WARN_THRESHOLD = 2000;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Browsers cap element heights (Firefox at about 17.9M px, Chromium and Safari at about 33.5M px):
|
|
29
|
+
* rows laid out below the cap cannot be scrolled to.
|
|
30
|
+
*/
|
|
31
|
+
export const MAX_SCROLLABLE_HEIGHT = 17_800_000;
|
|
32
|
+
|
|
15
33
|
export function useGridDevWarnings(params: UseGridDevWarningsParams): void {
|
|
16
|
-
const { paginationRequested, isRowGrouping, autoHeight, viewportHeight, renderedRowCount, totalRowCount } = params;
|
|
34
|
+
const { paginationRequested, isRowGrouping, autoHeight, viewportHeight, renderedRowCount, totalRowCount, pinnedRowsIgnored = false, scrollHeight = 0, listViewWithoutColumn = false, listViewRowCount = 0 } = params;
|
|
17
35
|
const warnedPaginationRef = useRef(false);
|
|
18
36
|
const warnedUnboundedRef = useRef(false);
|
|
37
|
+
const warnedPinnedRowsRef = useRef(false);
|
|
38
|
+
const warnedHeightRef = useRef(false);
|
|
39
|
+
const warnedListViewRef = useRef(false);
|
|
40
|
+
const warnedListViewSizeRef = useRef(false);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (process.env.NODE_ENV === 'production' || warnedListViewSizeRef.current || listViewRowCount <= LIST_VIEW_WARN_THRESHOLD) return;
|
|
44
|
+
warnedListViewSizeRef.current = true;
|
|
45
|
+
console.warn(
|
|
46
|
+
`[OpenGridX] The list view is rendering all ${listViewRowCount} items: it does not virtualize. ` +
|
|
47
|
+
'Above a few thousand items use `pagination`, server-side loading, or the grid view. See docs/features/list-view.md.'
|
|
48
|
+
);
|
|
49
|
+
}, [listViewRowCount]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (process.env.NODE_ENV === 'production' || warnedListViewRef.current || !listViewWithoutColumn) return;
|
|
53
|
+
warnedListViewRef.current = true;
|
|
54
|
+
console.warn(
|
|
55
|
+
'[OpenGridX] `listView` is set without a `listViewColumn`, so the grid view is shown instead. ' +
|
|
56
|
+
'Pass `listViewColumn` to render list items. See docs/features/list-view.md.'
|
|
57
|
+
);
|
|
58
|
+
}, [listViewWithoutColumn]);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (process.env.NODE_ENV === 'production' || warnedPinnedRowsRef.current || !pinnedRowsIgnored) return;
|
|
62
|
+
warnedPinnedRowsRef.current = true;
|
|
63
|
+
console.warn(
|
|
64
|
+
'[OpenGridX] `pinnedRows` is ignored while treeData or rowGroupingModel is active: the rows stay in their ' +
|
|
65
|
+
'place in the hierarchy. See docs/features/pinning.md.'
|
|
66
|
+
);
|
|
67
|
+
}, [pinnedRowsIgnored]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (process.env.NODE_ENV === 'production' || warnedHeightRef.current || scrollHeight <= MAX_SCROLLABLE_HEIGHT) return;
|
|
71
|
+
warnedHeightRef.current = true;
|
|
72
|
+
console.warn(
|
|
73
|
+
`[OpenGridX] The grid content is ${Math.round(scrollHeight)}px tall. Browsers cap element heights ` +
|
|
74
|
+
'(about 17.9M px in Firefox, 33.5M px in Chrome and Safari), so the last rows may not be reachable by scrolling. ' +
|
|
75
|
+
'Use pagination, server-side or infinite loading, or a smaller rowHeight. See docs/features/virtualization.md.'
|
|
76
|
+
);
|
|
77
|
+
}, [scrollHeight]);
|
|
19
78
|
|
|
20
79
|
useEffect(() => {
|
|
21
80
|
if (process.env.NODE_ENV === 'production' || warnedPaginationRef.current) return;
|
|
@@ -30,13 +89,53 @@ export function useGridDevWarnings(params: UseGridDevWarningsParams): void {
|
|
|
30
89
|
|
|
31
90
|
useEffect(() => {
|
|
32
91
|
if (process.env.NODE_ENV === 'production' || warnedUnboundedRef.current || autoHeight) return;
|
|
33
|
-
|
|
92
|
+
// Really unbounded: every row is rendered and the viewport is at least as tall as the rows, so it never scrolls.
|
|
93
|
+
if (viewportHeight > 0 && totalRowCount > UNBOUNDED_WARN_THRESHOLD && renderedRowCount >= totalRowCount && viewportHeight >= scrollHeight) {
|
|
34
94
|
warnedUnboundedRef.current = true;
|
|
35
95
|
console.warn(
|
|
36
96
|
`[OpenGridX] The grid viewport is ${Math.round(viewportHeight)}px tall and is rendering all ${totalRowCount} rows, ` +
|
|
37
|
-
'so row virtualization is effectively off
|
|
38
|
-
'
|
|
97
|
+
'so row virtualization is effectively off: the grid fills its container, and the container grows to fit every row. ' +
|
|
98
|
+
'Give the container a definite height (for example `height: 600px` or `100vh`). If the container, or any ancestor ' +
|
|
99
|
+
'up to the sized one, is a flex or grid item, add `min-height: 0` to it. Alternatively pass a `height` prop, ' +
|
|
100
|
+
'or `autoHeight` if rendering every row is intended. See docs/features/virtualization.md.'
|
|
39
101
|
);
|
|
40
102
|
}
|
|
41
|
-
}, [autoHeight, viewportHeight, renderedRowCount, totalRowCount]);
|
|
103
|
+
}, [autoHeight, viewportHeight, renderedRowCount, totalRowCount, scrollHeight]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* True when the grid's stylesheet does not reach `root` (the `.ogx` element). The stylesheet lays the
|
|
108
|
+
* root out as a flex column; without it the root is a plain block and the viewport does not scroll.
|
|
109
|
+
* jsdom applies no library CSS, so it is never reported there.
|
|
110
|
+
*/
|
|
111
|
+
export function isGridStylesheetMissing(root: HTMLElement): boolean {
|
|
112
|
+
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') return false;
|
|
113
|
+
if (typeof navigator !== 'undefined' && /jsdom/i.test(navigator.userAgent)) return false;
|
|
114
|
+
return window.getComputedStyle(root).display !== 'flex';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let warnedStylesheet = false;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Dev-only: warns once per page when a grid renders without `@opencorestack/opengridx/styles`.
|
|
121
|
+
* The JS bundle does not load the stylesheet, and without it the viewport grows to fit every row,
|
|
122
|
+
* which silently turns virtualization off.
|
|
123
|
+
*/
|
|
124
|
+
export function useGridStylesheetWarning(rootRef: RefObject<HTMLElement | null>): void {
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (process.env.NODE_ENV === 'production' || warnedStylesheet) return;
|
|
127
|
+
const root = rootRef.current;
|
|
128
|
+
if (!root || !isGridStylesheetMissing(root)) return;
|
|
129
|
+
warnedStylesheet = true;
|
|
130
|
+
console.warn(
|
|
131
|
+
'[OpenGridX] The grid stylesheet is not loaded, so the grid is unstyled and renders every row ' +
|
|
132
|
+
"(its viewport does not scroll). Add `import '@opencorestack/opengridx/styles'` once in your app root. " +
|
|
133
|
+
'See README.md, Getting Started.'
|
|
134
|
+
);
|
|
135
|
+
}, [rootRef]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Test helper: lets the stylesheet warning fire again. */
|
|
139
|
+
export function resetGridStylesheetWarning(): void {
|
|
140
|
+
warnedStylesheet = false;
|
|
42
141
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import type { RefObject } from 'react';
|
|
3
|
+
import type { FocusedCell } from '../../utils/focus';
|
|
4
|
+
import type { GridCellParams, GridRowId, GridRowModel } from '../../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Keyboard-mode flag: toggled via a DOM class name on the grid root, with no React state, so
|
|
8
|
+
* the focus ring appears instantly without a re-render cycle.
|
|
9
|
+
*/
|
|
10
|
+
export function useGridKeyboardMode(containerRef: RefObject<HTMLDivElement | null>): (on: boolean) => void {
|
|
11
|
+
return useCallback((on: boolean) => {
|
|
12
|
+
containerRef.current?.classList.toggle('ogx--kb', on);
|
|
13
|
+
}, [containerRef]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseGridPointerFocusHandlersParams<R extends GridRowModel> {
|
|
17
|
+
ensureGridFocus: () => void;
|
|
18
|
+
setKeyboardMode: (on: boolean) => void;
|
|
19
|
+
setFocusedCell: React.Dispatch<React.SetStateAction<FocusedCell | null>>;
|
|
20
|
+
getRowId: (row: GridRowModel) => GridRowId;
|
|
21
|
+
onCellClick: ((params: GridCellParams<R>) => void) | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseGridPointerFocusHandlersResult<R extends GridRowModel> {
|
|
25
|
+
handleCellClick: (params: GridCellParams<R>) => void;
|
|
26
|
+
handleHeaderClick: (field: string) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Cell and header clicks move grid focus there and leave keyboard mode. */
|
|
30
|
+
export function useGridPointerFocusHandlers<R extends GridRowModel>(
|
|
31
|
+
params: UseGridPointerFocusHandlersParams<R>
|
|
32
|
+
): UseGridPointerFocusHandlersResult<R> {
|
|
33
|
+
const { ensureGridFocus, setKeyboardMode, setFocusedCell, getRowId, onCellClick } = params;
|
|
34
|
+
|
|
35
|
+
const handleCellClick = useCallback((cellParams: GridCellParams<R>) => {
|
|
36
|
+
// Focus stays where the click put it (the cell, or a control rendered inside it).
|
|
37
|
+
ensureGridFocus();
|
|
38
|
+
setKeyboardMode(false);
|
|
39
|
+
setFocusedCell({ id: getRowId(cellParams.row), field: cellParams.field, rowIndex: cellParams.rowIndex });
|
|
40
|
+
onCellClick?.(cellParams);
|
|
41
|
+
}, [onCellClick, setKeyboardMode, setFocusedCell, ensureGridFocus, getRowId]);
|
|
42
|
+
|
|
43
|
+
const handleHeaderClick = useCallback((field: string) => {
|
|
44
|
+
ensureGridFocus();
|
|
45
|
+
setFocusedCell({ id: null, field });
|
|
46
|
+
setKeyboardMode(false);
|
|
47
|
+
}, [ensureGridFocus, setFocusedCell, setKeyboardMode]);
|
|
48
|
+
|
|
49
|
+
return { handleCellClick, handleHeaderClick };
|
|
50
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { GridColDef, GridColumnPinning, GridRowModel } from '../../types';
|
|
3
|
+
|
|
4
|
+
export interface UseGridGroupingColumnParams<R extends GridRowModel> {
|
|
5
|
+
/** The columns before the grouping column is added (the pivot's columns in pivot mode). */
|
|
6
|
+
baseColumns: GridColDef<R>[];
|
|
7
|
+
pinnedColumns: GridColumnPinning;
|
|
8
|
+
/** Show the synthetic `__group__` column (see `resolveGridModes`). */
|
|
9
|
+
hasGroupingColumn: boolean;
|
|
10
|
+
groupingColDef: Partial<GridColDef<R>> | undefined;
|
|
11
|
+
isTreeData: boolean;
|
|
12
|
+
getTreeDataPath: ((row: R) => string[]) | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseGridGroupingColumnResult<R extends GridRowModel> {
|
|
16
|
+
/** `baseColumns`, with the `__group__` column first when it is shown. */
|
|
17
|
+
activeColumns: GridColDef<R>[];
|
|
18
|
+
/** `pinnedColumns`, with `__group__` pinned first on the left when it is shown. */
|
|
19
|
+
effectivePinnedColumns: GridColumnPinning;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The dedicated `__group__` column that `groupingColDef` asks for while row grouping or tree
|
|
24
|
+
* data is active: prepended at position 0 and auto-pinned left.
|
|
25
|
+
*
|
|
26
|
+
* Both results MUST be memoized: a new array/object reference every render cascades through
|
|
27
|
+
* useRowGrouping's memos and effects into an infinite setState loop (Maximum update depth exceeded).
|
|
28
|
+
*/
|
|
29
|
+
export function useGridGroupingColumn<R extends GridRowModel>(
|
|
30
|
+
params: UseGridGroupingColumnParams<R>
|
|
31
|
+
): UseGridGroupingColumnResult<R> {
|
|
32
|
+
const { baseColumns, pinnedColumns, hasGroupingColumn, groupingColDef, isTreeData, getTreeDataPath } = params;
|
|
33
|
+
|
|
34
|
+
const effectivePinnedColumns = useMemo(() => (
|
|
35
|
+
hasGroupingColumn
|
|
36
|
+
? { ...pinnedColumns, left: ['__group__', ...((pinnedColumns?.left ?? []).filter(f => f !== '__group__'))] }
|
|
37
|
+
: pinnedColumns
|
|
38
|
+
), [hasGroupingColumn, pinnedColumns]);
|
|
39
|
+
|
|
40
|
+
const activeColumns = useMemo(() => (
|
|
41
|
+
hasGroupingColumn
|
|
42
|
+
? [
|
|
43
|
+
{
|
|
44
|
+
headerName: 'Group',
|
|
45
|
+
width: 220,
|
|
46
|
+
// Tree data: a row's own entry in the hierarchy is the last segment of its path.
|
|
47
|
+
...(isTreeData && getTreeDataPath ? { valueGetter: ({ row }: { row: R }) => { const path = getTreeDataPath(row); return path[path.length - 1]; } } : {}),
|
|
48
|
+
...groupingColDef,
|
|
49
|
+
field: '__group__',
|
|
50
|
+
hideable: false,
|
|
51
|
+
sortable: false,
|
|
52
|
+
filterable: false,
|
|
53
|
+
pinnable: false,
|
|
54
|
+
exportable: false,
|
|
55
|
+
} as unknown as GridColDef<R>,
|
|
56
|
+
...baseColumns,
|
|
57
|
+
]
|
|
58
|
+
: baseColumns
|
|
59
|
+
), [groupingColDef, hasGroupingColumn, isTreeData, getTreeDataPath, baseColumns]);
|
|
60
|
+
|
|
61
|
+
return { activeColumns, effectivePinnedColumns };
|
|
62
|
+
}
|