@opencorestack/opengridx 2.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +408 -3
- package/README.md +93 -72
- package/dist/index.d.ts +611 -121
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +7992 -5650
- 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 -161
- 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 +45 -22
- package/docs/components/datagrid.md +14 -12
- 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 +100 -36
- 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 +147 -41
- 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 +56 -19
- package/docs/features/pinning.md +23 -5
- package/docs/features/selection.md +50 -10
- package/docs/features/sorting-pagination.md +66 -14
- package/docs/features/state-persistence.md +78 -10
- package/docs/features/toolbar-customization.md +26 -17
- package/docs/features/tree-data-grouping.md +44 -9
- package/docs/features/virtualization.md +64 -15
- package/docs/migration/v1-to-v2.md +115 -0
- package/docs/migration/v2-to-v3.md +1222 -0
- package/docs/performance.md +124 -0
- package/docs/roadmap.md +16 -10
- package/lib/components/Cell/Cell.css +13 -1
- package/lib/components/Cell/Cell.tsx +130 -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 +11 -0
- 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 +84 -12
- package/lib/components/DataGrid/DataGrid.tsx +606 -872
- package/lib/components/DataGrid/GridAggregationFooter.tsx +53 -17
- package/lib/components/DataGrid/GridEmptyState.tsx +7 -1
- package/lib/components/DataGrid/GridErrorOverlay.tsx +15 -12
- package/lib/components/DataGrid/GridListView.tsx +166 -23
- 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 +65 -11
- package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +40 -38
- package/lib/components/DataGrid/GridToolbarSlot.tsx +51 -0
- package/lib/components/DataGrid/GridVirtualRows.tsx +48 -25
- 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 +3 -3
- package/lib/components/Header/Header.tsx +179 -160
- package/lib/components/ListView/ListView.css +25 -0
- package/lib/components/ListView/ListViewRow.tsx +79 -10
- package/lib/components/Pagination/Pagination.tsx +15 -7
- package/lib/components/Row/Row.tsx +185 -75
- 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 +15 -3
- 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 +284 -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 +123 -0
- 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 +452 -250
- 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 +20 -7
- 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 +86 -90
- 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 +335 -214
- package/lib/hooks/useRowReorder.ts +62 -31
- package/lib/hooks/useTreeData.ts +330 -262
- package/lib/index.ts +30 -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 +311 -108
- package/lib/utils/aggregation/index.ts +157 -0
- 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 +344 -121
- 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 +78 -0
- 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 +272 -166
- package/package.json +18 -6
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +0 -220
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.test.tsx +0 -85
- 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/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 -137
- package/lib/utils/export/exportToPdf.test.ts +0 -120
- package/lib/utils/export/groupedExport.test.ts +0 -144
- package/lib/utils/filtering/filtering.test.ts +0 -178
- package/lib/utils/sorting/sorting.test.ts +0 -88
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Row } from '../Row/Row';
|
|
3
|
+
import { DetailPanelContent } from '../DetailPanel/DetailPanelContent';
|
|
4
|
+
import { callGetDetailPanelHeight, resolveDetailPanelHeight } from '../../utils/detailPanel';
|
|
3
5
|
import type {
|
|
4
6
|
GridRowModel,
|
|
5
7
|
GridRowId,
|
|
@@ -13,6 +15,14 @@ import type {
|
|
|
13
15
|
GridRowMeta,
|
|
14
16
|
} from '../../types';
|
|
15
17
|
import type { CellColSpanInfo, RowSpanningCaches } from '../../hooks/features/useGridSpanning';
|
|
18
|
+
import type { GridEditingState } from '../../hooks/features/useGridEditing';
|
|
19
|
+
|
|
20
|
+
interface PinnedRowEditingHandlers {
|
|
21
|
+
editingCell: GridEditingState['editingCell'];
|
|
22
|
+
startCellEdit: (params: { id: GridRowId; field: string; value: unknown }) => void;
|
|
23
|
+
stopCellEdit: (params?: { cancel?: boolean; id?: GridRowId; field?: string }) => void;
|
|
24
|
+
setEditCellValue: (params: { id: GridRowId; field: string; value: unknown }) => void;
|
|
25
|
+
}
|
|
16
26
|
|
|
17
27
|
export interface GridPinnedRowsProps<R extends GridRowModel> {
|
|
18
28
|
rows: R[];
|
|
@@ -21,6 +31,7 @@ export interface GridPinnedRowsProps<R extends GridRowModel> {
|
|
|
21
31
|
selectedRowIds: Set<GridRowId>;
|
|
22
32
|
checkboxSelection: boolean;
|
|
23
33
|
onRowClick: (params: GridRowParams<R>) => void;
|
|
34
|
+
onRowDoubleClick?: (params: GridRowParams<R>) => void;
|
|
24
35
|
onCellClick: (params: GridCellParams<R>) => void;
|
|
25
36
|
onSelectionChange: (rowId: GridRowId, isSelected: boolean) => void;
|
|
26
37
|
columnWidths: Record<string, number>;
|
|
@@ -31,15 +42,34 @@ export interface GridPinnedRowsProps<R extends GridRowModel> {
|
|
|
31
42
|
getDetailPanelContent?: (params: GridDetailPanelParams<R>) => React.ReactNode;
|
|
32
43
|
getDetailPanelHeight?: (params: GridDetailPanelParams<R>) => GridDetailPanelHeight;
|
|
33
44
|
onDetailPanelToggle: (rowId: GridRowId) => void;
|
|
45
|
+
/** Receives the rendered height of `'auto'` detail panels. */
|
|
46
|
+
onDetailPanelHeightChange?: (rowId: GridRowId, height: number) => void;
|
|
34
47
|
pinCheckboxColumn?: boolean;
|
|
35
48
|
pinExpandColumn?: boolean;
|
|
36
|
-
focusedCell: { id: GridRowId; field: string } | null;
|
|
49
|
+
focusedCell: { id: GridRowId | null; field: string } | null;
|
|
37
50
|
colspanMap?: Map<GridRowId, Record<string, CellColSpanInfo>>;
|
|
38
51
|
rowSpanningCaches?: RowSpanningCaches;
|
|
39
52
|
rowHeight: number;
|
|
40
53
|
rowMetaMap: Map<GridRowId, GridRowMeta>;
|
|
54
|
+
/** Resolves a row's id; defaults to `row.id`. */
|
|
55
|
+
getRowId?: (row: R) => GridRowId;
|
|
56
|
+
/** Index of the first of these rows in the grid's renderable rows (top-pinned + centre + bottom-pinned). */
|
|
57
|
+
rowIndexOffset?: number;
|
|
58
|
+
/** `aria-rowindex` of the first of these rows. */
|
|
59
|
+
ariaRowIndexBase?: number;
|
|
60
|
+
columnIndexMap?: Map<string, number>;
|
|
61
|
+
/** Pinned rows are edited like any other row. */
|
|
62
|
+
editingHandlers?: PinnedRowEditingHandlers;
|
|
63
|
+
isCellEditable?: (params: GridCellParams<R>) => boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Renders the (empty, non-draggable) reorder cell so the row lines up with the header and the
|
|
66
|
+
* centre rows. Pinned rows cannot be dragged.
|
|
67
|
+
*/
|
|
68
|
+
rowReordering?: boolean;
|
|
41
69
|
}
|
|
42
70
|
|
|
71
|
+
const defaultGetRowId = <R extends GridRowModel>(row: R): GridRowId => row.id;
|
|
72
|
+
|
|
43
73
|
export function GridPinnedRows<R extends GridRowModel>({
|
|
44
74
|
rows,
|
|
45
75
|
position,
|
|
@@ -47,6 +77,7 @@ export function GridPinnedRows<R extends GridRowModel>({
|
|
|
47
77
|
selectedRowIds,
|
|
48
78
|
checkboxSelection,
|
|
49
79
|
onRowClick,
|
|
80
|
+
onRowDoubleClick,
|
|
50
81
|
onCellClick,
|
|
51
82
|
onSelectionChange,
|
|
52
83
|
columnWidths,
|
|
@@ -57,6 +88,7 @@ export function GridPinnedRows<R extends GridRowModel>({
|
|
|
57
88
|
getDetailPanelContent,
|
|
58
89
|
getDetailPanelHeight,
|
|
59
90
|
onDetailPanelToggle,
|
|
91
|
+
onDetailPanelHeightChange,
|
|
60
92
|
pinCheckboxColumn,
|
|
61
93
|
pinExpandColumn,
|
|
62
94
|
focusedCell,
|
|
@@ -64,39 +96,61 @@ export function GridPinnedRows<R extends GridRowModel>({
|
|
|
64
96
|
rowSpanningCaches,
|
|
65
97
|
rowHeight,
|
|
66
98
|
rowMetaMap,
|
|
99
|
+
getRowId = defaultGetRowId,
|
|
100
|
+
rowIndexOffset = 0,
|
|
101
|
+
ariaRowIndexBase,
|
|
102
|
+
columnIndexMap,
|
|
103
|
+
editingHandlers,
|
|
104
|
+
isCellEditable,
|
|
105
|
+
rowReordering = false,
|
|
67
106
|
}: GridPinnedRowsProps<R>) {
|
|
68
107
|
if (rows.length === 0) return null;
|
|
69
108
|
|
|
70
109
|
return (
|
|
71
110
|
<div className={`ogx__pinned-rows ogx__pinned-rows--${position}`} role="rowgroup">
|
|
72
|
-
{rows.map((row, index) =>
|
|
111
|
+
{rows.map((row, index) => {
|
|
112
|
+
const id = getRowId(row);
|
|
113
|
+
const rowIndex = rowIndexOffset + index;
|
|
114
|
+
return (
|
|
73
115
|
<Row<R>
|
|
74
|
-
key={
|
|
116
|
+
key={id}
|
|
75
117
|
row={row}
|
|
118
|
+
rowId={id}
|
|
76
119
|
columns={columns}
|
|
77
|
-
rowIndex={
|
|
78
|
-
|
|
120
|
+
rowIndex={rowIndex}
|
|
121
|
+
ariaRowIndex={ariaRowIndexBase !== undefined ? ariaRowIndexBase + index : undefined}
|
|
122
|
+
columnIndexMap={columnIndexMap}
|
|
123
|
+
isSelected={selectedRowIds.has(id)}
|
|
79
124
|
checkboxSelection={checkboxSelection}
|
|
80
125
|
onRowClick={onRowClick}
|
|
126
|
+
onRowDoubleClick={onRowDoubleClick}
|
|
81
127
|
onCellClick={onCellClick}
|
|
82
128
|
onSelectionChange={onSelectionChange}
|
|
83
129
|
columnWidths={columnWidths}
|
|
84
130
|
pinnedColumns={pinnedColumns}
|
|
85
131
|
pinnedRows={pinnedRows}
|
|
86
132
|
hasDetailPanel={hasDetailPanel}
|
|
87
|
-
isDetailPanelExpanded={expandedRowIds.has(
|
|
88
|
-
detailPanelContent={getDetailPanelContent ? getDetailPanelContent
|
|
89
|
-
detailPanelHeight={getDetailPanelHeight
|
|
133
|
+
isDetailPanelExpanded={expandedRowIds.has(id)}
|
|
134
|
+
detailPanelContent={expandedRowIds.has(id) && getDetailPanelContent ? <DetailPanelContent<R> getContent={getDetailPanelContent} params={{ row, id, rowIndex }} /> : null}
|
|
135
|
+
detailPanelHeight={expandedRowIds.has(id) ? resolveDetailPanelHeight(callGetDetailPanelHeight(getDetailPanelHeight, { row, id, rowIndex })) : undefined}
|
|
136
|
+
onDetailPanelHeightChange={onDetailPanelHeightChange}
|
|
90
137
|
onDetailPanelToggle={onDetailPanelToggle}
|
|
91
138
|
pinCheckboxColumn={pinCheckboxColumn}
|
|
92
139
|
pinExpandColumn={pinExpandColumn}
|
|
93
|
-
focusedCellField={focusedCell?.id ===
|
|
140
|
+
focusedCellField={focusedCell?.id === id ? focusedCell.field : null}
|
|
94
141
|
colspanMap={colspanMap}
|
|
95
142
|
rowSpanningCaches={rowSpanningCaches}
|
|
96
143
|
rowHeight={rowHeight}
|
|
97
|
-
rowMeta={rowMetaMap.get(
|
|
144
|
+
rowMeta={rowMetaMap.get(id)}
|
|
145
|
+
editingCell={editingHandlers?.editingCell}
|
|
146
|
+
onEditStart={editingHandlers?.startCellEdit}
|
|
147
|
+
onEditStop={editingHandlers?.stopCellEdit}
|
|
148
|
+
onEditCellValueChange={editingHandlers?.setEditCellValue}
|
|
149
|
+
isCellEditable={isCellEditable}
|
|
150
|
+
rowReordering={rowReordering}
|
|
98
151
|
/>
|
|
99
|
-
|
|
152
|
+
);
|
|
153
|
+
})}
|
|
100
154
|
</div>
|
|
101
155
|
);
|
|
102
156
|
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
1
|
import React, { useState, useLayoutEffect } from 'react';
|
|
2
2
|
import ReactDOM from 'react-dom';
|
|
3
3
|
import { ColumnVisibilityPanel } from '../ColumnVisibilityPanel/ColumnVisibilityPanel';
|
|
4
|
-
import type { GridColDef, GridRowModel,
|
|
4
|
+
import type { GridColDef, GridRowModel, GridColumnGroupingModel } from '../../types';
|
|
5
|
+
import { canReorderWithinColumnGroups } from '../../utils/columnGroups';
|
|
6
|
+
import { getViewportWidth } from '../../utils/viewport';
|
|
5
7
|
|
|
6
8
|
export interface GridStandaloneColumnPanelProps<R extends GridRowModel> {
|
|
7
9
|
isOpen: boolean;
|
|
8
10
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
9
11
|
panelRef?: React.RefObject<HTMLDivElement | null>;
|
|
10
|
-
|
|
12
|
+
/** Every column, in the grid's current column order. */
|
|
13
|
+
columns: GridColDef<R>[];
|
|
11
14
|
columnVisibilityModel: Record<string, boolean>;
|
|
12
|
-
effectiveColumnOrder: string[];
|
|
13
|
-
columnOrder?: string[];
|
|
14
15
|
disableColumnReorder: boolean;
|
|
15
16
|
onClose: () => void;
|
|
16
17
|
onColumnVisibilityChange: (model: Record<string, boolean>) => void;
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
/** Moves `fromField` to the position of `toField` in the column order. */
|
|
19
|
+
onColumnMove: (fromField: string, toField: string) => void;
|
|
20
|
+
/** Restores the columns' definition order (the panel's Reset). */
|
|
21
|
+
onColumnOrderReset: () => void;
|
|
22
|
+
/** When set, a column can only be moved within its own column group. */
|
|
23
|
+
columnGroupingModel?: GridColumnGroupingModel;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PanelPlacement {
|
|
27
|
+
top: number;
|
|
28
|
+
right: number;
|
|
29
|
+
target: Element;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
export function GridStandaloneColumnPanel<R extends GridRowModel>({
|
|
22
33
|
isOpen,
|
|
23
34
|
containerRef,
|
|
24
35
|
panelRef,
|
|
25
|
-
|
|
36
|
+
columns,
|
|
26
37
|
columnVisibilityModel,
|
|
27
|
-
effectiveColumnOrder,
|
|
28
|
-
columnOrder,
|
|
29
38
|
disableColumnReorder,
|
|
30
39
|
onClose,
|
|
31
40
|
onColumnVisibilityChange,
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
onColumnMove,
|
|
42
|
+
onColumnOrderReset,
|
|
43
|
+
columnGroupingModel,
|
|
34
44
|
}: GridStandaloneColumnPanelProps<R>) {
|
|
35
|
-
|
|
36
|
-
const [
|
|
37
|
-
const [portalTarget, setPortalTarget] = useState<Element>(() => document.body);
|
|
45
|
+
// Resolved from the DOM after mount, never during render, so the grid renders on the server.
|
|
46
|
+
const [placement, setPlacement] = useState<PanelPlacement | null>(null);
|
|
38
47
|
|
|
39
48
|
useLayoutEffect(() => {
|
|
40
49
|
if (!isOpen || !containerRef.current) return;
|
|
41
50
|
const rect = containerRef.current.getBoundingClientRect();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
setPlacement({
|
|
52
|
+
top: rect.top + 8,
|
|
53
|
+
right: getViewportWidth() - rect.right + 8,
|
|
54
|
+
target: containerRef.current.closest('.ogx-theme-provider') || document.body,
|
|
55
|
+
});
|
|
45
56
|
}, [isOpen, containerRef]);
|
|
46
57
|
|
|
47
|
-
if (!isOpen) return null;
|
|
58
|
+
if (!isOpen || !placement) return null;
|
|
48
59
|
|
|
49
60
|
const handleReorder = disableColumnReorder
|
|
50
61
|
? undefined
|
|
51
62
|
: (fromField: string, toField: string) => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const toIdx = currentOrder.indexOf(toField);
|
|
55
|
-
if (fromIdx === -1 || toIdx === -1) return;
|
|
56
|
-
const newOrder = [...currentOrder];
|
|
57
|
-
newOrder.splice(fromIdx, 1);
|
|
58
|
-
newOrder.splice(toIdx, 0, fromField);
|
|
59
|
-
if (!columnOrder) setInternalColumnOrder(newOrder);
|
|
60
|
-
onColumnOrderChange?.({
|
|
61
|
-
oldIndex: fromIdx,
|
|
62
|
-
targetIndex: toIdx,
|
|
63
|
-
column: effectiveColumns.find(c => c.field === fromField) as unknown as GridColDef,
|
|
64
|
-
});
|
|
63
|
+
if (!canReorderWithinColumnGroups(columnGroupingModel, fromField, toField)) return;
|
|
64
|
+
onColumnMove(fromField, toField);
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
return ReactDOM.createPortal(
|
|
@@ -69,13 +69,14 @@ export function GridStandaloneColumnPanel<R extends GridRowModel>({
|
|
|
69
69
|
ref={panelRef}
|
|
70
70
|
style={{
|
|
71
71
|
position: 'fixed',
|
|
72
|
-
top:
|
|
73
|
-
right:
|
|
72
|
+
top: placement.top,
|
|
73
|
+
right: placement.right,
|
|
74
74
|
zIndex: 9999,
|
|
75
75
|
display: 'inline-block',
|
|
76
76
|
}}
|
|
77
77
|
>
|
|
78
78
|
<button
|
|
79
|
+
type="button"
|
|
79
80
|
onClick={onClose}
|
|
80
81
|
style={{
|
|
81
82
|
position: 'absolute',
|
|
@@ -99,9 +100,9 @@ export function GridStandaloneColumnPanel<R extends GridRowModel>({
|
|
|
99
100
|
aria-label="Close"
|
|
100
101
|
>×</button>
|
|
101
102
|
<ColumnVisibilityPanel<R>
|
|
102
|
-
columns={
|
|
103
|
+
columns={columns}
|
|
103
104
|
visibleColumns={new Set(
|
|
104
|
-
|
|
105
|
+
columns
|
|
105
106
|
.filter(col => columnVisibilityModel[col.field] !== false)
|
|
106
107
|
.map(col => col.field)
|
|
107
108
|
)}
|
|
@@ -110,17 +111,18 @@ export function GridStandaloneColumnPanel<R extends GridRowModel>({
|
|
|
110
111
|
}}
|
|
111
112
|
onShowAll={() => {
|
|
112
113
|
const next = { ...columnVisibilityModel };
|
|
113
|
-
|
|
114
|
+
columns.forEach(col => { if (col.hideable !== false) next[col.field] = true; });
|
|
114
115
|
onColumnVisibilityChange(next);
|
|
115
116
|
}}
|
|
116
117
|
onHideAll={() => {
|
|
117
118
|
const next = { ...columnVisibilityModel };
|
|
118
|
-
|
|
119
|
+
columns.forEach(col => { if (col.hideable !== false) next[col.field] = false; });
|
|
119
120
|
onColumnVisibilityChange(next);
|
|
120
121
|
}}
|
|
121
122
|
onColumnReorder={handleReorder}
|
|
123
|
+
onColumnOrderReset={disableColumnReorder ? undefined : onColumnOrderReset}
|
|
122
124
|
/>
|
|
123
125
|
</div>,
|
|
124
|
-
|
|
126
|
+
placement.target
|
|
125
127
|
);
|
|
126
128
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { GridToolbarSlotProps as GridToolbarSlotComponentProps } from '../../types';
|
|
3
|
+
|
|
4
|
+
type ToolbarProps = GridToolbarSlotComponentProps & Record<string, unknown>;
|
|
5
|
+
type ToolbarRender = (props: ToolbarProps) => React.ReactNode;
|
|
6
|
+
|
|
7
|
+
interface GridToolbarSlotProps {
|
|
8
|
+
component: React.ComponentType<ToolbarProps>;
|
|
9
|
+
toolbarProps: ToolbarProps;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface InlineToolbarProps {
|
|
13
|
+
render: ToolbarRender;
|
|
14
|
+
toolbarProps: ToolbarProps;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A plain function component: not a class, and not a memo / forwardRef / lazy object. */
|
|
18
|
+
function isPlainFunctionComponent(component: React.ComponentType<ToolbarProps>): component is ToolbarRender {
|
|
19
|
+
if (typeof component !== 'function') return false;
|
|
20
|
+
const proto: unknown = component.prototype;
|
|
21
|
+
return !(typeof proto === 'object' && proto !== null && 'isReactComponent' in proto);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Runs a plain function toolbar inside its own fiber. The caller keys this element on the
|
|
26
|
+
* function's source, so the fiber (and every hook in it) survives when the consumer redefines
|
|
27
|
+
* the same toolbar inline on each render, but remounts when a different toolbar is passed.
|
|
28
|
+
*/
|
|
29
|
+
function InlineToolbar({ render, toolbarProps }: InlineToolbarProps) {
|
|
30
|
+
return <>{render(toolbarProps)}</>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders `slots.toolbar`.
|
|
35
|
+
*
|
|
36
|
+
* Consumers often write the toolbar as an inline function inside their own component, which
|
|
37
|
+
* gives it a new identity on every render. Rendering that as `<Toolbar />` would remount it on
|
|
38
|
+
* every keystroke and lose GridToolbar's state (open panels, typed search). A plain function is
|
|
39
|
+
* therefore called from a wrapper keyed on its source text: the same code keeps its state, and
|
|
40
|
+
* a switch to a toolbar with different code (and different hooks) remounts cleanly.
|
|
41
|
+
*
|
|
42
|
+
* Class, `React.memo`, `forwardRef` and `lazy` components cannot be called as functions, so they
|
|
43
|
+
* are rendered as ordinary elements.
|
|
44
|
+
*/
|
|
45
|
+
export function GridToolbarSlot({ component, toolbarProps }: GridToolbarSlotProps) {
|
|
46
|
+
if (isPlainFunctionComponent(component)) {
|
|
47
|
+
return <InlineToolbar key={`fn:${component.toString()}`} render={component} toolbarProps={toolbarProps} />;
|
|
48
|
+
}
|
|
49
|
+
const Toolbar = component;
|
|
50
|
+
return <Toolbar {...toolbarProps} />;
|
|
51
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Row } from '../Row/Row';
|
|
3
|
+
import { DetailPanelContent } from '../DetailPanel/DetailPanelContent';
|
|
3
4
|
import { SkeletonRow } from '../SkeletonRow';
|
|
4
|
-
import {
|
|
5
|
+
import { callGetDetailPanelHeight, resolveDetailPanelHeight } from '../../utils/detailPanel';
|
|
5
6
|
import type {
|
|
6
7
|
GridRowModel,
|
|
7
8
|
GridRowId,
|
|
@@ -9,7 +10,6 @@ import type {
|
|
|
9
10
|
GridRowParams,
|
|
10
11
|
GridCellParams,
|
|
11
12
|
GridColumnPinning,
|
|
12
|
-
GridRowPinning,
|
|
13
13
|
GridDetailPanelParams,
|
|
14
14
|
GridDetailPanelHeight,
|
|
15
15
|
GridRowMeta,
|
|
@@ -22,7 +22,7 @@ import type { CellColSpanInfo, RowSpanningCaches } from '../../hooks/features/us
|
|
|
22
22
|
interface RowEditingHandlers {
|
|
23
23
|
editingCell: GridEditingState['editingCell'];
|
|
24
24
|
startCellEdit: (params: { id: GridRowId; field: string; value: unknown }) => void;
|
|
25
|
-
stopCellEdit: (params?: { cancel?: boolean }) => void;
|
|
25
|
+
stopCellEdit: (params?: { cancel?: boolean; id?: GridRowId; field?: string }) => void;
|
|
26
26
|
setEditCellValue: (params: { id: GridRowId; field: string; value: unknown }) => void;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -38,9 +38,9 @@ export interface GridVirtualRowsProps<R extends GridRowModel> {
|
|
|
38
38
|
hasDetailPanel: boolean;
|
|
39
39
|
rowReordering: boolean;
|
|
40
40
|
rowHeight: number;
|
|
41
|
-
pinnedRows?: GridRowPinning;
|
|
42
41
|
selectedRowIds: Set<GridRowId>;
|
|
43
42
|
onRowClick: (params: GridRowParams<R>) => void;
|
|
43
|
+
onRowDoubleClick?: (params: GridRowParams<R>) => void;
|
|
44
44
|
onCellClick: (params: GridCellParams<R>) => void;
|
|
45
45
|
onSelectionChange: (rowId: GridRowId, isSelected: boolean) => void;
|
|
46
46
|
columnWidths: Record<string, number>;
|
|
@@ -49,11 +49,14 @@ export interface GridVirtualRowsProps<R extends GridRowModel> {
|
|
|
49
49
|
getDetailPanelContent?: (params: GridDetailPanelParams<R>) => React.ReactNode;
|
|
50
50
|
getDetailPanelHeight?: (params: GridDetailPanelParams<R>) => GridDetailPanelHeight;
|
|
51
51
|
onDetailPanelToggle: (rowId: GridRowId) => void;
|
|
52
|
+
/** Receives the rendered height of `'auto'` detail panels. */
|
|
53
|
+
onDetailPanelHeightChange?: (rowId: GridRowId, height: number) => void;
|
|
52
54
|
pinCheckboxColumn?: boolean;
|
|
53
55
|
pinExpandColumn?: boolean;
|
|
54
56
|
rowReorderHandlers: UseRowReorderReturn;
|
|
55
57
|
editingHandlers: RowEditingHandlers;
|
|
56
|
-
|
|
58
|
+
isCellEditable?: (params: GridCellParams<R>) => boolean;
|
|
59
|
+
focusedCell: { id: GridRowId | null; field: string } | null;
|
|
57
60
|
colspanMap?: Map<GridRowId, Record<string, CellColSpanInfo>>;
|
|
58
61
|
rowSpanningCaches?: RowSpanningCaches;
|
|
59
62
|
paginationMode: 'client' | 'server' | 'infinite';
|
|
@@ -61,7 +64,13 @@ export interface GridVirtualRowsProps<R extends GridRowModel> {
|
|
|
61
64
|
sortedUnpinnedRowCount: number;
|
|
62
65
|
infiniteScrollSkeletonCount: number;
|
|
63
66
|
unpinnedRowsLength: number;
|
|
67
|
+
/** Last center row index in the render window: skeleton rows render only once it reaches the end of the data. */
|
|
68
|
+
lastRenderedRowIndex: number;
|
|
64
69
|
rowMetaMap: Map<GridRowId, GridRowMeta>;
|
|
70
|
+
loadingOverlay?: React.ReactNode;
|
|
71
|
+
/** Added to a row's rowIndex to get its `aria-rowindex` (header rows and earlier pages included). */
|
|
72
|
+
ariaRowIndexOffset?: number;
|
|
73
|
+
columnIndexMap?: Map<string, number>;
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
export function GridVirtualRows<R extends GridRowModel>({
|
|
@@ -76,9 +85,9 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
76
85
|
hasDetailPanel,
|
|
77
86
|
rowReordering,
|
|
78
87
|
rowHeight,
|
|
79
|
-
pinnedRows,
|
|
80
88
|
selectedRowIds,
|
|
81
89
|
onRowClick,
|
|
90
|
+
onRowDoubleClick,
|
|
82
91
|
onCellClick,
|
|
83
92
|
onSelectionChange,
|
|
84
93
|
columnWidths,
|
|
@@ -87,10 +96,12 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
87
96
|
getDetailPanelContent,
|
|
88
97
|
getDetailPanelHeight,
|
|
89
98
|
onDetailPanelToggle,
|
|
99
|
+
onDetailPanelHeightChange,
|
|
90
100
|
pinCheckboxColumn,
|
|
91
101
|
pinExpandColumn,
|
|
92
102
|
rowReorderHandlers,
|
|
93
103
|
editingHandlers,
|
|
104
|
+
isCellEditable,
|
|
94
105
|
focusedCell,
|
|
95
106
|
colspanMap,
|
|
96
107
|
rowSpanningCaches,
|
|
@@ -99,7 +110,11 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
99
110
|
sortedUnpinnedRowCount,
|
|
100
111
|
infiniteScrollSkeletonCount,
|
|
101
112
|
unpinnedRowsLength,
|
|
113
|
+
lastRenderedRowIndex,
|
|
102
114
|
rowMetaMap,
|
|
115
|
+
loadingOverlay,
|
|
116
|
+
ariaRowIndexOffset,
|
|
117
|
+
columnIndexMap,
|
|
103
118
|
}: GridVirtualRowsProps<R>) {
|
|
104
119
|
const skeletonColumns: GridColDef<R>[] = baseColumns.length > 0
|
|
105
120
|
? baseColumns
|
|
@@ -117,11 +132,12 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
117
132
|
})) as GridColDef<R>[];
|
|
118
133
|
})();
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
// Pinned rows render in GridPinnedRows; ids are already unique (useGridVisibleRows de-duplicates).
|
|
136
|
+
const centerRows = visibleRows.filter(item => !item.pinned);
|
|
137
|
+
// Placeholders stand in for the next page, after the last data row: only draw them once the
|
|
138
|
+
// render window reaches it, or they would follow the window into the middle of the data.
|
|
139
|
+
const showInfiniteSkeletons = paginationMode === 'infinite' && dataSourceLoading && sortedUnpinnedRowCount > 0
|
|
140
|
+
&& lastRenderedRowIndex >= unpinnedRowsLength - 1;
|
|
125
141
|
|
|
126
142
|
return (
|
|
127
143
|
<div
|
|
@@ -134,7 +150,9 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
134
150
|
style={{ transform: `translateY(${offsetTop}px)` }}
|
|
135
151
|
role="rowgroup"
|
|
136
152
|
>
|
|
137
|
-
{effectiveLoading && visibleRows.length === 0 ? (
|
|
153
|
+
{effectiveLoading && visibleRows.length === 0 && loadingOverlay ? (
|
|
154
|
+
<div className="ogx__loading-overlay" style={{ width: viewportWidth || undefined }}>{loadingOverlay}</div>
|
|
155
|
+
) : effectiveLoading && visibleRows.length === 0 ? (
|
|
138
156
|
Array.from({ length: 10 }).map((_, index) => (
|
|
139
157
|
<SkeletonRow
|
|
140
158
|
key={`skeleton-${index}`}
|
|
@@ -146,48 +164,53 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
146
164
|
/>
|
|
147
165
|
))
|
|
148
166
|
) : (
|
|
149
|
-
centerRows.map(({ row, rowIndex: actualIndex }) => (
|
|
167
|
+
centerRows.map(({ row, id, rowIndex: actualIndex }) => (
|
|
150
168
|
<Row<R>
|
|
151
|
-
key={
|
|
169
|
+
key={id}
|
|
152
170
|
row={row}
|
|
171
|
+
rowId={id}
|
|
153
172
|
columns={virtualColumns}
|
|
154
173
|
rowIndex={actualIndex}
|
|
155
|
-
|
|
174
|
+
ariaRowIndex={ariaRowIndexOffset !== undefined ? actualIndex + ariaRowIndexOffset : undefined}
|
|
175
|
+
columnIndexMap={columnIndexMap}
|
|
176
|
+
isSelected={selectedRowIds.has(id)}
|
|
156
177
|
checkboxSelection={checkboxSelection}
|
|
157
178
|
onRowClick={onRowClick}
|
|
179
|
+
onRowDoubleClick={onRowDoubleClick}
|
|
158
180
|
onCellClick={onCellClick}
|
|
159
181
|
onSelectionChange={onSelectionChange}
|
|
160
182
|
columnWidths={columnWidths}
|
|
161
183
|
pinnedColumns={pinnedColumns}
|
|
162
|
-
pinnedRows={pinnedRows}
|
|
163
184
|
hasDetailPanel={hasDetailPanel}
|
|
164
|
-
isDetailPanelExpanded={expandedRowIds.has(
|
|
165
|
-
detailPanelContent={getDetailPanelContent ? getDetailPanelContent
|
|
166
|
-
detailPanelHeight={getDetailPanelHeight
|
|
185
|
+
isDetailPanelExpanded={expandedRowIds.has(id)}
|
|
186
|
+
detailPanelContent={expandedRowIds.has(id) && getDetailPanelContent ? <DetailPanelContent<R> getContent={getDetailPanelContent} params={{ row, id, rowIndex: actualIndex }} /> : null}
|
|
187
|
+
detailPanelHeight={expandedRowIds.has(id) ? resolveDetailPanelHeight(callGetDetailPanelHeight(getDetailPanelHeight, { row, id, rowIndex: actualIndex })) : undefined}
|
|
188
|
+
onDetailPanelHeightChange={onDetailPanelHeightChange}
|
|
167
189
|
onDetailPanelToggle={onDetailPanelToggle}
|
|
168
190
|
pinCheckboxColumn={pinCheckboxColumn}
|
|
169
191
|
pinExpandColumn={pinExpandColumn}
|
|
170
192
|
rowReordering={rowReordering}
|
|
171
|
-
onDragStart={rowReorderHandlers.onDragStart}
|
|
193
|
+
onDragStart={rowReorderHandlers.canReorderRow(id) ? rowReorderHandlers.onDragStart : undefined}
|
|
172
194
|
onDragOver={rowReorderHandlers.onDragOver}
|
|
173
195
|
onDragEnd={rowReorderHandlers.onDragEnd}
|
|
174
196
|
onDrop={rowReorderHandlers.onDrop}
|
|
175
|
-
isDragging={rowReorderHandlers.draggedRowId ===
|
|
176
|
-
isDragOver={rowReorderHandlers.dragOverRowId ===
|
|
197
|
+
isDragging={rowReorderHandlers.draggedRowId === id}
|
|
198
|
+
isDragOver={rowReorderHandlers.dragOverRowId === id}
|
|
177
199
|
editingCell={editingHandlers.editingCell}
|
|
178
200
|
onEditStart={editingHandlers.startCellEdit}
|
|
179
201
|
onEditStop={editingHandlers.stopCellEdit}
|
|
180
202
|
onEditCellValueChange={editingHandlers.setEditCellValue}
|
|
181
|
-
|
|
203
|
+
isCellEditable={isCellEditable}
|
|
204
|
+
focusedCellField={focusedCell != null && focusedCell.id === id ? focusedCell.field : null}
|
|
182
205
|
colspanMap={colspanMap}
|
|
183
206
|
rowSpanningCaches={rowSpanningCaches}
|
|
184
207
|
rowHeight={rowHeight}
|
|
185
|
-
rowMeta={rowMetaMap.get(
|
|
208
|
+
rowMeta={rowMetaMap.get(id)}
|
|
186
209
|
/>
|
|
187
210
|
))
|
|
188
211
|
)}
|
|
189
212
|
|
|
190
|
-
{
|
|
213
|
+
{showInfiniteSkeletons && (
|
|
191
214
|
<div className="ogx__skeleton-group">
|
|
192
215
|
{Array.from({ length: infiniteScrollSkeletonCount }).map((_, i) => (
|
|
193
216
|
<Row<R>
|
|
@@ -10,23 +10,52 @@ export interface DetailPanelProps<R extends GridRowModel = GridRowModel> {
|
|
|
10
10
|
content: React.ReactNode;
|
|
11
11
|
height: GridDetailPanelHeight;
|
|
12
12
|
isExpanded: boolean;
|
|
13
|
+
/** Referenced by the expand cell's `aria-controls`. */
|
|
14
|
+
id?: string;
|
|
15
|
+
/** Number of grid columns the panel spans (`aria-colspan` of its cell). */
|
|
16
|
+
colSpan?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Called with the panel's rendered height when `height` is `'auto'`: once when it mounts and
|
|
19
|
+
* again whenever its size changes, so the grid can lay the rows below it out correctly.
|
|
20
|
+
*/
|
|
21
|
+
onHeightChange?: (rowId: GridRowId, height: number) => void;
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
export function DetailPanel<R extends GridRowModel = GridRowModel>(props: DetailPanelProps<R>) {
|
|
16
|
-
const { content, height, isExpanded } = props;
|
|
25
|
+
const { rowId, content, height, isExpanded, onHeightChange, id, colSpan } = props;
|
|
26
|
+
const panelRef = React.useRef<HTMLDivElement>(null);
|
|
27
|
+
const isAuto = height === 'auto';
|
|
28
|
+
|
|
29
|
+
// Measured before paint, so the grid re-lays out rows under the panel before they are shown.
|
|
30
|
+
React.useLayoutEffect(() => {
|
|
31
|
+
const el = panelRef.current;
|
|
32
|
+
if (!el || !isAuto || !isExpanded || !onHeightChange) return;
|
|
33
|
+
const report = () => {
|
|
34
|
+
// Not laid out (hidden ancestor, or no layout engine): keep the previous height.
|
|
35
|
+
if (el.getClientRects().length === 0) return;
|
|
36
|
+
onHeightChange(rowId, el.offsetHeight);
|
|
37
|
+
};
|
|
38
|
+
report();
|
|
39
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
40
|
+
const observer = new ResizeObserver(report);
|
|
41
|
+
observer.observe(el);
|
|
42
|
+
return () => observer.disconnect();
|
|
43
|
+
}, [isAuto, isExpanded, onHeightChange, rowId]);
|
|
17
44
|
|
|
18
45
|
if (!isExpanded) {
|
|
19
46
|
return null;
|
|
20
47
|
}
|
|
21
48
|
|
|
22
49
|
const style: React.CSSProperties = {
|
|
23
|
-
height:
|
|
24
|
-
overflow:
|
|
50
|
+
height: isAuto ? 'auto' : `${height}px`,
|
|
51
|
+
overflow: isAuto ? 'visible' : 'auto'
|
|
25
52
|
};
|
|
26
53
|
|
|
54
|
+
// A detail panel sits between rows of the grid, so it is exposed as a row with one cell
|
|
55
|
+
// spanning every column (a bare div is not allowed inside role="rowgroup").
|
|
27
56
|
return (
|
|
28
|
-
<div className="ogx__detail-panel" style={style}>
|
|
29
|
-
<div className="ogx__detail-panel-content">
|
|
57
|
+
<div ref={panelRef} className="ogx__detail-panel" style={style} role="row" id={id}>
|
|
58
|
+
<div className="ogx__detail-panel-content" role="gridcell" aria-colspan={colSpan}>
|
|
30
59
|
{content}
|
|
31
60
|
</div>
|
|
32
61
|
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CellErrorBoundary } from '../Cell/CellErrorBoundary';
|
|
3
|
+
import type { GridDetailPanelParams, GridRowModel } from '../../types';
|
|
4
|
+
|
|
5
|
+
interface DetailPanelContentProps<R extends GridRowModel> {
|
|
6
|
+
getContent: (params: GridDetailPanelParams<R>) => React.ReactNode;
|
|
7
|
+
params: GridDetailPanelParams<R>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Calls `getDetailPanelContent` while rendering, below an error boundary, so a throwing callback
|
|
12
|
+
* shows an error marker in that one panel instead of unmounting the grid. The row lists render it
|
|
13
|
+
* only for an expanded row, so the callback never runs for collapsed rows.
|
|
14
|
+
*/
|
|
15
|
+
export function DetailPanelContent<R extends GridRowModel>({ getContent, params }: DetailPanelContentProps<R>) {
|
|
16
|
+
return <CellErrorBoundary field="detail panel" renderFn={() => getContent(params)} resetKey={params.row} />;
|
|
17
|
+
}
|