Skip to content

Commit

Permalink
release v3.0.0-beta.0
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinVandy committed Sep 2, 2024
1 parent 3cfdad1 commit 77194eb
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ export const routes: Array<RouteItem> = [
href: '/docs/guides/state-management',
label: 'State Management',
},
{
href: '/docs/guides/accessibility',
label: 'Accessibility / Keyboard Navigation',
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,16 @@ export const tableOptions: TableOption[] = [
source: '',
type: 'boolean',
},
{
tableOption: 'enableCellNavigation',
defaultValue: 'true',
description: '',
link: '',
linkText: '',
required: false,
source: '',
type: 'boolean',
},
{
tableOption: 'enablePagination',
defaultValue: 'true',
Expand Down
30 changes: 30 additions & 0 deletions apps/material-react-table-docs/pages/docs/guides/accessibility.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Head from 'next/head';
import TableOptionsTable from '../../../components/prop-tables/TableOptionsTable';
import ColumnOptionsTable from '../../../components/prop-tables/ColumnOptionsTable';
import StateOptionsTable from '../../../components/prop-tables/StateOptionsTable';

<Head>
<title>
{'Accessibility / Keyboard Navigation Guide - Material React Table V3 Docs'}
</title>
<meta
name="description"
content="How to use and customize the accessibility and keyboard navigation features of Material React Table"
/>
</Head>

## Accessibility / Keyboard Navigation Guide

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.

### Relevant Table Options

<TableOptionsTable
onlyOptions={new Set(['enableCellNavigation'])}
/>

### Keyboard Navigation

> New in v3
Material React Table uses the `tab` key to move focus between cells, rows, and columns.
2 changes: 1 addition & 1 deletion packages/material-react-table/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "3.0.0-alpha.0",
"version": "3.0.0-beta.0",
"license": "MIT",
"name": "material-react-table",
"description": "A fully featured Material UI V6 implementation of TanStack React Table V8, written from the ground up in TypeScript.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../../types';
import {
isCellEditable,
navigateToNextCell,
cellNavigation,
openEditingCell,
} from '../../utils/cell.utils';
import { getCommonMRTCellStyles } from '../../utils/style.utils';
Expand Down Expand Up @@ -234,7 +234,7 @@ export const MRT_TableBodyCell = <TData extends MRT_RowData>({

const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
if (enableCellNavigation) {
navigateToNextCell(e);
cellNavigation(e);
}
tableCellProps?.onKeyDown?.(e);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const MRT_TableFooter = <TData extends MRT_RowData>({
...rest
}: MRT_TableFooterProps<TData>) => {
const {
getFooterGroups,
getState,
options: { enableStickyFooter, layoutMode, muiTableFooterProps },
refs: { tableFooterRef },
Expand All @@ -36,6 +35,22 @@ export const MRT_TableFooter = <TData extends MRT_RowData>({
const stickFooter =
(isFullScreen || enableStickyFooter) && enableStickyFooter !== false;

const footerGroups = table.getFooterGroups();

//if no footer cells at all, skip footer
if (
!footerGroups.some((footerGroup) =>
footerGroup.headers?.some(
(header) =>
(typeof header.column.columnDef.footer === 'string' &&
!!header.column.columnDef.footer) ||
header.column.columnDef.Footer,
),
)
) {
return null;
}

return (
<TableFooter
{...tableFooterProps}
Expand All @@ -60,7 +75,7 @@ export const MRT_TableFooter = <TData extends MRT_RowData>({
...(parseFromValuesOrFunc(tableFooterProps?.sx, theme) as any),
})}
>
{getFooterGroups().map((footerGroup) => (
{footerGroups.map((footerGroup) => (
<MRT_TableFooterRow
columnVirtualizer={columnVirtualizer}
footerGroup={footerGroup as any}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '../../types';
import { getCommonMRTCellStyles } from '../../utils/style.utils';
import { parseFromValuesOrFunc } from '../../utils/utils';
import { navigateToNextCell } from '../../utils/cell.utils';
import { cellNavigation } from '../../utils/cell.utils';

export interface MRT_TableFooterCellProps<TData extends MRT_RowData>
extends TableCellProps {
Expand Down Expand Up @@ -50,7 +50,7 @@ export const MRT_TableFooterCell = <TData extends MRT_RowData>({

const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
if (enableCellNavigation) {
navigateToNextCell(e);
cellNavigation(e);
}
tableCellProps?.onKeyDown?.(e);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from '../../types';
import { getCommonMRTCellStyles } from '../../utils/style.utils';
import { parseFromValuesOrFunc } from '../../utils/utils';
import { navigateToNextCell } from '../../utils/cell.utils';
import { cellNavigation } from '../../utils/cell.utils';

export interface MRT_TableHeadCellProps<TData extends MRT_RowData>
extends TableCellProps {
Expand Down Expand Up @@ -151,7 +151,7 @@ export const MRT_TableHeadCell = <TData extends MRT_RowData>({

const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
if (enableCellNavigation) {
navigateToNextCell(e);
cellNavigation(e);
}
tableCellProps?.onKeyDown?.(e);
};
Expand Down
44 changes: 44 additions & 0 deletions packages/material-react-table/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ export type MRT_TableInstance<TData extends MRT_RowData> = Omit<
| 'getColumn'
| 'getExpandedRowModel'
| 'getFlatHeaders'
| 'getFooterGroups'
| 'getHeaderGroups'
| 'getLeafHeaders'
| 'getLeftLeafColumns'
Expand All @@ -293,6 +294,7 @@ export type MRT_TableInstance<TData extends MRT_RowData> = Omit<
getColumn: (columnId: string) => MRT_Column<TData>;
getExpandedRowModel: () => MRT_RowModel<TData>;
getFlatHeaders: () => MRT_Header<TData>[];
getFooterGroups: () => MRT_HeaderGroup<TData>[];
getHeaderGroups: () => MRT_HeaderGroup<TData>[];
getLeafHeaders: () => MRT_Header<TData>[];
getLeftLeafColumns: () => MRT_Column<TData>[];
Expand Down Expand Up @@ -920,18 +922,27 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
table: MRT_TableInstance<TData>;
}) => CircularProgressProps & { Component?: ReactNode })
| (CircularProgressProps & { Component?: ReactNode });
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiColumnActionsButtonProps?:
| ((props: {
column: MRT_Column<TData>;
table: MRT_TableInstance<TData>;
}) => IconButtonProps)
| IconButtonProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiColumnDragHandleProps?:
| ((props: {
column: MRT_Column<TData>;
table: MRT_TableInstance<TData>;
}) => IconButtonProps)
| IconButtonProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiCopyButtonProps?:
| ((props: {
cell: MRT_Cell<TData>;
Expand All @@ -958,6 +969,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
table: MRT_TableInstance<TData>;
}) => DialogProps)
| DialogProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiEditTextFieldProps?:
| ((props: {
cell: MRT_Cell<TData>;
Expand All @@ -976,45 +990,66 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
table: MRT_TableInstance<TData>;
}) => IconButtonProps)
| IconButtonProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiFilterAutocompleteProps?:
| ((props: {
column: MRT_Column<TData>;
table: MRT_TableInstance<TData>;
}) => AutocompleteProps<any, any, any, any>)
| AutocompleteProps<any, any, any, any>;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiFilterCheckboxProps?:
| ((props: {
column: MRT_Column<TData>;
table: MRT_TableInstance<TData>;
}) => CheckboxProps)
| CheckboxProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiFilterDatePickerProps?:
| ((props: {
column: MRT_Column<TData>;
rangeFilterIndex?: number;
table: MRT_TableInstance<TData>;
}) => DatePickerProps<never>)
| DatePickerProps<never>;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiFilterDateTimePickerProps?:
| ((props: {
column: MRT_Column<TData>;
rangeFilterIndex?: number;
table: MRT_TableInstance<TData>;
}) => DateTimePickerProps<never>)
| DateTimePickerProps<never>;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiFilterSliderProps?:
| ((props: {
column: MRT_Column<TData>;
table: MRT_TableInstance<TData>;
}) => SliderProps)
| SliderProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiFilterTextFieldProps?:
| ((props: {
column: MRT_Column<TData>;
rangeFilterIndex?: number;
table: MRT_TableInstance<TData>;
}) => TextFieldProps)
| TextFieldProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiFilterTimePickerProps?:
| ((props: {
column: MRT_Column<TData>;
Expand Down Expand Up @@ -1064,6 +1099,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
table: MRT_TableInstance<TData>;
}) => CheckboxProps | RadioProps)
| (CheckboxProps | RadioProps);
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiSkeletonProps?:
| ((props: {
cell: MRT_Cell<TData>;
Expand All @@ -1072,6 +1110,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
table: MRT_TableInstance<TData>;
}) => SkeletonProps)
| SkeletonProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiTableBodyCellProps?:
| ((props: {
cell: MRT_Cell<TData>;
Expand Down Expand Up @@ -1109,6 +1150,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
table: MRT_TableInstance<TData>;
}) => TableRowProps)
| TableRowProps;
/**
* @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
*/
muiTableHeadCellProps?:
| ((props: {
column: MRT_Column<TData>;
Expand Down
50 changes: 39 additions & 11 deletions packages/material-react-table/src/utils/cell.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,44 +49,72 @@ export const openEditingCell = <TData extends MRT_RowData>({
}
};

export const navigateToNextCell = (
export const cellNavigation = (
e: React.KeyboardEvent<HTMLTableCellElement>,
) => {
if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
if (
['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(
e.key,
)
) {
e.preventDefault();
const currentCell = e.currentTarget;
const currentRow = currentCell.closest('tr');

const tableElement = currentCell.closest('table');
if (!tableElement) return;
const allCells = Array.from(tableElement?.querySelectorAll('th, td') || []);
const currentCellIndex = allCells.indexOf(currentCell);

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

const findNextCell = (index: number, searchDirection: 'f' | 'b') => {
const allCells = Array.from(tableElement.querySelectorAll('th, td'));
const currentCellIndex = allCells.indexOf(currentCell);
//home/end first or last cell in row
const findEdgeCell = (rowIndex: 'c' | 'f' | 'l', edge: 'f' | 'l') => {
const row =
rowIndex === 'c'
? currentRow
: rowIndex === 'f'
? currentCell.closest('table')?.querySelector('tr')
: currentCell.closest('table')?.lastElementChild?.lastElementChild;
const rowCells = Array.from(row?.children || []);
const targetCell =
edge === 'f' ? rowCells[0] : rowCells[rowCells.length - 1];
return targetCell as HTMLElement;
};

const findAdjacentCell = (
columnIndex: number,
searchDirection: 'f' | 'b',
) => {
const searchArray =
searchDirection === 'f'
? allCells.slice(currentCellIndex + 1)
: allCells.slice(0, currentCellIndex).reverse();
return searchArray.find((cell) =>
cell.matches(`[data-index="${index}"]`),
cell.matches(`[data-index="${columnIndex}"]`),
) as HTMLElement | undefined;
};

switch (e.key) {
case 'ArrowRight':
nextCell = findNextCell(currentIndex + 1, 'f');
nextCell = findAdjacentCell(currentIndex + 1, 'f');
break;
case 'ArrowLeft':
nextCell = findNextCell(currentIndex - 1, 'b');
nextCell = findAdjacentCell(currentIndex - 1, 'b');
break;
case 'ArrowUp':
nextCell = findNextCell(currentIndex, 'b');
nextCell = findAdjacentCell(currentIndex, 'b');
break;
case 'ArrowDown':
nextCell = findNextCell(currentIndex, 'f');
nextCell = findAdjacentCell(currentIndex, 'f');
break;
case 'Home':
nextCell = findEdgeCell(e.ctrlKey ? 'f' : 'c', 'f');
break;
case 'End':
nextCell = findEdgeCell(e.ctrlKey ? 'l' : 'c', 'l');
break;
}

Expand Down

0 comments on commit 77194eb

Please sign in to comment.