@opencorestack/opengridx 3.9.0 → 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.
Files changed (65) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +11 -7
  3. package/dist/index.d.ts +55 -0
  4. package/dist/opengridx.es.js +6346 -6279
  5. package/dist/opengridx.es.js.map +1 -1
  6. package/dist/opengridx.umd.js +16 -16
  7. package/dist/opengridx.umd.js.map +1 -1
  8. package/docs/API_REFERENCE.md +4 -0
  9. package/docs/architecture/datagrid-orchestration.md +4 -2
  10. package/docs/architecture/use-grid-virtualization.md +2 -0
  11. package/docs/components/pagination.md +1 -1
  12. package/docs/features/ai-toolkit.md +1 -0
  13. package/docs/features/rtl.md +1 -1
  14. package/docs/performance.md +19 -1
  15. package/docs/roadmap.md +26 -5
  16. package/lib/components/Cell/Cell.tsx +2 -1
  17. package/lib/components/DataGrid/DataGrid.tsx +19 -18
  18. package/lib/components/DataGrid/GridListView.tsx +1 -1
  19. package/lib/components/DataGrid/GridPinnedRows.tsx +1 -1
  20. package/lib/components/DataGrid/GridViewport.tsx +2 -1
  21. package/lib/components/DataGrid/GridVirtualRows.tsx +1 -1
  22. package/lib/components/Header/Header.tsx +4 -3
  23. package/lib/components/Row/Row.tsx +6 -2
  24. package/lib/components/Toolbar/AggregationPanel.tsx +90 -0
  25. package/lib/components/Toolbar/ColumnsPanelWrapper.tsx +56 -0
  26. package/lib/components/Toolbar/FilterPanelWrapper.tsx +82 -0
  27. package/lib/components/Toolbar/GridToolbar.tsx +7 -380
  28. package/lib/components/Toolbar/ToolbarIcons.tsx +35 -0
  29. package/lib/components/Toolbar/useToolbarPanel.ts +71 -0
  30. package/lib/hooks/core/gridKeyDown.ts +272 -0
  31. package/lib/hooks/core/useDataGrid.ts +13 -5
  32. package/lib/hooks/core/useGridApiMethods.ts +2 -2
  33. package/lib/hooks/core/useGridCellRangeDrag.ts +345 -0
  34. package/lib/hooks/core/useGridCellSelection.ts +17 -297
  35. package/lib/hooks/core/useGridColumnLookup.ts +2 -1
  36. package/lib/hooks/core/useGridColumns.tsx +3 -2
  37. package/lib/hooks/core/useGridControlledState.ts +3 -2
  38. package/lib/hooks/core/useGridHierarchy.ts +1 -1
  39. package/lib/hooks/core/useGridKeyboardNavigation.ts +51 -235
  40. package/lib/hooks/core/useGridLiveRowSelection.ts +2 -2
  41. package/lib/hooks/core/useGridRowInteractions.ts +1 -1
  42. package/lib/hooks/core/useGridRowSelection.ts +11 -2
  43. package/lib/hooks/core/useGridToolbarProps.ts +3 -2
  44. package/lib/hooks/features/useGridAiAssistant.ts +3 -2
  45. package/lib/hooks/useColumnReorder.ts +2 -1
  46. package/lib/hooks/useTreeData.ts +4 -2
  47. package/lib/types/locale.ts +22 -0
  48. package/lib/utils/aggregation/index.ts +16 -0
  49. package/lib/utils/aiAssistant.ts +46 -15
  50. package/lib/utils/export/excelCells.ts +244 -0
  51. package/lib/utils/export/exportCsv.ts +179 -0
  52. package/lib/utils/export/exportExcelHtml.ts +220 -0
  53. package/lib/utils/export/exportJson.ts +179 -0
  54. package/lib/utils/export/exportRows.ts +85 -0
  55. package/lib/utils/export/exportToExcelAdvanced.ts +22 -242
  56. package/lib/utils/export/index.ts +5 -695
  57. package/lib/utils/export/printGrid.ts +282 -0
  58. package/lib/utils/gridLocale.ts +30 -0
  59. package/lib/utils/selectionSets.ts +36 -0
  60. package/lib/utils/untypedColumns.ts +16 -0
  61. package/llms.txt +3 -1
  62. package/package.json +6 -2
  63. package/lib/theme/__baseline__/default-theme.darwin.chromium.json +0 -1
  64. package/lib/theme/__baseline__/default-theme.darwin.firefox.json +0 -1
  65. package/lib/theme/__baseline__/default-theme.darwin.webkit.json +0 -1
