@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,41 +1,78 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { formatValueByType, getCellValue, getFormattedValue } from '../../utils/values';
|
|
3
|
+
import { ExpandIcon } from '../ui/ExpandIcon';
|
|
4
|
+
import { CellErrorBoundary } from '../Cell/CellErrorBoundary';
|
|
5
|
+
import { isSyntheticRowId } from '../../utils/syntheticRows';
|
|
6
|
+
import type { GridColDef, GridRowModel, GridRowId, GridRowMeta, GridListViewColDef, GridRenderCellParams } from '../../types';
|
|
3
7
|
|
|
4
8
|
export interface ListViewRowProps<R extends GridRowModel = GridRowModel> {
|
|
5
9
|
row: R;
|
|
10
|
+
/** The row's id (from getRowId). Defaults to `row.id`. */
|
|
11
|
+
rowId?: GridRowId;
|
|
12
|
+
/** Index of the row among the rendered rows; passed to renderCell and the row callbacks. */
|
|
6
13
|
rowIndex: number;
|
|
14
|
+
/** 1-based position of the row in the whole data set. Defaults to rowIndex + 1. */
|
|
15
|
+
ariaRowIndex?: number;
|
|
7
16
|
listViewColumn: GridListViewColDef<R>;
|
|
17
|
+
/** The grid column with the list field, if any: its valueGetter / valueFormatter produce value and formattedValue. */
|
|
18
|
+
gridColumn?: GridColDef<R>;
|
|
19
|
+
/** Tree-data / row-grouping metadata for the row. */
|
|
20
|
+
rowMeta?: GridRowMeta;
|
|
8
21
|
isSelected?: boolean;
|
|
9
22
|
checkboxSelection?: boolean;
|
|
10
23
|
rowHeight?: number;
|
|
11
24
|
onRowClick?: (row: R) => void;
|
|
12
25
|
onRowDoubleClick?: (row: R) => void;
|
|
13
26
|
onSelectionChange?: (rowId: GridRowId, isSelected: boolean) => void;
|
|
27
|
+
/** Roving tab stop: 0 for the one row the list's Tab stop lands on, -1 for the others. */
|
|
28
|
+
tabIndex?: number;
|
|
29
|
+
/** Called when the row element itself receives focus. */
|
|
30
|
+
onFocus?: (rowIndex: number) => void;
|
|
31
|
+
/** Expands / collapses a row with children. When set, hierarchy rows get an expand chevron. */
|
|
32
|
+
onToggleExpansion?: (rowId: GridRowId) => void;
|
|
14
33
|
}
|
|
15
34
|
|
|
16
35
|
export function ListViewRow<R extends GridRowModel = GridRowModel>({
|
|
17
36
|
row,
|
|
37
|
+
rowId,
|
|
18
38
|
rowIndex,
|
|
39
|
+
ariaRowIndex,
|
|
19
40
|
listViewColumn,
|
|
41
|
+
gridColumn,
|
|
42
|
+
rowMeta,
|
|
20
43
|
isSelected = false,
|
|
21
44
|
checkboxSelection = false,
|
|
22
45
|
rowHeight,
|
|
23
46
|
onRowClick,
|
|
24
47
|
onRowDoubleClick,
|
|
25
48
|
onSelectionChange,
|
|
49
|
+
tabIndex = -1,
|
|
50
|
+
onFocus,
|
|
51
|
+
onToggleExpansion,
|
|
26
52
|
}: ListViewRowProps<R>) {
|
|
53
|
+
const id = rowId ?? row.id;
|
|
54
|
+
const field = listViewColumn.field;
|
|
55
|
+
// Same value and formatting as a grid cell for this field (Cell.tsx). A valueGetter or
|
|
56
|
+
// valueFormatter that throws reads as undefined / the unformatted value instead of crashing.
|
|
57
|
+
const value = getCellValue(row, field, gridColumn);
|
|
58
|
+
const formattedValue = getFormattedValue(row, field, value, gridColumn) ?? formatValueByType(value, gridColumn);
|
|
59
|
+
// Row-grouping headers, subtotals and auto-created tree parents are not data rows.
|
|
60
|
+
const isSyntheticRow = rowMeta?.isGroupRow === true || isSyntheticRowId(id);
|
|
61
|
+
|
|
27
62
|
const params: GridRenderCellParams<R> = {
|
|
28
63
|
row,
|
|
29
|
-
value
|
|
30
|
-
|
|
31
|
-
|
|
64
|
+
value,
|
|
65
|
+
formattedValue,
|
|
66
|
+
field,
|
|
67
|
+
colDef: gridColumn ?? listViewColumn,
|
|
32
68
|
rowIndex,
|
|
33
69
|
colIndex: 0,
|
|
70
|
+
rowMeta,
|
|
34
71
|
};
|
|
35
72
|
|
|
36
73
|
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
37
74
|
e.stopPropagation();
|
|
38
|
-
onSelectionChange?.(
|
|
75
|
+
onSelectionChange?.(id, e.target.checked);
|
|
39
76
|
};
|
|
40
77
|
|
|
41
78
|
return (
|
|
@@ -45,21 +82,50 @@ export function ListViewRow<R extends GridRowModel = GridRowModel>({
|
|
|
45
82
|
onClick={() => onRowClick?.(row)}
|
|
46
83
|
onDoubleClick={onRowDoubleClick ? () => onRowDoubleClick(row) : undefined}
|
|
47
84
|
role="row"
|
|
48
|
-
aria-rowindex={rowIndex +
|
|
85
|
+
aria-rowindex={ariaRowIndex ?? rowIndex + 1}
|
|
49
86
|
aria-selected={isSelected}
|
|
87
|
+
aria-level={rowMeta?.treeDepth !== undefined ? rowMeta.treeDepth + 1 : undefined}
|
|
88
|
+
aria-expanded={rowMeta?.hasChildren ? rowMeta.isExpanded === true : undefined}
|
|
89
|
+
data-rowindex={rowIndex}
|
|
90
|
+
tabIndex={tabIndex}
|
|
91
|
+
onFocus={onFocus ? (e) => { if (e.target === e.currentTarget) onFocus(rowIndex); } : undefined}
|
|
50
92
|
>
|
|
51
93
|
{checkboxSelection && (
|
|
52
94
|
<div className="ogx-list-view__checkbox" onClick={(e) => e.stopPropagation()}>
|
|
53
|
-
<input
|
|
95
|
+
{!isSyntheticRow && <input
|
|
54
96
|
type="checkbox"
|
|
55
97
|
checked={isSelected}
|
|
56
98
|
onChange={handleCheckboxChange}
|
|
57
|
-
aria-label={`Select row ${
|
|
58
|
-
|
|
99
|
+
aria-label={`Select row ${id}`}
|
|
100
|
+
// Not a Tab stop of its own: the list is one Tab stop, Shift+Space selects the focused row.
|
|
101
|
+
tabIndex={-1}
|
|
102
|
+
/>}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
{rowMeta && onToggleExpansion && (
|
|
106
|
+
// Tree data / row grouping: indentation by depth, and a chevron on rows with children
|
|
107
|
+
// (not a Tab stop: Enter or Alt+Arrow toggle the focused row).
|
|
108
|
+
<div
|
|
109
|
+
className="ogx-list-view__expand"
|
|
110
|
+
style={{ paddingLeft: (rowMeta.treeDepth ?? 0) * 16 }}
|
|
111
|
+
onClick={(e) => e.stopPropagation()}
|
|
112
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
113
|
+
>
|
|
114
|
+
{rowMeta.hasChildren && (
|
|
115
|
+
<ExpandIcon isExpanded={rowMeta.isExpanded === true} onClick={() => onToggleExpansion(id)} tabIndex={-1} />
|
|
116
|
+
)}
|
|
59
117
|
</div>
|
|
60
118
|
)}
|
|
61
119
|
<div className="ogx-list-view__cell" role="gridcell">
|
|
62
|
-
|
|
120
|
+
<CellErrorBoundary
|
|
121
|
+
field={field}
|
|
122
|
+
resetKey={row}
|
|
123
|
+
renderFn={() => {
|
|
124
|
+
const content = listViewColumn.renderCell(params);
|
|
125
|
+
// A group row whose renderCell has nothing for it still shows its label.
|
|
126
|
+
return content === undefined && isSyntheticRow ? rowMeta?.groupLabel : content;
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
63
129
|
</div>
|
|
64
130
|
</div>
|
|
65
131
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import React, { useId } from 'react';
|
|
3
3
|
import { Button } from '../ui/Button';
|
|
4
|
+
import { normalizePageSize, getPageCount, clampPage } from '../../utils/pagination';
|
|
4
5
|
import type { GridLocaleText } from '../../types';
|
|
5
6
|
|
|
6
7
|
export interface PaginationProps {
|
|
@@ -29,11 +30,19 @@ export function Pagination(props: PaginationProps) {
|
|
|
29
30
|
localeText,
|
|
30
31
|
} = props;
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
-
const
|
|
33
|
+
const safePageSize = normalizePageSize(pageSize);
|
|
34
|
+
const pageCount = getPageCount(rowCount, safePageSize);
|
|
35
|
+
const currentPage = clampPage(page, pageCount);
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
// The select must be able to show the size in use, even when it is not one of the options
|
|
38
|
+
// (e.g. the default 100 with pageSizeOptions={[10, 25, 50]}); otherwise it displays the first
|
|
39
|
+
// option and choosing that option fires no change event.
|
|
40
|
+
const selectOptions = pageSizeOptions.includes(safePageSize)
|
|
41
|
+
? pageSizeOptions
|
|
42
|
+
: [...pageSizeOptions, safePageSize].sort((a, b) => a - b);
|
|
43
|
+
|
|
44
|
+
const firstRowIndex = currentPage * safePageSize;
|
|
45
|
+
const lastRowIndex = Math.min(firstRowIndex + safePageSize, rowCount);
|
|
37
46
|
const displayedRows = rowCount === 0 ? 0 : `${firstRowIndex + 1}–${lastRowIndex}`;
|
|
38
47
|
|
|
39
48
|
const rowsPerPageLabel = localeText?.paginationRowsPerPage ?? 'Rows per page:';
|
|
@@ -79,11 +88,10 @@ export function Pagination(props: PaginationProps) {
|
|
|
79
88
|
<select
|
|
80
89
|
id={pageSizeId}
|
|
81
90
|
className="ogx-pagination__select"
|
|
82
|
-
value={
|
|
91
|
+
value={safePageSize}
|
|
83
92
|
onChange={handlePageSizeChange}
|
|
84
|
-
aria-label="Rows per page"
|
|
85
93
|
>
|
|
86
|
-
{
|
|
94
|
+
{selectOptions.map(option => (
|
|
87
95
|
<option key={option} value={option}>
|
|
88
96
|
{option}
|
|
89
97
|
</option>
|
|
@@ -7,11 +7,45 @@ import { DragHandleIcon } from '../ui/DragHandleIcon';
|
|
|
7
7
|
import { DetailPanel } from '../DetailPanel/DetailPanel';
|
|
8
8
|
import type { GridRowModel, GridColDef, GridRowId, GridColumnPinning, GridRowPinning, GridRowParams, GridCellParams, GridDetailPanelHeight, GridRowMeta } from '../../types';
|
|
9
9
|
import type { CellColSpanInfo, RowSpanningCaches } from '../../hooks/features/useGridSpanning';
|
|
10
|
-
import { isColumnPinned, calculatePinnedPositions, isRowPinned } from '../../utils/pinning';
|
|
11
|
-
|
|
10
|
+
import { isColumnPinned, calculatePinnedPositions, getPinnedEdgeFields, isRowPinned } from '../../utils/pinning';
|
|
11
|
+
import { resolveCellEditable } from '../../utils/editing';
|
|
12
|
+
import { tryGetCellValue } from '../../utils/values';
|
|
13
|
+
import { isSyntheticRowId } from '../../utils/syntheticRows';
|
|
14
|
+
import { getRenderedColumnWidth } from '../../utils/columnWidth';
|
|
15
|
+
import { DEFAULT_DETAIL_PANEL_HEIGHT } from '../../utils/detailPanel';
|
|
16
|
+
|
|
17
|
+
/** Sticky system cells (drag handle, detail toggle, checkbox) sit above scrolled, spanned and focused cells. */
|
|
18
|
+
const SYSTEM_CELL_Z_INDEX = 12;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds the Cell span info for a colSpan origin: the merged width and flex-grow are the sums over
|
|
23
|
+
* the columns it covers, taken from the rendered (layout-resolved) columns. The span never crosses a
|
|
24
|
+
* spacer, so a span cut by the column window only covers what is rendered.
|
|
25
|
+
*/
|
|
26
|
+
function mergeColSpan<R extends GridRowModel>(
|
|
27
|
+
columns: GridColDef<R>[],
|
|
28
|
+
originIndex: number,
|
|
29
|
+
colSpan: number,
|
|
30
|
+
columnWidths: Record<string, number>,
|
|
31
|
+
): CellColSpanInfo {
|
|
32
|
+
let width = 0;
|
|
33
|
+
let flexGrow = 0;
|
|
34
|
+
let covered = 0;
|
|
35
|
+
for (let i = originIndex; i < columns.length && covered < colSpan; i++) {
|
|
36
|
+
const col = columns[i];
|
|
37
|
+
if (col.isSpacer) break;
|
|
38
|
+
width += getRenderedColumnWidth(col, columnWidths);
|
|
39
|
+
flexGrow += col.flex ?? 0;
|
|
40
|
+
covered++;
|
|
41
|
+
}
|
|
42
|
+
return { spannedByColSpan: false, cellProps: { colSpan: covered, width, flexGrow } };
|
|
43
|
+
}
|
|
12
44
|
|
|
13
45
|
export interface RowProps<R extends GridRowModel = GridRowModel> {
|
|
14
46
|
row: R;
|
|
47
|
+
/** The row's id (from getRowId). Defaults to `row.id`; the grid always passes it. */
|
|
48
|
+
rowId?: GridRowId;
|
|
15
49
|
columns: GridColDef<R>[];
|
|
16
50
|
rowIndex: number;
|
|
17
51
|
isSelected?: boolean;
|
|
@@ -29,6 +63,8 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
|
|
|
29
63
|
onDetailPanelToggle?: (rowId: GridRowId) => void;
|
|
30
64
|
detailPanelContent?: React.ReactNode;
|
|
31
65
|
detailPanelHeight?: GridDetailPanelHeight;
|
|
66
|
+
/** Called with the rendered height of an `'auto'` detail panel whenever it changes. */
|
|
67
|
+
onDetailPanelHeightChange?: (rowId: GridRowId, height: number) => void;
|
|
32
68
|
|
|
33
69
|
pinCheckboxColumn?: boolean;
|
|
34
70
|
pinExpandColumn?: boolean;
|
|
@@ -43,8 +79,11 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
|
|
|
43
79
|
|
|
44
80
|
editingCell?: { id: GridRowId; field: string; value: unknown; } | null;
|
|
45
81
|
onEditStart?: (params: { id: GridRowId, field: string, value: unknown }) => void;
|
|
46
|
-
|
|
82
|
+
/** `id` / `field` name the cell whose editor asked to stop. */
|
|
83
|
+
onEditStop?: (params?: { cancel?: boolean; id?: GridRowId; field?: string }) => void;
|
|
47
84
|
onEditCellValueChange?: (params: { id: GridRowId, field: string, value: unknown }) => void;
|
|
85
|
+
/** Per-cell editability predicate (`DataGrid.isCellEditable`). */
|
|
86
|
+
isCellEditable?: (params: GridCellParams<R>) => boolean;
|
|
48
87
|
|
|
49
88
|
focusedCellField?: string | null;
|
|
50
89
|
isFocusVisible?: boolean;
|
|
@@ -53,11 +92,16 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
|
|
|
53
92
|
rowSpanningCaches?: RowSpanningCaches;
|
|
54
93
|
rowHeight?: number;
|
|
55
94
|
rowMeta?: GridRowMeta;
|
|
95
|
+
/** Position of each visible data column in render order, for `colIndex` and `aria-colindex`. */
|
|
96
|
+
columnIndexMap?: Map<string, number>;
|
|
97
|
+
/** 1-based `aria-rowindex` of this row in the whole grid (header rows included). Defaults to `rowIndex + 2`. */
|
|
98
|
+
ariaRowIndex?: number;
|
|
56
99
|
}
|
|
57
100
|
|
|
58
101
|
export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
59
102
|
const {
|
|
60
103
|
row,
|
|
104
|
+
rowId: rowIdProp,
|
|
61
105
|
columns,
|
|
62
106
|
rowIndex,
|
|
63
107
|
isSelected = false,
|
|
@@ -73,7 +117,8 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
73
117
|
isDetailPanelExpanded = false,
|
|
74
118
|
onDetailPanelToggle,
|
|
75
119
|
detailPanelContent,
|
|
76
|
-
detailPanelHeight =
|
|
120
|
+
detailPanelHeight = DEFAULT_DETAIL_PANEL_HEIGHT,
|
|
121
|
+
onDetailPanelHeightChange,
|
|
77
122
|
pinCheckboxColumn = true,
|
|
78
123
|
pinExpandColumn = true,
|
|
79
124
|
rowReordering = false,
|
|
@@ -88,6 +133,7 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
88
133
|
onEditStart,
|
|
89
134
|
onEditStop,
|
|
90
135
|
onEditCellValueChange,
|
|
136
|
+
isCellEditable,
|
|
91
137
|
|
|
92
138
|
focusedCellField,
|
|
93
139
|
isFocusVisible,
|
|
@@ -95,22 +141,16 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
95
141
|
colspanMap,
|
|
96
142
|
rowSpanningCaches,
|
|
97
143
|
rowHeight = 52,
|
|
98
|
-
rowMeta
|
|
144
|
+
rowMeta,
|
|
145
|
+
columnIndexMap,
|
|
146
|
+
ariaRowIndex,
|
|
99
147
|
} = props;
|
|
100
148
|
|
|
149
|
+
const id = rowIdProp ?? row.id;
|
|
150
|
+
// All hooks must come before any early returns (Rules of Hooks).
|
|
151
|
+
// DOM focus is moved onto the focused cell by useGridKeyboardNavigation, not by the row.
|
|
101
152
|
const checkboxId = useId();
|
|
102
|
-
const
|
|
103
|
-
const checkboxCellRef = React.useRef<HTMLDivElement>(null);
|
|
104
|
-
|
|
105
|
-
// All hooks must come before any early returns (Rules of Hooks)
|
|
106
|
-
React.useLayoutEffect(() => {
|
|
107
|
-
if (focusedCellField === '__expand_col__' && expandCellRef.current) {
|
|
108
|
-
expandCellRef.current.focus({ preventScroll: true });
|
|
109
|
-
}
|
|
110
|
-
if (focusedCellField === '__checkbox_col__' && checkboxCellRef.current) {
|
|
111
|
-
checkboxCellRef.current.focus({ preventScroll: true });
|
|
112
|
-
}
|
|
113
|
-
}, [focusedCellField]);
|
|
153
|
+
const detailPanelId = useId();
|
|
114
154
|
|
|
115
155
|
const pinnedPositions = React.useMemo(() => {
|
|
116
156
|
return calculatePinnedPositions(
|
|
@@ -125,21 +165,28 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
125
165
|
);
|
|
126
166
|
}, [columns, columnWidths, pinnedColumns, checkboxSelection, pinCheckboxColumn, hasDetailPanel, pinExpandColumn, rowReordering]);
|
|
127
167
|
|
|
128
|
-
const
|
|
168
|
+
const pinnedEdges = React.useMemo(() => getPinnedEdgeFields(columns, pinnedColumns), [columns, pinnedColumns]);
|
|
129
169
|
|
|
170
|
+
const isGroupRow = rowMeta?.hasChildren === true;
|
|
171
|
+
// Group rows, subtotal rows and auto-created tree parents: not data, so no selection or detail panel.
|
|
172
|
+
const isSyntheticRow = rowMeta?.isGroupRow === true;
|
|
173
|
+
const showDetailPanel = hasDetailPanel && !isSyntheticRow;
|
|
174
|
+
// The pivot Grand Total is synthetic too, but its cells show values like any row.
|
|
175
|
+
const isSelectableRow = !isSyntheticRow && !isSyntheticRowId(id);
|
|
176
|
+
|
|
177
|
+
// Which cells may be edited is decided per cell below (resolveCellEditable), not per row:
|
|
178
|
+
// tree-data parents are real rows and stay editable; synthetic group rows never are.
|
|
130
179
|
const handleCellEditStart = React.useCallback((field: string, value: unknown) => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
}, [isGroupRow, row.id, onEditStart]);
|
|
180
|
+
onEditStart?.({ id, field, value });
|
|
181
|
+
}, [id, onEditStart]);
|
|
135
182
|
|
|
136
183
|
const handleCellValueChange = React.useCallback((field: string, newValue: unknown) => {
|
|
137
|
-
onEditCellValueChange?.({ id
|
|
138
|
-
}, [onEditCellValueChange,
|
|
184
|
+
onEditCellValueChange?.({ id, field, value: newValue });
|
|
185
|
+
}, [onEditCellValueChange, id]);
|
|
139
186
|
|
|
140
|
-
const handleEditStopWrapper = React.useCallback((cancel?: boolean) => {
|
|
141
|
-
onEditStop?.({ cancel });
|
|
142
|
-
}, [onEditStop]);
|
|
187
|
+
const handleEditStopWrapper = React.useCallback((cancel?: boolean, field?: string) => {
|
|
188
|
+
onEditStop?.({ cancel, id, field });
|
|
189
|
+
}, [onEditStop, id]);
|
|
143
190
|
|
|
144
191
|
// ── Skeleton row (shown during infinite-scroll fetch) ─────────────────────
|
|
145
192
|
if (row._isSkeleton) {
|
|
@@ -161,32 +208,42 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
161
208
|
);
|
|
162
209
|
}
|
|
163
210
|
|
|
211
|
+
// `.ogx__cell--editing` covers custom renderEditCell editors, which have no .ogx__edit-cell wrapper.
|
|
164
212
|
const handleRowClick = (event: React.MouseEvent) => {
|
|
165
213
|
|
|
166
|
-
if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell')) {
|
|
214
|
+
if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell, .ogx__cell--editing')) {
|
|
167
215
|
return;
|
|
168
216
|
}
|
|
169
|
-
onRowClick?.({ row, id
|
|
217
|
+
onRowClick?.({ row, id, rowIndex });
|
|
170
218
|
};
|
|
171
219
|
|
|
172
220
|
const handleRowDoubleClick = (event: React.MouseEvent) => {
|
|
173
|
-
if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell')) {
|
|
221
|
+
if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell, .ogx__cell--editing')) {
|
|
174
222
|
return;
|
|
175
223
|
}
|
|
176
|
-
onRowDoubleClick?.({ row, id
|
|
224
|
+
onRowDoubleClick?.({ row, id, rowIndex });
|
|
177
225
|
};
|
|
178
226
|
|
|
179
227
|
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
180
228
|
event.stopPropagation();
|
|
181
|
-
onSelectionChange?.(
|
|
229
|
+
onSelectionChange?.(id, event.target.checked);
|
|
182
230
|
};
|
|
183
231
|
|
|
184
232
|
const handleDetailPanelToggle = (event: React.MouseEvent) => {
|
|
185
233
|
event.stopPropagation();
|
|
186
|
-
onDetailPanelToggle?.(
|
|
234
|
+
onDetailPanelToggle?.(id);
|
|
187
235
|
};
|
|
188
236
|
|
|
189
|
-
|
|
237
|
+
// aria-colindex: system columns first (reorder, expand, checkbox), then the data columns.
|
|
238
|
+
const reorderColIndex = rowReordering ? 1 : 0;
|
|
239
|
+
const expandColIndex = hasDetailPanel ? reorderColIndex + 1 : reorderColIndex;
|
|
240
|
+
const checkboxColIndex = checkboxSelection ? expandColIndex + 1 : expandColIndex;
|
|
241
|
+
const systemColumnCount = checkboxColIndex;
|
|
242
|
+
const dataColumnCount = columnIndexMap?.size ?? columns.filter(c => !c.isSpacer).length;
|
|
243
|
+
const isHierarchyRow = rowMeta !== undefined;
|
|
244
|
+
const hasChildren = rowMeta?.hasChildren === true;
|
|
245
|
+
|
|
246
|
+
const rowPinnedPosition = isRowPinned(id, pinnedRows);
|
|
190
247
|
const isRowPinnedTop = rowPinnedPosition === 'top';
|
|
191
248
|
const isRowPinnedBottom = rowPinnedPosition === 'bottom';
|
|
192
249
|
|
|
@@ -199,7 +256,8 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
199
256
|
isRowPinnedBottom && 'ogx__row--pinned-bottom',
|
|
200
257
|
isDragging && 'ogx__row--dragging',
|
|
201
258
|
isDragOver && 'ogx__row--drag-over',
|
|
202
|
-
isGroupRow && 'ogx__row--group'
|
|
259
|
+
isGroupRow && 'ogx__row--group',
|
|
260
|
+
rowMeta?.isGroupFooter && 'ogx__row--group-footer'
|
|
203
261
|
].filter(Boolean).join(' ');
|
|
204
262
|
|
|
205
263
|
return (
|
|
@@ -209,11 +267,13 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
209
267
|
onClick={handleRowClick}
|
|
210
268
|
onDoubleClick={onRowDoubleClick ? handleRowDoubleClick : undefined}
|
|
211
269
|
role="row"
|
|
212
|
-
aria-rowindex={rowIndex +
|
|
270
|
+
aria-rowindex={ariaRowIndex ?? rowIndex + 2}
|
|
213
271
|
aria-selected={isSelected}
|
|
272
|
+
aria-level={isHierarchyRow ? (rowMeta?.treeDepth ?? 0) + 1 : undefined}
|
|
273
|
+
aria-expanded={hasChildren ? rowMeta?.isExpanded === true : undefined}
|
|
214
274
|
data-rowindex={rowIndex}
|
|
215
|
-
onDragOver={onDragOver ? onDragOver(
|
|
216
|
-
onDrop={onDrop ? onDrop(
|
|
275
|
+
onDragOver={onDragOver ? onDragOver(id) : undefined}
|
|
276
|
+
onDrop={onDrop ? onDrop(id) : undefined}
|
|
217
277
|
style={{
|
|
218
278
|
maxHeight: `${rowHeight}px`,
|
|
219
279
|
minHeight: `${rowHeight}px`,
|
|
@@ -224,20 +284,23 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
224
284
|
{ }
|
|
225
285
|
{rowReordering && (
|
|
226
286
|
<div
|
|
227
|
-
className=
|
|
287
|
+
className={`ogx__cell ogx__cell--drag-handle ${focusedCellField === '__reorder_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__reorder_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''}`}
|
|
228
288
|
role="gridcell"
|
|
229
|
-
aria-label=
|
|
289
|
+
aria-label={onDragStart ? 'Drag to reorder row' : undefined}
|
|
290
|
+
aria-colindex={reorderColIndex}
|
|
291
|
+
data-field="__reorder_col__"
|
|
292
|
+
tabIndex={-1}
|
|
230
293
|
style={{
|
|
231
294
|
position: 'sticky',
|
|
232
295
|
left: 0,
|
|
233
|
-
zIndex:
|
|
296
|
+
zIndex: SYSTEM_CELL_Z_INDEX
|
|
234
297
|
}}
|
|
235
|
-
draggable={
|
|
236
|
-
onDragStart={onDragStart ? onDragStart(
|
|
298
|
+
draggable={!!onDragStart}
|
|
299
|
+
onDragStart={onDragStart ? onDragStart(id) : undefined}
|
|
237
300
|
onDragEnd={onDragEnd}
|
|
238
301
|
onClick={(e) => e.stopPropagation()}
|
|
239
302
|
>
|
|
240
|
-
<DragHandleIcon />
|
|
303
|
+
{onDragStart && <DragHandleIcon />}
|
|
241
304
|
</div>
|
|
242
305
|
)}
|
|
243
306
|
|
|
@@ -248,14 +311,16 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
248
311
|
} ${focusedCellField === '__expand_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__expand_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''
|
|
249
312
|
}`}
|
|
250
313
|
role="gridcell"
|
|
251
|
-
aria-label={isDetailPanelExpanded ? 'Collapse row details' : 'Expand row details'}
|
|
252
|
-
aria-expanded={isDetailPanelExpanded}
|
|
314
|
+
aria-label={showDetailPanel ? (isDetailPanelExpanded ? 'Collapse row details' : 'Expand row details') : undefined}
|
|
315
|
+
aria-expanded={showDetailPanel ? isDetailPanelExpanded : undefined}
|
|
316
|
+
aria-controls={showDetailPanel && isDetailPanelExpanded ? detailPanelId : undefined}
|
|
317
|
+
aria-colindex={expandColIndex}
|
|
318
|
+
data-field="__expand_col__"
|
|
253
319
|
style={{
|
|
254
320
|
position: pinExpandColumn ? 'sticky' : undefined,
|
|
255
321
|
left: pinExpandColumn ? (rowReordering ? 48 : 0) : undefined,
|
|
256
|
-
zIndex: pinExpandColumn ?
|
|
322
|
+
zIndex: pinExpandColumn ? SYSTEM_CELL_Z_INDEX : undefined
|
|
257
323
|
}}
|
|
258
|
-
ref={expandCellRef}
|
|
259
324
|
tabIndex={-1}
|
|
260
325
|
onClick={(e) => {
|
|
261
326
|
e.stopPropagation();
|
|
@@ -269,11 +334,14 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
269
334
|
});
|
|
270
335
|
}}
|
|
271
336
|
>
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
337
|
+
{showDetailPanel && (
|
|
338
|
+
<ExpandIcon
|
|
339
|
+
isExpanded={isDetailPanelExpanded}
|
|
340
|
+
onClick={handleDetailPanelToggle}
|
|
341
|
+
variant="plus-minus"
|
|
342
|
+
tabIndex={-1}
|
|
343
|
+
/>
|
|
344
|
+
)}
|
|
277
345
|
</div>
|
|
278
346
|
)}
|
|
279
347
|
|
|
@@ -284,12 +352,13 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
284
352
|
} ${focusedCellField === '__checkbox_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__checkbox_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''
|
|
285
353
|
}`}
|
|
286
354
|
role="gridcell"
|
|
355
|
+
aria-colindex={checkboxColIndex}
|
|
356
|
+
data-field="__checkbox_col__"
|
|
287
357
|
style={{
|
|
288
358
|
position: pinCheckboxColumn ? 'sticky' : undefined,
|
|
289
359
|
left: pinCheckboxColumn ? ((rowReordering ? 48 : 0) + (hasDetailPanel && pinExpandColumn ? 48 : 0)) : undefined,
|
|
290
|
-
zIndex: pinCheckboxColumn ?
|
|
360
|
+
zIndex: pinCheckboxColumn ? SYSTEM_CELL_Z_INDEX : undefined
|
|
291
361
|
}}
|
|
292
|
-
ref={checkboxCellRef}
|
|
293
362
|
tabIndex={-1}
|
|
294
363
|
onClick={(e) => {
|
|
295
364
|
e.stopPropagation();
|
|
@@ -303,21 +372,23 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
303
372
|
});
|
|
304
373
|
}}
|
|
305
374
|
>
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
375
|
+
{isSelectableRow && (
|
|
376
|
+
<Checkbox
|
|
377
|
+
id={checkboxId}
|
|
378
|
+
name={`ogx-select-row-${id}`}
|
|
379
|
+
checked={isSelected}
|
|
380
|
+
onChange={handleCheckboxChange}
|
|
381
|
+
onClick={(e) => e.stopPropagation()}
|
|
382
|
+
aria-label={isSelected ? `Deselect row ${id}` : `Select row ${id}`}
|
|
383
|
+
tabIndex={-1}
|
|
384
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
385
|
+
/>
|
|
386
|
+
)}
|
|
316
387
|
</div>
|
|
317
388
|
)}
|
|
318
389
|
|
|
319
390
|
{ }
|
|
320
|
-
{columns.map((colDef,
|
|
391
|
+
{columns.map((colDef, localIndex) => {
|
|
321
392
|
if (colDef.isSpacer) {
|
|
322
393
|
return (
|
|
323
394
|
<div
|
|
@@ -327,22 +398,45 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
327
398
|
/>
|
|
328
399
|
);
|
|
329
400
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
401
|
+
// A synthetic row holds its grouping value and aggregates under the column fields; a
|
|
402
|
+
// valueGetter written for data rows would recompute (or crash) from fields it lacks.
|
|
403
|
+
// A valueGetter that throws is contained to its cell.
|
|
404
|
+
let value: unknown;
|
|
405
|
+
let valueError: unknown;
|
|
406
|
+
if (isSyntheticRow) {
|
|
407
|
+
value = row[colDef.field];
|
|
408
|
+
} else {
|
|
409
|
+
({ value, error: valueError } = tryGetCellValue(row, colDef.field, colDef));
|
|
410
|
+
}
|
|
411
|
+
const colIndex = columnIndexMap?.get(colDef.field) ?? localIndex;
|
|
333
412
|
|
|
334
413
|
const effectiveWidth = columnWidths[colDef.field] ?? colDef.width;
|
|
335
414
|
|
|
336
415
|
const pinnedPosition = isColumnPinned(colDef.field, pinnedColumns);
|
|
337
|
-
const pinnedOffset = pinnedPosition ? pinnedPositions[colDef.field] : undefined;
|
|
338
416
|
|
|
339
|
-
const
|
|
417
|
+
const isEditable = Boolean(onEditStart) && resolveCellEditable(
|
|
418
|
+
{ row, field: colDef.field, value, colDef, rowIndex, colIndex, rowMeta },
|
|
419
|
+
isCellEditable
|
|
420
|
+
);
|
|
421
|
+
const isEditing = editingCell?.id === id && editingCell?.field === colDef.field;
|
|
340
422
|
|
|
341
423
|
const cellValue = isEditing ? editingCell?.value : value;
|
|
342
424
|
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
|
|
425
|
+
const storedColSpanInfo = colspanMap?.get(id)?.[colDef.field];
|
|
426
|
+
const colSpanInfo = storedColSpanInfo && !storedColSpanInfo.spannedByColSpan && storedColSpanInfo.cellProps.colSpan > 1
|
|
427
|
+
? mergeColSpan(columns, colIndex, storedColSpanInfo.cellProps.colSpan, columnWidths)
|
|
428
|
+
: storedColSpanInfo;
|
|
429
|
+
// A merged cell ends at the last column it covers: a right-pinned one is anchored by
|
|
430
|
+
// that column's right edge, and it is the left-pinned edge cell if that column is.
|
|
431
|
+
const lastCoveredField = colSpanInfo && !colSpanInfo.spannedByColSpan
|
|
432
|
+
? columns[colIndex + colSpanInfo.cellProps.colSpan - 1]?.field ?? colDef.field
|
|
433
|
+
: colDef.field;
|
|
434
|
+
const offsetField = pinnedPosition === 'right' ? lastCoveredField : colDef.field;
|
|
435
|
+
const pinnedOffset = pinnedPosition ? pinnedPositions[offsetField] : undefined;
|
|
436
|
+
const isPinnedEdge = (pinnedPosition === 'left' && lastCoveredField === pinnedEdges.lastLeft)
|
|
437
|
+
|| (pinnedPosition === 'right' && colDef.field === pinnedEdges.firstRight);
|
|
438
|
+
const rowSpan = rowSpanningCaches?.spannedCells[id]?.[colDef.field];
|
|
439
|
+
const isHiddenByRowSpan = rowSpanningCaches?.hiddenCells[id]?.[colDef.field] || false;
|
|
346
440
|
|
|
347
441
|
return (
|
|
348
442
|
<Cell
|
|
@@ -352,16 +446,18 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
352
446
|
colDef={colDef}
|
|
353
447
|
rowIndex={rowIndex}
|
|
354
448
|
colIndex={colIndex}
|
|
449
|
+
ariaColIndex={systemColumnCount + colIndex + 1}
|
|
355
450
|
isSelected={isSelected}
|
|
356
451
|
onCellClick={onCellClick}
|
|
357
452
|
width={effectiveWidth}
|
|
358
453
|
pinnedPosition={pinnedPosition}
|
|
359
454
|
pinnedOffset={pinnedOffset}
|
|
455
|
+
isPinnedEdge={isPinnedEdge}
|
|
360
456
|
|
|
361
457
|
isFocused={focusedCellField === colDef.field}
|
|
362
458
|
isFocusVisible={isFocusVisible}
|
|
363
459
|
|
|
364
|
-
isEditable={
|
|
460
|
+
isEditable={isEditable}
|
|
365
461
|
isEditing={isEditing}
|
|
366
462
|
onCellEditStart={handleCellEditStart}
|
|
367
463
|
onEditStop={handleEditStopWrapper}
|
|
@@ -371,20 +467,24 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
371
467
|
rowSpan={rowSpan}
|
|
372
468
|
isHiddenByRowSpan={isHiddenByRowSpan}
|
|
373
469
|
rowMeta={rowMeta}
|
|
470
|
+
valueError={valueError}
|
|
374
471
|
/>
|
|
375
472
|
);
|
|
376
473
|
})}
|
|
377
474
|
</div>
|
|
378
475
|
|
|
379
476
|
{ }
|
|
380
|
-
{
|
|
477
|
+
{showDetailPanel && (
|
|
381
478
|
<DetailPanel
|
|
479
|
+
id={detailPanelId}
|
|
480
|
+
colSpan={systemColumnCount + dataColumnCount}
|
|
382
481
|
row={row}
|
|
383
|
-
rowId={
|
|
482
|
+
rowId={id}
|
|
384
483
|
rowIndex={rowIndex}
|
|
385
484
|
content={detailPanelContent}
|
|
386
485
|
height={detailPanelHeight}
|
|
387
486
|
isExpanded={isDetailPanelExpanded}
|
|
487
|
+
onHeightChange={onDetailPanelHeightChange}
|
|
388
488
|
/>
|
|
389
489
|
)}
|
|
390
490
|
</>
|