material-react-table 3.0.0-beta.0 → 3.0.0-beta.2

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.0.0-beta.0",
2
+ "version": "3.0.0-beta.2",
3
3
  "license": "MIT",
4
4
  "name": "material-react-table",
5
5
  "description": "A fully featured Material UI V6 implementation of TanStack React Table V8, written from the ground up in TypeScript.",
@@ -35,11 +35,11 @@
35
35
  "size-limit": [
36
36
  {
37
37
  "path": "dist/index.js",
38
- "limit": "53 KB"
38
+ "limit": "55 KB"
39
39
  },
40
40
  {
41
41
  "path": "dist/index.esm.js",
42
- "limit": "50 KB"
42
+ "limit": "51 KB"
43
43
  }
44
44
  ],
45
45
  "engines": {
@@ -78,11 +78,11 @@
78
78
  "@storybook/preview-api": "^8.2.9",
79
79
  "@storybook/react": "^8.2.9",
80
80
  "@storybook/react-vite": "^8.2.9",
81
- "@types/node": "^22.5.1",
81
+ "@types/node": "^22.5.2",
82
82
  "@types/react": "^18.3.5",
83
83
  "@types/react-dom": "^18.3.0",
84
- "@typescript-eslint/eslint-plugin": "^8.3.0",
85
- "@typescript-eslint/parser": "^8.3.0",
84
+ "@typescript-eslint/eslint-plugin": "^8.4.0",
85
+ "@typescript-eslint/parser": "^8.4.0",
86
86
  "@vitejs/plugin-react": "^4.3.1",
87
87
  "eslint": "^9.9.1",
88
88
  "eslint-plugin-mui-path-imports": "^0.0.15",
@@ -18,7 +18,7 @@ import {
18
18
  } from '../../types';
19
19
  import {
20
20
  isCellEditable,
21
- cellNavigation,
21
+ cellKeyboardShortcuts,
22
22
  openEditingCell,
23
23
  } from '../../utils/cell.utils';
24
24
  import { getCommonMRTCellStyles } from '../../utils/style.utils';
@@ -58,7 +58,7 @@ export const MRT_TableBodyCell = <TData extends MRT_RowData>({
58
58
  enableColumnOrdering,
59
59
  enableColumnPinning,
60
60
  enableGrouping,
61
- enableCellNavigation,
61
+ enableKeyboardShortcuts,
62
62
  layoutMode,
63
63
  mrtTheme: { draggingBorderColor },
64
64
  muiSkeletonProps,
@@ -232,11 +232,14 @@ export const MRT_TableBodyCell = <TData extends MRT_RowData>({
232
232
  }
233
233
  };
234
234
 
235
- const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
236
- if (enableCellNavigation) {
237
- cellNavigation(e);
238
- }
239
- tableCellProps?.onKeyDown?.(e);
235
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLTableCellElement>) => {
236
+ cellKeyboardShortcuts({
237
+ cell,
238
+ cellValue: cell.getValue<string>(),
239
+ event,
240
+ table,
241
+ });
242
+ tableCellProps?.onKeyDown?.(event);
240
243
  };
241
244
 
242
245
  return (
@@ -244,7 +247,7 @@ export const MRT_TableBodyCell = <TData extends MRT_RowData>({
244
247
  align={theme.direction === 'rtl' ? 'right' : 'left'}
245
248
  data-index={staticColumnIndex}
246
249
  data-pinned={!!isColumnPinned || undefined}
247
- tabIndex={enableCellNavigation ? 0 : undefined}
250
+ tabIndex={enableKeyboardShortcuts ? 0 : undefined}
248
251
  {...tableCellProps}
249
252
  onKeyDown={handleKeyDown}
250
253
  onContextMenu={handleContextMenu}
@@ -7,7 +7,7 @@ import {
7
7
  } from '../../types';
8
8
  import { getCommonMRTCellStyles } from '../../utils/style.utils';
9
9
  import { parseFromValuesOrFunc } from '../../utils/utils';
10
- import { cellNavigation } from '../../utils/cell.utils';
10
+ import { cellKeyboardShortcuts } from '../../utils/cell.utils';
11
11
 
12
12
  export interface MRT_TableFooterCellProps<TData extends MRT_RowData>
13
13
  extends TableCellProps {
@@ -28,7 +28,7 @@ export const MRT_TableFooterCell = <TData extends MRT_RowData>({
28
28
  options: {
29
29
  enableColumnPinning,
30
30
  muiTableFooterCellProps,
31
- enableCellNavigation,
31
+ enableKeyboardShortcuts,
32
32
  },
33
33
  } = table;
34
34
  const { density } = getState();
@@ -48,11 +48,13 @@ export const MRT_TableFooterCell = <TData extends MRT_RowData>({
48
48
  ...rest,
49
49
  };
50
50
 
51
- const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
52
- if (enableCellNavigation) {
53
- cellNavigation(e);
54
- }
55
- tableCellProps?.onKeyDown?.(e);
51
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLTableCellElement>) => {
52
+ cellKeyboardShortcuts({
53
+ event,
54
+ cellValue: footer.column.columnDef.footer,
55
+ table,
56
+ });
57
+ tableCellProps?.onKeyDown?.(event);
56
58
  };
57
59
 
58
60
  return (
@@ -67,6 +69,7 @@ export const MRT_TableFooterCell = <TData extends MRT_RowData>({
67
69
  colSpan={footer.colSpan}
68
70
  data-index={staticColumnIndex}
69
71
  data-pinned={!!isColumnPinned || undefined}
72
+ tabIndex={enableKeyboardShortcuts ? 0 : undefined}
70
73
  variant="footer"
71
74
  {...tableCellProps}
72
75
  onKeyDown={handleKeyDown}
@@ -17,7 +17,7 @@ import {
17
17
  } from '../../types';
18
18
  import { getCommonMRTCellStyles } from '../../utils/style.utils';
19
19
  import { parseFromValuesOrFunc } from '../../utils/utils';
20
- import { cellNavigation } from '../../utils/cell.utils';
20
+ import { cellKeyboardShortcuts } from '../../utils/cell.utils';
21
21
 
22
22
  export interface MRT_TableHeadCellProps<TData extends MRT_RowData>
23
23
  extends TableCellProps {
@@ -41,12 +41,12 @@ export const MRT_TableHeadCell = <TData extends MRT_RowData>({
41
41
  columnFilterDisplayMode,
42
42
  columnResizeDirection,
43
43
  columnResizeMode,
44
+ enableKeyboardShortcuts,
44
45
  enableColumnActions,
45
46
  enableColumnDragging,
46
47
  enableColumnOrdering,
47
48
  enableColumnPinning,
48
49
  enableGrouping,
49
- enableCellNavigation,
50
50
  enableMultiSort,
51
51
  layoutMode,
52
52
  mrtTheme: { draggingBorderColor },
@@ -149,11 +149,15 @@ export const MRT_TableHeadCell = <TData extends MRT_RowData>({
149
149
  }
150
150
  };
151
151
 
152
- const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
153
- if (enableCellNavigation) {
154
- cellNavigation(e);
155
- }
156
- tableCellProps?.onKeyDown?.(e);
152
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLTableCellElement>) => {
153
+ cellKeyboardShortcuts({
154
+ event,
155
+ cellValue: header.column.columnDef.header,
156
+ table,
157
+ header,
158
+ });
159
+
160
+ tableCellProps?.onKeyDown?.(event);
157
161
  };
158
162
 
159
163
  const HeaderElement =
@@ -194,7 +198,7 @@ export const MRT_TableHeadCell = <TData extends MRT_RowData>({
194
198
  }
195
199
  }
196
200
  }}
197
- tabIndex={enableCellNavigation ? 0 : undefined}
201
+ tabIndex={enableKeyboardShortcuts ? 0 : undefined}
198
202
  {...tableCellProps}
199
203
  onKeyDown={handleKeyDown}
200
204
  sx={(theme: Theme) => ({
@@ -338,6 +338,10 @@ export const MRT_FilterTextField = <TData extends MRT_RowData>({
338
338
  : filterPlaceholder,
339
339
  variant: 'standard',
340
340
  ...textFieldProps,
341
+ onKeyDown: (e) => {
342
+ e.stopPropagation();
343
+ textFieldProps.onKeyDown?.(e);
344
+ },
341
345
  sx: (theme) => ({
342
346
  minWidth: isDateFilter
343
347
  ? '160px'
@@ -35,6 +35,7 @@ export const MRT_ActionMenuItem = <TData extends MRT_RowData>({
35
35
  my: 0,
36
36
  py: '6px',
37
37
  }}
38
+ tabIndex={0}
38
39
  {...rest}
39
40
  >
40
41
  <Box
@@ -76,7 +76,7 @@ export const useMRT_TableOptions: <TData extends MRT_RowData>(
76
76
  enableGlobalFilterRankedResults = true,
77
77
  enableGrouping = false,
78
78
  enableHiding = true,
79
- enableCellNavigation = true,
79
+ enableKeyboardShortcuts = true,
80
80
  enableMultiRowSelection = true,
81
81
  enableMultiSort = true,
82
82
  enablePagination = true,
@@ -204,7 +204,7 @@ export const useMRT_TableOptions: <TData extends MRT_RowData>(
204
204
  enableGlobalFilterRankedResults,
205
205
  enableGrouping,
206
206
  enableHiding,
207
- enableCellNavigation,
207
+ enableKeyboardShortcuts,
208
208
  enableMultiRowSelection,
209
209
  enableMultiSort,
210
210
  enablePagination,
package/src/types.ts CHANGED
@@ -184,9 +184,10 @@ export interface MRT_Localization {
184
184
  filterFuzzy: string;
185
185
  filterGreaterThan: string;
186
186
  filterGreaterThanOrEqualTo: string;
187
- filterInNumberRange: string;
188
187
  filterIncludesString: string;
189
188
  filterIncludesStringSensitive: string;
189
+ filteringByColumn: string;
190
+ filterInNumberRange: string;
190
191
  filterLessThan: string;
191
192
  filterLessThanOrEqualTo: string;
192
193
  filterMode: string;
@@ -194,7 +195,6 @@ export interface MRT_Localization {
194
195
  filterNotEquals: string;
195
196
  filterStartsWith: string;
196
197
  filterWeakEquals: string;
197
- filteringByColumn: string;
198
198
  goToFirstPage: string;
199
199
  goToLastPage: string;
200
200
  goToNextPage: string;
@@ -412,6 +412,22 @@ export interface MRT_ColumnDef<TData extends MRT_RowData, TValue = unknown>
412
412
  | 'id'
413
413
  | 'sortingFn'
414
414
  > {
415
+ /**
416
+ * Either an `accessorKey` or a combination of an `accessorFn` and `id` are required for a data column definition.
417
+ * Specify a function here to point to the correct property in the data object.
418
+ *
419
+ * @example accessorFn: (row) => row.username
420
+ */
421
+ accessorFn?: (originalRow: TData) => TValue;
422
+ /**
423
+ * Either an `accessorKey` or a combination of an `accessorFn` and `id` are required for a data column definition.
424
+ * Specify which key in the row this column should use to access the correct data.
425
+ * Also supports Deep Key Dot Notation.
426
+ *
427
+ * @example accessorKey: 'username' //simple
428
+ * @example accessorKey: 'name.firstName' //deep key dot notation
429
+ */
430
+ accessorKey?: DeepKeys<TData> | (string & {});
415
431
  AggregatedCell?: (props: {
416
432
  cell: MRT_Cell<TData, TValue>;
417
433
  column: MRT_Column<TData, TValue>;
@@ -420,6 +436,7 @@ export interface MRT_ColumnDef<TData extends MRT_RowData, TValue = unknown>
420
436
  staticColumnIndex?: number;
421
437
  staticRowIndex?: number;
422
438
  }) => ReactNode;
439
+ aggregationFn?: Array<MRT_AggregationFn<TData>> | MRT_AggregationFn<TData>;
423
440
  Cell?: (props: {
424
441
  cell: MRT_Cell<TData, TValue>;
425
442
  column: MRT_Column<TData, TValue>;
@@ -430,63 +447,6 @@ export interface MRT_ColumnDef<TData extends MRT_RowData, TValue = unknown>
430
447
  staticRowIndex?: number;
431
448
  table: MRT_TableInstance<TData>;
432
449
  }) => ReactNode;
433
- Edit?: (props: {
434
- cell: MRT_Cell<TData, TValue>;
435
- column: MRT_Column<TData, TValue>;
436
- row: MRT_Row<TData>;
437
- table: MRT_TableInstance<TData>;
438
- }) => ReactNode;
439
- Filter?: (props: {
440
- column: MRT_Column<TData, TValue>;
441
- header: MRT_Header<TData>;
442
- rangeFilterIndex?: number;
443
- table: MRT_TableInstance<TData>;
444
- }) => ReactNode;
445
- Footer?:
446
- | ((props: {
447
- column: MRT_Column<TData, TValue>;
448
- footer: MRT_Header<TData>;
449
- table: MRT_TableInstance<TData>;
450
- }) => ReactNode)
451
- | ReactNode;
452
- GroupedCell?: (props: {
453
- cell: MRT_Cell<TData, TValue>;
454
- column: MRT_Column<TData, TValue>;
455
- row: MRT_Row<TData>;
456
- table: MRT_TableInstance<TData>;
457
- staticColumnIndex?: number;
458
- staticRowIndex?: number;
459
- }) => ReactNode;
460
- Header?:
461
- | ((props: {
462
- column: MRT_Column<TData, TValue>;
463
- header: MRT_Header<TData>;
464
- table: MRT_TableInstance<TData>;
465
- }) => ReactNode)
466
- | ReactNode;
467
- PlaceholderCell?: (props: {
468
- cell: MRT_Cell<TData, TValue>;
469
- column: MRT_Column<TData, TValue>;
470
- row: MRT_Row<TData>;
471
- table: MRT_TableInstance<TData>;
472
- }) => ReactNode;
473
- /**
474
- * Either an `accessorKey` or a combination of an `accessorFn` and `id` are required for a data column definition.
475
- * Specify a function here to point to the correct property in the data object.
476
- *
477
- * @example accessorFn: (row) => row.username
478
- */
479
- accessorFn?: (originalRow: TData) => TValue;
480
- /**
481
- * Either an `accessorKey` or a combination of an `accessorFn` and `id` are required for a data column definition.
482
- * Specify which key in the row this column should use to access the correct data.
483
- * Also supports Deep Key Dot Notation.
484
- *
485
- * @example accessorKey: 'username' //simple
486
- * @example accessorKey: 'name.firstName' //deep key dot notation
487
- */
488
- accessorKey?: DeepKeys<TData> | (string & {});
489
- aggregationFn?: Array<MRT_AggregationFn<TData>> | MRT_AggregationFn<TData>;
490
450
  /**
491
451
  * Specify what type of column this is. Either `data`, `display`, or `group`. Defaults to `data`.
492
452
  * Leave this blank if you are just creating a normal data column.
@@ -500,6 +460,12 @@ export interface MRT_ColumnDef<TData extends MRT_RowData, TValue = unknown>
500
460
  LiteralUnion<string & MRT_FilterOption>
501
461
  > | null;
502
462
  columns?: MRT_ColumnDef<TData, TValue>[];
463
+ Edit?: (props: {
464
+ cell: MRT_Cell<TData, TValue>;
465
+ column: MRT_Column<TData, TValue>;
466
+ row: MRT_Row<TData>;
467
+ table: MRT_TableInstance<TData>;
468
+ }) => ReactNode;
503
469
  editSelectOptions?:
504
470
  | ((props: {
505
471
  cell: MRT_Cell<TData, TValue>;
@@ -519,6 +485,12 @@ export interface MRT_ColumnDef<TData extends MRT_RowData, TValue = unknown>
519
485
  enableColumnOrdering?: boolean;
520
486
  enableEditing?: ((row: MRT_Row<TData>) => boolean) | boolean;
521
487
  enableFilterMatchHighlighting?: boolean;
488
+ Filter?: (props: {
489
+ column: MRT_Column<TData, TValue>;
490
+ header: MRT_Header<TData>;
491
+ rangeFilterIndex?: number;
492
+ table: MRT_TableInstance<TData>;
493
+ }) => ReactNode;
522
494
  filterFn?: MRT_FilterFn<TData>;
523
495
  filterSelectOptions?: DropdownOption[];
524
496
  filterVariant?:
@@ -539,6 +511,21 @@ export interface MRT_ColumnDef<TData extends MRT_RowData, TValue = unknown>
539
511
  * footer must be a string. If you want custom JSX to render the footer, you can also specify a `Footer` option. (Capital F)
540
512
  */
541
513
  footer?: string;
514
+ Footer?:
515
+ | ((props: {
516
+ column: MRT_Column<TData, TValue>;
517
+ footer: MRT_Header<TData>;
518
+ table: MRT_TableInstance<TData>;
519
+ }) => ReactNode)
520
+ | ReactNode;
521
+ GroupedCell?: (props: {
522
+ cell: MRT_Cell<TData, TValue>;
523
+ column: MRT_Column<TData, TValue>;
524
+ row: MRT_Row<TData>;
525
+ table: MRT_TableInstance<TData>;
526
+ staticColumnIndex?: number;
527
+ staticRowIndex?: number;
528
+ }) => ReactNode;
542
529
  /**
543
530
  * If `layoutMode` is `'grid'` or `'grid-no-grow'`, you can specify the flex grow value for individual columns to still grow and take up remaining space, or set to `false`/0 to not grow.
544
531
  */
@@ -547,6 +534,13 @@ export interface MRT_ColumnDef<TData extends MRT_RowData, TValue = unknown>
547
534
  * header must be a string. If you want custom JSX to render the header, you can also specify a `Header` option. (Capital H)
548
535
  */
549
536
  header: string;
537
+ Header?:
538
+ | ((props: {
539
+ column: MRT_Column<TData, TValue>;
540
+ header: MRT_Header<TData>;
541
+ table: MRT_TableInstance<TData>;
542
+ }) => ReactNode)
543
+ | ReactNode;
550
544
  /**
551
545
  * Either an `accessorKey` or a combination of an `accessorFn` and `id` are required for a data column definition.
552
546
  *
@@ -651,6 +645,12 @@ export interface MRT_ColumnDef<TData extends MRT_RowData, TValue = unknown>
651
645
  table: MRT_TableInstance<TData>;
652
646
  }) => TableCellProps)
653
647
  | TableCellProps;
648
+ PlaceholderCell?: (props: {
649
+ cell: MRT_Cell<TData, TValue>;
650
+ column: MRT_Column<TData, TValue>;
651
+ row: MRT_Row<TData>;
652
+ table: MRT_TableInstance<TData>;
653
+ }) => ReactNode;
654
654
  renderCellActionMenuItems?: (props: {
655
655
  cell: MRT_Cell<TData>;
656
656
  closeMenu: () => void;
@@ -813,12 +813,6 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
813
813
  columnFilterModeOptions?: Array<
814
814
  LiteralUnion<string & MRT_FilterOption>
815
815
  > | null;
816
- columnVirtualizerInstanceRef?: MutableRefObject<MRT_ColumnVirtualizer | null>;
817
- columnVirtualizerOptions?:
818
- | ((props: {
819
- table: MRT_TableInstance<TData>;
820
- }) => Partial<VirtualizerOptions<HTMLDivElement, HTMLTableCellElement>>)
821
- | Partial<VirtualizerOptions<HTMLDivElement, HTMLTableCellElement>>;
822
816
  /**
823
817
  * The columns to display in the table. `accessorKey`s or `accessorFn`s must match keys in the `data` table option.
824
818
  *
@@ -830,6 +824,12 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
830
824
  * @link https://www.material-react-table.com/docs/api/column-options
831
825
  */
832
826
  columns: MRT_ColumnDef<TData, any>[];
827
+ columnVirtualizerInstanceRef?: MutableRefObject<MRT_ColumnVirtualizer | null>;
828
+ columnVirtualizerOptions?:
829
+ | ((props: {
830
+ table: MRT_TableInstance<TData>;
831
+ }) => Partial<VirtualizerOptions<HTMLDivElement, HTMLTableCellElement>>)
832
+ | Partial<VirtualizerOptions<HTMLDivElement, HTMLTableCellElement>>;
833
833
  createDisplayMode?: 'custom' | 'modal' | 'row';
834
834
  /**
835
835
  * Pass your data as an array of objects. Objects can theoretically be any shape, but it's best to keep them consistent.
@@ -870,7 +870,7 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
870
870
  enableFullScreenToggle?: boolean;
871
871
  enableGlobalFilterModes?: boolean;
872
872
  enableGlobalFilterRankedResults?: boolean;
873
- enableCellNavigation?: boolean;
873
+ enableKeyboardShortcuts?: boolean;
874
874
  enablePagination?: boolean;
875
875
  enableRowActions?: boolean;
876
876
  enableRowDragging?: boolean;
@@ -1099,9 +1099,6 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
1099
1099
  table: MRT_TableInstance<TData>;
1100
1100
  }) => CheckboxProps | RadioProps)
1101
1101
  | (CheckboxProps | RadioProps);
1102
- /**
1103
- * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1104
- */
1105
1102
  muiSkeletonProps?:
1106
1103
  | ((props: {
1107
1104
  cell: MRT_Cell<TData>;
@@ -1236,6 +1233,9 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
1236
1233
  renderCaption?:
1237
1234
  | ((props: { table: MRT_TableInstance<TData> }) => ReactNode)
1238
1235
  | ReactNode;
1236
+ /**
1237
+ * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1238
+ */
1239
1239
  renderCellActionMenuItems?: (props: {
1240
1240
  cell: MRT_Cell<TData>;
1241
1241
  closeMenu: () => void;
@@ -1246,12 +1246,18 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
1246
1246
  staticRowIndex?: number;
1247
1247
  table: MRT_TableInstance<TData>;
1248
1248
  }) => ReactNode[];
1249
+ /**
1250
+ * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1251
+ */
1249
1252
  renderColumnActionsMenuItems?: (props: {
1250
1253
  closeMenu: () => void;
1251
1254
  column: MRT_Column<TData>;
1252
1255
  internalColumnMenuItems: ReactNode[];
1253
1256
  table: MRT_TableInstance<TData>;
1254
1257
  }) => ReactNode[];
1258
+ /**
1259
+ * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns.
1260
+ */
1255
1261
  renderColumnFilterModeMenuItems?: (props: {
1256
1262
  column: MRT_Column<TData>;
1257
1263
  internalFilterOptions: MRT_InternalFilterOption[];
@@ -1,10 +1,22 @@
1
1
  import {
2
+ MRT_Header,
2
3
  type MRT_Cell,
3
4
  type MRT_RowData,
4
5
  type MRT_TableInstance,
5
6
  } from '../types';
7
+ import {
8
+ getMRT_RowSelectionHandler,
9
+ getMRT_SelectAllHandler,
10
+ } from './row.utils';
6
11
  import { parseFromValuesOrFunc } from './utils';
7
12
 
13
+ const isWinCtrlMacMeta = (event: React.KeyboardEvent<HTMLTableCellElement>) => {
14
+ return (
15
+ (event.ctrlKey && navigator.platform.toLowerCase().includes('win')) ||
16
+ (event.metaKey && navigator.platform.toLowerCase().includes('mac'))
17
+ );
18
+ };
19
+
8
20
  export const isCellEditable = <TData extends MRT_RowData>({
9
21
  cell,
10
22
  table,
@@ -49,20 +61,99 @@ export const openEditingCell = <TData extends MRT_RowData>({
49
61
  }
50
62
  };
51
63
 
52
- export const cellNavigation = (
53
- e: React.KeyboardEvent<HTMLTableCellElement>,
54
- ) => {
55
- if (
56
- ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(
57
- e.key,
58
- )
64
+ export const cellKeyboardShortcuts = <TData extends MRT_RowData = MRT_RowData>({
65
+ cell,
66
+ cellElements,
67
+ cellValue,
68
+ containerElement,
69
+ event,
70
+ header,
71
+ parentElement,
72
+ table,
73
+ }: {
74
+ cell?: MRT_Cell<TData>;
75
+ header?: MRT_Header<TData>;
76
+ cellElements?: Array<HTMLTableCellElement>;
77
+ cellValue?: string;
78
+ containerElement?: HTMLTableElement;
79
+ event: React.KeyboardEvent<HTMLTableCellElement>;
80
+ parentElement?: HTMLTableRowElement;
81
+ table: MRT_TableInstance<TData>;
82
+ }) => {
83
+ if (!table.options.enableKeyboardShortcuts) return;
84
+ const currentCell = event.currentTarget;
85
+
86
+ if (cellValue && isWinCtrlMacMeta(event) && event.key === 'c') {
87
+ navigator.clipboard.writeText(cellValue);
88
+ } else if (['Enter', ' '].includes(event.key)) {
89
+ if (cell?.column?.id === 'mrt-row-select') {
90
+ event.preventDefault();
91
+ getMRT_RowSelectionHandler({
92
+ row: cell.row,
93
+ table,
94
+ //@ts-ignore
95
+ staticRowIndex: +event.target.getAttribute('data-index'),
96
+ })(event as any);
97
+ } else if (
98
+ header?.column?.id === 'mrt-row-select' &&
99
+ table.options.enableSelectAll
100
+ ) {
101
+ event.preventDefault();
102
+ getMRT_SelectAllHandler({
103
+ table,
104
+ })(event as any);
105
+ } else if (
106
+ cell?.column?.id === 'mrt-row-expand' &&
107
+ (cell.row.getCanExpand() ||
108
+ table.options.renderDetailPanel?.({ row: cell.row, table }))
109
+ ) {
110
+ event.preventDefault();
111
+ cell.row.toggleExpanded();
112
+ } else if (
113
+ header?.column?.id === 'mrt-row-expand' &&
114
+ table.options.enableExpandAll
115
+ ) {
116
+ event.preventDefault();
117
+ table.toggleAllRowsExpanded();
118
+ } else if (cell?.column.id === 'mrt-row-pin') {
119
+ event.preventDefault();
120
+ cell.row.getIsPinned()
121
+ ? cell.row.pin(false)
122
+ : cell.row.pin(
123
+ table.options.rowPinningDisplayMode?.includes('bottom')
124
+ ? 'bottom'
125
+ : 'top',
126
+ );
127
+ } else if (header && isWinCtrlMacMeta(event)) {
128
+ const actionsButton = currentCell.querySelector(
129
+ `button[aria-label="${table.options.localization.columnActions}"]`,
130
+ );
131
+ if (actionsButton) {
132
+ (actionsButton as HTMLButtonElement).click();
133
+ }
134
+ } else if (header?.column?.getCanSort()) {
135
+ event.preventDefault();
136
+ header.column.toggleSorting();
137
+ }
138
+ } else if (
139
+ [
140
+ 'ArrowRight',
141
+ 'ArrowLeft',
142
+ 'ArrowUp',
143
+ 'ArrowDown',
144
+ 'Home',
145
+ 'End',
146
+ 'PageUp',
147
+ 'PageDown',
148
+ ].includes(event.key)
59
149
  ) {
60
- e.preventDefault();
61
- const currentCell = e.currentTarget;
62
- const currentRow = currentCell.closest('tr');
150
+ event.preventDefault();
63
151
 
64
- const tableElement = currentCell.closest('table');
65
- const allCells = Array.from(tableElement?.querySelectorAll('th, td') || []);
152
+ const currentRow = parentElement || currentCell.closest('tr');
153
+ const tableElement = containerElement || currentCell.closest('table');
154
+ const allCells =
155
+ cellElements ||
156
+ Array.from(tableElement?.querySelectorAll('th, td') || []);
66
157
  const currentCellIndex = allCells.indexOf(currentCell);
67
158
 
68
159
  const currentIndex = parseInt(
@@ -76,14 +167,25 @@ export const cellNavigation = (
76
167
  rowIndex === 'c'
77
168
  ? currentRow
78
169
  : rowIndex === 'f'
79
- ? currentCell.closest('table')?.querySelector('tr')
80
- : currentCell.closest('table')?.lastElementChild?.lastElementChild;
170
+ ? tableElement?.querySelector('tr')
171
+ : tableElement?.lastElementChild?.lastElementChild;
81
172
  const rowCells = Array.from(row?.children || []);
82
173
  const targetCell =
83
174
  edge === 'f' ? rowCells[0] : rowCells[rowCells.length - 1];
84
175
  return targetCell as HTMLElement;
85
176
  };
86
177
 
178
+ //page up/down first or last cell in column
179
+ const findBottomTopCell = (columnIndex: number, edge: 'b' | 't') => {
180
+ const row =
181
+ edge === 't'
182
+ ? tableElement?.querySelector('tr')
183
+ : tableElement?.lastElementChild?.lastElementChild;
184
+ const rowCells = Array.from(row?.children || []);
185
+ const targetCell = rowCells[columnIndex];
186
+ return targetCell as HTMLElement;
187
+ };
188
+
87
189
  const findAdjacentCell = (
88
190
  columnIndex: number,
89
191
  searchDirection: 'f' | 'b',
@@ -97,7 +199,7 @@ export const cellNavigation = (
97
199
  ) as HTMLElement | undefined;
98
200
  };
99
201
 
100
- switch (e.key) {
202
+ switch (event.key) {
101
203
  case 'ArrowRight':
102
204
  nextCell = findAdjacentCell(currentIndex + 1, 'f');
103
205
  break;
@@ -111,10 +213,16 @@ export const cellNavigation = (
111
213
  nextCell = findAdjacentCell(currentIndex, 'f');
112
214
  break;
113
215
  case 'Home':
114
- nextCell = findEdgeCell(e.ctrlKey ? 'f' : 'c', 'f');
216
+ nextCell = findEdgeCell(isWinCtrlMacMeta(event) ? 'f' : 'c', 'f');
115
217
  break;
116
218
  case 'End':
117
- nextCell = findEdgeCell(e.ctrlKey ? 'l' : 'c', 'l');
219
+ nextCell = findEdgeCell(isWinCtrlMacMeta(event) ? 'l' : 'c', 'l');
220
+ break;
221
+ case 'PageUp':
222
+ nextCell = findBottomTopCell(currentIndex, 't');
223
+ break;
224
+ case 'PageDown':
225
+ nextCell = findBottomTopCell(currentIndex, 'b');
118
226
  break;
119
227
  }
120
228