@opencorestack/opengridx 3.8.1 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +11 -7
- package/dist/index.d.ts +340 -1
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +7529 -7285
- 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 +93 -4
- package/docs/architecture/datagrid-orchestration.md +4 -2
- package/docs/architecture/use-grid-virtualization.md +2 -0
- package/docs/components/pagination.md +1 -1
- package/docs/customization/theming.md +11 -2
- package/docs/features/ai-toolkit.md +1 -1
- package/docs/features/rtl.md +1 -1
- package/docs/performance.md +19 -1
- package/docs/roadmap.md +26 -5
- package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +16 -14
- package/lib/components/Cell/CellErrorBoundary.tsx +17 -10
- package/lib/components/CellSelectionStats/CellSelectionStats.tsx +5 -3
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +3 -1
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +6 -4
- package/lib/components/DataGrid/GridAggregationFooter.tsx +3 -1
- package/lib/components/DataGrid/GridErrorOverlay.tsx +4 -2
- package/lib/components/DataGrid/GridListView.tsx +7 -5
- package/lib/components/DataGrid/GridLiveRegion.tsx +5 -3
- package/lib/components/DataGrid/GridLoadingOverlay.tsx +3 -1
- package/lib/components/DataGrid/GridPinnedRows.tsx +1 -1
- package/lib/components/DataGrid/GridRoot.tsx +8 -1
- package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +3 -1
- package/lib/components/DataGrid/GridViewport.tsx +3 -1
- package/lib/components/DataGrid/GridVirtualRows.tsx +1 -1
- package/lib/components/DataGrid/gridLocaleContext.ts +13 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +18 -17
- package/lib/components/Header/ColumnMenu.css +5 -5
- package/lib/components/Header/ColumnMenu.tsx +17 -15
- package/lib/components/Header/Header.tsx +8 -6
- package/lib/components/HeaderFilter/HeaderFilterRow.tsx +3 -1
- package/lib/components/ListView/ListViewRow.tsx +3 -1
- package/lib/components/Pagination/Pagination.tsx +15 -18
- package/lib/components/QuickFilter/QuickFilter.tsx +5 -2
- package/lib/components/Row/Row.tsx +5 -3
- package/lib/components/Toolbar/GlobalSearch.tsx +6 -3
- package/lib/components/Toolbar/GridToolbar.tsx +35 -29
- package/lib/components/Toolbar/PivotPanel.tsx +26 -24
- package/lib/components/ui/Checkbox.css +5 -5
- package/lib/components/ui/DragHandleIcon.tsx +3 -1
- package/lib/components/ui/ExpandIcon.css +3 -3
- package/lib/components/ui/ExpandIcon.tsx +3 -1
- package/lib/hooks/core/useGridRowInteractions.ts +13 -3
- package/lib/hooks/core/useGridToolbarStack.ts +2 -2
- package/lib/hooks/core/useGridVirtualization.ts +40 -25
- package/lib/hooks/core/useShallowStableValue.ts +29 -0
- package/lib/hooks/features/useGridAiAssistant.ts +52 -22
- package/lib/theme/DataGridThemeProvider.tsx +9 -0
- package/lib/theme/types.ts +18 -0
- package/lib/types/locale.ts +344 -0
- package/lib/utils/gridLocale.ts +241 -0
- package/llms.txt +3 -1
- package/package.json +4 -2
- package/lib/theme/__baseline__/default-theme.darwin.chromium.json +0 -1
- package/lib/theme/__baseline__/default-theme.darwin.firefox.json +0 -1
- package/lib/theme/__baseline__/default-theme.darwin.webkit.json +0 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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}
|
|
202
|
-
{pagination ? ` ·
|
|
203
|
-
{selectedRowIds.size > 0 ? ` · ${selectedRowIds.size}
|
|
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=
|
|
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 ?
|
|
28
|
-
{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
|
-
?
|
|
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=
|
|
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
|
}
|
|
@@ -4,6 +4,7 @@ import { ColumnVisibilityPanel } from '../ColumnVisibilityPanel/ColumnVisibility
|
|
|
4
4
|
import type { GridColDef, GridRowModel, GridColumnGroupingModel } from '../../types';
|
|
5
5
|
import { canReorderWithinColumnGroups } from '../../utils/columnGroups';
|
|
6
6
|
import { getViewportWidth } from '../../utils/viewport';
|
|
7
|
+
import { useGridLocale } from './gridLocaleContext';
|
|
7
8
|
|
|
8
9
|
export interface GridStandaloneColumnPanelProps<R extends GridRowModel> {
|
|
9
10
|
isOpen: boolean;
|
|
@@ -42,6 +43,7 @@ export function GridStandaloneColumnPanel<R extends GridRowModel>({
|
|
|
42
43
|
onColumnOrderReset,
|
|
43
44
|
columnGroupingModel,
|
|
44
45
|
}: GridStandaloneColumnPanelProps<R>) {
|
|
46
|
+
const locale = useGridLocale();
|
|
45
47
|
// Resolved from the DOM after mount, never during render, so the grid renders on the server.
|
|
46
48
|
const [placement, setPlacement] = useState<PanelPlacement | null>(null);
|
|
47
49
|
|
|
@@ -97,7 +99,7 @@ export function GridStandaloneColumnPanel<R extends GridRowModel>({
|
|
|
97
99
|
zIndex: 1,
|
|
98
100
|
boxShadow: '0 1px 4px rgba(0,0,0,0.2)',
|
|
99
101
|
}}
|
|
100
|
-
aria-label=
|
|
102
|
+
aria-label={locale.columnsPanelClose}
|
|
101
103
|
>×</button>
|
|
102
104
|
<ColumnVisibilityPanel<R>
|
|
103
105
|
columns={columns}
|
|
@@ -4,6 +4,7 @@ import { GridPinnedRows, type GridPinnedRowsProps } from './GridPinnedRows';
|
|
|
4
4
|
import { GridVirtualRows, type GridVirtualRowsProps } from './GridVirtualRows';
|
|
5
5
|
import { GridEmptyState } from './GridEmptyState';
|
|
6
6
|
import { GridAggregationFooter } from './GridAggregationFooter';
|
|
7
|
+
import { useGridLocale } from './gridLocaleContext';
|
|
7
8
|
import type { GridAggregationModel, GridAggregationResult, GridColDef, GridColumnPinning, GridRowId, GridRowModel } from '../../types';
|
|
8
9
|
import type { GridAriaRowLayout } from '../../utils/aria';
|
|
9
10
|
import type { UseGridCellSelectionReturn } from '../../hooks/core/useGridCellSelection';
|
|
@@ -120,6 +121,7 @@ export interface GridViewportProps<R extends GridRowModel> {
|
|
|
120
121
|
* empty state, the virtual centre rows and the aggregation footer.
|
|
121
122
|
*/
|
|
122
123
|
export function GridViewport<R extends GridRowModel>(props: GridViewportProps<R>) {
|
|
124
|
+
const locale = useGridLocale();
|
|
123
125
|
const {
|
|
124
126
|
setViewportElement, handleScroll, ariaLabel, ariaRows, navigationColumnCount, rowSelectionEnabled,
|
|
125
127
|
disableMultipleRowSelection, cellSelection, effectiveLoading, viewportTabIndex, setKeyboardMode,
|
|
@@ -165,7 +167,7 @@ export function GridViewport<R extends GridRowModel>(props: GridViewportProps<R>
|
|
|
165
167
|
className="ogx__viewport"
|
|
166
168
|
onScroll={handleScroll}
|
|
167
169
|
role="grid"
|
|
168
|
-
aria-label={ariaLabel ||
|
|
170
|
+
aria-label={ariaLabel || locale.gridLabel}
|
|
169
171
|
aria-rowcount={ariaRows.rowCount}
|
|
170
172
|
aria-colcount={navigationColumnCount}
|
|
171
173
|
aria-multiselectable={(rowSelectionEnabled && !disableMultipleRowSelection) || cellSelection.enabled}
|
|
@@ -208,7 +208,7 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
208
208
|
onDrop={rowReorderHandlers.onDrop}
|
|
209
209
|
isDragging={rowReorderHandlers.draggedRowId === id}
|
|
210
210
|
isDragOver={rowReorderHandlers.dragOverRowId === id}
|
|
211
|
-
editingCell={editingHandlers.editingCell}
|
|
211
|
+
editingCell={editingHandlers.editingCell?.id === id ? editingHandlers.editingCell : null}
|
|
212
212
|
onEditStart={editingHandlers.startCellEdit}
|
|
213
213
|
onEditStop={editingHandlers.stopCellEdit}
|
|
214
214
|
onEditCellValueChange={editingHandlers.setEditCellValue}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import { resolveGridLocale, type ResolvedGridLocale } from '../../utils/gridLocale';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The grid's resolved `localeText`, provided once by `GridRoot`. Components rendered outside a grid
|
|
6
|
+
* (the exported `ColumnVisibilityPanel`, `GridToolbar`, `FilterPanel`, …) get the English default.
|
|
7
|
+
*/
|
|
8
|
+
export const GridLocaleContext = createContext<ResolvedGridLocale>(resolveGridLocale(undefined));
|
|
9
|
+
|
|
10
|
+
/** The strings of the enclosing grid, English outside one. */
|
|
11
|
+
export function useGridLocale(): ResolvedGridLocale {
|
|
12
|
+
return useContext(GridLocaleContext);
|
|
13
|
+
}
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import { filterValueToInputText, getOperatorsForType, NO_VALUE_OPERATORS } from '../../utils/filtering';
|
|
10
10
|
import { filterValueFromInputText, getFilterValueOptions, isSameFilterValue, matchesFilterValueOption } from '../../utils/filtering/filterInput';
|
|
11
11
|
import { Input } from '../ui/Input';
|
|
12
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
12
13
|
|
|
13
14
|
export interface FilterPanelProps {
|
|
14
15
|
filterModel: GridFilterModel;
|
|
@@ -69,6 +70,7 @@ const FilterRow: React.FC<{
|
|
|
69
70
|
onChange: (item: GridFilterItem | null) => void;
|
|
70
71
|
}> = ({ col, item, resetToken, onChange }) => {
|
|
71
72
|
const filterId = useId();
|
|
73
|
+
const locale = useGridLocale();
|
|
72
74
|
// Memoized so React Compiler treats the list as frozen and can keep the callbacks below memoized.
|
|
73
75
|
const typeOperators = useMemo(() => getOperatorsForType(col.type), [col.type]);
|
|
74
76
|
const defaultOperator = typeOperators[0];
|
|
@@ -153,7 +155,7 @@ const FilterRow: React.FC<{
|
|
|
153
155
|
const showValue = !NO_VALUE_OPERATORS.has(currentOperator);
|
|
154
156
|
const valueOptions = getFilterValueOptions(col);
|
|
155
157
|
const valueClassName = `ogx-filter__value-input${isActive ? ' ogx-filter__value-input--active' : ''}`;
|
|
156
|
-
const valueLabel =
|
|
158
|
+
const valueLabel = locale.filterPanelValueLabel(label);
|
|
157
159
|
|
|
158
160
|
let valueControl: React.ReactNode = null;
|
|
159
161
|
if (showValue && col.type === 'boolean') {
|
|
@@ -169,9 +171,9 @@ const FilterRow: React.FC<{
|
|
|
169
171
|
}}
|
|
170
172
|
aria-label={valueLabel}
|
|
171
173
|
>
|
|
172
|
-
<option value="">
|
|
173
|
-
<option value="true">
|
|
174
|
-
<option value="false">
|
|
174
|
+
<option value="">{locale.filterPanelAny}</option>
|
|
175
|
+
<option value="true">{locale.filterPanelTrue}</option>
|
|
176
|
+
<option value="false">{locale.filterPanelFalse}</option>
|
|
175
177
|
</select>
|
|
176
178
|
);
|
|
177
179
|
} else if (showValue && col.type === 'singleSelect' && valueOptions.length > 0 && currentOperator === 'isAnyOf') {
|
|
@@ -207,7 +209,7 @@ const FilterRow: React.FC<{
|
|
|
207
209
|
}}
|
|
208
210
|
aria-label={valueLabel}
|
|
209
211
|
>
|
|
210
|
-
<option value="">
|
|
212
|
+
<option value="">{locale.filterPanelAny}</option>
|
|
211
213
|
{valueOptions.map(o => <option key={o.key} value={o.key}>{o.label}</option>)}
|
|
212
214
|
</select>
|
|
213
215
|
);
|
|
@@ -220,7 +222,7 @@ const FilterRow: React.FC<{
|
|
|
220
222
|
type={col.type === 'date' ? 'date' : 'text'}
|
|
221
223
|
value={draft ?? toInputText(item?.value, col)}
|
|
222
224
|
onChange={(e) => setDraft(e.target.value)}
|
|
223
|
-
placeholder={col.type === 'number' ?
|
|
225
|
+
placeholder={col.type === 'number' ? locale.filterPanelNumberPlaceholder : locale.filterPanelValuePlaceholder}
|
|
224
226
|
aria-label={valueLabel}
|
|
225
227
|
/>
|
|
226
228
|
);
|
|
@@ -239,10 +241,10 @@ const FilterRow: React.FC<{
|
|
|
239
241
|
className={`ogx-filter__op-select${isActive ? ' ogx-filter__op-select--active' : ''}`}
|
|
240
242
|
value={currentOperator}
|
|
241
243
|
onChange={(e) => handleOperatorChange(e.target.value as GridFilterOperator)}
|
|
242
|
-
aria-label={
|
|
244
|
+
aria-label={locale.filterPanelOperatorLabel(label)}
|
|
243
245
|
>
|
|
244
246
|
{operators.map(op => (
|
|
245
|
-
<option key={op} value={op}>{op}</option>
|
|
247
|
+
<option key={op} value={op}>{locale.filterPanelOperator(op)}</option>
|
|
246
248
|
))}
|
|
247
249
|
</select>
|
|
248
250
|
|
|
@@ -254,8 +256,8 @@ const FilterRow: React.FC<{
|
|
|
254
256
|
type="button"
|
|
255
257
|
className="ogx-filter__row-clear"
|
|
256
258
|
onClick={handleClear}
|
|
257
|
-
aria-label={
|
|
258
|
-
title=
|
|
259
|
+
aria-label={locale.filterPanelClearLabel(label)}
|
|
260
|
+
title={locale.filterPanelClearTooltip}
|
|
259
261
|
>
|
|
260
262
|
×
|
|
261
263
|
</button>
|
|
@@ -268,6 +270,7 @@ const FilterRow: React.FC<{
|
|
|
268
270
|
// ── Root panel ────────────────────────────────────────────────────────────────
|
|
269
271
|
|
|
270
272
|
export const FilterPanel: React.FC<FilterPanelProps> = ({ filterModel, columns, onFilterModelChange }) => {
|
|
273
|
+
const locale = useGridLocale();
|
|
271
274
|
const items = useMemo<FilterEntry[]>(() => filterModel.items ?? [], [filterModel.items]);
|
|
272
275
|
const logicOperator = filterModel.logicOperator ?? 'and';
|
|
273
276
|
|
|
@@ -313,14 +316,14 @@ export const FilterPanel: React.FC<FilterPanelProps> = ({ filterModel, columns,
|
|
|
313
316
|
<>
|
|
314
317
|
{items.length > 1 && (
|
|
315
318
|
<div className="ogx-filter__logic-bar">
|
|
316
|
-
<span className="ogx-filter__logic-label">
|
|
319
|
+
<span className="ogx-filter__logic-label">{locale.filterPanelMatch}</span>
|
|
317
320
|
<button
|
|
318
321
|
type="button"
|
|
319
322
|
className={`ogx-filter__logic-pill${logicOperator === 'and' ? ' ogx-filter__logic-pill--active' : ''}`}
|
|
320
323
|
onClick={() => handleLogicChange('and')}
|
|
321
324
|
aria-pressed={logicOperator === 'and'}
|
|
322
325
|
>
|
|
323
|
-
|
|
326
|
+
{locale.filterPanelAnd}
|
|
324
327
|
</button>
|
|
325
328
|
<button
|
|
326
329
|
type="button"
|
|
@@ -328,21 +331,19 @@ export const FilterPanel: React.FC<FilterPanelProps> = ({ filterModel, columns,
|
|
|
328
331
|
onClick={() => handleLogicChange('or')}
|
|
329
332
|
aria-pressed={logicOperator === 'or'}
|
|
330
333
|
>
|
|
331
|
-
|
|
334
|
+
{locale.filterPanelOr}
|
|
332
335
|
</button>
|
|
333
336
|
</div>
|
|
334
337
|
)}
|
|
335
338
|
|
|
336
339
|
{notShownCount > 0 && (
|
|
337
340
|
<div className="ogx-filter__hidden-note" role="note">
|
|
338
|
-
{notShownCount
|
|
339
|
-
? '1 more condition (a filter group or a second condition on a column) is applied and not shown here.'
|
|
340
|
-
: `${notShownCount} more conditions (filter groups or further conditions on a column) are applied and not shown here.`}
|
|
341
|
+
{locale.filterPanelHiddenConditions(notShownCount)}
|
|
341
342
|
</div>
|
|
342
343
|
)}
|
|
343
344
|
|
|
344
345
|
{filterableColumns.length === 0 ? (
|
|
345
|
-
<div className="ogx-filter__empty">
|
|
346
|
+
<div className="ogx-filter__empty">{locale.filterPanelNoColumns}</div>
|
|
346
347
|
) : (
|
|
347
348
|
filterableColumns.map(col => (
|
|
348
349
|
<FilterRow
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
font-weight: 600;
|
|
15
15
|
text-transform: uppercase;
|
|
16
16
|
letter-spacing: 0.06em;
|
|
17
|
-
color: var(--ogx-color-gray-400, #94a3b8);
|
|
17
|
+
color: var(--ogx-overlay-section-label-text, var(--ogx-color-gray-400, #94a3b8));
|
|
18
18
|
user-select: none;
|
|
19
19
|
}
|
|
20
20
|
.ogx-menu-item {
|
|
@@ -49,21 +49,21 @@
|
|
|
49
49
|
display: flex;
|
|
50
50
|
align-items: center;
|
|
51
51
|
justify-content: center;
|
|
52
|
-
color: var(--ogx-overlay-item-selected-text, #1976d2);
|
|
52
|
+
color: var(--ogx-overlay-item-indicator, var(--ogx-overlay-item-selected-text, #1976d2));
|
|
53
53
|
}
|
|
54
54
|
.ogx-menu-item__icon {
|
|
55
55
|
flex: 0 0 16px;
|
|
56
56
|
display: flex;
|
|
57
57
|
align-items: center;
|
|
58
58
|
justify-content: center;
|
|
59
|
-
color: var(--ogx-color-gray-400, #94a3b8);
|
|
59
|
+
color: var(--ogx-overlay-icon, var(--ogx-color-gray-400, #94a3b8));
|
|
60
60
|
transition: color 0.1s;
|
|
61
61
|
}
|
|
62
62
|
.ogx-menu-item--active .ogx-menu-item__icon {
|
|
63
|
-
color: var(--ogx-color-primary, #1976d2);
|
|
63
|
+
color: var(--ogx-overlay-item-selected-icon, var(--ogx-color-primary, #1976d2));
|
|
64
64
|
}
|
|
65
65
|
.ogx-menu-divider {
|
|
66
66
|
height: 1px;
|
|
67
|
-
background: var(--ogx-overlay-border, #e2e8f0);
|
|
67
|
+
background: var(--ogx-overlay-divider, var(--ogx-overlay-border, #e2e8f0));
|
|
68
68
|
margin: 4px 0;
|
|
69
69
|
}
|
|
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
|
|
|
3
3
|
import type { GridColDef, GridRowModel, GridSortDirection, GridColumnPinning } from '../../types';
|
|
4
4
|
import { getViewportWidth, getViewportHeight } from '../../utils/viewport';
|
|
5
5
|
import { isRtl } from '../../utils/direction';
|
|
6
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
interface ColumnMenuProps {
|
|
@@ -123,6 +124,7 @@ const menuItemsOf = (menu: HTMLElement | null): HTMLElement[] =>
|
|
|
123
124
|
|
|
124
125
|
export function ColumnMenu({ colDef, sortModel, onSort, onHide, onPin, pinnedColumns, onManageColumns, onClose, anchorEl }: ColumnMenuProps) {
|
|
125
126
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
127
|
+
const locale = useGridLocale();
|
|
126
128
|
|
|
127
129
|
// Closes the menu; when it was left from the keyboard or by choosing an item, focus goes back
|
|
128
130
|
// to the header cell that owns it (WAI-ARIA menu button pattern).
|
|
@@ -234,7 +236,7 @@ export function ColumnMenu({ colDef, sortModel, onSort, onHide, onPin, pinnedCol
|
|
|
234
236
|
const colLabel = colDef.headerName || colDef.field;
|
|
235
237
|
|
|
236
238
|
return ReactDOM.createPortal(
|
|
237
|
-
<div ref={menuRef} className="ogx-column-menu" style={style} role="menu" aria-label={
|
|
239
|
+
<div ref={menuRef} className="ogx-column-menu" style={style} role="menu" aria-label={locale.columnMenuLabel(colLabel)} onKeyDown={handleMenuKeyDown}>
|
|
238
240
|
|
|
239
241
|
{ }
|
|
240
242
|
{onSort && colDef.sortable !== false && (
|
|
@@ -243,19 +245,19 @@ export function ColumnMenu({ colDef, sortModel, onSort, onHide, onPin, pinnedCol
|
|
|
243
245
|
onClick={() => handleSort('asc')} role="menuitemradio" aria-checked={currentSort === 'asc'}>
|
|
244
246
|
<span className="ogx-menu-item__indicator" aria-hidden="true">{currentSort === 'asc' ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
|
|
245
247
|
<span className="ogx-menu-item__icon" aria-hidden="true"><AscIcon /></span>
|
|
246
|
-
|
|
248
|
+
{locale.columnMenuSortAscending}
|
|
247
249
|
</button>
|
|
248
250
|
<button type="button" className={`ogx-menu-item${currentSort === 'desc' ? ' ogx-menu-item--active' : ''}`}
|
|
249
251
|
onClick={() => handleSort('desc')} role="menuitemradio" aria-checked={currentSort === 'desc'}>
|
|
250
252
|
<span className="ogx-menu-item__indicator" aria-hidden="true">{currentSort === 'desc' ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
|
|
251
253
|
<span className="ogx-menu-item__icon" aria-hidden="true"><DescIcon /></span>
|
|
252
|
-
|
|
254
|
+
{locale.columnMenuSortDescending}
|
|
253
255
|
</button>
|
|
254
256
|
<button type="button" className={`ogx-menu-item${isUnsorted ? ' ogx-menu-item--active' : ''}`}
|
|
255
257
|
onClick={() => handleSort(null)} role="menuitemradio" aria-checked={isUnsorted}>
|
|
256
258
|
<span className="ogx-menu-item__indicator" aria-hidden="true">{isUnsorted ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
|
|
257
259
|
<span className="ogx-menu-item__icon" aria-hidden="true"><UnsortIcon /></span>
|
|
258
|
-
|
|
260
|
+
{locale.columnMenuUnsort}
|
|
259
261
|
</button>
|
|
260
262
|
<div className="ogx-menu-divider" />
|
|
261
263
|
</>
|
|
@@ -264,24 +266,24 @@ export function ColumnMenu({ colDef, sortModel, onSort, onHide, onPin, pinnedCol
|
|
|
264
266
|
{ }
|
|
265
267
|
{canPin && (
|
|
266
268
|
<>
|
|
267
|
-
<div className="ogx-menu-section-label" aria-hidden="true">
|
|
269
|
+
<div className="ogx-menu-section-label" aria-hidden="true">{locale.columnMenuPinColumn}</div>
|
|
268
270
|
<button type="button" className={`ogx-menu-item${isPinnedScreenLeft ? ' ogx-menu-item--active' : ''}`}
|
|
269
|
-
onClick={() => handlePin(screenLeftSide)} role="menuitemradio" aria-checked={isPinnedScreenLeft} aria-label=
|
|
271
|
+
onClick={() => handlePin(screenLeftSide)} role="menuitemradio" aria-checked={isPinnedScreenLeft} aria-label={locale.columnMenuPinLeftLabel}>
|
|
270
272
|
<span className="ogx-menu-item__indicator" aria-hidden="true">{isPinnedScreenLeft ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
|
|
271
273
|
<span className="ogx-menu-item__icon" aria-hidden="true"><PinLeftIcon /></span>
|
|
272
|
-
|
|
274
|
+
{locale.columnMenuPinLeft}
|
|
273
275
|
</button>
|
|
274
276
|
<button type="button" className={`ogx-menu-item${isPinnedScreenRight ? ' ogx-menu-item--active' : ''}`}
|
|
275
|
-
onClick={() => handlePin(screenRightSide)} role="menuitemradio" aria-checked={isPinnedScreenRight} aria-label=
|
|
277
|
+
onClick={() => handlePin(screenRightSide)} role="menuitemradio" aria-checked={isPinnedScreenRight} aria-label={locale.columnMenuPinRightLabel}>
|
|
276
278
|
<span className="ogx-menu-item__indicator" aria-hidden="true">{isPinnedScreenRight ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
|
|
277
279
|
<span className="ogx-menu-item__icon" aria-hidden="true"><PinRightIcon /></span>
|
|
278
|
-
|
|
280
|
+
{locale.columnMenuPinRight}
|
|
279
281
|
</button>
|
|
280
282
|
<button type="button" className={`ogx-menu-item${isUnpinned ? ' ogx-menu-item--active' : ''}`}
|
|
281
|
-
onClick={() => handlePin(null)} role="menuitemradio" aria-checked={isUnpinned} aria-label=
|
|
283
|
+
onClick={() => handlePin(null)} role="menuitemradio" aria-checked={isUnpinned} aria-label={locale.columnMenuUnpinLabel}>
|
|
282
284
|
<span className="ogx-menu-item__indicator" aria-hidden="true">{isUnpinned ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
|
|
283
285
|
<span className="ogx-menu-item__icon" aria-hidden="true"><UnpinIcon /></span>
|
|
284
|
-
|
|
286
|
+
{locale.columnMenuUnpin}
|
|
285
287
|
</button>
|
|
286
288
|
<div className="ogx-menu-divider" />
|
|
287
289
|
</>
|
|
@@ -289,19 +291,19 @@ export function ColumnMenu({ colDef, sortModel, onSort, onHide, onPin, pinnedCol
|
|
|
289
291
|
|
|
290
292
|
{canHide && (
|
|
291
293
|
<>
|
|
292
|
-
<button type="button" className="ogx-menu-item" onClick={handleHide} role="menuitem" aria-label={
|
|
294
|
+
<button type="button" className="ogx-menu-item" onClick={handleHide} role="menuitem" aria-label={locale.columnMenuHideColumnLabel(colLabel)}>
|
|
293
295
|
<span className="ogx-menu-item__indicator" aria-hidden="true" />
|
|
294
296
|
<span className="ogx-menu-item__icon" aria-hidden="true"><HideIcon /></span>
|
|
295
|
-
|
|
297
|
+
{locale.columnMenuHideColumn}
|
|
296
298
|
</button>
|
|
297
299
|
<div className="ogx-menu-divider" />
|
|
298
300
|
</>
|
|
299
301
|
)}
|
|
300
302
|
|
|
301
|
-
<button type="button" className="ogx-menu-item" onClick={() => { onManageColumns?.(); close(false); }} role="menuitem" aria-label=
|
|
303
|
+
<button type="button" className="ogx-menu-item" onClick={() => { onManageColumns?.(); close(false); }} role="menuitem" aria-label={locale.columnMenuManageColumnsLabel}>
|
|
302
304
|
<span className="ogx-menu-item__indicator" aria-hidden="true" />
|
|
303
305
|
<span className="ogx-menu-item__icon" aria-hidden="true"><ManageColumnsIcon /></span>
|
|
304
|
-
|
|
306
|
+
{locale.columnMenuManageColumns}
|
|
305
307
|
</button>
|
|
306
308
|
</div>,
|
|
307
309
|
(anchorEl?.closest('.ogx-theme-provider') || document.body)
|
|
@@ -11,6 +11,7 @@ import { getHeaderLineClamp, shouldWrapHeaderText } from '../../utils/headerWrap
|
|
|
11
11
|
import { getHeaderColumnStyle } from './headerColumnStyle';
|
|
12
12
|
import { HeaderFilterRow } from '../HeaderFilter/HeaderFilterRow';
|
|
13
13
|
import { isRtl, logicalArrowKey } from '../../utils/direction';
|
|
14
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
function MenuIcon() {
|
|
@@ -93,6 +94,7 @@ export interface HeaderProps<R extends GridRowModel = GridRowModel> {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps<R>) {
|
|
97
|
+
const locale = useGridLocale();
|
|
96
98
|
const {
|
|
97
99
|
columns,
|
|
98
100
|
allColumns,
|
|
@@ -348,11 +350,11 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
348
350
|
<div
|
|
349
351
|
className={`ogx__header-cell ogx__header-cell--pinned ogx__header-cell--pinned-left ${isHeaderFocused('__reorder_col__') ? 'ogx__header-cell--focused' : ''}`}
|
|
350
352
|
role="columnheader"
|
|
351
|
-
aria-label=
|
|
353
|
+
aria-label={locale.headerRowReorderLabel}
|
|
352
354
|
aria-colindex={reorderColIndex}
|
|
353
355
|
data-field="__reorder_col__"
|
|
354
356
|
tabIndex={-1}
|
|
355
|
-
title=
|
|
357
|
+
title={locale.headerRowReorderTooltip}
|
|
356
358
|
style={{
|
|
357
359
|
minWidth: 48,
|
|
358
360
|
maxWidth: 48,
|
|
@@ -368,7 +370,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
368
370
|
className={`ogx__header-cell ogx__header-cell--expand ${pinExpandColumn ? 'ogx__header-cell--pinned ogx__header-cell--pinned-left' : ''
|
|
369
371
|
} ${isHeaderFocused('__expand_col__') ? 'ogx__header-cell--focused' : ''}`}
|
|
370
372
|
role="columnheader"
|
|
371
|
-
aria-label=
|
|
373
|
+
aria-label={locale.headerDetailPanelLabel}
|
|
372
374
|
aria-colindex={expandColIndex}
|
|
373
375
|
data-field="__expand_col__"
|
|
374
376
|
tabIndex={-1}
|
|
@@ -405,7 +407,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
405
407
|
checked={allSelected}
|
|
406
408
|
indeterminate={someSelected}
|
|
407
409
|
onChange={(e) => onSelectAll(e.target.checked)}
|
|
408
|
-
aria-label={allSelected ?
|
|
410
|
+
aria-label={allSelected ? locale.headerDeselectAllLabel : locale.headerSelectAllLabel}
|
|
409
411
|
tabIndex={-1}
|
|
410
412
|
onMouseDown={(e) => e.preventDefault()}
|
|
411
413
|
/>
|
|
@@ -434,7 +436,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
434
436
|
: sortPosition === 0 ? (sortDirection === 'asc' ? 'ascending' : 'descending')
|
|
435
437
|
: 'none';
|
|
436
438
|
const sortDescription = isSorted && sortModel.length > 1
|
|
437
|
-
?
|
|
439
|
+
? locale.headerSortDescription(sortDirection === 'asc' ? 'asc' : 'desc', sortPosition + 1, sortModel.length)
|
|
438
440
|
: undefined;
|
|
439
441
|
const isResizable = colDef.resizable !== false;
|
|
440
442
|
const pinnedPosition = isColumnPinned(colDef.field, pinnedColumns);
|
|
@@ -531,7 +533,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
531
533
|
type="button"
|
|
532
534
|
className="ogx__menu-icon-btn"
|
|
533
535
|
onClick={handleMenuOpen(colDef)}
|
|
534
|
-
aria-label={
|
|
536
|
+
aria-label={locale.headerColumnMenuButtonLabel(colDef.headerName || colDef.field)}
|
|
535
537
|
aria-haspopup="menu"
|
|
536
538
|
tabIndex={-1}
|
|
537
539
|
>
|
|
@@ -6,6 +6,7 @@ import { resolveHeaderFilterLocale } from '../../utils/headerFilterLocale';
|
|
|
6
6
|
import { getHeaderFilterState, hasHeaderFilter, setHeaderFilterItem } from '../../utils/headerFilters';
|
|
7
7
|
import { getHeaderColumnStyle } from '../Header/headerColumnStyle';
|
|
8
8
|
import { HeaderFilterCell } from './HeaderFilterCell';
|
|
9
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
9
10
|
|
|
10
11
|
export interface HeaderFilterRowProps {
|
|
11
12
|
/** The rendered columns of the column header row (render window with spacers), in render order. */
|
|
@@ -68,6 +69,7 @@ function SystemFilterCell({ field, ariaColIndex, sticky, left, focused }: {
|
|
|
68
69
|
* through the root-level items it owns (`header:<field>`).
|
|
69
70
|
*/
|
|
70
71
|
export function HeaderFilterRow(props: HeaderFilterRowProps) {
|
|
72
|
+
const gridLocale = useGridLocale();
|
|
71
73
|
const {
|
|
72
74
|
columns, columnWidths, pinnedColumns, pinnedPositions, pinnedEdges, rowReordering, hasDetailPanel,
|
|
73
75
|
checkboxSelection, pinExpandColumn, pinCheckboxColumn, columnIndexMap, ariaRowIndex, filterModel,
|
|
@@ -137,7 +139,7 @@ export function HeaderFilterRow(props: HeaderFilterRowProps) {
|
|
|
137
139
|
className={`ogx__header-filter-cell ogx__header-filter-cell--empty ${className}`}
|
|
138
140
|
style={style}
|
|
139
141
|
role="columnheader"
|
|
140
|
-
aria-label={
|
|
142
|
+
aria-label={gridLocale.headerFilterNoFilterLabel(colDef.headerName || colDef.field)}
|
|
141
143
|
aria-colindex={ariaColIndex}
|
|
142
144
|
data-field={colDef.field}
|
|
143
145
|
tabIndex={-1}
|
|
@@ -4,6 +4,7 @@ import { ExpandIcon } from '../ui/ExpandIcon';
|
|
|
4
4
|
import { Checkbox } from '../ui/Checkbox';
|
|
5
5
|
import { CellErrorBoundary } from '../Cell/CellErrorBoundary';
|
|
6
6
|
import { isSyntheticRowId } from '../../utils/syntheticRows';
|
|
7
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
7
8
|
import type { GridColDef, GridRowModel, GridRowId, GridRowMeta, GridListViewColDef, GridRenderCellParams } from '../../types';
|
|
8
9
|
|
|
9
10
|
export interface ListViewRowProps<R extends GridRowModel = GridRowModel> {
|
|
@@ -71,6 +72,7 @@ export function ListViewRow<R extends GridRowModel = GridRowModel>({
|
|
|
71
72
|
rowMeta,
|
|
72
73
|
};
|
|
73
74
|
|
|
75
|
+
const locale = useGridLocale();
|
|
74
76
|
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
75
77
|
e.stopPropagation();
|
|
76
78
|
onSelectionChange?.(id, e.target.checked);
|
|
@@ -96,7 +98,7 @@ export function ListViewRow<R extends GridRowModel = GridRowModel>({
|
|
|
96
98
|
{!isSyntheticRow && <Checkbox
|
|
97
99
|
checked={isSelected}
|
|
98
100
|
onChange={handleCheckboxChange}
|
|
99
|
-
aria-label={
|
|
101
|
+
aria-label={locale.rowSelectLabel(id)}
|
|
100
102
|
// Not a Tab stop of its own: the list is one Tab stop, Shift+Space selects the focused row.
|
|
101
103
|
tabIndex={-1}
|
|
102
104
|
/>}
|