@@ -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;
@@ -113,10 +113,12 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
113
113
  `GridToolbarSlot`, `GridAiAssistantArea` (the `aiAssistant` panel or `slots.aiAssistantPanel` while open, anchored under the toolbar), `GridStandaloneColumnPanel`, `GridListView` (list view) or `GridViewport` (`components/DataGrid/GridViewport.tsx`, the `role="grid"` viewport:
114
114
  `Header`, `GridPinnedRows` top, `GridEmptyState`, `GridVirtualRows`, `GridPinnedRows` bottom,
115
115
  `GridAggregationFooter`), `GridCellSelectionStatsArea` (`showCellSelectionStats` with `cellSelection`), then `slots.footer` or `GridPaginationArea`, `GridLiveRegion`,
116
- `GridLoadingOverlay` and `GridErrorOverlay`. `GridRoot` builds the overlay slot elements and passes the
116
+ `GridLoadingOverlay` and `GridErrorOverlay`. `GridRoot` also provides `GridLocaleContext` (v3.9) around all of them: `localeText`, kept stable while shallowly equal (`useShallowStableValue`), resolved once by `resolveGridLocale` (`lib/utils/gridLocale.ts`); components and `Row` read it with `useGridLocale()` instead of a prop, so `Row` gets no prop that changes per render. `GridRoot` builds the overlay slot elements and passes the
117
117
  rest of its props on to `GridViewport`. The props all three row renderers take alike are built once in
118
118
  `GridViewport` (`rowRenderProps`) and spread into each. `rowRenderProps.cellRange` is the cell range's
119
119
  render rectangle; each row renderer turns it into a per-row `cellRange` (`getRowCellRange`), `null` for rows
120
120
  outside it, and `Row` is memoised with a value comparison of that prop, so a drag only re-renders the rows the
121
- range enters or leaves. `rowRenderProps.fillHandle` (v3.5) adds `fillHandleCol` to the one row that draws the fill
121
+ range enters or leaves. The same rule holds for every row prop (v3.9): `editingCell` goes only to the row being
122
+ edited, `focusedCellField` only to the focused row, and the row click and checkbox handlers keep one identity
123
+ (`useGridRowInteractions` reads the latest handlers when called). `DataGrid.rowMemo.test.tsx` guards it. `rowRenderProps.fillHandle` (v3.5) adds `fillHandleCol` to the one row that draws the fill
122
124
  handle (a single selected cell gets an empty column interval, so it draws the handle but no range).
@@ -90,6 +90,8 @@ Row heights include an expanded detail panel's height, so the prefix sums accoun
90
90
 
91
91
  Columns use the same search on `unpinnedAccWidths` (also inclusive), relative to the left edge of the unpinned area (`scrollLeft - leftPinnedWidth`), widened by 6 columns on each side. The skipped widths become `__spacer_left__` / `__spacer_right__` entries (`isSpacer: true`) in `virtualColumns`.
92
92
 
93
+ The column window is computed in its own `useMemo` (v3.9), over the column parts of the layout (`unpinnedAccWidths`, `unpinnedColsWithWidth`, the pinned column lists and widths), `scrollLeft` and the viewport width only. A row change (an edit), a vertical scroll or a row-height change therefore keeps the same `virtualColumns` array, and `Row`, which is memoised on its props, does not re-render for it. `useLayout` returns the row and column parts from separate memos (v3.7.1), so the column fields keep their identity while rows change.
94
+
93
95
  When a column declares `colSpan` / `rowSpan`, `DataGrid` widens this window afterwards (`useGridSpanRowWindow` / `useGridSpanColumnWindow`) so a span whose origin is outside the window is not cut.
94
96
 
95
97
  ---
@@ -41,7 +41,7 @@ You can replace the internal pagination UI using the `pagination` slot:
41
41
 
42
42
  ## 🌍 Internationalisation
43
43
 
