@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,38 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { getAriaRowLayout, type GridAriaRowLayout } from '../../utils/aria';
|
|
3
|
+
import { getColumnGroupDepth } from '../../utils/columnGroups';
|
|
4
|
+
import type { GridColumnGroupingModel, GridPaginationModel } from '../../types';
|
|
5
|
+
|
|
6
|
+
export interface UseGridAriaRowsParams {
|
|
7
|
+
columnGroupingModel: GridColumnGroupingModel | undefined;
|
|
8
|
+
pinnedTopCount: number;
|
|
9
|
+
pinnedBottomCount: number;
|
|
10
|
+
pagination: boolean;
|
|
11
|
+
paginationMode: 'client' | 'server' | 'infinite';
|
|
12
|
+
paginationModel: GridPaginationModel;
|
|
13
|
+
/** What the pager pages through (see `getPaginationRowCount`). */
|
|
14
|
+
paginationRowCount: number;
|
|
15
|
+
paginatedUnpinnedRowCount: number;
|
|
16
|
+
sortedUnpinnedRowCount: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** `aria-rowcount` and the `aria-rowindex` bases of the pinned and centre rows. */
|
|
20
|
+
export function useGridAriaRows(params: UseGridAriaRowsParams): GridAriaRowLayout {
|
|
21
|
+
const {
|
|
22
|
+
columnGroupingModel, pinnedTopCount, pinnedBottomCount, pagination, paginationMode, paginationModel,
|
|
23
|
+
paginationRowCount, paginatedUnpinnedRowCount, sortedUnpinnedRowCount,
|
|
24
|
+
} = params;
|
|
25
|
+
const { page, pageSize } = paginationModel;
|
|
26
|
+
|
|
27
|
+
return useMemo(() => getAriaRowLayout({
|
|
28
|
+
headerRowCount: 1 + getColumnGroupDepth(columnGroupingModel),
|
|
29
|
+
pinnedTopCount,
|
|
30
|
+
pinnedBottomCount,
|
|
31
|
+
pageRowCount: pagination ? paginatedUnpinnedRowCount : sortedUnpinnedRowCount,
|
|
32
|
+
totalCenterRowCount: pagination && paginationMode === 'server'
|
|
33
|
+
? Math.max(paginationRowCount, paginatedUnpinnedRowCount)
|
|
34
|
+
: sortedUnpinnedRowCount,
|
|
35
|
+
pageOffset: pagination ? page * pageSize : 0,
|
|
36
|
+
}), [columnGroupingModel, pinnedTopCount, pinnedBottomCount, pagination, paginatedUnpinnedRowCount,
|
|
37
|
+
sortedUnpinnedRowCount, paginationMode, paginationRowCount, page, pageSize]);
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect } from 'react';
|
|
2
|
+
import type { MutableRefObject, RefObject } from 'react';
|
|
3
|
+
import { useGridClipboard } from '../features/useGridClipboard';
|
|
4
|
+
import type { GridApi, GridColDef, GridRowId, GridRowModel } from '../../types';
|
|
5
|
+
|
|
6
|
+
export interface UseGridClipboardApiParams {
|
|
7
|
+
apiRef: MutableRefObject<GridApi>;
|
|
8
|
+
/** The rendered data columns, in screen order. */
|
|
9
|
+
columns: GridColDef[];
|
|
10
|
+
getRowId: (row: GridRowModel) => GridRowId;
|
|
11
|
+
/** The grid root: Ctrl/Cmd+C only copies while focus is inside it. */
|
|
12
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
13
|
+
disableKeyboardShortcut: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Copies the selected rows (every page, collapsed groups, pinned rows) with the on-screen columns
|
|
18
|
+
* in screen order, on Ctrl/Cmd+C while focus is inside this grid, and installs `copySelectedRows`
|
|
19
|
+
* on the API for programmatic use.
|
|
20
|
+
*/
|
|
21
|
+
export function useGridClipboardApi(params: UseGridClipboardApiParams): void {
|
|
22
|
+
const { apiRef, columns, getRowId, containerRef, disableKeyboardShortcut } = params;
|
|
23
|
+
|
|
24
|
+
const getGridRootElement = useCallback(() => containerRef.current, [containerRef]);
|
|
25
|
+
const { copySelectedRows } = useGridClipboard({
|
|
26
|
+
getSelectedRowIds: () => apiRef.current.getSelectedRows(),
|
|
27
|
+
getColumns: () => columns,
|
|
28
|
+
getRows: () => apiRef.current.getAllFilteredRows(),
|
|
29
|
+
getRowId,
|
|
30
|
+
getRootElement: getGridRootElement,
|
|
31
|
+
disableKeyboardShortcut,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Stable identity.
|
|
35
|
+
useLayoutEffect(() => {
|
|
36
|
+
apiRef.current.copySelectedRows = copySelectedRows;
|
|
37
|
+
}, [copySelectedRows, apiRef]);
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { GridColDef, GridRowModel } from '../../types';
|
|
3
|
+
import { buildColumnLookup, type GridColumnLookup } from '../../utils/columnLookup';
|
|
4
|
+
|
|
5
|
+
const SEPARATOR = '\u0000';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The column lookup used by client-side filtering, sorting and the quick filter.
|
|
9
|
+
*
|
|
10
|
+
* Its identity changes only when the columns change or the set of hidden columns changes, so an
|
|
11
|
+
* inline `columnVisibilityModel` object (a new reference on every parent render) does not re-run
|
|
12
|
+
* the filter and sort passes.
|
|
13
|
+
*/
|
|
14
|
+
export function useGridColumnLookup<R extends GridRowModel>(
|
|
15
|
+
columns: GridColDef<R>[],
|
|
16
|
+
columnVisibilityModel: Record<string, boolean>,
|
|
17
|
+
): GridColumnLookup {
|
|
18
|
+
const hiddenFieldsKey = columns
|
|
19
|
+
.filter(col => columnVisibilityModel[col.field] === false)
|
|
20
|
+
.map(col => col.field)
|
|
21
|
+
.join(SEPARATOR);
|
|
22
|
+
|
|
23
|
+
return useMemo(() => {
|
|
24
|
+
const hidden: Record<string, boolean> = {};
|
|
25
|
+
if (hiddenFieldsKey) {
|
|
26
|
+
for (const field of hiddenFieldsKey.split(SEPARATOR)) hidden[field] = false;
|
|
27
|
+
}
|
|
28
|
+
return buildColumnLookup(columns as unknown as GridColDef[], hidden);
|
|
29
|
+
}, [columns, hiddenFieldsKey]);
|
|
30
|
+
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { useState, useMemo, useCallback, useEffect } from 'react';
|
|
2
2
|
import { useColumnReorder } from '../useColumnReorder';
|
|
3
3
|
import { ExpandIcon } from '../../components/ui/ExpandIcon';
|
|
4
|
+
import { isColumnPinned } from '../../utils/pinning';
|
|
5
|
+
import { formatAggregateForColumn } from '../../utils/aggregation';
|
|
6
|
+
import { CellErrorBoundary } from '../../components/Cell/CellErrorBoundary';
|
|
7
|
+
import { CHECKBOX_FIELD, EXPAND_FIELD, REORDER_FIELD } from '../../utils/focus';
|
|
4
8
|
import type {
|
|
5
9
|
GridColDef,
|
|
6
10
|
GridRowModel,
|
|
7
11
|
GridRowId,
|
|
8
12
|
GridColumnOrderChangeParams,
|
|
13
|
+
GridColumnPinning,
|
|
9
14
|
GridRenderCellParams,
|
|
15
|
+
GridAggregationModel,
|
|
10
16
|
} from '../../types';
|
|
11
17
|
import type { GridInitialState } from '../../state/types';
|
|
12
18
|
|
|
@@ -14,11 +20,119 @@ interface HierarchyHandlers {
|
|
|
14
20
|
toggleExpansion: (id: GridRowId) => void;
|
|
15
21
|
}
|
|
16
22
|
|
|
23
|
+
// A group row carries each column's aggregate in the column's field. It is shown like the footer
|
|
24
|
+
// shows totals: the stored aggregate (a valueGetter would recompute it from fields a group row does
|
|
25
|
+
// not have), formatted with formatAggregateForColumn (so a count is never put in a currency format).
|
|
26
|
+
function withGroupRowAggregates<R extends GridRowModel>(
|
|
27
|
+
col: GridColDef<R>,
|
|
28
|
+
fnName: string | undefined,
|
|
29
|
+
groupingRows: ReadonlyMap<GridRowId, unknown> | undefined,
|
|
30
|
+
): Pick<GridColDef<R>, 'valueGetter' | 'valueFormatter'> {
|
|
31
|
+
if (!fnName || !groupingRows) return { valueGetter: col.valueGetter, valueFormatter: col.valueFormatter };
|
|
32
|
+
const isGroupRow = (row: R) => groupingRows.has(row.id);
|
|
33
|
+
const ownGetter = col.valueGetter;
|
|
34
|
+
const ownFormatter = col.valueFormatter;
|
|
35
|
+
return {
|
|
36
|
+
valueGetter: ownGetter
|
|
37
|
+
? (p) => (isGroupRow(p.row) ? p.value : ownGetter(p))
|
|
38
|
+
: undefined,
|
|
39
|
+
valueFormatter: (p) => {
|
|
40
|
+
if (isGroupRow(p.row)) return formatAggregateForColumn(p.value, fnName, col, p.row);
|
|
41
|
+
if (ownFormatter) return ownFormatter(p);
|
|
42
|
+
return p.value == null ? '' : String(p.value);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Columns in render order: left-pinned in `pinnedColumns.left` order, unpinned in column order,
|
|
49
|
+
* right-pinned in `pinnedColumns.right` order — the order useLayout renders them in.
|
|
50
|
+
*/
|
|
51
|
+
function inPinnedRenderOrder<C extends { field: string }>(columns: C[], pinnedColumns?: GridColumnPinning): C[] {
|
|
52
|
+
if (!pinnedColumns) return columns;
|
|
53
|
+
const left = pinnedColumns.left ?? [];
|
|
54
|
+
const right = pinnedColumns.right ?? [];
|
|
55
|
+
const rank = (col: C) => {
|
|
56
|
+
const side = isColumnPinned(col.field, pinnedColumns);
|
|
57
|
+
if (side === 'left') return left.indexOf(col.field) - left.length;
|
|
58
|
+
if (side === 'right') return 1 + right.indexOf(col.field);
|
|
59
|
+
return 0;
|
|
60
|
+
};
|
|
61
|
+
return [...columns].sort((a, b) => rank(a) - rank(b));
|
|
62
|
+
}
|
|
63
|
+
|
|
17
64
|
// Injected hierarchy renderers replace the plain-cell path in Cell, so they must
|
|
18
65
|
// render the formatted value themselves or valueFormatter is silently lost.
|
|
19
66
|
const displayValue = <R extends GridRowModel>(cellParams: GridRenderCellParams<R>): React.ReactNode =>
|
|
20
67
|
cellParams.formattedValue ?? (cellParams.value as React.ReactNode);
|
|
21
68
|
|
|
69
|
+
interface ConsumerCellResult {
|
|
70
|
+
useDefault: boolean;
|
|
71
|
+
content?: React.ReactNode;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Calls the consumer's renderCell for a hierarchy column cell. For a synthetic row (group, subtotal or
|
|
76
|
+
* auto-created tree parent) a result of `undefined` means "use the grid's default content". In the
|
|
77
|
+
* hierarchy column the output is wrapped in its own error boundary, so a renderCell that fails on a
|
|
78
|
+
* group row shows the error icon next to the expand toggle instead of replacing the toggle.
|
|
79
|
+
*/
|
|
80
|
+
function renderConsumerCell<R extends GridRowModel>(
|
|
81
|
+
col: GridColDef<R>,
|
|
82
|
+
cellParams: GridRenderCellParams<R>,
|
|
83
|
+
isolate: boolean,
|
|
84
|
+
): ConsumerCellResult {
|
|
85
|
+
const renderCell = col.renderCell;
|
|
86
|
+
if (!renderCell) return { useDefault: true };
|
|
87
|
+
const isSynthetic = Boolean(cellParams.rowMeta?.isGroupRow);
|
|
88
|
+
if (!isolate) {
|
|
89
|
+
const output = renderCell(cellParams);
|
|
90
|
+
return isSynthetic && output === undefined ? { useDefault: true } : { useDefault: false, content: output };
|
|
91
|
+
}
|
|
92
|
+
let output: React.ReactNode;
|
|
93
|
+
try {
|
|
94
|
+
output = renderCell(cellParams);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return {
|
|
97
|
+
useDefault: false,
|
|
98
|
+
content: <CellErrorBoundary field={col.field} resetKey={cellParams.row} renderFn={() => { throw error; }} />,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (isSynthetic && output === undefined) return { useDefault: true };
|
|
102
|
+
return { useDefault: false, content: <CellErrorBoundary field={col.field} resetKey={cellParams.row} renderFn={() => output} /> };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** What the hierarchy column shows without a consumer renderCell (or when it returns undefined for a synthetic row). */
|
|
106
|
+
function defaultHierarchyContent<R extends GridRowModel>(
|
|
107
|
+
cellParams: GridRenderCellParams<R>,
|
|
108
|
+
isRowGrouping: boolean,
|
|
109
|
+
isTreeData: boolean,
|
|
110
|
+
): React.ReactNode {
|
|
111
|
+
const meta = cellParams.rowMeta;
|
|
112
|
+
if (!meta?.isGroupRow) return displayValue(cellParams);
|
|
113
|
+
// A subtotal row carries only aggregates; its label column stays empty.
|
|
114
|
+
if (meta.isGroupFooter) return null;
|
|
115
|
+
const descendantCount = meta.descendantCount;
|
|
116
|
+
if (isRowGrouping && meta.hasChildren) {
|
|
117
|
+
const groupLabel = meta.groupLabel ?? `${meta.groupingField}: ${String(meta.groupingValue)}`;
|
|
118
|
+
return (
|
|
119
|
+
<div className="ogx__group-cell-content">
|
|
120
|
+
{groupLabel}
|
|
121
|
+
{descendantCount !== undefined ? ` (${descendantCount})` : ''}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (isTreeData) {
|
|
126
|
+
return (
|
|
127
|
+
<div className="ogx__group-cell-content">
|
|
128
|
+
{meta.groupLabel}
|
|
129
|
+
{descendantCount !== undefined && descendantCount > 0 ? ` (${descendantCount})` : ''}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return displayValue(cellParams);
|
|
134
|
+
}
|
|
135
|
+
|
|
22
136
|
export interface UseGridColumnsParams<R extends GridRowModel> {
|
|
23
137
|
activeColumns: GridColDef<R>[];
|
|
24
138
|
isHierarchyEnabled: boolean;
|
|
@@ -28,6 +142,8 @@ export interface UseGridColumnsParams<R extends GridRowModel> {
|
|
|
28
142
|
columnVisibilityModel: Record<string, boolean>;
|
|
29
143
|
columnOrder?: string[];
|
|
30
144
|
onColumnOrderChange?: (params: GridColumnOrderChangeParams) => void;
|
|
145
|
+
/** Receives the whole new column order after every reorder and reset (not in pivot mode). */
|
|
146
|
+
onColumnOrderModelChange?: (columnOrder: string[]) => void;
|
|
31
147
|
disableColumnReorder: boolean;
|
|
32
148
|
pivotMode: boolean;
|
|
33
149
|
checkboxSelection: boolean;
|
|
@@ -35,17 +151,37 @@ export interface UseGridColumnsParams<R extends GridRowModel> {
|
|
|
35
151
|
rowReordering: boolean;
|
|
36
152
|
initialState?: GridInitialState;
|
|
37
153
|
setColumns: (cols: GridColDef[]) => void;
|
|
154
|
+
pinnedColumns?: GridColumnPinning;
|
|
155
|
+
/** Resolves a row's id (getRowId) when the injected expand toggle is clicked; defaults to `row.id`. */
|
|
156
|
+
getRowId?: (row: R) => GridRowId;
|
|
157
|
+
/** Formats the aggregates on row-grouping group rows. */
|
|
158
|
+
aggregationModel?: GridAggregationModel;
|
|
159
|
+
/** Row grouping only: the synthetic group rows, keyed by id. */
|
|
160
|
+
groupingRows?: ReadonlyMap<GridRowId, unknown>;
|
|
38
161
|
}
|
|
39
162
|
|
|
163
|
+
const defaultGetRowId = <R extends GridRowModel>(row: R): GridRowId => row.id;
|
|
164
|
+
|
|
40
165
|
export interface UseGridColumnsResult<R extends GridRowModel> {
|
|
41
166
|
effectiveColumns: GridColDef<R>[];
|
|
167
|
+
/** Tree data / row grouping: the column that shows the expand toggle, indent and group labels. */
|
|
168
|
+
hierarchyField: string | undefined;
|
|
42
169
|
orderedColumns: GridColDef<R>[];
|
|
43
170
|
visibleOrderedColumns: GridColDef<R>[];
|
|
44
|
-
|
|
171
|
+
/** Visible data columns in render order: left-pinned, unpinned, right-pinned (`apiRef.getVisibleColumns`). */
|
|
172
|
+
renderOrderedColumns: GridColDef<R>[];
|
|
173
|
+
/** Focusable columns in render order: system columns, then the visible data columns (left-pinned, unpinned, right-pinned). */
|
|
174
|
+
navigationColumns: Array<GridColDef<R> | { field: string; sortable: false; editable: false }>;
|
|
175
|
+
/** Position of each visible data column in render order (system columns excluded): the public `colIndex`. */
|
|
176
|
+
columnIndexMap: Map<string, number>;
|
|
45
177
|
columnWidths: Record<string, number>;
|
|
46
178
|
effectiveColumnOrder: string[];
|
|
47
179
|
setInternalColumnOrder: React.Dispatch<React.SetStateAction<string[]>>;
|
|
48
180
|
columnReorderHandlers: ReturnType<typeof useColumnReorder>;
|
|
181
|
+
/** Moves `fromField` to the position of `toField` in the column order (Columns panel drag). */
|
|
182
|
+
moveColumn: (fromField: string, toField: string) => void;
|
|
183
|
+
/** Restores the columns' definition order. */
|
|
184
|
+
resetColumnOrder: () => void;
|
|
49
185
|
handleColumnResize: (field: string, newWidth: number) => void;
|
|
50
186
|
}
|
|
51
187
|
|
|
@@ -61,6 +197,7 @@ export function useGridColumns<R extends GridRowModel>(
|
|
|
61
197
|
columnVisibilityModel,
|
|
62
198
|
columnOrder,
|
|
63
199
|
onColumnOrderChange,
|
|
200
|
+
onColumnOrderModelChange,
|
|
64
201
|
disableColumnReorder,
|
|
65
202
|
pivotMode,
|
|
66
203
|
checkboxSelection,
|
|
@@ -68,59 +205,95 @@ export function useGridColumns<R extends GridRowModel>(
|
|
|
68
205
|
rowReordering,
|
|
69
206
|
initialState,
|
|
70
207
|
setColumns,
|
|
208
|
+
pinnedColumns,
|
|
209
|
+
getRowId = defaultGetRowId,
|
|
210
|
+
aggregationModel,
|
|
211
|
+
groupingRows,
|
|
71
212
|
} = params;
|
|
72
213
|
|
|
214
|
+
// ── Column order ──────────────────────────────────────────────────────────
|
|
215
|
+
const naturalOrder = useMemo(() => activeColumns.map(col => col.field), [activeColumns]);
|
|
216
|
+
|
|
217
|
+
// null = the columns' own order, until the user reorders. Deriving it (instead of snapshotting the
|
|
218
|
+
// columns at mount) keeps it right when the grid mounts in pivot mode or the columns change.
|
|
219
|
+
const [storedColumnOrder, setStoredColumnOrder] = useState<string[] | null>(
|
|
220
|
+
() => initialState?.columns?.columnOrder ?? null
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Generated pivot columns keep an order of their own, so pivoting never rewrites the user's column
|
|
224
|
+
// order, and a controlled `columnOrder` (which names source columns) does not apply to them. It
|
|
225
|
+
// resets whenever the generated column set changes.
|
|
226
|
+
const pivotColumnsKey = pivotMode ? naturalOrder.join('\u0000') : '';
|
|
227
|
+
const [pivotOrderState, setPivotOrderState] = useState<{ key: string; order: string[] } | null>(null);
|
|
228
|
+
const pivotColumnOrder = pivotOrderState && pivotOrderState.key === pivotColumnsKey ? pivotOrderState.order : naturalOrder;
|
|
229
|
+
|
|
230
|
+
const internalColumnOrder = storedColumnOrder ?? naturalOrder;
|
|
231
|
+
const effectiveColumnOrder = pivotMode ? pivotColumnOrder : (columnOrder ?? internalColumnOrder);
|
|
232
|
+
|
|
233
|
+
const setInternalColumnOrder = useCallback<React.Dispatch<React.SetStateAction<string[]>>>((action) => {
|
|
234
|
+
if (pivotMode) {
|
|
235
|
+
setPivotOrderState(prev => {
|
|
236
|
+
const base = prev && prev.key === pivotColumnsKey ? prev.order : naturalOrder;
|
|
237
|
+
return { key: pivotColumnsKey, order: typeof action === 'function' ? action(base) : action };
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
setStoredColumnOrder(prev => (typeof action === 'function' ? action(prev ?? naturalOrder) : action));
|
|
242
|
+
}, [pivotMode, pivotColumnsKey, naturalOrder]);
|
|
243
|
+
|
|
244
|
+
// In pivot mode the row-label columns (marked hideable: false) always show: the pivot rows are
|
|
245
|
+
// labelled by them, and they share their field with the source column a visibility model may hide.
|
|
246
|
+
const isColumnShown = useCallback(
|
|
247
|
+
(col: GridColDef<R>) => (pivotMode && col.hideable === false) || columnVisibilityModel[col.field] !== false,
|
|
248
|
+
[pivotMode, columnVisibilityModel],
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// The expand toggle, indentation and group label go on the leftmost column actually
|
|
252
|
+
// on screen, so hiding, reordering or pinning columns never strips group rows of them.
|
|
253
|
+
// Mirrors the render order: left-pinned, then unpinned, then right-pinned.
|
|
254
|
+
const hierarchyField = useMemo<string | undefined>(() => {
|
|
255
|
+
if (!isHierarchyEnabled) return undefined;
|
|
256
|
+
const orderIndex = new Map(effectiveColumnOrder.map((field, idx) => [field, idx]));
|
|
257
|
+
const rank = (col: GridColDef<R>) => orderIndex.get(col.field) ?? activeColumns.indexOf(col);
|
|
258
|
+
const ordered = [...activeColumns].sort((a, b) => rank(a) - rank(b));
|
|
259
|
+
const onScreen = inPinnedRenderOrder(ordered.filter(col => columnVisibilityModel[col.field] !== false), pinnedColumns);
|
|
260
|
+
return (onScreen[0] ?? activeColumns[0])?.field;
|
|
261
|
+
}, [isHierarchyEnabled, effectiveColumnOrder, activeColumns, columnVisibilityModel, pinnedColumns]);
|
|
262
|
+
|
|
73
263
|
// ── Effective columns (hierarchy cell renderer injection) ─────────────────
|
|
74
264
|
const effectiveColumns = useMemo<GridColDef<R>[]>(() => {
|
|
75
265
|
if (!isHierarchyEnabled) return activeColumns;
|
|
76
266
|
|
|
77
|
-
|
|
78
|
-
|
|
267
|
+
const groupRows = isRowGrouping ? groupingRows : undefined;
|
|
268
|
+
|
|
269
|
+
return activeColumns.map((col) => {
|
|
270
|
+
const groupRowAggregates = withGroupRowAggregates(col, aggregationModel?.[col.field], groupRows);
|
|
271
|
+
if (col.field === hierarchyField) {
|
|
79
272
|
return {
|
|
80
273
|
...col,
|
|
274
|
+
...groupRowAggregates,
|
|
81
275
|
renderCell: (cellParams: GridRenderCellParams<R>) => {
|
|
82
276
|
const meta = cellParams.rowMeta;
|
|
83
277
|
const depth = meta?.treeDepth ?? 0;
|
|
84
278
|
const hasChildren = Boolean(meta?.hasChildren);
|
|
85
279
|
const isExpanded = Boolean(meta?.isExpanded);
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const isGroupRow = Boolean(meta?.isGroupRow);
|
|
90
|
-
|
|
91
|
-
let content: React.ReactNode = col.renderCell ? col.renderCell(cellParams) : displayValue(cellParams);
|
|
92
|
-
|
|
93
|
-
if (isTreeData && hasChildren && isGroupRow) {
|
|
94
|
-
content = (
|
|
95
|
-
<div className="ogx__group-cell-content">
|
|
96
|
-
{cellParams.value as React.ReactNode}
|
|
97
|
-
{descendantCount !== undefined && descendantCount > 0 ? ` (${descendantCount})` : ''}
|
|
98
|
-
</div>
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (isRowGrouping && hasChildren && groupingField) {
|
|
103
|
-
const groupLabel = meta?.groupLabel ?? `${groupingField}: ${String(groupingValue)}`;
|
|
104
|
-
content = (
|
|
105
|
-
<div className="ogx__group-cell-content">
|
|
106
|
-
{groupLabel}
|
|
107
|
-
{descendantCount !== undefined ? ` (${descendantCount})` : ''}
|
|
108
|
-
</div>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
280
|
+
|
|
281
|
+
const custom = renderConsumerCell(col, cellParams, true);
|
|
282
|
+
const content = custom.useDefault ? defaultHierarchyContent(cellParams, isRowGrouping, isTreeData) : custom.content;
|
|
111
283
|
|
|
112
284
|
return (
|
|
113
285
|
<div style={{ display: 'flex', alignItems: 'center', paddingLeft: depth * 24, width: '100%', height: '100%' }}>
|
|
114
286
|
<div style={{ marginRight: 4, width: 24, height: 24, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: hasChildren ? 'pointer' : 'default', flexShrink: 0 }}>
|
|
115
287
|
{hasChildren ? (
|
|
116
288
|
<div
|
|
117
|
-
onClick={(e) => { e.stopPropagation(); e.preventDefault(); activeHierarchyHandlers?.toggleExpansion(cellParams.row
|
|
289
|
+
onClick={(e) => { e.stopPropagation(); e.preventDefault(); activeHierarchyHandlers?.toggleExpansion(getRowId(cellParams.row)); }}
|
|
118
290
|
onMouseDown={(e) => { e.stopPropagation(); }}
|
|
119
291
|
style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'auto', zIndex: 10, position: 'relative' }}
|
|
120
292
|
>
|
|
121
293
|
<ExpandIcon
|
|
122
294
|
isExpanded={isExpanded}
|
|
123
|
-
|
|
295
|
+
tabIndex={-1}
|
|
296
|
+
onClick={(e) => { e.stopPropagation(); e.preventDefault(); activeHierarchyHandlers?.toggleExpansion(getRowId(cellParams.row)); }}
|
|
124
297
|
/>
|
|
125
298
|
</div>
|
|
126
299
|
) : null}
|
|
@@ -134,24 +307,22 @@ export function useGridColumns<R extends GridRowModel>(
|
|
|
134
307
|
|
|
135
308
|
return {
|
|
136
309
|
...col,
|
|
310
|
+
...groupRowAggregates,
|
|
137
311
|
renderCell: (cellParams: GridRenderCellParams<R>) => {
|
|
138
312
|
const meta = cellParams.rowMeta;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (isRowGrouping && hasChildren) {
|
|
143
|
-
if (col.field === groupingField) return null;
|
|
144
|
-
if (cellParams.value !== undefined && cellParams.value !== null) {
|
|
145
|
-
return col.renderCell ? col.renderCell(cellParams) : displayValue(cellParams);
|
|
146
|
-
}
|
|
147
|
-
return null;
|
|
313
|
+
if (!meta?.isGroupRow) {
|
|
314
|
+
return col.renderCell ? col.renderCell(cellParams) : displayValue(cellParams);
|
|
148
315
|
}
|
|
149
|
-
|
|
150
|
-
|
|
316
|
+
// Synthetic rows: the consumer's renderCell decides; `undefined` keeps the default,
|
|
317
|
+
// which is the aggregate (or nothing) and never the grouping value the label already shows.
|
|
318
|
+
const custom = renderConsumerCell(col, cellParams, false);
|
|
319
|
+
if (!custom.useDefault) return custom.content;
|
|
320
|
+
if (col.field === meta.groupingField && !meta.isGroupFooter) return null;
|
|
321
|
+
return cellParams.value == null ? null : displayValue(cellParams);
|
|
151
322
|
}
|
|
152
323
|
};
|
|
153
324
|
}) as GridColDef<R>[];
|
|
154
|
-
}, [activeColumns, isHierarchyEnabled, isRowGrouping, isTreeData, activeHierarchyHandlers]);
|
|
325
|
+
}, [activeColumns, isHierarchyEnabled, isRowGrouping, isTreeData, activeHierarchyHandlers, hierarchyField, getRowId, aggregationModel, groupingRows]);
|
|
155
326
|
|
|
156
327
|
// ── Column widths ─────────────────────────────────────────────────────────
|
|
157
328
|
const [columnWidths, setColumnWidths] = useState<Record<string, number>>(
|
|
@@ -162,72 +333,104 @@ export function useGridColumns<R extends GridRowModel>(
|
|
|
162
333
|
setColumnWidths(prev => ({ ...prev, [field]: newWidth }));
|
|
163
334
|
}, []);
|
|
164
335
|
|
|
165
|
-
// ── Column order ──────────────────────────────────────────────────────────
|
|
166
|
-
const [internalColumnOrder, setInternalColumnOrder] = useState<string[]>(
|
|
167
|
-
() => initialState?.columns?.columnOrder ?? activeColumns.map(col => col.field)
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
const effectiveColumnOrder = columnOrder ?? internalColumnOrder;
|
|
171
|
-
|
|
172
336
|
useEffect(() => {
|
|
173
337
|
setColumns(activeColumns as unknown as GridColDef[]);
|
|
174
338
|
}, [activeColumns, setColumns]);
|
|
175
339
|
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
if (pivotMode) {
|
|
178
|
-
setInternalColumnOrder(activeColumns.map(col => col.field));
|
|
179
|
-
}
|
|
180
|
-
}, [pivotMode, activeColumns]);
|
|
181
|
-
|
|
182
340
|
// ── Ordered / visible columns ─────────────────────────────────────────────
|
|
341
|
+
// A controlled or initial column order always applies. disableColumnReorder only removes the
|
|
342
|
+
// user's ways to change it (header drag, Columns-panel reorder).
|
|
183
343
|
const orderedColumns = useMemo<GridColDef<R>[]>(() => {
|
|
184
|
-
if (disableColumnReorder) return effectiveColumns;
|
|
185
|
-
|
|
186
344
|
const orderMap = new Map(effectiveColumnOrder.map((field, idx) => [field, idx]));
|
|
187
345
|
return [...effectiveColumns].sort((a, b) => {
|
|
188
346
|
const ai = orderMap.get(a.field) ?? effectiveColumns.indexOf(a);
|
|
189
347
|
const bi = orderMap.get(b.field) ?? effectiveColumns.indexOf(b);
|
|
190
348
|
return ai - bi;
|
|
191
349
|
});
|
|
192
|
-
}, [effectiveColumns, effectiveColumnOrder
|
|
350
|
+
}, [effectiveColumns, effectiveColumnOrder]);
|
|
193
351
|
|
|
194
352
|
const visibleOrderedColumns = useMemo<GridColDef<R>[]>(
|
|
195
|
-
() => orderedColumns.filter(
|
|
196
|
-
[orderedColumns,
|
|
353
|
+
() => orderedColumns.filter(isColumnShown),
|
|
354
|
+
[orderedColumns, isColumnShown]
|
|
197
355
|
);
|
|
198
356
|
|
|
199
|
-
// ── Column reorder
|
|
357
|
+
// ── Column reorder ────────────────────────────────────────────────────────
|
|
358
|
+
// Every move works on the full current order (orderedColumns: all columns, including ones added
|
|
359
|
+
// after mount or missing from a stored/controlled order, and the synthetic __group__ column),
|
|
360
|
+
// so the reported indices and the stored order always describe the same list.
|
|
361
|
+
const commitColumnOrder = useCallback((newOrder: string[], change?: GridColumnOrderChangeParams) => {
|
|
362
|
+
// A controlled columnOrder names source columns; generated pivot columns keep their own order.
|
|
363
|
+
if (pivotMode || !columnOrder) setInternalColumnOrder(newOrder);
|
|
364
|
+
if (change) onColumnOrderChange?.(change);
|
|
365
|
+
if (!pivotMode) onColumnOrderModelChange?.(newOrder);
|
|
366
|
+
}, [pivotMode, columnOrder, setInternalColumnOrder, onColumnOrderChange, onColumnOrderModelChange]);
|
|
367
|
+
|
|
368
|
+
const applyColumnMove = useCallback((oldIndex: number, targetIndex: number) => {
|
|
369
|
+
const newOrder = orderedColumns.map(col => col.field);
|
|
370
|
+
const [movedField] = newOrder.splice(oldIndex, 1);
|
|
371
|
+
newOrder.splice(targetIndex, 0, movedField);
|
|
372
|
+
commitColumnOrder(newOrder, {
|
|
373
|
+
column: orderedColumns[oldIndex] as unknown as GridColDef,
|
|
374
|
+
oldIndex,
|
|
375
|
+
targetIndex,
|
|
376
|
+
});
|
|
377
|
+
}, [orderedColumns, commitColumnOrder]);
|
|
378
|
+
|
|
200
379
|
const columnReorderHandlers = useColumnReorder({
|
|
201
380
|
columns: orderedColumns,
|
|
202
|
-
onColumnOrderChange: useCallback(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
newOrder.splice(targetIndex, 0, movedField);
|
|
207
|
-
if (!columnOrder) setInternalColumnOrder(newOrder);
|
|
208
|
-
onColumnOrderChange?.(reorderParams);
|
|
209
|
-
}, [effectiveColumnOrder, columnOrder, onColumnOrderChange]),
|
|
381
|
+
onColumnOrderChange: useCallback(
|
|
382
|
+
({ oldIndex, targetIndex }: GridColumnOrderChangeParams) => applyColumnMove(oldIndex, targetIndex),
|
|
383
|
+
[applyColumnMove]
|
|
384
|
+
),
|
|
210
385
|
disableColumnReorder,
|
|
211
386
|
});
|
|
212
387
|
|
|
213
|
-
|
|
388
|
+
const moveColumn = useCallback((fromField: string, toField: string) => {
|
|
389
|
+
const oldIndex = orderedColumns.findIndex(col => col.field === fromField);
|
|
390
|
+
const targetIndex = orderedColumns.findIndex(col => col.field === toField);
|
|
391
|
+
if (oldIndex === -1 || targetIndex === -1 || oldIndex === targetIndex) return;
|
|
392
|
+
applyColumnMove(oldIndex, targetIndex);
|
|
393
|
+
}, [orderedColumns, applyColumnMove]);
|
|
394
|
+
|
|
395
|
+
const resetColumnOrder = useCallback(() => {
|
|
396
|
+
commitColumnOrder(naturalOrder);
|
|
397
|
+
}, [commitColumnOrder, naturalOrder]);
|
|
398
|
+
|
|
399
|
+
// ── Navigation columns (system cols + rendered data cols, in render order) ─
|
|
400
|
+
// Mirrors what Row and Header render, so arrow keys follow the screen and never
|
|
401
|
+
// land on a hidden column. System columns are never sortable or editable.
|
|
402
|
+
const renderedDataColumns = useMemo(
|
|
403
|
+
() => inPinnedRenderOrder(visibleOrderedColumns, pinnedColumns),
|
|
404
|
+
[visibleOrderedColumns, pinnedColumns]
|
|
405
|
+
);
|
|
406
|
+
|
|
214
407
|
const navigationColumns = useMemo(() => {
|
|
215
|
-
const specials: { field: string }[] = [];
|
|
216
|
-
if (rowReordering) specials.push({ field:
|
|
217
|
-
if (hasDetailPanel) specials.push({ field:
|
|
218
|
-
if (checkboxSelection) specials.push({ field:
|
|
219
|
-
return [...specials, ...
|
|
220
|
-
}, [
|
|
408
|
+
const specials: { field: string; sortable: false; editable: false }[] = [];
|
|
409
|
+
if (rowReordering) specials.push({ field: REORDER_FIELD, sortable: false, editable: false });
|
|
410
|
+
if (hasDetailPanel) specials.push({ field: EXPAND_FIELD, sortable: false, editable: false });
|
|
411
|
+
if (checkboxSelection) specials.push({ field: CHECKBOX_FIELD, sortable: false, editable: false });
|
|
412
|
+
return [...specials, ...renderedDataColumns];
|
|
413
|
+
}, [renderedDataColumns, checkboxSelection, hasDetailPanel, rowReordering]);
|
|
414
|
+
|
|
415
|
+
const columnIndexMap = useMemo(
|
|
416
|
+
() => new Map(renderedDataColumns.map((col, index) => [col.field, index])),
|
|
417
|
+
[renderedDataColumns]
|
|
418
|
+
);
|
|
221
419
|
|
|
222
420
|
return {
|
|
223
421
|
effectiveColumns,
|
|
422
|
+
hierarchyField,
|
|
224
423
|
orderedColumns,
|
|
225
424
|
visibleOrderedColumns,
|
|
425
|
+
renderOrderedColumns: renderedDataColumns,
|
|
226
426
|
navigationColumns,
|
|
427
|
+
columnIndexMap,
|
|
227
428
|
columnWidths,
|
|
228
429
|
effectiveColumnOrder,
|
|
229
430
|
setInternalColumnOrder,
|
|
230
431
|
columnReorderHandlers,
|
|
432
|
+
moveColumn,
|
|
433
|
+
resetColumnOrder,
|
|
231
434
|
handleColumnResize,
|
|
232
435
|
};
|
|
233
436
|
}
|