@opencorestack/opengridx 3.8.1 → 3.9.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 (53) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/index.d.ts +340 -1
  3. package/dist/opengridx.css +1 -1
  4. package/dist/opengridx.es.js +7529 -7285
  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 +93 -4
  9. package/docs/customization/theming.md +11 -2
  10. package/docs/features/ai-toolkit.md +1 -1
  11. package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +16 -14
  12. package/lib/components/Cell/CellErrorBoundary.tsx +17 -10
  13. package/lib/components/CellSelectionStats/CellSelectionStats.tsx +5 -3
  14. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +3 -1
  15. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +6 -4
  16. package/lib/components/DataGrid/GridAggregationFooter.tsx +3 -1
  17. package/lib/components/DataGrid/GridErrorOverlay.tsx +4 -2
  18. package/lib/components/DataGrid/GridListView.tsx +7 -5
  19. package/lib/components/DataGrid/GridLiveRegion.tsx +5 -3
  20. package/lib/components/DataGrid/GridLoadingOverlay.tsx +3 -1
  21. package/lib/components/DataGrid/GridPinnedRows.tsx +1 -1
  22. package/lib/components/DataGrid/GridRoot.tsx +8 -1
  23. package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +3 -1
  24. package/lib/components/DataGrid/GridViewport.tsx +3 -1
  25. package/lib/components/DataGrid/GridVirtualRows.tsx +1 -1
  26. package/lib/components/DataGrid/gridLocaleContext.ts +13 -0
  27. package/lib/components/FilterPanel/FilterPanel.tsx +18 -17
  28. package/lib/components/Header/ColumnMenu.css +5 -5
  29. package/lib/components/Header/ColumnMenu.tsx +17 -15
  30. package/lib/components/Header/Header.tsx +8 -6
  31. package/lib/components/HeaderFilter/HeaderFilterRow.tsx +3 -1
  32. package/lib/components/ListView/ListViewRow.tsx +3 -1
  33. package/lib/components/Pagination/Pagination.tsx +15 -18
  34. package/lib/components/QuickFilter/QuickFilter.tsx +5 -2
  35. package/lib/components/Row/Row.tsx +5 -3
  36. package/lib/components/Toolbar/GlobalSearch.tsx +6 -3
  37. package/lib/components/Toolbar/GridToolbar.tsx +35 -29
  38. package/lib/components/Toolbar/PivotPanel.tsx +26 -24
  39. package/lib/components/ui/Checkbox.css +5 -5
  40. package/lib/components/ui/DragHandleIcon.tsx +3 -1
  41. package/lib/components/ui/ExpandIcon.css +3 -3
  42. package/lib/components/ui/ExpandIcon.tsx +3 -1
  43. package/lib/hooks/core/useGridRowInteractions.ts +13 -3
  44. package/lib/hooks/core/useGridToolbarStack.ts +2 -2
  45. package/lib/hooks/core/useGridVirtualization.ts +40 -25
  46. package/lib/hooks/core/useShallowStableValue.ts +29 -0
  47. package/lib/hooks/features/useGridAiAssistant.ts +52 -22
  48. package/lib/theme/DataGridThemeProvider.tsx +9 -0
  49. package/lib/theme/types.ts +18 -0
  50. package/lib/types/locale.ts +344 -0
  51. package/lib/utils/gridLocale.ts +241 -0
  52. package/llms.txt +3 -1
  53. package/package.json +1 -1
