@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
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
align-items: center;
|
|
4
4
|
gap: var(--ogx-spacing-sm);
|
|
5
5
|
padding: var(--ogx-spacing-sm) var(--ogx-spacing-sm);
|
|
6
|
-
border-bottom: var(--ogx-border-width-thin) solid var(--ogx-grid-border-color);
|
|
7
|
-
background: var(--ogx-grid-header-background);
|
|
6
|
+
border-bottom: var(--ogx-border-width-thin) solid var(--ogx-toolbar-border, var(--ogx-grid-border-color));
|
|
7
|
+
background: var(--ogx-toolbar-background, var(--ogx-grid-header-background));
|
|
8
|
+
color: var(--ogx-toolbar-text, var(--ogx-grid-header-text));
|
|
8
9
|
min-height: 44px;
|
|
9
10
|
position: relative;
|
|
10
11
|
z-index: 100;
|
|
@@ -37,17 +38,17 @@
|
|
|
37
38
|
padding: 0;
|
|
38
39
|
border: none;
|
|
39
40
|
border-radius: 8px;
|
|
40
|
-
background: transparent;
|
|
41
|
-
color: var(--ogx-grid-header-text);
|
|
41
|
+
background: var(--ogx-toolbar-btn-bg, transparent);
|
|
42
|
+
color: var(--ogx-toolbar-btn-text, var(--ogx-grid-header-text));
|
|
42
43
|
cursor: pointer;
|
|
43
44
|
transition: background 0.15s, color 0.15s;
|
|
44
45
|
position: relative;
|
|
45
46
|
}
|
|
46
47
|
.ogx-toolbar__icon-btn:hover {
|
|
47
|
-
background: var(--ogx-grid-header-hover-background);
|
|
48
|
+
background: var(--ogx-toolbar-btn-hover, var(--ogx-grid-header-hover-background));
|
|
48
49
|
}
|
|
49
50
|
.ogx-toolbar__icon-btn--active {
|
|
50
|
-
background: var(--ogx-grid-header-hover-background);
|
|
51
|
+
background: var(--ogx-toolbar-btn-hover, var(--ogx-grid-header-hover-background));
|
|
51
52
|
outline: none;
|
|
52
53
|
}
|
|
53
54
|
.ogx-toolbar__icon-btn:focus {
|
|
@@ -106,7 +107,7 @@
|
|
|
106
107
|
border: var(--ogx-border-width-thin) solid var(--ogx-border-color);
|
|
107
108
|
border-radius: var(--ogx-border-radius-sm);
|
|
108
109
|
background: transparent;
|
|
109
|
-
color: var(--ogx-color-error, #ef4444);
|
|
110
|
+
color: var(--ogx-toolbar-btn-danger-text, var(--ogx-color-error, #ef4444));
|
|
110
111
|
font-family: var(--ogx-font-family);
|
|
111
112
|
font-size: var(--ogx-font-size-xs);
|
|
112
113
|
font-weight: 500;
|
|
@@ -114,8 +115,8 @@
|
|
|
114
115
|
transition: background 0.15s, border-color 0.15s;
|
|
115
116
|
}
|
|
116
117
|
.ogx-toolbar__clear-btn:hover {
|
|
117
|
-
background: var(--ogx-toolbar-btn-danger-
|
|
118
|
-
border-color: var(--ogx-color-error, #ef4444);
|
|
118
|
+
background: var(--ogx-toolbar-btn-danger-hover, rgba(239, 68, 68, 0.15));
|
|
119
|
+
border-color: var(--ogx-toolbar-btn-danger-text, var(--ogx-color-error, #ef4444));
|
|
119
120
|
}
|
|
120
121
|
.ogx-toolbar__close-btn {
|
|
121
122
|
padding: 2px 10px;
|
|
@@ -189,8 +190,8 @@
|
|
|
189
190
|
padding: 3px 6px;
|
|
190
191
|
border: var(--ogx-border-width-thin) solid var(--ogx-overlay-border, var(--ogx-border-color));
|
|
191
192
|
border-radius: 999px;
|
|
192
|
-
background: transparent;
|
|
193
|
-
color: var(--ogx-grid-row-text);
|
|
193
|
+
background: var(--ogx-toolbar-chip-bg, transparent);
|
|
194
|
+
color: var(--ogx-toolbar-chip-text, var(--ogx-grid-row-text));
|
|
194
195
|
font-family: var(--ogx-font-family-mono, monospace);
|
|
195
196
|
font-size: 10px;
|
|
196
197
|
font-weight: 500;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect, useCallback, useLayoutEffect } from 'react';
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback, useLayoutEffect, useContext } from 'react';
|
|
2
2
|
import ReactDOM from 'react-dom';
|
|
3
3
|
import type { GridColDef, GridAggregationModel, GridPivotModel, GridFilterModel } from '../../types';
|
|
4
|
+
import { AGGREGATION_FUNCTIONS } from '../../utils/aggregation';
|
|
5
|
+
import type { BuiltInAggFn } from '../../utils/aggregation';
|
|
6
|
+
import { getViewportWidth } from '../../utils/viewport';
|
|
7
|
+
import { GridToolbarHostContext } from '../../hooks/core/gridToolbarHostContext';
|
|
4
8
|
import { PivotPanel, PivotIcon } from './PivotPanel';
|
|
5
9
|
import { GlobalSearch } from './GlobalSearch';
|
|
6
10
|
import { ColumnVisibilityPanel } from '../ColumnVisibilityPanel/ColumnVisibilityPanel';
|
|
@@ -78,7 +82,23 @@ export interface GridToolbarProps {
|
|
|
78
82
|
showNonHideableColumns?: boolean;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
const
|
|
85
|
+
const BUILT_IN_AGGREGATION_FUNCTIONS = Object.keys(AGGREGATION_FUNCTIONS) as BuiltInAggFn[];
|
|
86
|
+
|
|
87
|
+
function isBuiltInAggregationFunction(fn: string): fn is BuiltInAggFn {
|
|
88
|
+
return Object.prototype.hasOwnProperty.call(AGGREGATION_FUNCTIONS, fn);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The summary functions offered for a column: its `availableAggregationFunctions` when set,
|
|
93
|
+
* otherwise every built-in one. Names the aggregation engine does not implement are dropped,
|
|
94
|
+
* because choosing one would show the label in the header but never compute a value.
|
|
95
|
+
*/
|
|
96
|
+
function getAggregationFunctions(col: GridColDef): BuiltInAggFn[] {
|
|
97
|
+
if (!col.availableAggregationFunctions) return BUILT_IN_AGGREGATION_FUNCTIONS;
|
|
98
|
+
return Array.from(new Set(col.availableAggregationFunctions.filter(isBuiltInAggregationFunction)));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
type ToolbarPanel = 'summaries' | 'pivot' | 'columns' | 'filters';
|
|
82
102
|
|
|
83
103
|
function SigmaIcon() {
|
|
84
104
|
return (
|
|
@@ -135,7 +155,7 @@ function AggregationPanel({
|
|
|
135
155
|
}) {
|
|
136
156
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
137
157
|
const [pos, setPos] = useState<PanelPosition>({ top: 0, right: 0 });
|
|
138
|
-
const [portalContainer, setPortalContainer] = useState<Element>(
|
|
158
|
+
const [portalContainer, setPortalContainer] = useState<Element | null>(null);
|
|
139
159
|
|
|
140
160
|
useLayoutEffect(() => {
|
|
141
161
|
setPortalContainer(anchorRef.current?.closest('.ogx-theme-provider') ?? document.body);
|
|
@@ -146,7 +166,7 @@ function AggregationPanel({
|
|
|
146
166
|
const rect = anchorRef.current.getBoundingClientRect();
|
|
147
167
|
return {
|
|
148
168
|
top: rect.bottom + 6,
|
|
149
|
-
right:
|
|
169
|
+
right: getViewportWidth() - rect.right,
|
|
150
170
|
};
|
|
151
171
|
}, [anchorRef]);
|
|
152
172
|
|
|
@@ -195,6 +215,7 @@ function AggregationPanel({
|
|
|
195
215
|
</span>
|
|
196
216
|
{activeCount > 0 && (
|
|
197
217
|
<button
|
|
218
|
+
type="button"
|
|
198
219
|
className="ogx-toolbar__clear-btn"
|
|
199
220
|
onClick={onClearAll}
|
|
200
221
|
title="Clear all summaries"
|
|
@@ -219,8 +240,9 @@ function AggregationPanel({
|
|
|
219
240
|
{col.headerName || col.field}
|
|
220
241
|
</span>
|
|
221
242
|
<div className="ogx-toolbar__agg-pills">
|
|
222
|
-
{
|
|
243
|
+
{['none', ...getAggregationFunctions(col)].map((fn) => (
|
|
223
244
|
<button
|
|
245
|
+
type="button"
|
|
224
246
|
key={fn}
|
|
225
247
|
className={`ogx-toolbar__pill${currentFn === fn ? ' ogx-toolbar__pill--active' : ''}`}
|
|
226
248
|
onClick={() => onFunctionChange(col.field, fn)}
|
|
@@ -238,7 +260,7 @@ function AggregationPanel({
|
|
|
238
260
|
</div>
|
|
239
261
|
);
|
|
240
262
|
|
|
241
|
-
return ReactDOM.createPortal(panel, portalContainer);
|
|
263
|
+
return portalContainer ? ReactDOM.createPortal(panel, portalContainer) : null;
|
|
242
264
|
}
|
|
243
265
|
|
|
244
266
|
function ColumnsPanelWrapper({
|
|
@@ -266,7 +288,7 @@ function ColumnsPanelWrapper({
|
|
|
266
288
|
}) {
|
|
267
289
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
268
290
|
const [pos, setPos] = useState<PanelPosition>({ top: 0, right: 0 });
|
|
269
|
-
const [portalContainer, setPortalContainer] = useState<Element>(
|
|
291
|
+
const [portalContainer, setPortalContainer] = useState<Element | null>(null);
|
|
270
292
|
|
|
271
293
|
useLayoutEffect(() => {
|
|
272
294
|
setPortalContainer(anchorRef.current?.closest('.ogx-theme-provider') ?? document.body);
|
|
@@ -277,7 +299,7 @@ function ColumnsPanelWrapper({
|
|
|
277
299
|
const rect = anchorRef.current.getBoundingClientRect();
|
|
278
300
|
return {
|
|
279
301
|
top: rect.bottom + 6,
|
|
280
|
-
right:
|
|
302
|
+
right: getViewportWidth() - rect.right,
|
|
281
303
|
};
|
|
282
304
|
}, [anchorRef]);
|
|
283
305
|
|
|
@@ -305,6 +327,12 @@ function ColumnsPanelWrapper({
|
|
|
305
327
|
return () => document.removeEventListener('mousedown', handleClick);
|
|
306
328
|
}, [anchorRef, onClose]);
|
|
307
329
|
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
function onKey(e: KeyboardEvent) { if (e.key === 'Escape') onClose(); }
|
|
332
|
+
document.addEventListener('keydown', onKey);
|
|
333
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
334
|
+
}, [onClose]);
|
|
335
|
+
|
|
308
336
|
const panel = (
|
|
309
337
|
<div
|
|
310
338
|
ref={panelRef}
|
|
@@ -326,7 +354,7 @@ function ColumnsPanelWrapper({
|
|
|
326
354
|
</div>
|
|
327
355
|
);
|
|
328
356
|
|
|
329
|
-
return ReactDOM.createPortal(panel, portalContainer);
|
|
357
|
+
return portalContainer ? ReactDOM.createPortal(panel, portalContainer) : null;
|
|
330
358
|
}
|
|
331
359
|
|
|
332
360
|
function FilterPanelWrapper({
|
|
@@ -349,7 +377,7 @@ function FilterPanelWrapper({
|
|
|
349
377
|
// which could fire the close callback during the re-register window.
|
|
350
378
|
const onCloseRef = useRef(onClose);
|
|
351
379
|
useLayoutEffect(() => { onCloseRef.current = onClose; }, [onClose]);
|
|
352
|
-
const [portalContainer, setPortalContainer] = useState<Element>(
|
|
380
|
+
const [portalContainer, setPortalContainer] = useState<Element | null>(null);
|
|
353
381
|
|
|
354
382
|
useLayoutEffect(() => {
|
|
355
383
|
setPortalContainer(anchorRef.current?.closest('.ogx-theme-provider') ?? document.body);
|
|
@@ -360,7 +388,7 @@ function FilterPanelWrapper({
|
|
|
360
388
|
const rect = anchorRef.current.getBoundingClientRect();
|
|
361
389
|
return {
|
|
362
390
|
top: rect.bottom + 6,
|
|
363
|
-
right:
|
|
391
|
+
right: getViewportWidth() - rect.right,
|
|
364
392
|
};
|
|
365
393
|
}, [anchorRef]);
|
|
366
394
|
|
|
@@ -412,6 +440,7 @@ function FilterPanelWrapper({
|
|
|
412
440
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
413
441
|
{activeFilterCount > 0 && (
|
|
414
442
|
<button
|
|
443
|
+
type="button"
|
|
415
444
|
className="ogx-toolbar__clear-btn"
|
|
416
445
|
onClick={handleClearAll}
|
|
417
446
|
title="Clear all filters"
|
|
@@ -420,6 +449,7 @@ function FilterPanelWrapper({
|
|
|
420
449
|
</button>
|
|
421
450
|
)}
|
|
422
451
|
<button
|
|
452
|
+
type="button"
|
|
423
453
|
className="ogx-toolbar__close-btn"
|
|
424
454
|
onClick={() => onCloseRef.current()}
|
|
425
455
|
title="Close filters"
|
|
@@ -440,7 +470,7 @@ function FilterPanelWrapper({
|
|
|
440
470
|
</div>
|
|
441
471
|
);
|
|
442
472
|
|
|
443
|
-
return ReactDOM.createPortal(panel, portalContainer);
|
|
473
|
+
return portalContainer ? ReactDOM.createPortal(panel, portalContainer) : null;
|
|
444
474
|
}
|
|
445
475
|
|
|
446
476
|
const EMPTY_PIVOT: GridPivotModel = { rowFields: [], columnFields: [], valueFields: [] };
|
|
@@ -471,20 +501,35 @@ export function GridToolbar({
|
|
|
471
501
|
className,
|
|
472
502
|
showNonHideableColumns,
|
|
473
503
|
}: GridToolbarProps) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const [
|
|
504
|
+
// At most one panel is open. Every way the Columns panel closes (its button, a custom
|
|
505
|
+
// button, another panel opening, click-outside, Escape) reports onColumnsPanelClose, so the
|
|
506
|
+
// grid can ask for it again with forceColumnsOpen.
|
|
507
|
+
const [openPanel, setOpenPanel] = useState<ToolbarPanel | null>(null);
|
|
508
|
+
const aggOpen = openPanel === 'summaries';
|
|
509
|
+
const pivotOpen = openPanel === 'pivot';
|
|
510
|
+
const colsOpen = openPanel === 'columns';
|
|
511
|
+
const filterOpen = openPanel === 'filters';
|
|
512
|
+
|
|
513
|
+
const switchPanel = (next: ToolbarPanel | null) => {
|
|
514
|
+
if (openPanel === 'columns' && next !== 'columns') onColumnsPanelClose?.();
|
|
515
|
+
setOpenPanel(next);
|
|
516
|
+
};
|
|
517
|
+
const togglePanel = (panel: ToolbarPanel) => switchPanel(openPanel === panel ? null : panel);
|
|
518
|
+
const closePanel = (panel: ToolbarPanel) => { if (openPanel === panel) switchPanel(null); };
|
|
478
519
|
|
|
479
520
|
useEffect(() => {
|
|
480
|
-
if (forceColumnsOpen)
|
|
481
|
-
setColsOpen(true);
|
|
482
|
-
setAggOpen(false);
|
|
483
|
-
setPivotOpen(false);
|
|
484
|
-
setFilterOpen(false);
|
|
485
|
-
}
|
|
521
|
+
if (forceColumnsOpen) setOpenPanel('columns');
|
|
486
522
|
}, [forceColumnsOpen]);
|
|
487
523
|
|
|
524
|
+
// Tell the grid this toolbar shows the Columns panel for the column menu; it can only do so
|
|
525
|
+
// when the grid's forceColumnsOpen reaches it and the Columns panel is enabled.
|
|
526
|
+
const toolbarHost = useContext(GridToolbarHostContext);
|
|
527
|
+
const hostsColumnsPanel = forceColumnsOpen !== undefined && Boolean(onColumnVisibilityModelChange);
|
|
528
|
+
useEffect(() => {
|
|
529
|
+
if (!toolbarHost || !hostsColumnsPanel) return undefined;
|
|
530
|
+
return toolbarHost.registerColumnsPanel();
|
|
531
|
+
}, [toolbarHost, hostsColumnsPanel]);
|
|
532
|
+
|
|
488
533
|
const aggButtonRef = useRef<HTMLButtonElement>(null);
|
|
489
534
|
const pivotButtonRef = useRef<HTMLButtonElement>(null);
|
|
490
535
|
const colsButtonRef = useRef<HTMLButtonElement>(null);
|
|
@@ -501,8 +546,13 @@ export function GridToolbar({
|
|
|
501
546
|
|| currentPivotModel.columnFields.length > 0
|
|
502
547
|
|| currentPivotModel.valueFields.length > 0;
|
|
503
548
|
|
|
549
|
+
// aggregationModel keys name source columns, so generated pivot columns (absent from baseColumns)
|
|
550
|
+
// are never offered: a summary on them would do nothing and leave a stray key in the model.
|
|
551
|
+
const baseFields = baseColumns ? new Set(baseColumns.map((c) => c.field)) : null;
|
|
504
552
|
const aggregableColumns = columns.filter(
|
|
505
553
|
(c) => c.aggregable !== false && (c.type === 'number' || c.aggregable === true)
|
|
554
|
+
&& getAggregationFunctions(c).length > 0
|
|
555
|
+
&& (!baseFields || baseFields.has(c.field))
|
|
506
556
|
);
|
|
507
557
|
|
|
508
558
|
const activeCount = Object.keys(aggregationModel).filter(
|
|
@@ -530,7 +580,9 @@ export function GridToolbar({
|
|
|
530
580
|
const handleSearchChange = useCallback((value: string) => {
|
|
531
581
|
if (!onFilterModelChange) return;
|
|
532
582
|
|
|
533
|
-
|
|
583
|
+
// Each whitespace-separated word is its own term; a row matches when every term is found
|
|
584
|
+
// in some column, so "john london" finds first = John, city = London.
|
|
585
|
+
const values = value.trim().split(/\s+/).filter(Boolean);
|
|
534
586
|
const nextModel = {
|
|
535
587
|
...(filterModel || {}),
|
|
536
588
|
quickFilterValues: values,
|
|
@@ -539,7 +591,7 @@ export function GridToolbar({
|
|
|
539
591
|
onFilterModelChange(nextModel);
|
|
540
592
|
}, [filterModel, onFilterModelChange]);
|
|
541
593
|
|
|
542
|
-
const searchValue = filterModel?.quickFilterValues
|
|
594
|
+
const searchValue = (filterModel?.quickFilterValues ?? []).join(' ');
|
|
543
595
|
const activeFilterCount = (filterModel?.items?.length || 0);
|
|
544
596
|
|
|
545
597
|
// Column Visibility Logic
|
|
@@ -604,22 +656,20 @@ export function GridToolbar({
|
|
|
604
656
|
<div ref={colsWrapperRef} className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
|
|
605
657
|
{renderColumnsButton
|
|
606
658
|
? renderColumnsButton({
|
|
607
|
-
onClick: () =>
|
|
659
|
+
onClick: () => togglePanel('columns'),
|
|
608
660
|
isOpen: colsOpen,
|
|
609
661
|
activeCount: columns.length - visibleColumns.size,
|
|
610
662
|
})
|
|
611
663
|
: (
|
|
612
664
|
<GridTooltip title="Columns">
|
|
613
665
|
<button
|
|
666
|
+
type="button"
|
|
614
667
|
ref={colsButtonRef}
|
|
615
668
|
className={`ogx-toolbar__icon-btn${colsOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
|
|
616
669
|
aria-label="Manage columns"
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
setPivotOpen(false);
|
|
621
|
-
setFilterOpen(false);
|
|
622
|
-
}}
|
|
670
|
+
aria-haspopup="dialog"
|
|
671
|
+
aria-expanded={colsOpen}
|
|
672
|
+
onClick={() => togglePanel('columns')}
|
|
623
673
|
>
|
|
624
674
|
<ViewColumnIcon />
|
|
625
675
|
</button>
|
|
@@ -636,7 +686,7 @@ export function GridToolbar({
|
|
|
636
686
|
onHideAll={handleHideAllColumns}
|
|
637
687
|
onColumnReorder={onColumnReorder}
|
|
638
688
|
onColumnOrderReset={onColumnOrderReset}
|
|
639
|
-
onClose={() =>
|
|
689
|
+
onClose={() => closePanel('columns')}
|
|
640
690
|
showNonHideableColumns={showNonHideableColumns}
|
|
641
691
|
/>
|
|
642
692
|
)}
|
|
@@ -648,22 +698,20 @@ export function GridToolbar({
|
|
|
648
698
|
<div ref={filterWrapperRef} className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
|
|
649
699
|
{renderFilterButton
|
|
650
700
|
? renderFilterButton({
|
|
651
|
-
onClick: () =>
|
|
701
|
+
onClick: () => togglePanel('filters'),
|
|
652
702
|
isOpen: filterOpen,
|
|
653
703
|
activeCount: activeFilterCount,
|
|
654
704
|
})
|
|
655
705
|
: (
|
|
656
706
|
<GridTooltip title="Filters">
|
|
657
707
|
<button
|
|
708
|
+
type="button"
|
|
658
709
|
ref={filterButtonRef}
|
|
659
710
|
className={`ogx-toolbar__icon-btn${filterOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
|
|
660
711
|
aria-label="Advanced filters"
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
setPivotOpen(false);
|
|
665
|
-
setColsOpen(false);
|
|
666
|
-
}}
|
|
712
|
+
aria-haspopup="dialog"
|
|
713
|
+
aria-expanded={filterOpen}
|
|
714
|
+
onClick={() => togglePanel('filters')}
|
|
667
715
|
>
|
|
668
716
|
<FilterIcon />
|
|
669
717
|
{activeFilterCount > 0 && (
|
|
@@ -679,7 +727,7 @@ export function GridToolbar({
|
|
|
679
727
|
columns={columns}
|
|
680
728
|
filterModel={filterModel || { items: [] }}
|
|
681
729
|
onFilterModelChange={onFilterModelChange}
|
|
682
|
-
onClose={() =>
|
|
730
|
+
onClose={() => closePanel('filters')}
|
|
683
731
|
/>
|
|
684
732
|
)}
|
|
685
733
|
</div>
|
|
@@ -690,16 +738,13 @@ export function GridToolbar({
|
|
|
690
738
|
<div className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
|
|
691
739
|
<GridTooltip title="Pivot">
|
|
692
740
|
<button
|
|
741
|
+
type="button"
|
|
693
742
|
ref={pivotButtonRef}
|
|
694
743
|
className={`ogx-toolbar__icon-btn${pivotOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
|
|
695
744
|
aria-label="Configure pivot"
|
|
745
|
+
aria-haspopup="dialog"
|
|
696
746
|
aria-expanded={pivotOpen}
|
|
697
|
-
onClick={() =>
|
|
698
|
-
setPivotOpen(!pivotOpen);
|
|
699
|
-
setAggOpen(false);
|
|
700
|
-
setColsOpen(false);
|
|
701
|
-
setFilterOpen(false);
|
|
702
|
-
}}
|
|
747
|
+
onClick={() => togglePanel('pivot')}
|
|
703
748
|
>
|
|
704
749
|
<PivotIcon />
|
|
705
750
|
{pivotActive && (
|
|
@@ -714,55 +759,54 @@ export function GridToolbar({
|
|
|
714
759
|
columns={baseColumns ?? columns}
|
|
715
760
|
model={currentPivotModel}
|
|
716
761
|
onChange={(m) => { onPivotModelChange?.(m); }}
|
|
717
|
-
onClose={() =>
|
|
762
|
+
onClose={() => closePanel('pivot')}
|
|
718
763
|
/>
|
|
719
764
|
)}
|
|
720
765
|
</div>
|
|
721
766
|
)}
|
|
722
767
|
|
|
723
768
|
{/* Summaries button */}
|
|
724
|
-
|
|
725
|
-
{
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
<
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
</div>
|
|
769
|
+
{onAggregationModelChange && (
|
|
770
|
+
<div ref={aggWrapperRef} className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
|
|
771
|
+
{renderAggregationButton
|
|
772
|
+
? renderAggregationButton({
|
|
773
|
+
onClick: () => togglePanel('summaries'),
|
|
774
|
+
isOpen: aggOpen,
|
|
775
|
+
activeCount,
|
|
776
|
+
})
|
|
777
|
+
: (
|
|
778
|
+
<GridTooltip title="Summaries">
|
|
779
|
+
<button
|
|
780
|
+
type="button"
|
|
781
|
+
ref={aggButtonRef}
|
|
782
|
+
className={`ogx-toolbar__icon-btn${aggOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
|
|
783
|
+
aria-label="Configure summaries"
|
|
784
|
+
aria-haspopup="dialog"
|
|
785
|
+
aria-expanded={aggOpen}
|
|
786
|
+
onClick={() => togglePanel('summaries')}
|
|
787
|
+
>
|
|
788
|
+
<SigmaIcon />
|
|
789
|
+
{activeCount > 0 && (
|
|
790
|
+
<span className="ogx-toolbar__dot" aria-label={`${activeCount} active summaries`} />
|
|
791
|
+
)}
|
|
792
|
+
</button>
|
|
793
|
+
</GridTooltip>
|
|
794
|
+
)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
{aggOpen && (
|
|
798
|
+
<AggregationPanel
|
|
799
|
+
anchorRef={renderAggregationButton ? aggWrapperRef : aggButtonRef}
|
|
800
|
+
aggregationModel={aggregationModel}
|
|
801
|
+
aggregableColumns={aggregableColumns}
|
|
802
|
+
onFunctionChange={handleFunctionChange}
|
|
803
|
+
onClearAll={clearAllAgg}
|
|
804
|
+
onClose={() => closePanel('summaries')}
|
|
805
|
+
activeCount={activeCount}
|
|
806
|
+
/>
|
|
807
|
+
)}
|
|
808
|
+
</div>
|
|
809
|
+
)}
|
|
766
810
|
|
|
767
811
|
{/* Export button slot */}
|
|
768
812
|
{renderExportButton && renderExportButton()}
|