@opencorestack/opengridx 3.9.1 → 3.10.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 +20 -0
- package/dist/index.d.ts +55 -0
- package/dist/opengridx.es.js +6346 -6279
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +16 -16
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +4 -0
- package/docs/features/ai-toolkit.md +1 -0
- package/docs/performance.md +1 -1
- package/docs/roadmap.md +5 -5
- package/lib/components/Cell/Cell.tsx +2 -1
- package/lib/components/DataGrid/DataGrid.tsx +19 -18
- package/lib/components/DataGrid/GridListView.tsx +1 -1
- package/lib/components/DataGrid/GridPinnedRows.tsx +1 -1
- package/lib/components/DataGrid/GridViewport.tsx +2 -1
- package/lib/components/DataGrid/GridVirtualRows.tsx +1 -1
- package/lib/components/Header/Header.tsx +4 -3
- package/lib/components/Row/Row.tsx +6 -2
- package/lib/components/Toolbar/AggregationPanel.tsx +90 -0
- package/lib/components/Toolbar/ColumnsPanelWrapper.tsx +56 -0
- package/lib/components/Toolbar/FilterPanelWrapper.tsx +82 -0
- package/lib/components/Toolbar/GridToolbar.tsx +7 -380
- package/lib/components/Toolbar/ToolbarIcons.tsx +35 -0
- package/lib/components/Toolbar/useToolbarPanel.ts +71 -0
- package/lib/hooks/core/gridKeyDown.ts +272 -0
- package/lib/hooks/core/useDataGrid.ts +13 -5
- package/lib/hooks/core/useGridApiMethods.ts +2 -2
- package/lib/hooks/core/useGridCellRangeDrag.ts +345 -0
- package/lib/hooks/core/useGridCellSelection.ts +17 -297
- package/lib/hooks/core/useGridColumnLookup.ts +2 -1
- package/lib/hooks/core/useGridColumns.tsx +3 -2
- package/lib/hooks/core/useGridControlledState.ts +3 -2
- package/lib/hooks/core/useGridHierarchy.ts +1 -1
- package/lib/hooks/core/useGridKeyboardNavigation.ts +51 -235
- package/lib/hooks/core/useGridLiveRowSelection.ts +2 -2
- package/lib/hooks/core/useGridRowInteractions.ts +1 -1
- package/lib/hooks/core/useGridRowSelection.ts +11 -2
- package/lib/hooks/core/useGridToolbarProps.ts +3 -2
- package/lib/hooks/features/useGridAiAssistant.ts +3 -2
- package/lib/hooks/useColumnReorder.ts +2 -1
- package/lib/hooks/useTreeData.ts +4 -2
- package/lib/types/locale.ts +22 -0
- package/lib/utils/aggregation/index.ts +16 -0
- package/lib/utils/aiAssistant.ts +46 -15
- package/lib/utils/export/excelCells.ts +244 -0
- package/lib/utils/export/exportCsv.ts +179 -0
- package/lib/utils/export/exportExcelHtml.ts +220 -0
- package/lib/utils/export/exportJson.ts +179 -0
- package/lib/utils/export/exportRows.ts +85 -0
- package/lib/utils/export/exportToExcelAdvanced.ts +22 -242
- package/lib/utils/export/index.ts +5 -695
- package/lib/utils/export/printGrid.ts +282 -0
- package/lib/utils/gridLocale.ts +30 -0
- package/lib/utils/selectionSets.ts +36 -0
- package/lib/utils/untypedColumns.ts +16 -0
- package/llms.txt +3 -1
- package/package.json +4 -2
package/docs/API_REFERENCE.md
CHANGED
|
@@ -988,6 +988,10 @@ interface GridLocaleText {
|
|
|
988
988
|
aiAssistantAppliedChanges; aiAssistantRemoveChange(label); aiAssistantUndo; aiAssistantIgnored; aiAssistantIgnoredItem(message);
|
|
989
989
|
aiAssistantEarlier; aiAssistantThinking; aiAssistantStopped; aiAssistantUndone; aiAssistantChangeRemoved;
|
|
990
990
|
aiAssistantApplied(applied, ignored); aiAssistantNotValidated; aiAssistantFailed(message); aiAssistantUnknownError;
|
|
991
|
+
// AI assistant change chips (v3.10; filter operators in the chips use filterPanelOperators)
|
|
992
|
+
aiAssistantChipSearch(words); aiAssistantChipNoFilters; aiAssistantChipSort(field, 'asc' | 'desc'); aiAssistantChipNoSorting;
|
|
993
|
+
aiAssistantChipGroup(field); aiAssistantChipNoGrouping; aiAssistantChipSummary(fn, field); aiAssistantChipNoSummaries;
|
|
994
|
+
aiAssistantChipPivot(fields); aiAssistantChipNoPivot; aiAssistantChipVisibility(field, visible);
|
|
991
995
|
// Pagination buttons
|
|
992
996
|
paginationLabel; paginationFirstPageLabel; paginationFirstPageTooltip; paginationPreviousPageLabel;
|
|
993
997
|
paginationPreviousPageTooltip; paginationNextPageLabel; paginationNextPageTooltip;
|
|
@@ -146,6 +146,7 @@ const onPrompt = createGridAiPromptHandler({
|
|
|
146
146
|
- **Applying.** The reply is applied immediately as one undoable step. Each change shows as a removable chip: a filter item, the quick-filter words, a sort key, a grouping field, an aggregation, a column shown or hidden, the pivot. Removing a chip re-applies the reply without it (a part with no chip left returns to its value from before the reply). **Undo** restores the state from before the reply. This view-state undo is separate from the cell-edit undo history (`undoRedo`).
|
|
147
147
|
- **What is applied.** Each part in `result.state` replaces the grid's model, except column visibility, which is merged over the current model (`{ tax: false }` hides one column and leaves the others). Parts outside `aiAssistant.parts` are ignored. A part the validator emptied (every entry dropped, such as a sort on an unknown field) is not applied, so it cannot clear the grid's model by accident. Changes go through the same handlers as the UI, so `onSortModelChange`, `onRowGroupingModelChange` and the rest fire; with a controlled model, update your state from them.
|
|
148
148
|
- **What was dropped** is listed under the chips, such as `Ignored: Unknown field "revnue"`.
|
|
149
|
+
- **Translating.** Every string of the panel is a `localeText` key (v3.9), including the chips' own texts since v3.10: `aiAssistantChipSort: (field, direction) => \`Tri : ${field}, ${direction === 'desc' ? 'décroissant' : 'croissant'}\``, `aiAssistantChipSummary: (fn, field) => …` (`fn` is the function id, such as `avg`), and the other `aiAssistantChip*` keys in the API reference. Filter operators in the chips use `filterPanelOperators`.
|
|
149
150
|
- **Accessibility.** The panel is a `role="dialog"` labelled "Ask AI". Focus moves to the input when it opens and back to the toolbar button (or wherever it was) when it closes. Escape closes it. The status ("Thinking…", "Applied 3 changes", errors) is announced through the grid's live region.
|
|
150
151
|
- **Events.** `onAiAssistantApply(result)` after a reply was applied; `onAiAssistantError(error)` when `onPrompt` rejects (Stop does not count) or its result was not validated.
|
|
151
152
|
|
package/docs/performance.md
CHANGED
|
@@ -174,7 +174,7 @@ Measured in Chromium on an Apple M1, 1,000,000 rows, from the event to the end o
|
|
|
174
174
|
| Fill 1,000 cells | 809 ms | ~50 ms | |
|
|
175
175
|
| Expand a group | 279 ms | ~135 ms | leaf row metadata built once per tree |
|
|
176
176
|
| Collapse a group | ~120 ms | ~1 ms | |
|
|
177
|
-
| Select all | ~270 ms | ~270 ms |
|
|
177
|
+
| Select all | ~270 ms | ~270 ms (~130 ms in 3.10) | 3.10 builds the 1M-id set once and reuses it for the model it returns; the rest is the live-selection check |
|
|
178
178
|
|
|
179
179
|
3.9.0 keeps rows memoised: selecting a row, starting or committing an edit, and vertical scrolling re-render only the rows they change (a click re-rendered every visible row, now one). React's own work per interaction is about 3–5 ms at 100k rows.
|
|
180
180
|
|
package/docs/roadmap.md
CHANGED
|
@@ -93,16 +93,16 @@ This document tracks the current status of features in `OpenGridX` and outlines
|
|
|
93
93
|
| **3.8.1** ✅ | shipped 13 Oct | **Grid buttons keep their own styles** (AI panel, toolbar active states) | Exact `bigint` sort; stricter number text; dead-code cleanup; every export documented |
|
|
94
94
|
| **3.9.0** ✅ | shipped 13 Oct | **`localeText` for every UI string** (~200 keys) | 9 theme keys + Theme Builder controls; rows stay memoised through selection and editing; demo sidebar by priority |
|
|
95
95
|
| **3.9.1** ✅ | shipped 13 Oct | **npm package no longer ships 1.6 MB of test baselines** | `check:package` guard in CI; version badges read `package.json`; docs brought up to date |
|
|
96
|
-
| 3.10.0 |
|
|
96
|
+
| **3.10.0** ✅ | shipped 13 Oct | **Backlog cleared**: refactors (R4–R6), CI guards, select-all ~2× faster | AI change chips translatable; demo syntax highlighting |
|
|
97
97
|
| Freeze | 19 Dec – 5 Jan | Bug fixes only | — |
|
|
98
98
|
|
|
99
|
-
### Now: backlog first (feature hold since 13 Oct)
|
|
99
|
+
### Now: backlog first (feature hold since 13 Oct; backlog cleared in 3.10)
|
|
100
100
|
|
|
101
101
|
New features (the *Next* list below) wait until the open fixes, refactors and performance work are done. Tracked in [`superpowers/plans/2026-10-13-audit-tracker.md`](superpowers/plans/2026-10-13-audit-tracker.md):
|
|
102
102
|
|
|
103
|
-
* **Refactors** (no behaviour change):
|
|
104
|
-
* **Performance**:
|
|
105
|
-
* **Demo**: syntax highlighting in the source viewer.
|
|
103
|
+
* ✅ **Refactors** (no behaviour change, 3.10): `as unknown as` casts 43 → 15 (R4), files over 600 lines split (R5), keydown handling as a typed, tested function (R6); CI guards against regressions (`check:compiler`, `check:versions`, ESLint `no-explicit-any` and no new `as unknown as`).
|
|
104
|
+
* **Performance**: ✅ select-all at 1M rows ~260 → ~130 ms (3.10, no API change). ❌ The time-sliced filter / sort pipeline is closed: it was 1.5–2× slower in total (target ≤ 20 %) and the remaining 1M-row sort pause is better served by a `dataSource`; see the [proposal's status](superpowers/specs/2026-10-12-nonblocking-pipeline-proposal.md).
|
|
105
|
+
* ✅ **Demo**: syntax highlighting in the source viewer (3.10).
|
|
106
106
|
* **Bundle size (4.0)**: an app using only `DataGrid` ships 87 KB gzipped; opt-in [feature modules](superpowers/specs/2026-10-13-feature-modules-proposal.md) would save ~20 KB. Breaking, so planned for 4.0.
|
|
107
107
|
|
|
108
108
|
### Across the quarter
|
|
@@ -8,6 +8,7 @@ import { CellErrorBoundary } from './CellErrorBoundary';
|
|
|
8
8
|
import { formatValueByType } from '../../utils/values';
|
|
9
9
|
import { attempt } from '../../utils/attempt';
|
|
10
10
|
import { RANGE_BOTTOM, RANGE_CELL, RANGE_FILL_HANDLE, RANGE_LEFT, RANGE_RIGHT, RANGE_TOP } from '../../utils/cellSelection';
|
|
11
|
+
import { untypedColumn } from '../../utils/untypedColumns';
|
|
11
12
|
|
|
12
13
|
export interface CellProps<R extends GridRowModel = GridRowModel> {
|
|
13
14
|
onCellClick?: (params: GridCellParams<R>) => void;
|
|
@@ -306,7 +307,7 @@ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
|
|
|
306
307
|
formattedValue={formattedValue}
|
|
307
308
|
row={row}
|
|
308
309
|
field={colDef.field}
|
|
309
|
-
colDef={colDef
|
|
310
|
+
colDef={untypedColumn(colDef)}
|
|
310
311
|
rowIndex={rowIndex}
|
|
311
312
|
colIndex={colIndex}
|
|
312
313
|
rowMeta={rowMeta}
|
|
@@ -3,7 +3,7 @@ import { useLayout } from '../../hooks/core/useLayout';
|
|
|
3
3
|
import { useGridKeyboardNavigation } from '../../hooks/core/useGridKeyboardNavigation';
|
|
4
4
|
import { useGridControlledState } from '../../hooks/core/useGridControlledState';
|
|
5
5
|
import { useGridThemeDimensions } from '../../hooks/core/useGridThemeDimensions';
|
|
6
|
-
import { useGridRowPipeline
|
|
6
|
+
import { useGridRowPipeline } from '../../hooks/core/useGridRowPipeline';
|
|
7
7
|
import { countReachableRows } from '../../utils/maxElementHeight';
|
|
8
8
|
import { useGridColumns } from '../../hooks/core/useGridColumns';
|
|
9
9
|
import { useGridDetailPanel } from '../../hooks/core/useGridDetailPanel';
|
|
@@ -44,7 +44,8 @@ import { useGridKeyboardMode, useGridPointerFocusHandlers } from '../../hooks/co
|
|
|
44
44
|
import { getPaginationRowCount } from '../../utils/pagination';
|
|
45
45
|
import { useGridToolbarStack } from '../../hooks/core/useGridToolbarStack';
|
|
46
46
|
import { DEFAULT_HEADER_FILTER_HEIGHT } from '../../utils/headerFilters';
|
|
47
|
-
import type { DataGridProps, DataGridUntypedColumnsProps, GridValidRowModel, GridRowModel,
|
|
47
|
+
import type { DataGridProps, DataGridUntypedColumnsProps, GridValidRowModel, GridRowModel, GridColDef } from '../../types';
|
|
48
|
+
import { untypedColumns } from '../../utils/untypedColumns';
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* The grid. `R` is your row type: any object type (an interface works), inferred from `rows`. `columns`
|
|
@@ -133,7 +134,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
133
134
|
|
|
134
135
|
const pivot = useGridPivot({
|
|
135
136
|
rows: rows as GridRowModel[],
|
|
136
|
-
columns: columns
|
|
137
|
+
columns: untypedColumns(columns),
|
|
137
138
|
pivotMode,
|
|
138
139
|
pivotModel: currentPivotModel,
|
|
139
140
|
filterModel,
|
|
@@ -166,7 +167,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
166
167
|
});
|
|
167
168
|
|
|
168
169
|
const activeRows = pivot.rows as unknown as R[];
|
|
169
|
-
const baseColumns = pivot.columns as
|
|
170
|
+
const baseColumns = pivot.columns as GridColDef<R>[];
|
|
170
171
|
|
|
171
172
|
const { activeColumns, effectivePinnedColumns } = useGridGroupingColumn<R>({
|
|
172
173
|
baseColumns, pinnedColumns, hasGroupingColumn, groupingColDef, isTreeData, getTreeDataPath,
|
|
@@ -222,13 +223,13 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
222
223
|
|
|
223
224
|
// Steps 12a–13: the edit commit channel, the batch edit and single-cell editing.
|
|
224
225
|
const { editCommitChannel, batchEdit, editingHandlers } = useGridEditPipeline<R>({
|
|
225
|
-
rowsLookup:
|
|
226
|
+
rowsLookup: gridData.rowsLookup,
|
|
226
227
|
rows: effectiveRows,
|
|
227
228
|
getRowId: getRowIdOf,
|
|
228
229
|
columns: activeColumns,
|
|
229
230
|
processRowUpdate,
|
|
230
231
|
onProcessRowUpdateError,
|
|
231
|
-
replaceRows
|
|
232
|
+
replaceRows,
|
|
232
233
|
});
|
|
233
234
|
|
|
234
235
|
// The selection without ids of removed rows. Only client-owned rows are pruned: a server page
|
|
@@ -241,12 +242,12 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
241
242
|
onSelectionModelChange: handleRowSelectionModelChange,
|
|
242
243
|
});
|
|
243
244
|
|
|
244
|
-
const dataSourceHandlers = useGridDataSource({
|
|
245
|
+
const dataSourceHandlers = useGridDataSource<R>({
|
|
245
246
|
dataSource, sortModel, filterModel,
|
|
246
247
|
paginationModel: effectivePaginationModel,
|
|
247
248
|
paginationMode, sortingMode, filterMode, aggregationModel,
|
|
248
249
|
rowGroupingActive: isRowGrouping,
|
|
249
|
-
getRowId: effectiveGetRowId
|
|
250
|
+
getRowId: effectiveGetRowId,
|
|
250
251
|
setRows, setRowCount, setDataSourceLoading, setDataSourceError,
|
|
251
252
|
onAggregationResults: setServerAggregationResults,
|
|
252
253
|
onPaginationModelChange: handlePaginationModelChange,
|
|
@@ -290,8 +291,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
290
291
|
density: effectiveDensity,
|
|
291
292
|
});
|
|
292
293
|
|
|
293
|
-
const rowPipeline = useGridRowPipeline<
|
|
294
|
-
effectiveRows
|
|
294
|
+
const rowPipeline = useGridRowPipeline<R>({
|
|
295
|
+
effectiveRows,
|
|
295
296
|
activeHierarchyHandlers,
|
|
296
297
|
filterMode,
|
|
297
298
|
filterModel: pivot.pipelineFilterModel,
|
|
@@ -303,7 +304,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
303
304
|
});
|
|
304
305
|
const {
|
|
305
306
|
filteredRows, dataRows, pinnedTopRows, pinnedBottomRows, sortedUnpinnedRows, paginatedUnpinnedRows, allRenderableRows,
|
|
306
|
-
} = rowPipeline
|
|
307
|
+
} = rowPipeline;
|
|
307
308
|
// The unpinned rows on screen: the current page, or every row without pagination.
|
|
308
309
|
const centerRows = pagination ? paginatedUnpinnedRows : sortedUnpinnedRows;
|
|
309
310
|
|
|
@@ -345,7 +346,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
345
346
|
hierarchyField,
|
|
346
347
|
dataRows,
|
|
347
348
|
}),
|
|
348
|
-
visibleColumns: renderOrderedColumns
|
|
349
|
+
visibleColumns: renderOrderedColumns,
|
|
349
350
|
},
|
|
350
351
|
});
|
|
351
352
|
|
|
@@ -359,7 +360,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
359
360
|
|
|
360
361
|
const { aggregationResult, isLoading: isAggregationLoading } = useAggregation({
|
|
361
362
|
rows: dataRows,
|
|
362
|
-
columns: activeColumns
|
|
363
|
+
columns: untypedColumns(activeColumns),
|
|
363
364
|
// The pivot's Grand Total row is the aggregate in pivot mode: nothing to total here.
|
|
364
365
|
aggregationModel: isPivotActive ? NO_AGGREGATION_MODEL : aggregationModel,
|
|
365
366
|
// When the server drives the rows (paginates, sorts or filters them), the grid only holds
|
|
@@ -437,8 +438,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
437
438
|
// Steps 30–31a: copy, scrollToIndexes and column auto-size on the API.
|
|
438
439
|
const handleColumnAutosize = useGridViewApis<R>({
|
|
439
440
|
apiRef, containerRef, viewportRef,
|
|
440
|
-
copyColumns: renderedDataColumns
|
|
441
|
-
getRowId: getRowIdOf
|
|
441
|
+
copyColumns: renderedDataColumns,
|
|
442
|
+
getRowId: getRowIdOf,
|
|
442
443
|
disableClipboardCopy,
|
|
443
444
|
getCellRangeCopyText: cellSelection.enabled ? cellSelection.getCopyText : undefined,
|
|
444
445
|
layout,
|
|
@@ -497,7 +498,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
497
498
|
getPendingModel: controlledState.getPendingCellSelectionModel,
|
|
498
499
|
focusedCell, setFocusedCell, allRenderableRows,
|
|
499
500
|
getRowId: getRowIdOf,
|
|
500
|
-
navigationColumns
|
|
501
|
+
navigationColumns,
|
|
501
502
|
columnIndexMap, isCellEditable, rowMetaMap,
|
|
502
503
|
getSpanOrigin: spanning.getSpanOrigin,
|
|
503
504
|
isEditing: editingHandlers.editingCell !== null,
|
|
@@ -559,7 +560,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
559
560
|
apiRef, containerRef, localeText,
|
|
560
561
|
aiAssistant: {
|
|
561
562
|
options: aiAssistant,
|
|
562
|
-
columns: activeColumns
|
|
563
|
+
columns: untypedColumns(activeColumns),
|
|
563
564
|
onApply: onAiAssistantApply,
|
|
564
565
|
onError: onAiAssistantError,
|
|
565
566
|
},
|
|
@@ -598,7 +599,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
598
599
|
pinnedTopRows, pinnedBottomRows, pinnedRows, getRowIdOf,
|
|
599
600
|
showEmptyState: !effectiveLoading && !state.dataSource.error && filteredRows.length === 0,
|
|
600
601
|
effectiveNoRowsLabel, spanRowWindow, visibleRows,
|
|
601
|
-
baseColumns: columns as
|
|
602
|
+
baseColumns: columns as GridColDef<R>[], viewportWidth: state.dimensions.viewportWidth,
|
|
602
603
|
rowReorderHandlers, paginationMode, dataSourceLoading: state.dataSource.loading,
|
|
603
604
|
sortedUnpinnedRowCount: sortedUnpinnedRows.length, pageSize: effectivePaginationModel.pageSize,
|
|
604
605
|
unpinnedRowsLength: layout.unpinnedRowsLength,
|
|
@@ -35,7 +35,7 @@ export interface GridListViewProps<R extends GridRowModel> {
|
|
|
35
35
|
/** The page actually shown (the requested page clamped to the last page). */
|
|
36
36
|
currentPage: number;
|
|
37
37
|
pageSizeOptions: number[];
|
|
38
|
-
selectedRowIds:
|
|
38
|
+
selectedRowIds: ReadonlySet<GridRowId>;
|
|
39
39
|
listViewColumn: GridListViewColDef<R>;
|
|
40
40
|
/** Grid columns by field; a list field that names one gets its valueGetter / valueFormatter. */
|
|
41
41
|
columnsByField: ReadonlyMap<string, GridColDef>;
|
|
@@ -31,7 +31,7 @@ export interface GridPinnedRowsProps<R extends GridRowModel> {
|
|
|
31
31
|
rows: R[];
|
|
32
32
|
position: 'top' | 'bottom';
|
|
33
33
|
columns: GridColDef<R>[];
|
|
34
|
-
selectedRowIds:
|
|
34
|
+
selectedRowIds: ReadonlySet<GridRowId>;
|
|
35
35
|
checkboxSelection: boolean;
|
|
36
36
|
onRowClick: (params: GridRowParams<R>) => void;
|
|
37
37
|
onRowDoubleClick?: (params: GridRowParams<R>) => void;
|
|
@@ -15,6 +15,7 @@ import type { UseGridViewportResult } from '../../hooks/core/useGridViewport';
|
|
|
15
15
|
import type { GridVirtualizationResult } from '../../hooks/core/useGridVirtualization';
|
|
16
16
|
import type { UseColumnReorderReturn } from '../../hooks/useColumnReorder';
|
|
17
17
|
import type { GridSpanningResult } from '../../hooks/features/useGridSpanning';
|
|
18
|
+
import { untypedColumns } from '../../utils/untypedColumns';
|
|
18
19
|
|
|
19
20
|
/** The props the row renderers (top-pinned, centre and bottom-pinned rows) all take. */
|
|
20
21
|
export type GridRowRenderProps<R extends GridRowModel> = Pick<GridVirtualRowsProps<R>,
|
|
@@ -294,7 +295,7 @@ export function GridViewport<R extends GridRowModel>(props: GridViewportProps<R>
|
|
|
294
295
|
hidden, pinned and scrolled columns line up. Off in pivot mode (hasAggregation). */}
|
|
295
296
|
{showAggregationFooter && (
|
|
296
297
|
<GridAggregationFooter
|
|
297
|
-
columns={virtualization.virtualColumns
|
|
298
|
+
columns={untypedColumns(virtualization.virtualColumns)}
|
|
298
299
|
aggregationModel={aggregationModel}
|
|
299
300
|
aggregationResult={aggregationResult}
|
|
300
301
|
columnWidths={columnWidths}
|
|
@@ -41,7 +41,7 @@ export interface GridVirtualRowsProps<R extends GridRowModel> {
|
|
|
41
41
|
hasDetailPanel: boolean;
|
|
42
42
|
rowReordering: boolean;
|
|
43
43
|
rowHeight: number;
|
|
44
|
-
selectedRowIds:
|
|
44
|
+
selectedRowIds: ReadonlySet<GridRowId>;
|
|
45
45
|
onRowClick: (params: GridRowParams<R>) => void;
|
|
46
46
|
onRowDoubleClick?: (params: GridRowParams<R>) => void;
|
|
47
47
|
onCellClick: (params: GridCellParams<R>) => void;
|
|
@@ -12,6 +12,7 @@ import { getHeaderColumnStyle } from './headerColumnStyle';
|
|
|
12
12
|
import { HeaderFilterRow } from '../HeaderFilter/HeaderFilterRow';
|
|
13
13
|
import { isRtl, logicalArrowKey } from '../../utils/direction';
|
|
14
14
|
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
15
|
+
import { untypedColumn, untypedColumns } from '../../utils/untypedColumns';
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
function MenuIcon() {
|
|
@@ -469,7 +470,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
469
470
|
};
|
|
470
471
|
|
|
471
472
|
const headerContent = colDef.renderHeader
|
|
472
|
-
? colDef.renderHeader({ field: colDef.field, colDef: colDef
|
|
473
|
+
? colDef.renderHeader({ field: colDef.field, colDef: untypedColumn(colDef), colIndex })
|
|
473
474
|
: colDef.headerName || colDef.field;
|
|
474
475
|
|
|
475
476
|
const handleHeaderClick = (e: React.MouseEvent) => {
|
|
@@ -560,7 +561,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
560
561
|
|
|
561
562
|
{menuOpenParams && (
|
|
562
563
|
<ColumnMenu
|
|
563
|
-
colDef={menuOpenParams.colDef
|
|
564
|
+
colDef={untypedColumn(menuOpenParams.colDef)}
|
|
564
565
|
sortModel={sortModel}
|
|
565
566
|
onSort={onSort || onSortAdd ? handleMenuSort : undefined}
|
|
566
567
|
onHide={onHideColumn}
|
|
@@ -576,7 +577,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
576
577
|
{/* ── Header filter row ── */}
|
|
577
578
|
{headerFilters && (
|
|
578
579
|
<HeaderFilterRow
|
|
579
|
-
columns={columns
|
|
580
|
+
columns={untypedColumns(columns)}
|
|
580
581
|
columnWidths={columnWidths}
|
|
581
582
|
pinnedColumns={pinnedColumns}
|
|
582
583
|
pinnedPositions={pinnedPositions}
|
|
@@ -108,6 +108,10 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
|
|
|
108
108
|
cellRange?: GridRowCellRange | null;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// The system columns' definitions passed to error params of their cells (stable, module level).
|
|
112
|
+
const EXPAND_COLUMN: GridColDef = { field: '__expand_col__', width: 48 };
|
|
113
|
+
const CHECKBOX_COLUMN: GridColDef = { field: '__checkbox_col__', width: 48 };
|
|
114
|
+
|
|
111
115
|
function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
112
116
|
const locale = useGridLocale();
|
|
113
117
|
const {
|
|
@@ -341,7 +345,7 @@ function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
341
345
|
row,
|
|
342
346
|
field: '__expand_col__',
|
|
343
347
|
value: isDetailPanelExpanded,
|
|
344
|
-
colDef:
|
|
348
|
+
colDef: EXPAND_COLUMN as GridColDef<R>,
|
|
345
349
|
rowIndex,
|
|
346
350
|
colIndex: -1
|
|
347
351
|
});
|
|
@@ -379,7 +383,7 @@ function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
379
383
|
row,
|
|
380
384
|
field: '__checkbox_col__',
|
|
381
385
|
value: isSelected,
|
|
382
|
-
colDef:
|
|
386
|
+
colDef: CHECKBOX_COLUMN as GridColDef<R>,
|
|
383
387
|
rowIndex,
|
|
384
388
|
colIndex: -1
|
|
385
389
|
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import ReactDOM from 'react-dom';
|
|
2
|
+
import type { GridAggregationModel, GridColDef } from '../../types';
|
|
3
|
+
import { getOfferedAggregationFunctions } from '../../utils/aggregation';
|
|
4
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
5
|
+
import { SigmaIcon } from './ToolbarIcons';
|
|
6
|
+
import { useToolbarPanel } from './useToolbarPanel';
|
|
7
|
+
|
|
8
|
+
/** The toolbar's Summaries panel: one row of function pills per aggregable column. */
|
|
9
|
+
export function AggregationPanel({
|
|
10
|
+
anchorRef,
|
|
11
|
+
aggregationModel,
|
|
12
|
+
aggregableColumns,
|
|
13
|
+
onFunctionChange,
|
|
14
|
+
onClearAll,
|
|
15
|
+
onClose,
|
|
16
|
+
activeCount,
|
|
17
|
+
}: {
|
|
18
|
+
anchorRef: React.RefObject<HTMLElement | null>;
|
|
19
|
+
aggregationModel: GridAggregationModel;
|
|
20
|
+
aggregableColumns: GridColDef[];
|
|
21
|
+
onFunctionChange: (field: string, fn: string) => void;
|
|
22
|
+
onClearAll: () => void;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
activeCount: number;
|
|
25
|
+
}) {
|
|
26
|
+
const locale = useGridLocale();
|
|
27
|
+
const { panelRef, position: pos, portalContainer } = useToolbarPanel(anchorRef, onClose);
|
|
28
|
+
|
|
29
|
+
const panel = (
|
|
30
|
+
<div
|
|
31
|
+
ref={panelRef}
|
|
32
|
+
className="ogx-toolbar__panel"
|
|
33
|
+
role="dialog"
|
|
34
|
+
aria-label={locale.toolbarSummariesPanelLabel}
|
|
35
|
+
style={{ top: pos.top, right: pos.right, left: pos.left, width: 400, maxWidth: 'calc(100vw - 24px)' }}
|
|
36
|
+
>
|
|
37
|
+
<div className="ogx-toolbar__panel-header">
|
|
38
|
+
<span className="ogx-toolbar__panel-title">
|
|
39
|
+
<SigmaIcon />
|
|
40
|
+
{locale.toolbarSummaries}
|
|
41
|
+
</span>
|
|
42
|
+
{activeCount > 0 && (
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
className="ogx-toolbar__clear-btn"
|
|
46
|
+
onClick={onClearAll}
|
|
47
|
+
title={locale.toolbarSummariesClearAllTooltip}
|
|
48
|
+
>
|
|
49
|
+
{locale.toolbarSummariesClearAll}
|
|
50
|
+
</button>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="ogx-toolbar__panel-body">
|
|
55
|
+
{aggregableColumns.length === 0 ? (
|
|
56
|
+
<p className="ogx-toolbar__empty">
|
|
57
|
+
{locale.toolbarSummariesEmpty}<br />
|
|
58
|
+
{`${locale.toolbarSummariesEmptyHintBefore} `}<code>aggregable: true</code>{` ${locale.toolbarSummariesEmptyHintAfter}`}
|
|
59
|
+
</p>
|
|
60
|
+
) : (
|
|
61
|
+
aggregableColumns.map((col) => {
|
|
62
|
+
const currentFn = aggregationModel[col.field] || 'none';
|
|
63
|
+
return (
|
|
64
|
+
<div key={col.field} className="ogx-toolbar__agg-row">
|
|
65
|
+
<span className="ogx-toolbar__agg-label" title={col.field}>
|
|
66
|
+
{col.headerName || col.field}
|
|
67
|
+
</span>
|
|
68
|
+
<div className="ogx-toolbar__agg-pills">
|
|
69
|
+
{['none', ...getOfferedAggregationFunctions(col)].map((fn) => (
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
key={fn}
|
|
73
|
+
className={`ogx-toolbar__pill${currentFn === fn ? ' ogx-toolbar__pill--active' : ''}`}
|
|
74
|
+
onClick={() => onFunctionChange(col.field, fn)}
|
|
75
|
+
aria-pressed={currentFn === fn}
|
|
76
|
+
>
|
|
77
|
+
{locale.toolbarAggregationFunction(fn)}
|
|
78
|
+
</button>
|
|
79
|
+
))}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
})
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return portalContainer ? ReactDOM.createPortal(panel, portalContainer) : null;
|
|
90
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import ReactDOM from 'react-dom';
|
|
2
|
+
import type { GridColDef } from '../../types';
|
|
3
|
+
import { ColumnVisibilityPanel } from '../ColumnVisibilityPanel/ColumnVisibilityPanel';
|
|
4
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
5
|
+
import { useToolbarPanel } from './useToolbarPanel';
|
|
6
|
+
|
|
7
|
+
/** The toolbar's Columns panel: `ColumnVisibilityPanel` in a positioned, portalled popover. */
|
|
8
|
+
export function ColumnsPanelWrapper({
|
|
9
|
+
anchorRef,
|
|
10
|
+
columns,
|
|
11
|
+
visibleColumns,
|
|
12
|
+
onVisibilityChange,
|
|
13
|
+
onShowAll,
|
|
14
|
+
onHideAll,
|
|
15
|
+
onColumnReorder,
|
|
16
|
+
onColumnOrderReset,
|
|
17
|
+
onClose,
|
|
18
|
+
showNonHideableColumns,
|
|
19
|
+
}: {
|
|
20
|
+
anchorRef: React.RefObject<HTMLElement | null>;
|
|
21
|
+
columns: GridColDef[];
|
|
22
|
+
visibleColumns: Set<string>;
|
|
23
|
+
onVisibilityChange: (field: string, isVisible: boolean) => void;
|
|
24
|
+
onShowAll: () => void;
|
|
25
|
+
onHideAll: () => void;
|
|
26
|
+
onColumnReorder?: (fromField: string, toField: string) => void;
|
|
27
|
+
onColumnOrderReset?: () => void;
|
|
28
|
+
onClose: () => void;
|
|
29
|
+
showNonHideableColumns?: boolean;
|
|
30
|
+
}) {
|
|
31
|
+
const locale = useGridLocale();
|
|
32
|
+
const { panelRef, position: pos, portalContainer } = useToolbarPanel(anchorRef, onClose);
|
|
33
|
+
|
|
34
|
+
const panel = (
|
|
35
|
+
<div
|
|
36
|
+
ref={panelRef}
|
|
37
|
+
className="ogx-toolbar__panel"
|
|
38
|
+
role="dialog"
|
|
39
|
+
aria-label={locale.toolbarColumnsPanelLabel}
|
|
40
|
+
style={{ top: pos.top, right: pos.right, left: pos.left, width: 280, padding: 0 }}
|
|
41
|
+
>
|
|
42
|
+
<ColumnVisibilityPanel
|
|
43
|
+
columns={columns}
|
|
44
|
+
visibleColumns={visibleColumns}
|
|
45
|
+
onVisibilityChange={onVisibilityChange}
|
|
46
|
+
onShowAll={onShowAll}
|
|
47
|
+
onHideAll={onHideAll}
|
|
48
|
+
onColumnReorder={onColumnReorder}
|
|
49
|
+
onColumnOrderReset={onColumnOrderReset}
|
|
50
|
+
showNonHideableColumns={showNonHideableColumns}
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return portalContainer ? ReactDOM.createPortal(panel, portalContainer) : null;
|
|
56
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import ReactDOM from 'react-dom';
|
|
2
|
+
import type { GridColDef, GridFilterModel } from '../../types';
|
|
3
|
+
import { FilterPanel } from '../FilterPanel/FilterPanel';
|
|
4
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
5
|
+
import { FilterIcon } from './ToolbarIcons';
|
|
6
|
+
import { useToolbarPanel } from './useToolbarPanel';
|
|
7
|
+
|
|
8
|
+
/** The toolbar's Filters panel: `FilterPanel` with a count, Clear all and Close. */
|
|
9
|
+
export function FilterPanelWrapper({
|
|
10
|
+
anchorRef,
|
|
11
|
+
columns,
|
|
12
|
+
filterModel,
|
|
13
|
+
onFilterModelChange,
|
|
14
|
+
onClose
|
|
15
|
+
}: {
|
|
16
|
+
anchorRef: React.RefObject<HTMLElement | null>;
|
|
17
|
+
columns: GridColDef[];
|
|
18
|
+
filterModel: GridFilterModel;
|
|
19
|
+
onFilterModelChange: (model: GridFilterModel) => void;
|
|
20
|
+
onClose: () => void;
|
|
21
|
+
}) {
|
|
22
|
+
const locale = useGridLocale();
|
|
23
|
+
// Escape closes it; a press outside does not: users type in its inputs, and it has a Close button.
|
|
24
|
+
const { panelRef, position: pos, portalContainer } = useToolbarPanel(anchorRef, onClose, { closeOnOutsideClick: false });
|
|
25
|
+
|
|
26
|
+
const activeFilterCount = filterModel.items?.length ?? 0;
|
|
27
|
+
|
|
28
|
+
const handleClearAll = () => {
|
|
29
|
+
onFilterModelChange({ ...filterModel, items: [], logicOperator: 'and' });
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const panel = (
|
|
33
|
+
<div
|
|
34
|
+
ref={panelRef}
|
|
35
|
+
className="ogx-toolbar__panel ogx-toolbar__panel--filter"
|
|
36
|
+
role="dialog"
|
|
37
|
+
aria-label={locale.toolbarFiltersLabel}
|
|
38
|
+
style={{ top: pos.top, right: pos.right, left: pos.left, width: 540, maxWidth: 'calc(100vw - 24px)' }}
|
|
39
|
+
>
|
|
40
|
+
<div className="ogx-toolbar__panel-header">
|
|
41
|
+
<span className="ogx-toolbar__panel-title">
|
|
42
|
+
<FilterIcon />
|
|
43
|
+
{locale.toolbarFilters}
|
|
44
|
+
{activeFilterCount > 0 && (
|
|
45
|
+
<span className="ogx-toolbar__panel-count">{activeFilterCount}</span>
|
|
46
|
+
)}
|
|
47
|
+
</span>
|
|
48
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
49
|
+
{activeFilterCount > 0 && (
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
className="ogx-toolbar__clear-btn"
|
|
53
|
+
onClick={handleClearAll}
|
|
54
|
+
title={locale.toolbarFiltersClearAllTooltip}
|
|
55
|
+
>
|
|
56
|
+
{locale.toolbarFiltersClearAll}
|
|
57
|
+
</button>
|
|
58
|
+
)}
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
className="ogx-toolbar__close-btn"
|
|
62
|
+
onClick={onClose}
|
|
63
|
+
title={locale.toolbarFiltersCloseTooltip}
|
|
64
|
+
aria-label={locale.toolbarFiltersCloseLabel}
|
|
65
|
+
>
|
|
66
|
+
{locale.toolbarFiltersClose}
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div className="ogx-toolbar__panel-body ogx-toolbar__panel-body--filter">
|
|
72
|
+
<FilterPanel
|
|
73
|
+
columns={columns}
|
|
74
|
+
filterModel={filterModel}
|
|
75
|
+
onFilterModelChange={onFilterModelChange}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return portalContainer ? ReactDOM.createPortal(panel, portalContainer) : null;
|
|
82
|
+
}
|