@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
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React, { useId } from 'react';
|
|
2
2
|
import { Checkbox } from '../ui/Checkbox';
|
|
3
3
|
import { ColumnResizeHandle } from '../ColumnResizeHandle/ColumnResizeHandle';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import { clampResizeWidth } from '../ColumnResizeHandle/clampResizeWidth';
|
|
5
|
+
import type { GridColDef, GridRowModel, GridSortDirection, GridColumnPinning, GridAggregationModel, GridColumnGroupingModel } from '../../types';
|
|
6
|
+
import { isColumnPinned, calculatePinnedPositions, getPinnedEdgeFields } from '../../utils/pinning';
|
|
7
|
+
import { getRenderedColumnWidth } from '../../utils/columnWidth';
|
|
8
|
+
import { buildColumnGroupRow, getColumnGroupDepth, getColumnGroupPaths } from '../../utils/columnGroups';
|
|
6
9
|
import { ColumnMenu } from './ColumnMenu';
|
|
7
10
|
|
|
8
11
|
|
|
@@ -23,92 +26,14 @@ function SwapIcon() {
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
|
|
26
|
-
// ─── Group header helpers ─────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
/** Collect all leaf field names from a group node (recursive). */
|
|
29
|
-
function collectGroupFields(group: GridColumnGroup): string[] {
|
|
30
|
-
const out: string[] = [];
|
|
31
|
-
for (const child of group.children) {
|
|
32
|
-
if (typeof child === 'string') out.push(child);
|
|
33
|
-
else out.push(...collectGroupFields(child));
|
|
34
|
-
}
|
|
35
|
-
return out;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Max nesting depth of the group tree (1 = single level). */
|
|
39
|
-
function groupTreeDepth(groups: GridColumnGroupingModel): number {
|
|
40
|
-
let d = 0;
|
|
41
|
-
function walk(g: GridColumnGroup, depth: number) {
|
|
42
|
-
d = Math.max(d, depth);
|
|
43
|
-
for (const c of g.children) if (typeof c !== 'string') walk(c, depth + 1);
|
|
44
|
-
}
|
|
45
|
-
groups.forEach(g => walk(g, 1));
|
|
46
|
-
return d;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface GroupCell { key: string; label: string; width: number; isGroup: boolean; }
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Builds the flat array of GroupCell descriptors for one row (level).
|
|
53
|
-
* level 0 = top-most group row.
|
|
54
|
-
* allFields is the ORDERED list of all visible columns (including spacers).
|
|
55
|
-
*/
|
|
56
|
-
function buildGroupRow(
|
|
57
|
-
groups: GridColumnGroupingModel,
|
|
58
|
-
allFields: { field: string; width: number }[],
|
|
59
|
-
level: number,
|
|
60
|
-
): GroupCell[] {
|
|
61
|
-
// Map each field → the group that owns it at `level`
|
|
62
|
-
function findAtLevel(g: GridColumnGroup, field: string, cur: number): GridColumnGroup | null {
|
|
63
|
-
if (!collectGroupFields(g).includes(field)) return null;
|
|
64
|
-
if (cur === level) return g;
|
|
65
|
-
for (const child of g.children) {
|
|
66
|
-
if (typeof child !== 'string') {
|
|
67
|
-
const found = findAtLevel(child, field, cur + 1);
|
|
68
|
-
if (found) return found;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const fieldGroup = new Map<string, GridColumnGroup | null>();
|
|
75
|
-
for (const f of allFields) {
|
|
76
|
-
let found: GridColumnGroup | null = null;
|
|
77
|
-
for (const g of groups) {
|
|
78
|
-
found = findAtLevel(g, f.field, 0);
|
|
79
|
-
if (found) break;
|
|
80
|
-
}
|
|
81
|
-
fieldGroup.set(f.field, found);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const cells: GroupCell[] = [];
|
|
85
|
-
let i = 0;
|
|
86
|
-
while (i < allFields.length) {
|
|
87
|
-
const item = allFields[i];
|
|
88
|
-
const group = fieldGroup.get(item.field) ?? null;
|
|
89
|
-
const w = item.width;
|
|
90
|
-
|
|
91
|
-
if (!group) {
|
|
92
|
-
cells.push({ key: `fg-${item.field}-${level}`, label: '', width: w, isGroup: false });
|
|
93
|
-
i++;
|
|
94
|
-
} else {
|
|
95
|
-
let totalW = 0;
|
|
96
|
-
let j = i;
|
|
97
|
-
while (j < allFields.length && fieldGroup.get(allFields[j].field) === group) {
|
|
98
|
-
totalW += allFields[j].width;
|
|
99
|
-
j++;
|
|
100
|
-
}
|
|
101
|
-
cells.push({ key: `${group.groupId}-${level}`, label: group.headerName, width: totalW, isGroup: true });
|
|
102
|
-
i = j;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return cells;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
29
|
// ─── Props ────────────────────────────────────────────────────────────────────
|
|
109
30
|
|
|
110
31
|
export interface HeaderProps<R extends GridRowModel = GridRowModel> {
|
|
111
32
|
columns: GridColDef<R>[];
|
|
33
|
+
/**
|
|
34
|
+
* Every rendered data column in render order (left-pinned, unpinned, right-pinned) with its
|
|
35
|
+
* resolved width. Column group header rows are laid out over these; defaults to `columns`.
|
|
36
|
+
*/
|
|
112
37
|
allColumns?: GridColDef<R>[];
|
|
113
38
|
columnGroupingModel?: GridColumnGroupingModel;
|
|
114
39
|
checkboxSelection?: boolean;
|
|
@@ -132,13 +57,16 @@ export interface HeaderProps<R extends GridRowModel = GridRowModel> {
|
|
|
132
57
|
onDragOver?: (field: string) => (event: React.DragEvent) => void;
|
|
133
58
|
onDragEnd?: () => void;
|
|
134
59
|
onDrop?: (targetField: string) => (event: React.DragEvent) => void;
|
|
135
|
-
|
|
60
|
+
/** The grid's focus position; `id: null` means a header cell is focused. */
|
|
61
|
+
focusedCell?: { id: string | number | null; field: string } | null;
|
|
136
62
|
onHeaderClick?: (field: string) => void;
|
|
137
63
|
onSortAdd?: (field: string, direction: GridSortDirection) => void;
|
|
138
64
|
multiSort?: boolean;
|
|
139
65
|
onHideColumn?: (field: string) => void;
|
|
140
66
|
onPinColumn?: (field: string, side: 'left' | 'right' | null) => void;
|
|
141
67
|
onManageColumns?: () => void;
|
|
68
|
+
/** Position of each visible data column in render order, for `colIndex` and `aria-colindex`. */
|
|
69
|
+
columnIndexMap?: Map<string, number>;
|
|
142
70
|
}
|
|
143
71
|
|
|
144
72
|
export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps<R>) {
|
|
@@ -173,10 +101,10 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
173
101
|
onHideColumn,
|
|
174
102
|
onPinColumn,
|
|
175
103
|
onManageColumns,
|
|
104
|
+
columnIndexMap,
|
|
176
105
|
} = props;
|
|
177
106
|
|
|
178
107
|
const selectAllId = useId();
|
|
179
|
-
const cellRefs = React.useRef<Record<string, HTMLElement | null>>({});
|
|
180
108
|
const [menuOpenParams, setMenuOpenParams] = React.useState<{ colDef: GridColDef<R>; anchorEl: HTMLElement } | null>(null);
|
|
181
109
|
|
|
182
110
|
const handleMenuOpen = (colDef: GridColDef<R>) => (event: React.MouseEvent) => {
|
|
@@ -184,18 +112,38 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
184
112
|
setMenuOpenParams({ colDef, anchorEl: event.currentTarget as HTMLElement });
|
|
185
113
|
};
|
|
186
114
|
|
|
187
|
-
const handleMenuClose = () => {
|
|
115
|
+
const handleMenuClose = React.useCallback(() => {
|
|
188
116
|
setMenuOpenParams(null);
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
// Alt+ArrowDown or Ctrl/Cmd+Enter on a focused header cell opens its column menu.
|
|
120
|
+
// DOM focus is moved onto the focused header cell by useGridKeyboardNavigation.
|
|
121
|
+
const handleHeaderKeyDown = (colDef: GridColDef<R>) => (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
122
|
+
// Alt+ArrowLeft / Alt+ArrowRight resize the column (Shift for bigger steps).
|
|
123
|
+
const resizes = event.altKey && (event.key === 'ArrowLeft' || event.key === 'ArrowRight');
|
|
124
|
+
if (resizes && event.target === event.currentTarget) {
|
|
125
|
+
if (colDef.resizable === false || !onColumnResize) return;
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
event.stopPropagation();
|
|
128
|
+
const width = getRenderedColumnWidth(colDef, columnWidths);
|
|
129
|
+
const step = (event.shiftKey ? 50 : 10) * (event.key === 'ArrowRight' ? 1 : -1);
|
|
130
|
+
onColumnResize(colDef.field, clampResizeWidth(width + step, width, colDef.minWidth, colDef.maxWidth));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const opensMenu = (event.altKey && event.key === 'ArrowDown') || ((event.ctrlKey || event.metaKey) && event.key === 'Enter');
|
|
134
|
+
if (!opensMenu || colDef.disableColumnMenu || event.target !== event.currentTarget) return;
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
event.stopPropagation();
|
|
137
|
+
const anchor = event.currentTarget.querySelector<HTMLElement>('.ogx__menu-icon-btn') ?? event.currentTarget;
|
|
138
|
+
setMenuOpenParams({ colDef, anchorEl: anchor });
|
|
189
139
|
};
|
|
190
140
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
}, [focusedCell]);
|
|
141
|
+
const isHeaderFocused = (field: string) => focusedCell?.id === null && focusedCell.field === field;
|
|
142
|
+
|
|
143
|
+
// aria-colindex: system columns first (reorder, expand, checkbox), then the data columns.
|
|
144
|
+
const reorderColIndex = rowReordering ? 1 : 0;
|
|
145
|
+
const expandColIndex = hasDetailPanel ? reorderColIndex + 1 : reorderColIndex;
|
|
146
|
+
const checkboxColIndex = checkboxSelection ? expandColIndex + 1 : expandColIndex;
|
|
199
147
|
|
|
200
148
|
const handleColumnClick = (colDef: GridColDef<R>) => (e: React.MouseEvent) => {
|
|
201
149
|
if (colDef.sortable === false) return;
|
|
@@ -218,6 +166,19 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
218
166
|
}
|
|
219
167
|
};
|
|
220
168
|
|
|
169
|
+
// Column menu: act on this column only. "Unsort" removes just this key, and picking a direction
|
|
170
|
+
// for a column that is already sorted keeps the other keys. A new sort replaces the model
|
|
171
|
+
// unless multiSort is on, matching a plain header click.
|
|
172
|
+
const handleMenuSort = (field: string, direction: GridSortDirection) => {
|
|
173
|
+
const isSorted = sortModel.some(item => item.field === field);
|
|
174
|
+
const keepOtherKeys = direction === null || isSorted || multiSort;
|
|
175
|
+
if (onSortAdd && (keepOtherKeys || !onSort)) {
|
|
176
|
+
onSortAdd(field, direction);
|
|
177
|
+
} else {
|
|
178
|
+
onSort?.(field, direction);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
221
182
|
const getSortIcon = (field: string) => {
|
|
222
183
|
const sortItem = sortModel.find(item => item.field === field);
|
|
223
184
|
if (!sortItem) return null;
|
|
@@ -246,16 +207,23 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
246
207
|
);
|
|
247
208
|
}, [columns, columnWidths, pinnedColumns, checkboxSelection, pinCheckboxColumn, hasDetailPanel, pinExpandColumn, rowReordering]);
|
|
248
209
|
|
|
249
|
-
const
|
|
210
|
+
const pinnedEdges = React.useMemo(() => getPinnedEdgeFields(columns, pinnedColumns), [columns, pinnedColumns]);
|
|
250
211
|
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
212
|
+
const groupDepth = getColumnGroupDepth(columnGroupingModel);
|
|
213
|
+
const groupPaths = React.useMemo(() => getColumnGroupPaths(columnGroupingModel), [columnGroupingModel]);
|
|
214
|
+
|
|
215
|
+
// Group rows are laid out over every rendered data column (not just the horizontal render
|
|
216
|
+
// window), in render order and with the layout-resolved widths, so they line up with the headers.
|
|
217
|
+
const groupColumns = React.useMemo(() => {
|
|
218
|
+
if (groupDepth === 0) return [];
|
|
219
|
+
return (allColumns ?? columns).map(c => ({
|
|
256
220
|
field: c.field,
|
|
257
|
-
width:
|
|
221
|
+
width: getRenderedColumnWidth(c, columnWidths),
|
|
222
|
+
flex: c.isSpacer ? 0 : c.flex,
|
|
223
|
+
pinned: c.isSpacer ? null : isColumnPinned(c.field, pinnedColumns),
|
|
224
|
+
isSpacer: c.isSpacer,
|
|
258
225
|
}));
|
|
226
|
+
}, [groupDepth, allColumns, columns, columnWidths, pinnedColumns]);
|
|
259
227
|
|
|
260
228
|
// --- CHECKBOX / DETAIL / REORDER prefix width (for group filler) ---
|
|
261
229
|
const CHECKBOX_W = 48;
|
|
@@ -265,6 +233,12 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
265
233
|
(rowReordering ? REORDER_W : 0) +
|
|
266
234
|
(hasDetailPanel ? EXPAND_W : 0) +
|
|
267
235
|
(checkboxSelection ? CHECKBOX_W : 0);
|
|
236
|
+
// The prefix filler sticks with the system columns when all of them are pinned.
|
|
237
|
+
const isPrefixPinned =
|
|
238
|
+
(rowReordering || hasDetailPanel || checkboxSelection) &&
|
|
239
|
+
(!hasDetailPanel || pinExpandColumn) &&
|
|
240
|
+
(!checkboxSelection || pinCheckboxColumn);
|
|
241
|
+
const systemColumnCount = (rowReordering ? 1 : 0) + (hasDetailPanel ? 1 : 0) + (checkboxSelection ? 1 : 0);
|
|
268
242
|
|
|
269
243
|
return (
|
|
270
244
|
// Outer sticky wrapper — both group rows AND column header row live here
|
|
@@ -272,53 +246,85 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
272
246
|
<div className="ogx__header-wrap">
|
|
273
247
|
{/* ── Group header rows ── */}
|
|
274
248
|
{groupDepth > 0 && Array.from({ length: groupDepth }, (_, level) => {
|
|
275
|
-
const
|
|
276
|
-
columnGroupingModel!,
|
|
277
|
-
allLeafFields,
|
|
278
|
-
level,
|
|
279
|
-
);
|
|
249
|
+
const cells = buildColumnGroupRow(groupColumns, groupPaths, level);
|
|
280
250
|
return (
|
|
281
|
-
<div key={`ogx-grp-${level}`} className="ogx-col-group-row" role="row">
|
|
251
|
+
<div key={`ogx-grp-${level}`} className="ogx-col-group-row" role="row" aria-rowindex={level + 1}>
|
|
282
252
|
{/* Filler for checkbox / reorder / detailPanel prefix */}
|
|
283
253
|
{prefixWidth > 0 && (
|
|
284
254
|
<div
|
|
285
|
-
className=
|
|
286
|
-
style={{
|
|
287
|
-
|
|
255
|
+
className={`ogx-col-group-cell ogx-col-group-cell--filler${isPrefixPinned ? ' ogx-col-group-cell--pinned ogx-col-group-cell--pinned-left' : ''}`}
|
|
256
|
+
style={{
|
|
257
|
+
width: prefixWidth,
|
|
258
|
+
minWidth: prefixWidth,
|
|
259
|
+
flexShrink: 0,
|
|
260
|
+
position: isPrefixPinned ? 'sticky' : undefined,
|
|
261
|
+
left: isPrefixPinned ? 0 : undefined,
|
|
262
|
+
}}
|
|
263
|
+
aria-hidden="true"
|
|
288
264
|
/>
|
|
289
265
|
)}
|
|
290
|
-
{
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
266
|
+
{cells.map((cell, cellIndex) => {
|
|
267
|
+
const pinnedClass = cell.pinned ? ` ogx-col-group-cell--pinned ogx-col-group-cell--pinned-${cell.pinned}` : '';
|
|
268
|
+
const style: React.CSSProperties = {
|
|
269
|
+
width: cell.width,
|
|
270
|
+
minWidth: cell.width,
|
|
271
|
+
flexShrink: 0,
|
|
272
|
+
flexGrow: cell.flexGrow,
|
|
273
|
+
};
|
|
274
|
+
if (cell.pinned === 'left') {
|
|
275
|
+
style.position = 'sticky';
|
|
276
|
+
style.left = pinnedPositions[cell.fields[0]];
|
|
277
|
+
} else if (cell.pinned === 'right') {
|
|
278
|
+
style.position = 'sticky';
|
|
279
|
+
style.right = pinnedPositions[cell.fields[cell.fields.length - 1]];
|
|
280
|
+
// Free space goes before the right-pinned section, as in the column header row.
|
|
281
|
+
if (cells[cellIndex - 1]?.pinned !== 'right') style.marginLeft = 'auto';
|
|
282
|
+
}
|
|
283
|
+
if (!cell.isGroup) {
|
|
284
|
+
return (
|
|
285
|
+
<div
|
|
286
|
+
key={cell.key}
|
|
287
|
+
className={`ogx-col-group-cell ogx-col-group-cell--filler${pinnedClass}`}
|
|
288
|
+
style={style}
|
|
289
|
+
aria-hidden="true"
|
|
290
|
+
/>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return (
|
|
294
|
+
<div
|
|
295
|
+
key={cell.key}
|
|
296
|
+
role="columnheader"
|
|
297
|
+
aria-colspan={cell.fields.length}
|
|
298
|
+
aria-colindex={systemColumnCount + cell.firstDataIndex + 1}
|
|
299
|
+
className={`ogx-col-group-cell ogx-col-group-cell--group${pinnedClass}${cell.headerClassName ? ` ${cell.headerClassName}` : ''}`}
|
|
300
|
+
style={style}
|
|
301
|
+
title={cell.label}
|
|
302
|
+
>
|
|
299
303
|
<span className="ogx-col-group-cell__label">{cell.label}</span>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
)
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
})}
|
|
303
307
|
</div>
|
|
304
308
|
);
|
|
305
309
|
})}
|
|
306
310
|
|
|
307
311
|
{/* ── Column header row ── */}
|
|
308
|
-
<div className="ogx__header" role="row">
|
|
312
|
+
<div className="ogx__header" role="row" aria-rowindex={groupDepth + 1}>
|
|
309
313
|
{ }
|
|
310
314
|
{rowReordering && (
|
|
311
315
|
<div
|
|
312
|
-
className=
|
|
316
|
+
className={`ogx__header-cell ogx__header-cell--pinned ogx__header-cell--pinned-left ${isHeaderFocused('__reorder_col__') ? 'ogx__header-cell--focused' : ''}`}
|
|
313
317
|
role="columnheader"
|
|
314
318
|
aria-label="Row reorder handle"
|
|
319
|
+
aria-colindex={reorderColIndex}
|
|
320
|
+
data-field="__reorder_col__"
|
|
321
|
+
tabIndex={-1}
|
|
315
322
|
title="Drag to reorder rows"
|
|
316
323
|
style={{
|
|
317
324
|
minWidth: 48,
|
|
318
325
|
maxWidth: 48,
|
|
319
326
|
position: 'sticky',
|
|
320
327
|
left: 0,
|
|
321
|
-
zIndex: 4
|
|
322
328
|
}}
|
|
323
329
|
/>
|
|
324
330
|
)}
|
|
@@ -327,15 +333,17 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
327
333
|
{hasDetailPanel && (
|
|
328
334
|
<div
|
|
329
335
|
className={`ogx__header-cell ogx__header-cell--expand ${pinExpandColumn ? 'ogx__header-cell--pinned ogx__header-cell--pinned-left' : ''
|
|
330
|
-
}`}
|
|
336
|
+
} ${isHeaderFocused('__expand_col__') ? 'ogx__header-cell--focused' : ''}`}
|
|
331
337
|
role="columnheader"
|
|
332
338
|
aria-label="Expand row details"
|
|
339
|
+
aria-colindex={expandColIndex}
|
|
340
|
+
data-field="__expand_col__"
|
|
341
|
+
tabIndex={-1}
|
|
333
342
|
style={{
|
|
334
343
|
minWidth: 48,
|
|
335
344
|
maxWidth: 48,
|
|
336
345
|
position: pinExpandColumn ? 'sticky' : undefined,
|
|
337
346
|
left: pinExpandColumn ? (rowReordering ? 48 : 0) : undefined,
|
|
338
|
-
zIndex: pinExpandColumn ? 5 : undefined
|
|
339
347
|
}}
|
|
340
348
|
>
|
|
341
349
|
{ }
|
|
@@ -346,25 +354,29 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
346
354
|
{checkboxSelection && (
|
|
347
355
|
<div
|
|
348
356
|
className={`ogx__header-cell ogx__header-cell--checkbox ${pinCheckboxColumn ? 'ogx__header-cell--pinned ogx__header-cell--pinned-left' : ''
|
|
349
|
-
}`}
|
|
357
|
+
} ${isHeaderFocused('__checkbox_col__') ? 'ogx__header-cell--focused' : ''}`}
|
|
350
358
|
role="columnheader"
|
|
359
|
+
aria-colindex={checkboxColIndex}
|
|
360
|
+
data-field="__checkbox_col__"
|
|
351
361
|
style={{
|
|
352
362
|
position: pinCheckboxColumn ? 'sticky' : undefined,
|
|
353
363
|
left: pinCheckboxColumn ? ((rowReordering ? 48 : 0) + (hasDetailPanel && pinExpandColumn ? 48 : 0)) : undefined,
|
|
354
364
|
|
|
355
365
|
}}
|
|
356
366
|
>
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
367
|
+
{/* No select-all without a handler (single-row selection has none). */}
|
|
368
|
+
{onSelectAll && (
|
|
369
|
+
<Checkbox
|
|
370
|
+
id={selectAllId}
|
|
371
|
+
name={selectAllId}
|
|
372
|
+
checked={allSelected}
|
|
373
|
+
indeterminate={someSelected}
|
|
374
|
+
onChange={(e) => onSelectAll(e.target.checked)}
|
|
375
|
+
aria-label={allSelected ? 'Deselect all rows' : 'Select all rows'}
|
|
376
|
+
tabIndex={-1}
|
|
377
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
378
|
+
/>
|
|
379
|
+
)}
|
|
368
380
|
</div>
|
|
369
381
|
)}
|
|
370
382
|
|
|
@@ -379,7 +391,18 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
379
391
|
);
|
|
380
392
|
}
|
|
381
393
|
const isSortable = colDef.sortable !== false;
|
|
382
|
-
const
|
|
394
|
+
const sortPosition = sortModel.findIndex(item => item.field === colDef.field);
|
|
395
|
+
const isSorted = sortPosition !== -1;
|
|
396
|
+
const sortDirection = isSorted ? sortModel[sortPosition].sort : null;
|
|
397
|
+
const colIndex = columnIndexMap?.get(colDef.field) ?? index;
|
|
398
|
+
// Only the primary sort column carries aria-sort (WAI-ARIA allows one per grid);
|
|
399
|
+
// with multi-sort every sorted column describes its direction and priority.
|
|
400
|
+
const ariaSort = !isSortable ? undefined
|
|
401
|
+
: sortPosition === 0 ? (sortDirection === 'asc' ? 'ascending' : 'descending')
|
|
402
|
+
: 'none';
|
|
403
|
+
const sortDescription = isSorted && sortModel.length > 1
|
|
404
|
+
? `Sorted ${sortDirection === 'asc' ? 'ascending' : 'descending'}, sort priority ${sortPosition + 1} of ${sortModel.length}`
|
|
405
|
+
: undefined;
|
|
383
406
|
const isResizable = colDef.resizable !== false;
|
|
384
407
|
const pinnedPosition = isColumnPinned(colDef.field, pinnedColumns);
|
|
385
408
|
const isPinned = pinnedPosition !== null;
|
|
@@ -393,9 +416,11 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
393
416
|
isSorted && 'ogx__header-cell--sorted',
|
|
394
417
|
isPinned && `ogx__header-cell--pinned`,
|
|
395
418
|
isPinned && `ogx__header-cell--pinned-${pinnedPosition}`,
|
|
419
|
+
pinnedPosition === 'left' && colDef.field === pinnedEdges.lastLeft && 'ogx__header-cell--pinned-left-last',
|
|
420
|
+
pinnedPosition === 'right' && colDef.field === pinnedEdges.firstRight && 'ogx__header-cell--pinned-right-first',
|
|
396
421
|
isDragging && 'ogx__header-cell--dragging',
|
|
397
422
|
isDragOver && 'ogx__header-cell--drag-over',
|
|
398
|
-
(
|
|
423
|
+
isHeaderFocused(colDef.field) && 'ogx__header-cell--focused',
|
|
399
424
|
colDef.headerClassName
|
|
400
425
|
].filter(Boolean).join(' ');
|
|
401
426
|
|
|
@@ -424,7 +449,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
424
449
|
}
|
|
425
450
|
|
|
426
451
|
const headerContent = colDef.renderHeader
|
|
427
|
-
? colDef.renderHeader({ field: colDef.field, colDef: colDef as unknown as GridColDef, colIndex
|
|
452
|
+
? colDef.renderHeader({ field: colDef.field, colDef: colDef as unknown as GridColDef, colIndex })
|
|
428
453
|
: colDef.headerName || colDef.field;
|
|
429
454
|
|
|
430
455
|
const handleHeaderClick = (e: React.MouseEvent) => {
|
|
@@ -438,9 +463,10 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
438
463
|
}
|
|
439
464
|
};
|
|
440
465
|
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
const
|
|
466
|
+
// Pinned columns keep their place: they are neither dragged nor drop targets.
|
|
467
|
+
const handleDragStart = onDragStart && !isPinned ? onDragStart(colDef.field) : undefined;
|
|
468
|
+
const handleDragOver = onDragOver && !isPinned ? onDragOver(colDef.field) : undefined;
|
|
469
|
+
const handleDrop = onDrop && !isPinned ? onDrop(colDef.field) : undefined;
|
|
444
470
|
|
|
445
471
|
return (
|
|
446
472
|
<div
|
|
@@ -450,24 +476,17 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
450
476
|
title={colDef.description}
|
|
451
477
|
onClick={handleHeaderClick}
|
|
452
478
|
role="columnheader"
|
|
453
|
-
draggable={!!
|
|
479
|
+
draggable={!!handleDragStart}
|
|
454
480
|
onDragStart={handleDragStart}
|
|
455
481
|
onDragOver={handleDragOver}
|
|
456
482
|
onDragEnd={onDragEnd}
|
|
457
483
|
onDrop={handleDrop}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
? 'ascending'
|
|
463
|
-
: 'descending'
|
|
464
|
-
: 'none')
|
|
465
|
-
: undefined
|
|
466
|
-
}
|
|
467
|
-
aria-colindex={index + 1 + (checkboxSelection ? 1 : 0)}
|
|
484
|
+
onKeyDown={handleHeaderKeyDown(colDef)}
|
|
485
|
+
aria-sort={ariaSort}
|
|
486
|
+
aria-description={sortDescription}
|
|
487
|
+
aria-colindex={systemColumnCount + colIndex + 1}
|
|
468
488
|
data-field={colDef.field}
|
|
469
489
|
tabIndex={-1}
|
|
470
|
-
ref={(el) => { cellRefs.current[colDef.field] = el; }}
|
|
471
490
|
>
|
|
472
491
|
<div className="ogx__header-cell-content">
|
|
473
492
|
{isDragging && (
|
|
@@ -488,6 +507,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
488
507
|
{isSortable && getSortIcon(colDef.field)}
|
|
489
508
|
{!colDef.disableColumnMenu && (
|
|
490
509
|
<button
|
|
510
|
+
type="button"
|
|
491
511
|
className="ogx__menu-icon-btn"
|
|
492
512
|
onClick={handleMenuOpen(colDef)}
|
|
493
513
|
aria-label={`Open column menu for ${colDef.headerName || colDef.field}`}
|
|
@@ -503,10 +523,11 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
503
523
|
{isResizable && onColumnResize && (
|
|
504
524
|
<ColumnResizeHandle
|
|
505
525
|
field={colDef.field}
|
|
506
|
-
currentWidth={
|
|
526
|
+
currentWidth={getRenderedColumnWidth(colDef, columnWidths)}
|
|
507
527
|
onResize={onColumnResize}
|
|
508
528
|
minWidth={colDef.minWidth}
|
|
509
529
|
maxWidth={colDef.maxWidth}
|
|
530
|
+
edge={pinnedPosition === 'right' ? 'start' : 'end'}
|
|
510
531
|
/>
|
|
511
532
|
)}
|
|
512
533
|
</div>
|
|
@@ -517,7 +538,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
517
538
|
<ColumnMenu
|
|
518
539
|
colDef={menuOpenParams.colDef as unknown as GridColDef<GridRowModel>}
|
|
519
540
|
sortModel={sortModel}
|
|
520
|
-
onSort={onSort}
|
|
541
|
+
onSort={onSort || onSortAdd ? handleMenuSort : undefined}
|
|
521
542
|
onHide={onHideColumn}
|
|
522
543
|
onPin={onPinColumn}
|
|
523
544
|
pinnedColumns={pinnedColumns}
|
|
@@ -31,6 +31,15 @@
|
|
|
31
31
|
background: var(--ogx-color-gray-50, #f8fafc);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/* Keyboard focus (roving tab stop between list rows). */
|
|
35
|
+
.ogx-list-view__row:focus {
|
|
36
|
+
outline: none;
|
|
37
|
+
}
|
|
38
|
+
.ogx-list-view__row:focus-visible {
|
|
39
|
+
outline: 2px solid var(--ogx-color-primary);
|
|
40
|
+
outline-offset: -2px;
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
.ogx-list-view__row--selected {
|
|
35
44
|
background: var(--ogx-selection-bg, rgba(59, 130, 246, 0.08));
|
|
36
45
|
}
|
|
@@ -40,6 +49,13 @@
|
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
/* ─── Checkbox column inside list row ────────────────────────────────────── */
|
|
52
|
+
.ogx-list-view__expand {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
min-width: 32px;
|
|
57
|
+
}
|
|
58
|
+
|
|
43
59
|
.ogx-list-view__checkbox {
|
|
44
60
|
display: flex;
|
|
45
61
|
align-items: center;
|
|
@@ -110,3 +126,12 @@
|
|
|
110
126
|
display: none;
|
|
111
127
|
}
|
|
112
128
|
}
|
|
129
|
+
|
|
130
|
+
/* Loading with no rows yet: a progress bar, or slots.loadingOverlay centred. */
|
|
131
|
+
.ogx-list-view__loading {
|
|
132
|
+
position: relative;
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
min-height: 120px;
|
|
137
|
+
}
|