44
- Pass `localeText` on `<DataGrid />` to override the text labels in the Pagination component (rows-per-page, range, and page-counter labels). The rows-per-page select takes its accessible name from the `paginationRowsPerPage` label. The navigation landmark label (`aria-label="Pagination"`) and the button labels and titles (`Go to next page`, `Next page`, …) are hardcoded and not overrideable via `localeText`.
44
+ Pass `localeText` on `<DataGrid />` to override the text labels in the Pagination component (rows-per-page, range, and page-counter labels). The rows-per-page select takes its accessible name from the `paginationRowsPerPage` label. Since v3.9 the navigation landmark label (`paginationLabel`, default `"Pagination"`) and each button's label and tooltip (`paginationNextPageLabel` / `paginationNextPageTooltip` → `"Go to next page"` / `"Next page"`, and the same for First, Previous and Last) are `localeText` keys as well.
45
45
 
46
46
  | Key | Type | Default |
47
47
  | :--- | :--- | :--- |
@@ -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
 
@@ -42,7 +42,7 @@ The column menu is labelled by screen side: in RTL *Pin to Left* adds the column
42
42
 
43
43
  ## Known limitations
44
44
 
45
- - **Menu and panel labels are hard-coded English** ("Pin to Left", "Sort Ascending"); there is no `localeText` key for the column menu yet.
45
+ - ~~Menu and panel labels are hard-coded English~~: since v3.9 every built-in string, including the column menu (`columnMenuPinLeft`, `columnMenuSortAscending`, …), is a `localeText` key. See *Locale text for every UI string* in [`API_REFERENCE.md`](../API_REFERENCE.md).
46
46
  - **Header filter operator symbols** are mirrored by the browser's bidi rules (`∋` shows as `∈`), as in any RTL text.
47
47
  - **`direction` set through CSS** (a class or stylesheet, not the `dir` attribute) and changed at runtime is not detected; scroll the grid or remount it with a `key`.
48
48
  - **Headless Chromium tests:** Playwright launches Chromium with `--hide-scrollbars`. With classic scrollbars (Linux) `scrollbar-gutter: stable` still reserves the gutter, and in RTL Chromium then cannot scroll the last gutter-width of the start into view (`scrollLeft` stops at minus the gutter). A plain scroller without the grid does the same and real browsers with visible scrollbars do not. Tests must compare with the browser's scroll origin, as `DataGrid.rtl.browser.test.tsx` does. To reproduce on macOS: `defaults write chrome-headless-shell AppleShowScrollBars Always` (undo with `defaults delete chrome-headless-shell`).
@@ -136,7 +136,7 @@ Measured with `npm run bench` (`bench/`), which installs the packed npm package
136
136
  | 500,000 (3.5.0) | 295 ms | 493 / 641 ms | 59 ms | 958 ms | 331 ms | 0 % | 39.4 MB |
137
137
  | 1,000,000 | 621 ms | 467 / 467 ms | 109 ms | 1,813 ms | 693 ms | 0 % | 76.9 MB |
138
138
 
139
- ### Before and after the faster sort, quick filter and grouping (unreleased)
139
+ ### Before and after the faster sort, quick filter and grouping (3.6.0)
140
140
 
141
141
  Medians of 5 runs, the same machine, the 3.5.1 code (`122d431`) and the changed code (`e1df2f1`) measured back to back on 2026-10-12 (`bench/results/2026-10-12-apple-m1-darwin-before.json` and `bench/results/2026-10-12-apple-m1-darwin.json`). Load average at the start of each run: 2.9 (before) and 4.6 (after); an IDE, a browser and other tools were open. Row counts after every filter and grouping were identical in both runs.
142
142
 
@@ -162,6 +162,24 @@ What changed: sort keys are read once per row into typed arrays and strings are
162
162
  - **Sorting, the quick filter and grouping run on the main thread in one piece.** Up to 100k rows they finish in a few frames; at 1M a sort blocks the page for about 0.35–0.6 s in every engine, grouping for 0.7 s (1.8 s in Firefox), and the first quick-filter keystroke for about 1.8–3 s. For sorting or searching a million rows interactively, use server-side sorting and filtering.
163
163
  - **Reach:** past the browser's height cap rows cannot be scrolled to, and in Firefox the grid stops scrolling altogether. See [Reach](#reach-the-browser-height-ceiling).
164
164
 
