Skip to content

Commit 77194eb

Browse files
committed
release v3.0.0-beta.0
1 parent 3cfdad1 commit 77194eb

File tree

10 files changed

+151
-20
lines changed

10 files changed

+151
-20
lines changed

apps/material-react-table-docs/components/navigation/routes.ts

+4
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,10 @@ export const routes: Array<RouteItem> = [
353353
href: '/docs/guides/state-management',
354354
label: 'State Management',
355355
},
356+
{
357+
href: '/docs/guides/accessibility',
358+
label: 'Accessibility / Keyboard Navigation',
359+
},
356360
],
357361
},
358362
{

apps/material-react-table-docs/components/prop-tables/tableOptions.ts

+10
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,16 @@ export const tableOptions: TableOption[] = [
492492
source: '',
493493
type: 'boolean',
494494
},
495+
{
496+
tableOption: 'enableCellNavigation',
497+
defaultValue: 'true',
498+
description: '',
499+
link: '',
500+
linkText: '',
501+
required: false,
502+
source: '',
503+
type: 'boolean',
504+
},
495505
{
496506
tableOption: 'enablePagination',
497507
defaultValue: 'true',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Head from 'next/head';
2+
import TableOptionsTable from '../../../components/prop-tables/TableOptionsTable';
3+
import ColumnOptionsTable from '../../../components/prop-tables/ColumnOptionsTable';
4+
import StateOptionsTable from '../../../components/prop-tables/StateOptionsTable';
5+
6+
<Head>
7+
<title>
8+
{'Accessibility / Keyboard Navigation Guide - Material React Table V3 Docs'}
9+
</title>
10+
<meta
11+
name="description"
12+
content="How to use and customize the accessibility and keyboard navigation features of Material React Table"
13+
/>
14+
</Head>
15+
16+
## Accessibility / Keyboard Navigation Guide
17+
18+
Material React Table tries to get the basics of data grid accessibility right out of the box. But since you can easily add event handlers to just about any interaction inside of the table, you can heavily customize the accessibility of your table to your needs.
19+
20+
### Relevant Table Options
21+
22+
<TableOptionsTable
23+
onlyOptions={new Set(['enableCellNavigation'])}
24+
/>
25+
26+
### Keyboard Navigation
27+
28+
> New in v3
29+
30+
Material React Table uses the `tab` key to move focus between cells, rows, and columns.

packages/material-react-table/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "3.0.0-alpha.0",
2+
"version": "3.0.0-beta.0",
33
"license": "MIT",
44
"name": "material-react-table",
55
"description": "A fully featured Material UI V6 implementation of TanStack React Table V8, written from the ground up in TypeScript.",

packages/material-react-table/src/components/body/MRT_TableBodyCell.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from '../../types';
1919
import {
2020
isCellEditable,
21-
navigateToNextCell,
21+
cellNavigation,
2222
openEditingCell,
2323
} from '../../utils/cell.utils';
2424
import { getCommonMRTCellStyles } from '../../utils/style.utils';
@@ -234,7 +234,7 @@ export const MRT_TableBodyCell = <TData extends MRT_RowData>({
234234

235235
const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
236236
if (enableCellNavigation) {
237-
navigateToNextCell(e);
237+
cellNavigation(e);
238238
}
239239
tableCellProps?.onKeyDown?.(e);
240240
};

packages/material-react-table/src/components/footer/MRT_TableFooter.tsx

+17-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export const MRT_TableFooter = <TData extends MRT_RowData>({
1919
...rest
2020
}: MRT_TableFooterProps<TData>) => {
2121
const {
22-
getFooterGroups,
2322
getState,
2423
options: { enableStickyFooter, layoutMode, muiTableFooterProps },
2524
refs: { tableFooterRef },
@@ -36,6 +35,22 @@ export const MRT_TableFooter = <TData extends MRT_RowData>({
3635
const stickFooter =
3736
(isFullScreen || enableStickyFooter) && enableStickyFooter !== false;
3837

38+
const footerGroups = table.getFooterGroups();
39+
40+
//if no footer cells at all, skip footer
41+
if (
42+
!footerGroups.some((footerGroup) =>
43+
footerGroup.headers?.some(
44+
(header) =>
45+
(typeof header.column.columnDef.footer === 'string' &&
46+
!!header.column.columnDef.footer) ||
47+
header.column.columnDef.Footer,
48+
),
49+
)
50+
) {
51+
return null;
52+
}
53+
3954
return (
4055
<TableFooter
4156
{...tableFooterProps}
@@ -60,7 +75,7 @@ export const MRT_TableFooter = <TData extends MRT_RowData>({
6075
...(parseFromValuesOrFunc(tableFooterProps?.sx, theme) as any),
6176
})}
6277
>
63-
{getFooterGroups().map((footerGroup) => (
78+
{footerGroups.map((footerGroup) => (
6479
<MRT_TableFooterRow
6580
columnVirtualizer={columnVirtualizer}
6681
footerGroup={footerGroup as any}

packages/material-react-table/src/components/footer/MRT_TableFooterCell.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '../../types';
88
import { getCommonMRTCellStyles } from '../../utils/style.utils';
99
import { parseFromValuesOrFunc } from '../../utils/utils';
10-
import { navigateToNextCell } from '../../utils/cell.utils';
10+
import { cellNavigation } from '../../utils/cell.utils';
1111

1212
export interface MRT_TableFooterCellProps<TData extends MRT_RowData>
1313
extends TableCellProps {
@@ -50,7 +50,7 @@ export const MRT_TableFooterCell = <TData extends MRT_RowData>({
5050

5151
const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
5252
if (enableCellNavigation) {
53-
navigateToNextCell(e);
53+
cellNavigation(e);
5454
}
5555
tableCellProps?.onKeyDown?.(e);
5656
};

packages/material-react-table/src/components/head/MRT_TableHeadCell.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from '../../types';
1818
import { getCommonMRTCellStyles } from '../../utils/style.utils';
1919
import { parseFromValuesOrFunc } from '../../utils/utils';
20-
import { navigateToNextCell } from '../../utils/cell.utils';
20+
import { cellNavigation } from '../../utils/cell.utils';
2121

2222
export interface MRT_TableHeadCellProps<TData extends MRT_RowData>
2323
extends TableCellProps {
@@ -151,7 +151,7 @@ export const MRT_TableHeadCell = <TData extends MRT_RowData>({
151151

152152
const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
153153
if (enableCellNavigation) {
154-
navigateToNextCell(e);
154+
cellNavigation(e);
155155
}
156156
tableCellProps?.onKeyDown?.(e);
157157
};

packages/material-react-table/src/types.ts

+44
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ export type MRT_TableInstance<TData extends MRT_RowData> = Omit<
271271
| 'getColumn'
272272
| 'getExpandedRowModel'
273273
| 'getFlatHeaders'
274+
| 'getFooterGroups'
274275
| 'getHeaderGroups'
275276
| 'getLeafHeaders'
276277
| 'getLeftLeafColumns'
@@ -293,6 +294,7 @@ export type MRT_TableInstance<TData extends MRT_RowData> = Omit<
293294
getColumn: (columnId: string) => MRT_Column<TData>;
294295
getExpandedRowModel: () => MRT_RowModel<TData>;
295296
getFlatHeaders: () => MRT_Header<TData>[];
297+
getFooterGroups: () => MRT_HeaderGroup<TData>[];
296298
getHeaderGroups: () => MRT_HeaderGroup<TData>[];
297299
getLeafHeaders: () => MRT_Header<TData>[];
298300
getLeftLeafColumns: () => MRT_Column<TData>[];
@@ -920,18 +922,27 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
920922
table: MRT_TableInstance<TData>;
921923
}) => CircularProgressProps & { Component?: ReactNode })
922924
| (CircularProgressProps & { Component?: ReactNode });
925+
/**
926+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
927+
*/
923928
muiColumnActionsButtonProps?:
924929
| ((props: {
925930
column: MRT_Column<TData>;
926931
table: MRT_TableInstance<TData>;
927932
}) => IconButtonProps)
928933
| IconButtonProps;
934+
/**
935+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
936+
*/
929937
muiColumnDragHandleProps?:
930938
| ((props: {
931939
column: MRT_Column<TData>;
932940
table: MRT_TableInstance<TData>;
933941
}) => IconButtonProps)
934942
| IconButtonProps;
943+
/**
944+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
945+
*/
935946
muiCopyButtonProps?:
936947
| ((props: {
937948
cell: MRT_Cell<TData>;
@@ -958,6 +969,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
958969
table: MRT_TableInstance<TData>;
959970
}) => DialogProps)
960971
| DialogProps;
972+
/**
973+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
974+
*/
961975
muiEditTextFieldProps?:
962976
| ((props: {
963977
cell: MRT_Cell<TData>;
@@ -976,45 +990,66 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
976990
table: MRT_TableInstance<TData>;
977991
}) => IconButtonProps)
978992
| IconButtonProps;
993+
/**
994+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
995+
*/
979996
muiFilterAutocompleteProps?:
980997
| ((props: {
981998
column: MRT_Column<TData>;
982999
table: MRT_TableInstance<TData>;
9831000
}) => AutocompleteProps<any, any, any, any>)
9841001
| AutocompleteProps<any, any, any, any>;
1002+
/**
1003+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1004+
*/
9851005
muiFilterCheckboxProps?:
9861006
| ((props: {
9871007
column: MRT_Column<TData>;
9881008
table: MRT_TableInstance<TData>;
9891009
}) => CheckboxProps)
9901010
| CheckboxProps;
1011+
/**
1012+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1013+
*/
9911014
muiFilterDatePickerProps?:
9921015
| ((props: {
9931016
column: MRT_Column<TData>;
9941017
rangeFilterIndex?: number;
9951018
table: MRT_TableInstance<TData>;
9961019
}) => DatePickerProps<never>)
9971020
| DatePickerProps<never>;
1021+
/**
1022+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1023+
*/
9981024
muiFilterDateTimePickerProps?:
9991025
| ((props: {
10001026
column: MRT_Column<TData>;
10011027
rangeFilterIndex?: number;
10021028
table: MRT_TableInstance<TData>;
10031029
}) => DateTimePickerProps<never>)
10041030
| DateTimePickerProps<never>;
1031+
/**
1032+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1033+
*/
10051034
muiFilterSliderProps?:
10061035
| ((props: {
10071036
column: MRT_Column<TData>;
10081037
table: MRT_TableInstance<TData>;
10091038
}) => SliderProps)
10101039
| SliderProps;
1040+
/**
1041+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1042+
*/
10111043
muiFilterTextFieldProps?:
10121044
| ((props: {
10131045
column: MRT_Column<TData>;
10141046
rangeFilterIndex?: number;
10151047
table: MRT_TableInstance<TData>;
10161048
}) => TextFieldProps)
10171049
| TextFieldProps;
1050+
/**
1051+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1052+
*/
10181053
muiFilterTimePickerProps?:
10191054
| ((props: {
10201055
column: MRT_Column<TData>;
@@ -1064,6 +1099,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
10641099
table: MRT_TableInstance<TData>;
10651100
}) => CheckboxProps | RadioProps)
10661101
| (CheckboxProps | RadioProps);
1102+
/**
1103+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1104+
*/
10671105
muiSkeletonProps?:
10681106
| ((props: {
10691107
cell: MRT_Cell<TData>;
@@ -1072,6 +1110,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
10721110
table: MRT_TableInstance<TData>;
10731111
}) => SkeletonProps)
10741112
| SkeletonProps;
1113+
/**
1114+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1115+
*/
10751116
muiTableBodyCellProps?:
10761117
| ((props: {
10771118
cell: MRT_Cell<TData>;
@@ -1109,6 +1150,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
11091150
table: MRT_TableInstance<TData>;
11101151
}) => TableRowProps)
11111152
| TableRowProps;
1153+
/**
1154+
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1155+
*/
11121156
muiTableHeadCellProps?:
11131157
| ((props: {
11141158
column: MRT_Column<TData>;

packages/material-react-table/src/utils/cell.utils.ts

+39-11
Original file line numberDiff line numberDiff line change
@@ -49,44 +49,72 @@ export const openEditingCell = <TData extends MRT_RowData>({
4949
}
5050
};
5151

52-
export const navigateToNextCell = (
52+
export const cellNavigation = (
5353
e: React.KeyboardEvent<HTMLTableCellElement>,
5454
) => {
55-
if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
55+
if (
56+
['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(
57+
e.key,
58+
)
59+
) {
5660
e.preventDefault();
5761
const currentCell = e.currentTarget;
62+
const currentRow = currentCell.closest('tr');
63+
5864
const tableElement = currentCell.closest('table');
59-
if (!tableElement) return;
65+
const allCells = Array.from(tableElement?.querySelectorAll('th, td') || []);
66+
const currentCellIndex = allCells.indexOf(currentCell);
6067

6168
const currentIndex = parseInt(
6269
currentCell.getAttribute('data-index') || '0',
6370
);
6471
let nextCell: HTMLElement | undefined = undefined;
6572

66-
const findNextCell = (index: number, searchDirection: 'f' | 'b') => {
67-
const allCells = Array.from(tableElement.querySelectorAll('th, td'));
68-
const currentCellIndex = allCells.indexOf(currentCell);
73+
//home/end first or last cell in row
74+
const findEdgeCell = (rowIndex: 'c' | 'f' | 'l', edge: 'f' | 'l') => {
75+
const row =
76+
rowIndex === 'c'
77+
? currentRow
78+
: rowIndex === 'f'
79+
? currentCell.closest('table')?.querySelector('tr')
80+
: currentCell.closest('table')?.lastElementChild?.lastElementChild;
81+
const rowCells = Array.from(row?.children || []);
82+
const targetCell =
83+
edge === 'f' ? rowCells[0] : rowCells[rowCells.length - 1];
84+
return targetCell as HTMLElement;
85+
};
86+
87+
const findAdjacentCell = (
88+
columnIndex: number,
89+
searchDirection: 'f' | 'b',
90+
) => {
6991
const searchArray =
7092
searchDirection === 'f'
7193
? allCells.slice(currentCellIndex + 1)
7294
: allCells.slice(0, currentCellIndex).reverse();
7395
return searchArray.find((cell) =>
74-
cell.matches(`[data-index="${index}"]`),
96+
cell.matches(`[data-index="${columnIndex}"]`),
7597
) as HTMLElement | undefined;
7698
};
7799

78100
switch (e.key) {
79101
case 'ArrowRight':
80-
nextCell = findNextCell(currentIndex + 1, 'f');
102+
nextCell = findAdjacentCell(currentIndex + 1, 'f');
81103
break;
82104
case 'ArrowLeft':
83-
nextCell = findNextCell(currentIndex - 1, 'b');
105+
nextCell = findAdjacentCell(currentIndex - 1, 'b');
84106
break;
85107
case 'ArrowUp':
86-
nextCell = findNextCell(currentIndex, 'b');
108+
nextCell = findAdjacentCell(currentIndex, 'b');
87109
break;
88110
case 'ArrowDown':
89-
nextCell = findNextCell(currentIndex, 'f');
111+
nextCell = findAdjacentCell(currentIndex, 'f');
112+
break;
113+
case 'Home':
114+
nextCell = findEdgeCell(e.ctrlKey ? 'f' : 'c', 'f');
115+
break;
116+
case 'End':
117+
nextCell = findEdgeCell(e.ctrlKey ? 'l' : 'c', 'l');
90118
break;
91119
}
92120

0 commit comments

Comments
 (0)