@@ -243,7 +243,7 @@ Public CSS: `ogx__cell-fill-handle` (24 × 24 px hit area in the corner cell; th
243
243
 
244
244
  | Prop | Type | Default | Description |
245
245
  | :--- | :--- | :--- | :--- |
246
- | `localeText` | `GridLocaleText` | — | Override user-visible strings: the Pagination labels, the empty-state label (`noRowsLabel`, which wins over the `noRowsLabel` prop), the cell-range announcement, the Ask AI button (`aiAssistantButton`) and the header filter row (`headerFilter*`, v3.6). See [`GridLocaleText`](#gridlocaletext). |
246
+ | `localeText` | `GridLocaleText` | — | Override user-visible strings: the Pagination labels, the empty-state label (`noRowsLabel`, which wins over the `noRowsLabel` prop), the cell-range announcement, the Ask AI button (`aiAssistantButton`), the header filter row (`headerFilter*`, v3.6) and, since v3.9, every other built-in string (toolbar, column menu, filter, columns, pivot and AI panels, rows, overlays, live region). See [`GridLocaleText`](#gridlocaletext). |
247
247
 
248
248
  #### Imperative Ref
249
249
 
@@ -663,8 +663,8 @@ Context provider for overriding the grid's visual system.
663
663
  - **`colors`**: `primary`, `primaryDark`, `primaryLight`, `primaryFocus`, `secondary*`, `success`, `warning`, `error`, `info`, `white`, `black`, `gray` (`{ 50 … 900 }`). Toolbar, menu, selection and focus accents derive from `primary*` unless set directly.
664
664
  - **`typography`**: `fontFamily`, `fontFamilyMono`, `fontSizeXs` … `fontSizeXl`.
665
665
  - **`spacing`**: `xs` … `xxl`. **`borders`**: `widthThin/Medium/Thick`, `radiusSm` … `radiusXl`, `color`, `colorHover`. **`shadows`**: `sm` … `xl`.
666
- - **`grid`**: surfaces (`background`, `borderColor`, `headerBackground`, `headerText`, `headerHoverBackground`, `headerSortedBackground`, `rowText`, `rowHoverBackground`, `rowAlternateBackground`, `rowSelectedBackground`, `rowSelectedHoverBackground`, `cellFocusBorder`, `rangeBackground`, `rangeBorder` (v3.3), `pinnedLeftShadow`, `pinnedRightShadow`, `checkboxBg`, `checkboxBorder`), sizing (`rowHeightCompact`, `rowHeightStandard`, `rowHeightComfortable`, `headerHeight` in px, read by the grid's layout; the `rowHeight` / `headerHeight` props win), `cellPaddingX`, `cellPaddingY`, `cellFontSize`, `headerFontSize`; v3.7: fonts (`headerFontFamily`, `headerFontWeight`, `cellFontFamily`, `cellFontWeight`), `headerPaddingX`, outer `borderWidth` and `borderRadius`, dividers (`columnDividerWidth`, `headerColumnDividerWidth`, `rowDividerWidth`, `columnDividerColor`, `rowDividerColor`; `0` hides a divider).
667
- - **`overlays`**: colours (`background`, `text`, `border`, `shadow`, `item*`) and, v3.7, `fontFamily`, `fontWeight`, `fontSize` for menus and panels.
666
+ - **`grid`**: surfaces (`background`, `borderColor`, `headerBackground`, `headerText`, `headerHoverBackground`, `headerSortedBackground`, `rowText`, `rowHoverBackground`, `rowAlternateBackground`, `rowSelectedBackground`, `rowSelectedHoverBackground`, `cellFocusBorder`, `rangeBackground`, `rangeBorder` (v3.3), `pinnedLeftShadow`, `pinnedRightShadow`, `checkboxBg`, `checkboxBorder`; v3.9: `checkboxCheckedBackground`, `checkboxCheckmark`, `expandIconColor`, `expandIconHoverColor`), sizing (`rowHeightCompact`, `rowHeightStandard`, `rowHeightComfortable`, `headerHeight` in px, read by the grid's layout; the `rowHeight` / `headerHeight` props win), `cellPaddingX`, `cellPaddingY`, `cellFontSize`, `headerFontSize`; v3.7: fonts (`headerFontFamily`, `headerFontWeight`, `cellFontFamily`, `cellFontWeight`), `headerPaddingX`, outer `borderWidth` and `borderRadius`, dividers (`columnDividerWidth`, `headerColumnDividerWidth`, `rowDividerWidth`, `columnDividerColor`, `rowDividerColor`; `0` hides a divider).
667
+ - **`overlays`**: colours (`background`, `text`, `border`, `shadow`, `item*`) and, v3.7, `fontFamily`, `fontWeight`, `fontSize` for menus and panels; v3.9: `itemIndicatorColor` (the selection dot), `itemSelectedIcon`, `iconColor`, `sectionLabelText`, `dividerColor`.
668
668
  - **`toolbar`**, **`scrollbar`** (`thumbColor`, `trackColor`, `size`), **`skeleton`** (`baseColor`, `highlightColor`), **`transitions`**.
669
669
 
670
670
  #### Theme types
@@ -906,7 +906,7 @@ interface GridPivotValueField {
906
906
 
907
907
  ### `GridLocaleText`
908
908
 
909
- Override user-visible strings in the Pagination component and the empty state. All fields are optional.
909
+ Override the grid's user-visible strings (visible text, `aria-label`, `title`, placeholder). All fields are optional; each one left out keeps its English default. Strings that contain a value (a header, a row id, a count) are functions.
910
910
 
911
911
  ```typescript
912
912
  interface GridLocaleText {
@@ -931,9 +931,98 @@ interface GridLocaleText {
931
931
  headerFilterTrue?: string; // "Yes"
932
932
  headerFilterFalse?: string; // "No"
933
933
  headerFilterOperators?: Partial<Record<GridFilterOperator, string>>; // operator names
934
+
935
+ // ── v3.9: every other UI string (default English shown) ──
936
+ // Grid, list view, overlays, live region
937
+ gridLabel; listViewLabel; // "Data grid", "Data grid list view" (the aria-label prop wins)
938
+ listViewItemCount(n); listViewPage(p, pc); listViewSelectedCount(n); // "3 items", "page 1 of 2", "2 selected"
939
+ loadingLabel; liveRegionLoading; // "Loading data", "Loading data..."
940
+ liveRegionError(message); liveRegionRowsFound(n); // "Error: …", "4 rows found"
941
+ errorOverlayTitle; errorOverlayRetry; // "Oops! Something went wrong", "Retry"
942
+ aggregationFooterLabel; // "Aggregation totals"
943
+ cellErrorLabel(field); cellErrorTooltip; // "Error in cell: name", "Render error"
944
+ // Header
945
+ headerRowReorderLabel; headerRowReorderTooltip; headerDetailPanelLabel;
946
+ headerSelectAllLabel; headerDeselectAllLabel; // "Select all rows", "Deselect all rows"
947
+ headerSortDescription(direction, priority, total); // "Sorted ascending, sort priority 1 of 2"
948
+ headerColumnMenuButtonLabel(header); // "Open column menu for Name"
949
+ headerColumnResizeLabel(field); headerFilterNoFilterLabel(header);
950
+ // Rows
951
+ rowSelectLabel(id); rowDeselectLabel(id); // "Select row 1", "Deselect row 1"
952
+ rowReorderLabel; rowDetailPanelExpandLabel; rowDetailPanelCollapseLabel; rowExpandLabel; rowCollapseLabel;
953
+ // Column menu
954
+ columnMenuLabel(header); columnMenuSortAscending; columnMenuSortDescending; columnMenuUnsort;
955
+ columnMenuPinColumn; columnMenuPinLeft; columnMenuPinLeftLabel; columnMenuPinRight; columnMenuPinRightLabel;
956
+ columnMenuUnpin; columnMenuUnpinLabel; columnMenuHideColumn; columnMenuHideColumnLabel(header);
957
+ columnMenuManageColumns; columnMenuManageColumnsLabel;
958
+ // Columns panel
959
+ columnsPanelSearchPlaceholder; columnsPanelDragToReorder; columnsPanelShowHideAll; columnsPanelReset; columnsPanelClose;
960
+ // Filter panel
961
+ filterPanelValueLabel(header); filterPanelOperatorLabel(header); filterPanelClearLabel(header); filterPanelClearTooltip;
962
+ filterPanelAny; filterPanelTrue; filterPanelFalse; filterPanelNumberPlaceholder; filterPanelValuePlaceholder;
963
+ filterPanelMatch; filterPanelAnd; filterPanelOr; filterPanelHiddenConditions(n); filterPanelNoColumns;
964
+ filterPanelOperators; // Partial<Record<GridFilterOperator, string>>, default: the operator id
965
+ // Toolbar and search (toolbarSearchPlaceholder also for QuickFilter)
966
+ toolbarLabel; toolbarSearchPlaceholder; toolbarSearchLabel; toolbarSearchClear;
967
+ toolbarColumns; toolbarColumnsLabel; toolbarColumnsPanelLabel;
968
+ toolbarFilters; toolbarFiltersLabel; toolbarFiltersActive; toolbarFiltersClearAll; toolbarFiltersClearAllTooltip;
969
+ toolbarFiltersClose; toolbarFiltersCloseTooltip; toolbarFiltersCloseLabel;
970
+ toolbarPivot; toolbarPivotLabel; toolbarPivotActive;
971
+ toolbarSummaries; toolbarSummariesLabel; toolbarSummariesActive(n); toolbarSummariesPanelLabel;
972
+ toolbarSummariesClearAll; toolbarSummariesClearAllTooltip; toolbarSummariesEmpty;
973
+ toolbarSummariesEmptyHintBefore; toolbarSummariesEmptyHintAfter;
974
+ toolbarAggregationFunctions; // Partial<Record<string, string>>, default: the function id
975
+ // Pivot panel
976
+ pivotPanelLabel; pivotMode; pivotReset; pivotResetTooltip;
977
+ pivotRowFields; pivotColumnFields; pivotValueFields;
978
+ pivotRowFieldsPlaceholder; pivotColumnFieldsPlaceholder; pivotValueFieldsPlaceholder;
979
+ pivotRemoveTooltip(header); pivotRemoveRowLabel(header); pivotRemoveColumnLabel(header); pivotRemoveValueLabel(header, fn);
980
+ pivotAggregationTooltip; pivotAggregationLabel(header);
981
+ pivotAggregationFunctions; // Partial<Record<string, string>>, default: the id in upper case
982
+ pivotAvailableFields; pivotAllFieldsInUse; pivotAddRow; pivotAddColumn; pivotAddValue;
983
+ pivotAddRowTooltip; pivotAddColumnTooltip; pivotAddValueTooltip;
984
+ pivotAddRowLabel(header); pivotAddColumnLabel(header); pivotAddValueLabel(header);
985
+ // AI assistant panel (aiAssistant.placeholder wins over aiAssistantPlaceholder)
986
+ aiAssistantTitle; aiAssistantClose; aiAssistantPromptLabel; aiAssistantPlaceholder;
987
+ aiAssistantStartDictation; aiAssistantStopDictation; aiAssistantAsk; aiAssistantStop; aiAssistantSuggestions;
988
+ aiAssistantAppliedChanges; aiAssistantRemoveChange(label); aiAssistantUndo; aiAssistantIgnored; aiAssistantIgnoredItem(message);
989
+ aiAssistantEarlier; aiAssistantThinking; aiAssistantStopped; aiAssistantUndone; aiAssistantChangeRemoved;
990
+ aiAssistantApplied(applied, ignored); aiAssistantNotValidated; aiAssistantFailed(message); aiAssistantUnknownError;
991
+ // Pagination buttons
992
+ paginationLabel; paginationFirstPageLabel; paginationFirstPageTooltip; paginationPreviousPageLabel;
993
+ paginationPreviousPageTooltip; paginationNextPageLabel; paginationNextPageTooltip;
994
+ paginationLastPageLabel; paginationLastPageTooltip;
995
+ // Cell selection stats bar
996
+ cellSelectionStatsCount; cellSelectionStatsSum; cellSelectionStatsAverage;
934
997
  }
935
998
  ```
936
999
 
1000
+ Each key's English default and parameter types are documented on `GridLocaleText` in `lib/types/locale.ts` (shown by editors on hover).
1001
+
1002
+ ### Locale text for every UI string (v3.9)
1003
+
1004
+ Since v3.9 every built-in string of the grid goes through `localeText`: the toolbar and its panels, the column menu, the filter, columns, pivot and AI assistant panels, the row and select-all checkboxes, the pagination buttons, the loading and error overlays, the aggregation footer and the live-region announcements. Pass only the keys you translate; the rest stay English.
1005
+
1006
+ ```tsx
1007
+ <DataGrid
1008
+ rows={rows}
1009
+ columns={columns}
1010
+ slots={{ toolbar: GridToolbar }}
1011
+ localeText={{
1012
+ toolbarSearchPlaceholder: 'Rechercher…',
1013
+ columnMenuSortAscending: 'Tri croissant',
1014
+ columnMenuHideColumnLabel: (header) => `Masquer la colonne ${header}`,
1015
+ filterPanelOperators: { contains: 'contient' },
1016
+ rowSelectLabel: (id) => `Sélectionner la ligne ${id}`,
1017
+ }}
1018
+ />
1019
+ ```
1020
+
1021
+ - The strings reach portalled menus and panels through React context, so a custom toolbar slot that renders the built-in `GridToolbar` is translated too.
1022
+ - Components used outside a grid (`ColumnVisibilityPanel`, `GridToolbar`, `FilterPanel`, `Pagination`, `QuickFilter`) show English. Their own props (`placeholder`, `aiAssistantLabel`, `Pagination`'s `localeText`) still win.
1023
+ - Not covered: the grouping column's default header (`'Group'`, set `groupingColDef.headerName`), pivot's `'Grand Total'` row label, export file contents, the AI change chips' labels, and developer console warnings.
1024
+ - An inline object of strings is fine: while it holds the same entries as on the previous render (records such as `filterPanelOperators` compared one level deep), the grid keeps the resolved strings and the rows are not re-rendered. Functions are compared by identity, so define function entries (`rowSelectLabel: id => …`) outside the render, or keep the whole object in a module constant or `useMemo`.
1025
+
937
1026
  ### `GridRowMeta`
938
1027
 
939
1028
  Available via `params.rowMeta` in `renderCell`. Undefined for non-hierarchy rows.
@@ -262,8 +262,12 @@ For visible gaps between cells instead of lines, set the divider colour to the g
262
262
  | `--ogx-fill-handle-color` | `var(--ogx-range-border)` | `var(--ogx-range-border)` | The fill handle square (`ogx__cell-fill-handle`, v3.5). Unset in the stylesheet, where it falls back to `--ogx-range-border`; every theme of `DataGridThemeProvider` sets it to `var(--ogx-range-border)`. Theme key `grid.fillHandleColor` |
263
263
  | `--ogx-grid-pinned-left-shadow` | `4px 0 24px...` | (darker) | Shadow on left-pinned columns |
264
264
  | `--ogx-grid-pinned-right-shadow` | `-4px 0 24px...` | (darker) | Shadow on right-pinned columns |
265
- | `--ogx-checkbox-bg` | — | `#1e293b` | Checkbox background (dark only) |
266
- | `--ogx-checkbox-border` | — | `#64748b` | Checkbox border (dark only) |
265
+ | `--ogx-checkbox-bg` | — | `#1e293b` | Checkbox background (dark only). Theme key `grid.checkboxBg` |
266
+ | `--ogx-checkbox-border` | — | `#64748b` | Checkbox border (dark only). Theme key `grid.checkboxBorder` |
267
+ | `--ogx-checkbox-checked-bg` | primary | primary | Fill and border of a checked or indeterminate checkbox (v3.9). Theme key `grid.checkboxCheckedBackground` |
268
+ | `--ogx-checkbox-checkmark` | `white` | `white` | Check mark and indeterminate dash (v3.9). Theme key `grid.checkboxCheckmark` |
269
+ | `--ogx-expand-icon-color` | header text | header text | Expand / collapse chevron of group and tree rows (v3.9). Theme key `grid.expandIconColor` |
270
+ | `--ogx-expand-icon-hover-color` | row text | row text | The chevron on hover (v3.9). Theme key `grid.expandIconHoverColor` |
267
271
 
268
272
  ### Toolbar, Overlay and Scrollbar Colors
269
273
 
@@ -282,6 +286,11 @@ Read with a fallback, so a grid without a provider looks the same as before. Ins
282
286
  | `--ogx-overlay-*` | `overlays.*` | white surface, gray border | Menus, panels, tooltips |
283
287
  | `--ogx-overlay-item-selected-bg` / `-text` | `overlays.itemSelected*` | 8% primary / primary-dark | Selected menu item |
284
288
  | `--ogx-overlay-item-danger-bg` / `-text` | `overlays.itemDanger*` | red tints | Remove-condition button in the filter panel |
289
+ | `--ogx-overlay-item-indicator` | `overlays.itemIndicatorColor` (v3.9) | the selected item text | Dot before the selected option of a menu group (sort, pin) |
290
+ | `--ogx-overlay-item-selected-icon` | `overlays.itemSelectedIcon` (v3.9) | primary | Icon of the selected menu option |
291
+ | `--ogx-overlay-icon` | `overlays.iconColor` (v3.9) | gray-400 | Menu item icons |
292
+ | `--ogx-overlay-section-label-text` | `overlays.sectionLabelText` (v3.9) | gray-400 | Menu section labels ("Pin column") |
293
+ | `--ogx-overlay-divider` | `overlays.dividerColor` (v3.9) | overlay border | Lines between menu sections |
285
294
  | `--ogx-scrollbar-thumb` / `-track` / `-size` | `scrollbar.thumbColor` / `trackColor` / `size` | gray-300 / transparent / 8px | Grid viewport scrollbars |
286
295
  | `--ogx-skeleton-base` / `-highlight` | `skeleton.baseColor` / `highlightColor` | light grays | Loading skeleton rows |
287
296
 
@@ -1,6 +1,6 @@
1
1
  # AI Toolkit: Schema, Validator, Assistant & Agent Tools
2
2
 
3
- *Since v3.4.0 (schema and validator); v3.5.0 (assistant panel, prompt handler, agent tools)* · Demos: [AI: Grid Schema & Validator](https://opencorestack.github.io/OpenGridX/ai-schema), [AI Assistant](https://opencorestack.github.io/OpenGridX/ai-assistant)
3
+ *Since v3.4.0 (schema and validator); v3.5.0 (assistant panel, prompt handler, agent tools)* · Demos: [Grid Schema & Validator](https://opencorestack.github.io/OpenGridX/ai-schema), [AI Assistant](https://opencorestack.github.io/OpenGridX/ai-assistant)
4
4
 
5
5
  Let users drive the grid in plain language ("paid orders over $10k in the North, biggest first") with the model your app already uses. The `@opencorestack/opengridx/ai` entry point gives you the pieces the grid can own:
6
6
 
@@ -3,6 +3,7 @@ import type { GridAiAssistantPanelProps } from '../../types';
3
3
  import { Button } from '../ui/Button';
4
4
  import { Input } from '../ui/Input';
5
5
  import { useSpeechInput } from './useSpeechInput';
6
+ import { useGridLocale } from '../DataGrid/gridLocaleContext';
6
7
 
7
8
  /** The sparkle of the Ask AI button and the panel title. */
8
9
  export function AiSparkleIcon() {
@@ -33,6 +34,7 @@ export function GridAiAssistantPanel(props: GridAiAssistantPanelProps) {
33
34
  status, statusText, message, errors, chips, history, suggestions, placeholder, voiceAvailable, canUndo,
34
35
  submit, stop, undo, removeChip, close,
35
36
  } = props;
37
+ const locale = useGridLocale();
36
38
  const [text, setText] = useState('');
37
39
  const inputRef = useRef<HTMLInputElement>(null);
38
40
  const speech = useSpeechInput(setText);
@@ -59,10 +61,10 @@ export function GridAiAssistantPanel(props: GridAiAssistantPanelProps) {
59
61
  };
60
62
 
61
63
  return (
62
- <div className="ogx-ai-panel" role="dialog" aria-label="Ask AI">
64
+ <div className="ogx-ai-panel" role="dialog" aria-label={locale.aiAssistantTitle}>
63
65
  <div className="ogx-ai-panel__header">
64
- <span className="ogx-ai-panel__title"><AiSparkleIcon />Ask AI</span>
65
- <Button size="small" className="ogx-ai-panel__close" aria-label="Close" onClick={close}>×</Button>
66
+ <span className="ogx-ai-panel__title"><AiSparkleIcon />{locale.aiAssistantTitle}</span>
67
+ <Button size="small" className="ogx-ai-panel__close" aria-label={locale.aiAssistantClose} onClick={close}>×</Button>
66
68
  </div>
67
69
 
68
70
  <form className="ogx-ai-panel__form" onSubmit={handleSubmit}>
@@ -71,7 +73,7 @@ export function GridAiAssistantPanel(props: GridAiAssistantPanelProps) {
71
73
  className="ogx-ai-panel__input"
72
74
  value={text}
73
75
  placeholder={placeholder}
74
- aria-label="Prompt"
76
+ aria-label={locale.aiAssistantPromptLabel}
75
77
  autoComplete="off"
76
78
  onChange={(event) => setText(event.target.value)}
77
79
  />
@@ -80,7 +82,7 @@ export function GridAiAssistantPanel(props: GridAiAssistantPanelProps) {
80
82
  size="small"
81
83
  variant="outlined"
82
84
  className={`ogx-ai-panel__mic${speech.listening ? ' ogx-ai-panel__mic--on' : ''}`}
83
- aria-label={speech.listening ? 'Stop dictation' : 'Dictate a prompt'}
85
+ aria-label={speech.listening ? locale.aiAssistantStopDictation : locale.aiAssistantStartDictation}
84
86
  aria-pressed={speech.listening}
85
87
  onClick={speech.toggle}
86
88
  >
@@ -89,12 +91,12 @@ export function GridAiAssistantPanel(props: GridAiAssistantPanelProps) {
89
91
  )}
90
92
  {/* Separate keys: one reused <button> would turn into the submit button during the Stop click and submit again. */}
91
93
  {running
92
- ? <Button key="stop" size="small" variant="outlined" onClick={thenFocusInput(stop)}>Stop</Button>
93
- : <Button key="ask" size="small" variant="contained" color="primary" type="submit" disabled={text.trim() === ''}>Ask</Button>}
94
+ ? <Button key="stop" size="small" variant="outlined" onClick={thenFocusInput(stop)}>{locale.aiAssistantStop}</Button>
95
+ : <Button key="ask" size="small" variant="contained" color="primary" type="submit" disabled={text.trim() === ''}>{locale.aiAssistantAsk}</Button>}
94
96
  </form>
95
97
 
96
98
  {suggestions.length > 0 && (
97
- <div className="ogx-ai-panel__suggestions" aria-label="Suggestions" role="group">
99
+ <div className="ogx-ai-panel__suggestions" aria-label={locale.aiAssistantSuggestions} role="group">
98
100
  {suggestions.map((suggestion) => (
99
101
  <button key={suggestion} type="button" className="ogx-ai-panel__suggestion" disabled={running} onClick={() => send(suggestion)}>
100
102
  {suggestion}
@@ -108,29 +110,29 @@ export function GridAiAssistantPanel(props: GridAiAssistantPanelProps) {
108
110
 
109
111
  {(chips.length > 0 || canUndo) && (
110
112
  <div className="ogx-ai-panel__changes">
111
- <ul className="ogx-ai-panel__chips" aria-label="Applied changes">
113
+ <ul className="ogx-ai-panel__chips" aria-label={locale.aiAssistantAppliedChanges}>
112
114
  {chips.map((chip) => (
113
115
  <li key={chip.id} className="ogx-ai-chip" data-part={chip.part}>
114
116
  <span className="ogx-ai-chip__label">{chip.label}</span>
115
- <button type="button" className="ogx-ai-chip__remove" aria-label={`Remove ${chip.label}`} onClick={thenFocusInput(() => removeChip(chip.id))}>×</button>
117
+ <button type="button" className="ogx-ai-chip__remove" aria-label={locale.aiAssistantRemoveChange(chip.label)} onClick={thenFocusInput(() => removeChip(chip.id))}>×</button>
116
118
  </li>
117
119
  ))}
118
120
  </ul>
119
- {canUndo && <Button size="small" variant="outlined" className="ogx-ai-panel__undo" onClick={thenFocusInput(undo)}>Undo</Button>}
121
+ {canUndo && <Button size="small" variant="outlined" className="ogx-ai-panel__undo" onClick={thenFocusInput(undo)}>{locale.aiAssistantUndo}</Button>}
120
122
  </div>
121
123
  )}
122
124
 
123
125
  {errors.length > 0 && (
124
- <ul className="ogx-ai-panel__errors" aria-label="Ignored">
126
+ <ul className="ogx-ai-panel__errors" aria-label={locale.aiAssistantIgnored}>
125
127
  {errors.map((error, i) => (
126
- <li key={`${error.path}:${i}`}>{error.path ? `Ignored: ${error.message}` : error.message}</li>
128
+ <li key={`${error.path}:${i}`}>{error.path ? locale.aiAssistantIgnoredItem(error.message) : error.message}</li>
127
129
  ))}
128
130
  </ul>
129
131
  )}
130
132
 
131
133
  {history.length > 0 && (
132
134
  <div className="ogx-ai-panel__history">
133
- <div className="ogx-ai-panel__history-title">Earlier</div>
135
+ <div className="ogx-ai-panel__history-title">{locale.aiAssistantEarlier}</div>
134
136
  <ul>
135
137
  {history.map((entry, i) => (
136
138
  <li key={`${i}:${entry.prompt}`}>
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { useGridLocale } from '../DataGrid/gridLocaleContext';
2
3
 
3
4
  // Must live at module scope (not inline) so React sees a stable component type.
4
5
  // Calling renderFn() here puts the throw one level BELOW CellErrorBoundary,
@@ -10,6 +11,21 @@ function CellRenderTarget({ renderFn }: { renderFn: () => React.ReactNode }) {
10
11
  return <>{renderFn()}</>;
11
12
  }
12
13
 
14
+ /** The cell content after `renderCell` threw: a warning sign labelled with the field. */
15
+ function CellErrorMarker({ field, message }: { field: string; message: string | undefined }) {
16
+ const locale = useGridLocale();
17
+ return (
18
+ <div
19
+ className="ogx__cell-error"
20
+ role="img"
21
+ aria-label={locale.cellErrorLabel(field)}
22
+ title={message ?? locale.cellErrorTooltip}
23
+ >
24
+ ⚠
25
+ </div>
26
+ );
27
+ }
28
+
13
29
  interface CellErrorBoundaryProps {
14
30
  renderFn: () => React.ReactNode;
15
31
  field: string;
@@ -59,16 +75,7 @@ export class CellErrorBoundary extends React.Component<CellErrorBoundaryProps, C
59
75
 
60
76
  override render() {
61
77
  if (this.state.hasError) {
62
- return (
63
- <div
64
- className="ogx__cell-error"
65
- role="img"
66
- aria-label={`Error in cell: ${this.props.field}`}
67
- title={this.state.error?.message ?? 'Render error'}
68
- >
69
- ⚠
70
- </div>
71
- );
78
+ return <CellErrorMarker field={this.props.field} message={this.state.error?.message} />;
72
79
  }
73
80
  return <CellRenderTarget renderFn={this.props.renderFn} />;
74
81
  }
@@ -1,5 +1,6 @@
1
1
  import { formatAggregationValue } from '../../utils/aggregation';
2
2
  import type { GridCellSelectionStatsSlotProps } from '../../types';
3
+ import { useGridLocale } from '../DataGrid/gridLocaleContext';
3
4
 
4
5
  export type CellSelectionStatsProps = GridCellSelectionStatsSlotProps & Record<string, unknown>;
5
6
 
@@ -9,21 +10,22 @@ export type CellSelectionStatsProps = GridCellSelectionStatsSlotProps & Record<s
9
10
  * @since v3.3
10
11
  */
11
12
  export function CellSelectionStats({ count, sum, average }: CellSelectionStatsProps) {
13
+ const locale = useGridLocale();
12
14
  return (
13
15
  <div className="ogx__cell-selection-stats">
14
16
  <span className="ogx__cell-selection-stat">
15
- <span className="ogx__cell-selection-stat-label">Count</span>
17
+ <span className="ogx__cell-selection-stat-label">{locale.cellSelectionStatsCount}</span>
16
18
  <span className="ogx__cell-selection-stat-value" data-stat="count">{count.toLocaleString()}</span>
17
19
  </span>
18
20
  {sum !== null && (
19
21
  <span className="ogx__cell-selection-stat">
20
- <span className="ogx__cell-selection-stat-label">Sum</span>
22
+ <span className="ogx__cell-selection-stat-label">{locale.cellSelectionStatsSum}</span>
21
23
  <span className="ogx__cell-selection-stat-value" data-stat="sum">{formatAggregationValue(sum, 'sum')}</span>
22
24
  </span>
23
25
  )}
24
26
  {average !== null && (
25
27
  <span className="ogx__cell-selection-stat">
26
- <span className="ogx__cell-selection-stat-label">Average</span>
28
+ <span className="ogx__cell-selection-stat-label">{locale.cellSelectionStatsAverage}</span>
27
29
  <span className="ogx__cell-selection-stat-value" data-stat="average">{formatAggregationValue(average, 'avg')}</span>
28
30
  </span>
29
31
  )}
@@ -1,6 +1,7 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { clampResizeWidth } from './clampResizeWidth';
3
3
  import { isRtl, logicalArrowKey } from '../../utils/direction';
4
+ import { useGridLocale } from '../DataGrid/gridLocaleContext';
4
5
 
5
6
 
6
7
  export interface ColumnResizeHandleProps {
@@ -49,6 +50,7 @@ function suppressNextClick(): void {
49
50
 
50
51
  export function ColumnResizeHandle(props: ColumnResizeHandleProps) {
51
52
  const { field, currentWidth, onResize, onAutosize, minWidth, maxWidth, edge = 'end' } = props;
53
+ const locale = useGridLocale();
52
54
  const [isDragging, setIsDragging] = useState(false);
53
55
  const activeRef = useRef<ActiveResize | null>(null);
54
56
 
@@ -164,7 +166,7 @@ export function ColumnResizeHandle(props: ColumnResizeHandleProps) {
164
166
  onKeyDown={handleKeyDown}
165
167
  role="separator"
166
168
  aria-orientation="vertical"
167
- aria-label={`Resize ${field} column`}
169
+ aria-label={locale.headerColumnResizeLabel(field)}
168
170
  aria-valuenow={Math.round(currentWidth)}
169
171
  aria-valuemin={Math.round(Math.min(minWidth ?? 50, currentWidth))}
170
172
  aria-valuemax={maxWidth}
@@ -1,6 +1,7 @@
1
1
  import { useState, useMemo, useRef, useId } from 'react';
2
2
  import { Checkbox } from '../ui/Checkbox';
3
3
  import { Input } from '../ui/Input';
4
+ import { useGridLocale } from '../DataGrid/gridLocaleContext';
4
5
  import type { GridColDef, GridRowModel, GridValidRowModel } from '../../types';
5
6
 
6
7
 
@@ -48,6 +49,7 @@ export function ColumnVisibilityPanel<R extends GridValidRowModel = GridRowModel
48
49
  props: ColumnVisibilityPanelProps<R>
49
50
  ) {
50
51
  const { columns, visibleColumns, onVisibilityChange, onShowAll, onHideAll, onColumnReorder, onColumnOrderReset, showNonHideableColumns } = props;
52
+ const locale = useGridLocale();
51
53
  const [searchQuery, setSearchQuery] = useState('');
52
54
  // Each checkbox is named by a <label htmlFor> holding the column name. (Wrapping Checkbox,
53
55
  // which renders its own <label>, in another <label> nested labels and left the name to a
@@ -119,7 +121,7 @@ export function ColumnVisibilityPanel<R extends GridValidRowModel = GridRowModel
119
121
  <Input
120
122
  autoComplete="off"
121
123
  inputClassName="ogx-column-visibility-panel__search-input"
122
- placeholder="Search"
124
+ placeholder={locale.columnsPanelSearchPlaceholder}
123
125
  value={searchQuery}
124
126
  onChange={(e) => setSearchQuery(e.target.value)}
125
127
  />
@@ -153,7 +155,7 @@ export function ColumnVisibilityPanel<R extends GridValidRowModel = GridRowModel
153
155
  className="ogx-column-visibility-panel__drag-handle-wrapper"
154
156
  draggable
155
157
  onDragStart={handleDragStart(col.field)}
156
- title="Drag to reorder"
158
+ title={locale.columnsPanelDragToReorder}
157
159
  >
158
160
  <DragHandleIcon />
159
161
  </span>
@@ -185,7 +187,7 @@ export function ColumnVisibilityPanel<R extends GridValidRowModel = GridRowModel
185
187
  onChange={handleToggleAll}
186
188
  />
187
189
  <label htmlFor={`${idBase}-all`} className="ogx-column-visibility-panel__label">
188
- Show/Hide All
190
+ {locale.columnsPanelShowHideAll}
189
191
  </label>
190
192
  </div>
191
193
  <button
@@ -193,7 +195,7 @@ export function ColumnVisibilityPanel<R extends GridValidRowModel = GridRowModel
193
195
  className="ogx-column-visibility-panel__reset-btn"
194
196
  onClick={() => { onShowAll(); onColumnOrderReset?.(); }}
195
197
  >
196
- Reset
198
+ {locale.columnsPanelReset}
197
199
  </button>
198
200
  </div>
199
201
  </div>
@@ -3,6 +3,7 @@ import { formatAggregateForColumn, isAggregationAllowed } from '../../utils/aggr
3
3
  import type { GridColDef, GridAggregationModel, GridAggregationResult, GridColumnPinning } from '../../types';
4
4
  import { calculatePinnedPositions, getPinnedEdgeFields, isColumnPinned } from '../../utils/pinning';
5
5
  import { getRenderedColumnWidth } from '../../utils/columnWidth';
6
+ import { useGridLocale } from './gridLocaleContext';
6
7
 
7
8
  const SYSTEM_COLUMN_WIDTH = 48;
8
9
 
@@ -41,6 +42,7 @@ export function GridAggregationFooter({
41
42
  pinnedColumns,
42
43
  loading = false,
43
44
  }: GridAggregationFooterProps) {
45
+ const locale = useGridLocale();
44
46
  // Same offsets as Row, so pinned totals sit exactly under their pinned body cells.
45
47
  const pinnedOffsets = useMemo(
46
48
  () => calculatePinnedPositions(
@@ -77,7 +79,7 @@ export function GridAggregationFooter({
77
79
  <div
78
80
  className={`ogx__aggregation-footer${loading ? ' ogx__aggregation-footer--loading' : ''}`}
79
81
  role="row"
80
- aria-label="Aggregation totals"
82
+ aria-label={locale.aggregationFooterLabel}
81
83
  aria-live="polite"
82
84
  aria-busy={loading || undefined}
83
85
  style={{ minHeight: `${rowHeight}px` }}
@@ -1,4 +1,5 @@
1
1
  import { getDataSourceErrorMessage } from '../../utils/dataSource';
2
+ import { useGridLocale } from './gridLocaleContext';
2
3
 
3
4
  interface GridErrorOverlayProps {
4
5
  error: unknown;
@@ -7,6 +8,7 @@ interface GridErrorOverlayProps {
7
8
  }
8
9
 
9
10
  export function GridErrorOverlay({ error, onRetry }: GridErrorOverlayProps) {
11
+ const locale = useGridLocale();
10
12
  if (!error) return null;
11
13
  const message = getDataSourceErrorMessage(error);
12
14
  return (
@@ -17,7 +19,7 @@ export function GridErrorOverlay({ error, onRetry }: GridErrorOverlayProps) {
17
19
  <line x1="12" y1="16" x2="12.01" y2="16" />
18
20
  </svg>
19
21
  <div className="ogx-error-overlay__title">
20
- Oops! Something went wrong
22
+ {locale.errorOverlayTitle}
21
23
  </div>
22
24
  <div className="ogx-error-overlay__message">
23
25
  {message}
@@ -28,7 +30,7 @@ export function GridErrorOverlay({ error, onRetry }: GridErrorOverlayProps) {
28
30
  onClick={onRetry}
29
31
  className="ogx-button ogx-button--primary"
30
32
  >
31
- Retry
33
+ {locale.errorOverlayRetry}
32
34
  </button>
33
35
  )}
34
36
  </div>
@@ -4,6 +4,7 @@ import { Pagination } from '../Pagination/Pagination';
4
4
  import { ListViewRow } from '../ListView/ListViewRow';
5
5
  import { getPageCount, normalizePageSize } from '../../utils/pagination';
6
6
  import { useRowsScrollEndLatch } from '../../hooks/core/useRowsScrollEndLatch';
7
+ import { useGridLocale } from './gridLocaleContext';
7
8
  import type {
8
9
  GridColDef,
9
10
  GridRowModel,
@@ -110,6 +111,7 @@ export function GridListView<R extends GridRowModel>({
110
111
  selectable = false,
111
112
  onToggleExpansion,
112
113
  }: GridListViewProps<R>) {
114
+ const locale = useGridLocale();
113
115
  const PaginationComponent = paginationSlot || Pagination;
114
116
 
115
117
  // Roving tab stop: the list is one Tab stop, on the last focused row (the first row until then,
@@ -192,15 +194,15 @@ export function GridListView<R extends GridRowModel>({
192
194
  <div
193
195
  className="ogx-list-view"
194
196
  role="grid"
195
- aria-label={ariaLabel || 'Data grid list view'}
197
+ aria-label={ariaLabel || locale.listViewLabel}
196
198
  aria-rowcount={itemCount}
197
199
  aria-multiselectable={multiselectable}
198
200
  >
199
201
  <div className="ogx-list-view__toolbar">
200
202
  <span>
201
- {itemCount} {itemCount === 1 ? 'item' : 'items'}
202
- {pagination ? ` · page ${currentPage + 1} of ${pageCount}` : ''}
203
- {selectedRowIds.size > 0 ? ` · ${selectedRowIds.size} selected` : ''}
203
+ {locale.listViewItemCount(itemCount)}
204
+ {pagination ? ` · ${locale.listViewPage(currentPage + 1, pageCount)}` : ''}
205
+ {selectedRowIds.size > 0 ? ` · ${locale.listViewSelectedCount(selectedRowIds.size)}` : ''}
204
206
  </span>
205
207
  </div>
206
208
 
@@ -209,7 +211,7 @@ export function GridListView<R extends GridRowModel>({
209
211
  {allRenderableRows.length === 0 && loading ? (
210
212
  // The grid's live region announces loading; the list shows no "No Data" meanwhile.
211
213
  <div className="ogx-list-view__loading">
212
- {loadingOverlay ?? <div className="ogx__loading-bar" role="progressbar" aria-label="Loading data" />}
214
+ {loadingOverlay ?? <div className="ogx__loading-bar" role="progressbar" aria-label={locale.loadingLabel} />}
213
215
  </div>
214
216
  ) : allRenderableRows.length === 0 && noRowsOverlay ? (
215
217
  <div className="ogx-list-view__empty">{noRowsOverlay}</div>
@@ -1,5 +1,6 @@
1
1
  import { getDataSourceErrorMessage } from '../../utils/dataSource';
2
2
  import type { GridFilterModel } from '../../types';
3
+ import { useGridLocale } from './gridLocaleContext';
3
4
 
4
5
  interface GridLiveRegionProps {
5
6
  loading: boolean;
@@ -22,15 +23,16 @@ interface GridLiveRegionProps {
22
23
  * the size of the selected cell range; and the AI assistant's status.
23
24
  */
24
25
  export function GridLiveRegion({ loading, error, noRowsLabel, filteredRowCount, dataRowCount, filterModel, cellSelectionAnnouncement, aiAssistantAnnouncement }: GridLiveRegionProps) {
26
+ const locale = useGridLocale();
25
27
  return (
26
28
  <div className="ogx-aria-live-status" role="status" aria-live="polite">
27
- {loading ? 'Loading data...' : ''}
28
- {error ? `Error: ${getDataSourceErrorMessage(error)}` : ''}
29
+ {loading ? locale.liveRegionLoading : ''}
30
+ {error ? locale.liveRegionError(getDataSourceErrorMessage(error)) : ''}
29
31
  {!loading && !error && (
30
32
  filteredRowCount === 0
31
33
  ? noRowsLabel
32
34
  : (filterModel && ((filterModel.quickFilterValues?.length || 0) > 0 || (filterModel.items?.length || 0) > 0))
33
- ? `${dataRowCount} ${dataRowCount === 1 ? 'row' : 'rows'} found`
35
+ ? locale.liveRegionRowsFound(dataRowCount)
34
36
  : ''
35
37
  )}
36
38
  {cellSelectionAnnouncement ? <span className="ogx-aria-live-status__cells"> {cellSelectionAnnouncement}</span> : null}
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { useGridLocale } from './gridLocaleContext';
2
3
 
3
4
  interface GridLoadingOverlayProps {
4
5
  /** `slots.loadingOverlay`, rendered centred over the rows on a dimmed backdrop. */
@@ -12,6 +13,7 @@ interface GridLoadingOverlayProps {
12
13
  * or the loading overlay in place of the rows, so this is not rendered.
13
14
  */
14
15
  export function GridLoadingOverlay({ overlay }: GridLoadingOverlayProps) {
16
+ const locale = useGridLocale();
15
17
  if (overlay) {
16
18
  return (
17
19
  <div className="ogx__loading-overlay ogx__loading-overlay--over-rows ogx__loading-overlay--custom">
@@ -21,7 +23,7 @@ export function GridLoadingOverlay({ overlay }: GridLoadingOverlayProps) {
21
23
  }
22
24
  return (
23
25
  <div className="ogx__loading-overlay ogx__loading-overlay--over-rows">
24
- <div className="ogx__loading-bar" role="progressbar" aria-label="Loading data" />
26
+ <div className="ogx__loading-bar" role="progressbar" aria-label={locale.loadingLabel} />
25
27
  </div>
26
28
  );
27
29
  }
@@ -154,7 +154,7 @@ export function GridPinnedRows<R extends GridRowModel>({
154
154
  rowSpanningCaches={rowSpanningCaches}
155
155
  rowHeight={rowHeight}
156
156
  rowMeta={rowMetaMap.get(id)}
157
- editingCell={editingHandlers?.editingCell}
157
+ editingCell={editingHandlers?.editingCell?.id === id ? editingHandlers.editingCell : null}
158
158
  onEditStart={editingHandlers?.startCellEdit}
159
159
  onEditStop={editingHandlers?.stopCellEdit}
160
160
  onEditCellValueChange={editingHandlers?.setEditCellValue}
@@ -1,4 +1,7 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
+ import { GridLocaleContext } from './gridLocaleContext';
3
+ import { resolveGridLocale } from '../../utils/gridLocale';
4
+ import { useShallowStableValue } from '../../hooks/core/useShallowStableValue';
2
5
  import { GridToolbarHostContext } from '../../hooks/core/gridToolbarHostContext';
3
6
  import { GridToolbarSlot } from './GridToolbarSlot';
4
7
  import { GridAiAssistantArea } from '../AiAssistant/GridAiAssistantArea';
@@ -95,8 +98,11 @@ export function GridRoot<R extends GridRowModel>(props: GridRootProps<R>) {
95
98
  // body shows skeletons or the loading overlay instead, and infinite scroll appends skeleton rows.
96
99
  const showLoadingOverRows = effectiveLoading && allRenderableRows.length > 0 && paginationMode !== 'infinite';
97
100
  const ToolbarSlot = slots?.toolbar;
101
+ const stableLocaleText = useShallowStableValue(localeText);
102
+ const locale = useMemo(() => resolveGridLocale(stableLocaleText), [stableLocaleText]);
98
103
 
99
104
  return (
105
+ <GridLocaleContext.Provider value={locale}>
100
106
  <div
101
107
  ref={containerRef}
102
108
  className={['ogx', className, autoHeight && 'ogx--auto-height', !autoHeight && height === undefined && style?.height === undefined && 'ogx--fill', hasRowSpanning && 'ogx--row-spanning', listView && 'ogx--list-view'].filter(Boolean).join(' ')}
@@ -235,5 +241,6 @@ export function GridRoot<R extends GridRowModel>(props: GridRootProps<R>) {
235
241
 
236
242
  <GridErrorOverlay error={dataSourceError} onRetry={onRetry} />
237
243
  </div>
244
+ </GridLocaleContext.Provider>
238
245
  );
239
246
  }