UNPKG

material-react-table

Version:

A fully featured Material UI V6 implementation of TanStack React Table V8, written from the ground up in TypeScript.

235 lines (221 loc) 6.76 kB
import { MRT_Header, type MRT_Cell, type MRT_RowData, type MRT_TableInstance, } from '../types'; import { getMRT_RowSelectionHandler, getMRT_SelectAllHandler, } from './row.utils'; import { parseFromValuesOrFunc } from './utils'; const isWinCtrlMacMeta = (event: React.KeyboardEvent<HTMLTableCellElement>) => { return ( (event.ctrlKey && navigator.platform.toLowerCase().includes('win')) || (event.metaKey && navigator.platform.toLowerCase().includes('mac')) ); }; export const isCellEditable = <TData extends MRT_RowData>({ cell, table, }: { cell: MRT_Cell<TData>; table: MRT_TableInstance<TData>; }) => { const { enableEditing } = table.options; const { column: { columnDef }, row, } = cell; return ( !cell.getIsPlaceholder() && parseFromValuesOrFunc(enableEditing, row) && parseFromValuesOrFunc(columnDef.enableEditing, row) !== false ); }; export const openEditingCell = <TData extends MRT_RowData>({ cell, table, }: { cell: MRT_Cell<TData>; table: MRT_TableInstance<TData>; }) => { const { options: { editDisplayMode }, refs: { editInputRefs }, } = table; const { column } = cell; if (isCellEditable({ cell, table }) && editDisplayMode === 'cell') { table.setEditingCell(cell); queueMicrotask(() => { const textField = editInputRefs.current?.[column.id]; if (textField) { textField.focus(); textField.select?.(); } }); } }; export const cellKeyboardShortcuts = <TData extends MRT_RowData = MRT_RowData>({ cell, cellElements, cellValue, containerElement, event, header, parentElement, table, }: { cell?: MRT_Cell<TData>; header?: MRT_Header<TData>; cellElements?: Array<HTMLTableCellElement>; cellValue?: string; containerElement?: HTMLTableElement; event: React.KeyboardEvent<HTMLTableCellElement>; parentElement?: HTMLTableRowElement; table: MRT_TableInstance<TData>; }) => { if (!table.options.enableKeyboardShortcuts) return; if (event.isPropagationStopped()) return; const currentCell = event.currentTarget; if (cellValue && isWinCtrlMacMeta(event) && event.key === 'c') { navigator.clipboard.writeText(cellValue); } else if (['Enter', ' '].includes(event.key)) { if (cell?.column?.id === 'mrt-row-select') { event.preventDefault(); getMRT_RowSelectionHandler({ row: cell.row, table, //@ts-expect-error staticRowIndex: +event.target.getAttribute('data-index'), })(event as any); } else if ( header?.column?.id === 'mrt-row-select' && table.options.enableSelectAll ) { event.preventDefault(); getMRT_SelectAllHandler({ table, })(event as any); } else if ( cell?.column?.id === 'mrt-row-expand' && (cell.row.getCanExpand() || table.options.renderDetailPanel?.({ row: cell.row, table })) ) { event.preventDefault(); cell.row.toggleExpanded(); } else if ( header?.column?.id === 'mrt-row-expand' && table.options.enableExpandAll ) { event.preventDefault(); table.toggleAllRowsExpanded(); } else if (cell?.column.id === 'mrt-row-pin') { event.preventDefault(); cell.row.getIsPinned() ? cell.row.pin(false) : cell.row.pin( table.options.rowPinningDisplayMode?.includes('bottom') ? 'bottom' : 'top', ); } else if (header && isWinCtrlMacMeta(event)) { const actionsButton = currentCell.querySelector( `button[aria-label="${table.options.localization.columnActions}"]`, ); if (actionsButton) { (actionsButton as HTMLButtonElement).click(); } } else if (header?.column?.getCanSort()) { event.preventDefault(); header.column.toggleSorting(); } } else if ( [ 'ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown', ].includes(event.key) ) { event.preventDefault(); const currentRow = parentElement || currentCell.closest('tr'); const tableElement = containerElement || currentCell.closest('table'); const allCells = cellElements || Array.from(tableElement?.querySelectorAll('th, td') || []); const currentCellIndex = allCells.indexOf(currentCell); const currentIndex = parseInt( currentCell.getAttribute('data-index') || '0', ); let nextCell: HTMLElement | undefined = undefined; //home/end first or last cell in row const findEdgeCell = (rowIndex: 'c' | 'f' | 'l', edge: 'f' | 'l') => { const row = rowIndex === 'c' ? currentRow : rowIndex === 'f' ? tableElement?.querySelector('tr') : tableElement?.lastElementChild?.lastElementChild; const rowCells = Array.from(row?.children || []); const targetCell = edge === 'f' ? rowCells[0] : rowCells[rowCells.length - 1]; return targetCell as HTMLElement; }; //page up/down first or last cell in column const findBottomTopCell = (columnIndex: number, edge: 'b' | 't') => { const row = edge === 't' ? tableElement?.querySelector('tr') : tableElement?.lastElementChild?.lastElementChild; const rowCells = Array.from(row?.children || []); const targetCell = rowCells[columnIndex]; 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="${columnIndex}"]`), ) as HTMLElement | undefined; }; switch (event.key) { case 'ArrowRight': nextCell = findAdjacentCell(currentIndex + 1, 'f'); break; case 'ArrowLeft': nextCell = findAdjacentCell(currentIndex - 1, 'b'); break; case 'ArrowUp': nextCell = findAdjacentCell(currentIndex, 'b'); break; case 'ArrowDown': nextCell = findAdjacentCell(currentIndex, 'f'); break; case 'Home': nextCell = findEdgeCell(isWinCtrlMacMeta(event) ? 'f' : 'c', 'f'); break; case 'End': nextCell = findEdgeCell(isWinCtrlMacMeta(event) ? 'l' : 'c', 'l'); break; case 'PageUp': nextCell = findBottomTopCell(currentIndex, 't'); break; case 'PageDown': nextCell = findBottomTopCell(currentIndex, 'b'); break; } if (nextCell) { nextCell.focus(); } } };