165
+ ### Editing, selection and grouping at 1M rows (3.8 – 3.9)
166
+
167
+ Measured in Chromium on an Apple M1, 1,000,000 rows, from the event to the end of its long task (best of three; not part of `npm run bench`, which covers mount, scroll, sort and filter).
168
+
169
+ | Interaction | 3.7.0 | 3.8.0 | |
170
+ | :--- | ---: | ---: | :--- |
171
+ | Edit one cell | 561 ms | ~35 ms | the row store is updated in place; id lists over the visible rows are reused |
172
+ | Paste 1,000 × 10 cells | ~790 ms | ~45 ms | |
173
+ | Undo / redo of that paste | ~790 ms | ~45 ms | |
174
+ | Fill 1,000 cells | 809 ms | ~50 ms | |
175
+ | Expand a group | 279 ms | ~135 ms | leaf row metadata built once per tree |
176
+ | Collapse a group | ~120 ms | ~1 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
+
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
+
181
+ To keep this, change rows by passing new row objects (never mutate them), keep `columns` and `localeText` shallowly stable (inline objects of plain values are fine; define function entries outside the render). Handlers such as `onRowClick` may be inline: the grid passes rows its own stable handlers.
182
+
165
183
  ### Method
166
184
 
167
185
  - **The app is the built package.** `bench/app` installs the tarball from `npm pack`, like a consumer, and is a production build (no React development checks).
package/docs/roadmap.md CHANGED
@@ -85,14 +85,32 @@ This document tracks the current status of features in `OpenGridX` and outlines
85
85
  | **3.3.0** ✅ | shipped 7 Oct | **Cell range selection** (opt-in `cellSelection` prop): mouse drag, Shift+click, Shift+arrows; Ctrl+C / Cmd+C copies the block as TSV | Selection status bar (count, sum, average of selected cells); wrapped header text; React Compiler compatibility check |
86
86
  | **3.4.0** ✅ | shipped 8 Oct | **Paste from Excel / Google Sheets** into the selection, through `processRowUpdate` (values parsed per column type or `valueParser`, non-editable cells skipped, `onClipboardPaste` summary); **Delete clears a range**; built-in inputs and editors use the shared `Input` / `Checkbox` (`Input variant="cell"`). **Undo / redo** for cell edits and pastes (Ctrl+Z, Ctrl+Shift+Z / Ctrl+Y, `apiRef.undo()` / `redo()`) | **AI toolkit, part 1** in a new `@opencorestack/opengridx/ai` entry point: `getGridAiSchema(columns)` (JSON Schema of the filter, sort, grouping, aggregation, pivot and visibility models the columns allow) and `validateGridAiState(json, columns)` (drops unknown fields and operators, coerces values, returns errors) |
87
87
  | **3.5.0** ✅ | shipped 9 Oct | **AI toolkit, part 2**: `aiAssistant={{ onPrompt }}` Ask AI panel (chips, Undo, voice) that calls the app's own model through `createGridAiPromptHandler`; only validated replies apply; `createGridAgentTools(apiRef, columns)` (plain tool objects for CopilotKit, the Vercel AI SDK, provider tool calling or WebMCP) | **Header filter row** (`headerFilters`); **fill handle** (series, Alt copy, Ctrl/Cmd+D / R); `rowGroupingModel` controlled/uncontrolled pair |
88
+ | **3.5.1** ✅ | shipped 10 Oct | **Content height capped to the browser's limit** (Firefox collapsed grids taller than ~17.9M px) | Dev warning names the reachable row count |
89
+ | **3.6.0** ✅ | shipped 12 Oct | **Sorting 3–5× faster** (typed-array keys) | Next.js `'use client'` banner; optional peers safe in webpack / Turbopack; **RTL parts 1 and 2** (pinned columns); header filter `localeText` |
90
+ | **3.7.0** ✅ | shipped 13 Oct | **Theme Builder** demo page + 16 theme keys | `DataGrid.tsx` split (`GridRoot` / `GridViewport`, composite hooks); types split per domain; demo smoke test |
91
+ | **3.7.1** ✅ | shipped 13 Oct | Audit fixes: exports survive a throwing `valueGetter`; **formula escaping of `valueFormatter` text** (security) | Quick filter matches shown text; date paste fixes; list view theme; CSS reset scoped to the grid |
92
+ | **3.8.0** ✅ | shipped 13 Oct | **Edits at 1M rows 10–20× faster** (in-place row store; edit ~35 ms, paste ~45 ms) | Group expand ~2×, collapse ~1 ms |
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
+ | **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
+ | **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** ✅ | shipped 13 Oct | **Backlog cleared**: refactors (R4–R6), CI guards, select-all ~2× faster | AI change chips translatable; demo syntax highlighting |
88
97
  | Freeze | 19 Dec – 5 Jan | Bug fixes only | — |
89
98
 
99
+ ### Now: backlog first (feature hold since 13 Oct; backlog cleared in 3.10)
100
+
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
+
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
+ * **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
+
90
108
  ### Across the quarter
91
109
 
92
110
  * ✅ **Published benchmark** (done 9 Oct): reproducible 10k / 100k / 500k / 1M-row numbers (scroll frame times, sort, filter and grouping time, memory, the reachable row) in [`docs/performance.md`](performance.md#benchmarks); rerun with `npm run bench`.
93
111
  * ✅ **Next.js App Router guide** (done 12 Oct): [`docs/features/nextjs.md`](features/nextjs.md), verified with `next build` / `next start` on Next.js 15.5 and 16.4. The ES bundle now starts with `'use client'`.
94
- * ✅ **RTL check** (done 12 Oct): [`docs/features/rtl.md`](features/rtl.md). Fixed: text alignment, horizontal scrolling and column virtualization, arrow keys, the resize and fill handles, range outlines and auto-scroll, toolbar panel placement. Pinned columns remain a limitation (Q1 2027).
95
- * **Demo site**: source viewer on all examples (`DocsLayout` with Vite `?raw`) and syntax highlighting, done alongside each release's demo page.
112
+ * ✅ **RTL check** (done 12 Oct): [`docs/features/rtl.md`](features/rtl.md). Fixed: text alignment, horizontal scrolling and column virtualization, arrow keys, the resize and fill handles, range outlines and auto-scroll, toolbar panel placement; pinned columns followed in RTL part 2.
113
+ * **Demo site**: ✅ source viewer on every example (`DocsLayout` with Vite `?raw`); syntax highlighting still open.
96
114
 
97
115
  ### Definition of done for each feature
98
116
 
@@ -115,7 +133,7 @@ This document tracks the current status of features in `OpenGridX` and outlines
115
133
  * **Undo must cover rejected updates**: a `processRowUpdate` that throws or a paste that partly fails must leave the history consistent.
116
134
  * **Wrong AI filters** that look plausible: the validator, visible chips and undo are required, not optional.
117
135
 
118
- ## 🔭 Next (Q1 2027)
136
+ ## 🔭 Next (Q1 2027) — on hold until the backlog above is closed
119
137
 
120
138
 
121
139
  * **Row reordering inside tree data and row groups**, touch-friendly (MUI #4821, 405 👍, not in MUI X).
@@ -125,7 +143,7 @@ This document tracks the current status of features in `OpenGridX` and outlines
125
143
  * **Sparklines** and a charts adapter that passes grid state to any chart library.
126
144
  * **AI toolkit, part 3**: smart paste (unstructured text → pending rows) and `apiRef.getAiContext()` for "summarise selection"; an `opengridx-mcp` docs server for coding assistants.
127
145
  * **Tailwind / shadcn theme preset**, sticky header against page scroll.
128
- * ✅ **RTL, part 2** (done 12 Oct, [`rtl.md`](features/rtl.md)): pinned columns and pinned system columns with logical sticky offsets and mirrored shadows, column menu pinning by screen side, mirrored pagination arrows and tree chevrons, a direction change on a mounted grid, column reorder by drag checked. Remaining: hard-coded English menu labels (no `localeText` yet), and the bidi-mirrored header filter operator symbols.
146
+ * ✅ **RTL, part 2** (done 12 Oct, [`rtl.md`](features/rtl.md)): pinned columns and pinned system columns with logical sticky offsets and mirrored shadows, column menu pinning by screen side, mirrored pagination arrows and tree chevrons, a direction change on a mounted grid, column reorder by drag checked. Remaining: the bidi-mirrored header filter operator symbols (the menu labels became `localeText` keys in 3.9).
129
147
 
130
148
  ### Not planned
131
149
 
@@ -136,6 +154,9 @@ This document tracks the current status of features in `OpenGridX` and outlines
136
154
 
137
155
  ## ✅ Implemented Features (Recent)
138
156
 
157
+ ### 3.3 – 3.9 *(2026-10-07 → 2026-10-13)*
158
+ - Spreadsheet editing (range selection, paste, undo / redo, fill handle), the AI toolkit (schema, validator, Ask AI panel, agent tools), header filters, the 1M-row benchmark and height cap, 3–5× faster sorting, RTL, the Theme Builder, a full audit with security and correctness fixes, 10–20× faster edits at 1M rows and `localeText` for every string. See the release table above and [CHANGELOG.md](../CHANGELOG.md).
159
+
139
160
  ### 3.x releases *(2026-09-29 → 2026-10-01)*
140
161
  - **3.0.0** correctness release (rows never modified, typed generics, React 18 support), **3.0.1** fill-height layout fix, **3.1.0** `sortComparator` and column auto-size, **3.2.0** non-blocking PDF export, **3.2.1** Safari boolean editor fix, **3.2.2** linear-time export regexes. Details in [CHANGELOG.md](../CHANGELOG.md); upgrade guide in [migration/v2-to-v3.md](migration/v2-to-v3.md).
141
162
  - Package smoke suite against the packed tarball in React 18 and 19 apps, pull-request CI, community files and a wiki generated from these docs.
@@ -158,5 +179,5 @@ This document tracks the current status of features in `OpenGridX` and outlines
158
179
  - **Core hook tests**: `useGridRowPipeline`, `useGridControlledState`, `useGridKeyboardNavigation` now have full test coverage (25 new tests).
159
180
  - **`CLAUDE.md`**: Root-level AI context file auto-loaded by Claude Code and other AI coding assistants.
160
181
 
161
- * **Demo app (partial)**: 32 interactive examples across 6 categories (Main features, Advanced features, Components, Customization, Tutorials, Resources) with categorized sidebar navigation and per-page Table of Contents. Source code viewer present on 6 of 32 examples. Syntax highlighting and public deployment are in the upcoming Demo Site section above.
182
+ * **Demo app**: 56 pages in 13 categories ordered by what developers look for first (3.9), a per-page table of contents, the source viewer on every example, and public deployment on GitHub Pages with a smoke test of every route. Syntax highlighting is still open.
162
183
  * **Quickstart & Installation Guides**: Comprehensive onboarding documentation for developers.
@@ -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 as unknown as GridColDef}
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, type GridRowPipelineResult } from '../../hooks/core/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, GridRowId, GridColDef } from '../../types';
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 as unknown as GridColDef[],
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 unknown as GridColDef<R>[];
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: state.rows.idRowsLookup as unknown as ReadonlyMap<GridRowId, R>,
226
+ rowsLookup: gridData.rowsLookup,
226
227
  rows: effectiveRows,
227
228
  getRowId: getRowIdOf,
228
229
  columns: activeColumns,
229
230
  processRowUpdate,
230
231
  onProcessRowUpdateError,
231
- replaceRows: replaceRows as unknown as (rows: ReadonlyMap<GridRowId, R>) => void,
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 as unknown as (row: GridRowModel) => GridRowId,
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<GridRowModel>({
294
- effectiveRows: effectiveRows as GridRowModel[],
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 as unknown as GridRowPipelineResult<R>;
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 as unknown as GridColDef[],
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 as unknown as GridColDef[],
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 as unknown as GridColDef[],
441
- getRowId: getRowIdOf as unknown as (row: GridRowModel) => GridRowId,
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: navigationColumns as unknown as GridColDef<R>[],
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 as unknown as GridColDef[],
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 unknown as GridColDef<R>[], viewportWidth: state.dimensions.viewportWidth,
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: Set<GridRowId>;
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: Set<GridRowId>;
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 as unknown as GridColDef[]}
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: Set<GridRowId>;
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 as unknown as GridColDef, colIndex })
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 as unknown as GridColDef<GridRowModel>}
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 as unknown as GridColDef[]}
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: { field: '__expand_col__', width: 48 } as unknown as GridColDef<R>,
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: { field: '__checkbox_col__', width: 48 } as unknown as GridColDef<R>,
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
+ }