Skip to content

Commit

Permalink
[DataGrid] Edit Cell Navigation (#1205)
Browse files Browse the repository at this point in the history
  • Loading branch information
dtassone authored Mar 18, 2021
1 parent 07b3f48 commit 2cf4818
Show file tree
Hide file tree
Showing 36 changed files with 1,242 additions and 848 deletions.
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/GridComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { GridComponentProps } from './GridComponentProps';
import { useGridColumnMenu } from './hooks/features/columnMenu/useGridColumnMenu';
import { useGridColumns } from './hooks/features/columns/useGridColumns';
import { useGridState } from './hooks/features/core/useGridState';
import { useGridKeyboardNavigation } from './hooks/features/keyboard/useGridKeyboardNavigation';
import { useGridPagination } from './hooks/features/pagination/useGridPagination';
import { useGridPreferencesPanel } from './hooks/features/preferencesPanel/useGridPreferencesPanel';
import { useGridParamsApi } from './hooks/features/rows/useGridParamsApi';
Expand Down Expand Up @@ -81,6 +82,7 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
useGridRows(apiRef, props.rows, props.getRowId);
useGridEditRows(apiRef);
useGridKeyboard(rootContainerRef, apiRef);
useGridKeyboardNavigation(rootContainerRef, apiRef);
useGridSelection(apiRef);
useGridSorting(apiRef, props.rows);
useGridColumnMenu(apiRef);
Expand Down
5 changes: 2 additions & 3 deletions packages/grid/_modules_/grid/components/GridViewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { renderStateSelector } from '../hooks/features/virtualization/renderingS
import { optionsSelector } from '../hooks/utils/optionsSelector';
import { GridApiContext } from './GridApiContext';
import { GridDataContainer } from './containers/GridDataContainer';
import { GridEmptyCell } from './GridEmptyCell';
import { GridEmptyCell } from './cell/GridEmptyCell';
import { GridRenderingZone } from './GridRenderingZone';
import { GridRow } from './GridRow';
import { GridRowCells } from './GridRowCells';
import { GridRowCells } from './cell/GridRowCells';
import { GridStickyContainer } from './GridStickyContainer';
import {
gridContainerSizesSelector,
Expand Down Expand Up @@ -68,7 +68,6 @@ export const GridViewport: ViewportType = React.forwardRef<HTMLDivElement, {}>(
extendRowFullWidth={!options.disableExtendRowFullWidth}
rowIndex={renderState.renderContext!.firstRowIdx! + idx}
cellFocus={cellFocus}
domIndex={idx}
/>
<GridEmptyCell width={renderState.renderContext!.rightEmptyWidth} height={rowHeight} />
</GridRow>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { capitalize } from '@material-ui/core/utils';
import * as React from 'react';
import { GRID_CELL_CSS_CLASS } from '../constants/cssClassesConstants';
import { GRID_CELL_CSS_CLASS } from '../../constants/cssClassesConstants';
import {
GRID_CELL_CLICK,
GRID_CELL_DOUBLE_CLICK,
GRID_CELL_ENTER,
GRID_CELL_KEYDOWN,
GRID_CELL_LEAVE,
GRID_CELL_MOUSE_DOWN,
GRID_CELL_OUT,
GRID_CELL_OVER,
} from '../constants/eventsConstants';
import { GridAlignment, GridCellValue, GridRowId } from '../models';
import { classnames } from '../utils';
import { GridApiContext } from './GridApiContext';
} from '../../constants/eventsConstants';
import { GridAlignment, GridCellMode, GridCellValue, GridRowId } from '../../models/index';
import { classnames } from '../../utils/index';
import { GridApiContext } from '../GridApiContext';

export interface GridCellProps {
align: GridAlignment;
Expand All @@ -25,16 +27,17 @@ export interface GridCellProps {
isEditable?: boolean;
rowIndex?: number;
showRightBorder?: boolean;
tabIndex?: number;
value?: GridCellValue;
width: number;
cellMode?: GridCellMode;
}

export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
const {
align,
children,
colIndex,
cellMode,
cssClass,
field,
formattedValue,
Expand All @@ -44,7 +47,6 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
rowIndex,
rowId,
showRightBorder,
tabIndex,
value,
width,
} = props;
Expand All @@ -63,12 +65,6 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
},
);

React.useEffect(() => {
if (hasFocus && cellRef.current) {
cellRef.current.focus();
}
}, [hasFocus]);

const publishClick = React.useCallback(
(eventName: string) => (event: React.MouseEvent) => {
const params = apiRef!.current.getCellParams(rowId, field || '');
Expand All @@ -81,7 +77,7 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
);

const publish = React.useCallback(
(eventName: string) => (event: React.MouseEvent) =>
(eventName: string) => (event: React.SyntheticEvent) =>
apiRef!.current.publishEvent(
eventName,
apiRef!.current.getCellParams(rowId!, field || ''),
Expand All @@ -90,14 +86,16 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
[apiRef, field, rowId],
);

const mouseEventsHandlers = React.useMemo(
const eventsHandlers = React.useMemo(
() => ({
onClick: publishClick(GRID_CELL_CLICK),
onDoubleClick: publish(GRID_CELL_DOUBLE_CLICK),
onMouseDown: publish(GRID_CELL_MOUSE_DOWN),
onMouseOver: publish(GRID_CELL_OVER),
onMouseOut: publish(GRID_CELL_OUT),
onMouseEnter: publish(GRID_CELL_ENTER),
onMouseLeave: publish(GRID_CELL_LEAVE),
onKeyDown: publish(GRID_CELL_KEYDOWN),
}),
[publish, publishClick],
);
Expand All @@ -110,6 +108,12 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
maxHeight: height,
};

React.useLayoutEffect(() => {
if (hasFocus && cellMode === 'view' && cellRef.current) {
cellRef.current!.focus();
}
});

return (
<div
ref={cellRef}
Expand All @@ -119,10 +123,12 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
data-field={field}
data-rowindex={rowIndex}
data-editable={isEditable}
data-mode={cellMode}
aria-colindex={colIndex}
style={style}
tabIndex={tabIndex}
{...mouseEventsHandlers}
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
tabIndex={hasFocus ? 0 : -1}
{...eventsHandlers}
>
{children || valueToRender?.toString()}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from 'react';
import InputBase, { InputBaseProps } from '@material-ui/core/InputBase';
import { GRID_CELL_EDIT_BLUR } from '../../constants/eventsConstants';
import { GridCellParams } from '../../models/params/gridCellParams';
import { isCellEditCommitKeys } from '../../utils/keyboardUtils';
import { formatDateToLocalInputDate, isDate, mapColDefTypeToInputType } from '../../utils/utils';
import { GridEditRowUpdate } from '../../models/gridEditRowModel';
import { GridEditRowApi } from '../../models/api/gridEditRowApi';

export function EditInputCell(props: GridCellParams & InputBaseProps) {
export function GridEditInputCell(props: GridCellParams & InputBaseProps) {
const {
id,
value,
Expand All @@ -14,42 +14,45 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) {
field,
row,
colDef,
cellMode,
getValue,
rowIndex,
colIndex,
isEditable,
...inputBaseProps
} = props;

const editRowApi = api as GridEditRowApi;
const [valueState, setValueState] = React.useState(value);

const onValueChange = React.useCallback(
const handleBlur = React.useCallback(
(event: React.SyntheticEvent) => {
const params = api.getCellParams(id, field);
api.publishEvent(GRID_CELL_EDIT_BLUR, params, event);
},
[api, field, id],
);

const handleChange = React.useCallback(
(event) => {
const newValue = event.target.value;
const update: GridEditRowUpdate = {};
update[field] = {
const editProps = {
value: colDef.type === 'date' || colDef.type === 'dateTime' ? new Date(newValue) : newValue,
};
setValueState(newValue);
editRowApi.setEditCellProps(row.id, update);
api.setEditCellProps({ id, field, props: editProps });
},
[editRowApi, colDef.type, field, row.id],
[api, colDef.type, field, id],
);

const onKeyDown = React.useCallback(
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent) => {
if (!inputBaseProps.error && event.key === 'Enter') {
const update: GridEditRowUpdate = {};
update[field] = { value };
editRowApi.commitCellChange(row.id, update);
}

if (event.key === 'Escape') {
editRowApi.setCellMode(row.id, field, 'view');
if (inputBaseProps.error && isCellEditCommitKeys(event.key)) {
// Account for when tab/enter is pressed
event.preventDefault();
event.stopPropagation();
}
},
[inputBaseProps.error, row.id, field, value, editRowApi],
[inputBaseProps.error],
);

const inputType = mapColDefTypeToInputType(colDef.type);
Expand All @@ -65,14 +68,15 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) {
return (
<InputBase
autoFocus
fullWidth
className="MuiDataGrid-editCellInputBase"
onKeyDown={onKeyDown}
value={inputFormattedValue}
onChange={onValueChange}
fullWidth
type={inputType}
value={inputFormattedValue}
onBlur={handleBlur}
onChange={handleChange}
onKeyDown={handleKeyDown}
{...inputBaseProps}
/>
);
}
export const renderEditInputCell = (params) => <EditInputCell {...params} />;
export const renderEditInputCell = (params) => <GridEditInputCell {...params} />;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { GRID_CELL_CSS_CLASS } from '../constants/cssClassesConstants';
import { GRID_CELL_CSS_CLASS } from '../../constants/cssClassesConstants';

export interface GridEmptyCellProps {
width?: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import * as React from 'react';
import { gridEditRowsStateSelector } from '../hooks/features/rows/gridEditRowsSelector';
import { gridEditRowsStateSelector } from '../../hooks/features/rows/gridEditRowsSelector';
import {
GridCellClassParams,
GridColumns,
GridRowModel,
GridCellClassRules,
GridCellParams,
GridCellIndexCoordinates,
} from '../models';
} from '../../models/index';
import { GridCell, GridCellProps } from './GridCell';
import { GridApiContext } from './GridApiContext';
import { classnames, isFunction } from '../utils';
import { gridDensityRowHeightSelector } from '../hooks/features/density/densitySelector';
import { useGridSelector } from '../hooks/features/core/useGridSelector';
import { GridApiContext } from '../GridApiContext';
import { classnames, isFunction } from '../../utils/index';
import { gridDensityRowHeightSelector } from '../../hooks/features/density/densitySelector';
import { useGridSelector } from '../../hooks/features/core/useGridSelector';

function applyCssClassRules(cellClassRules: GridCellClassRules, params: GridCellClassParams) {
return Object.entries(cellClassRules).reduce((appliedCss, entry) => {
Expand All @@ -24,7 +24,6 @@ function applyCssClassRules(cellClassRules: GridCellClassRules, params: GridCell

interface RowCellsProps {
columns: GridColumns;
domIndex: number;
extendRowFullWidth: boolean;
firstColIdx: number;
hasScroll: { y: boolean; x: boolean };
Expand All @@ -39,7 +38,6 @@ interface RowCellsProps {
export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
const {
columns,
domIndex,
firstColIdx,
hasScroll,
lastColIdx,
Expand Down Expand Up @@ -102,8 +100,8 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
formattedValue: cellParams.formattedValue,
align: column.align || 'left',
...cssClassProp,
tabIndex: domIndex === 0 && colIdx === 0 ? 0 : -1,
rowIndex,
cellMode: cellParams.cellMode,
colIndex: cellParams.colIndex,
children: cellComponent,
isEditable: cellParams.isEditable,
Expand Down
4 changes: 4 additions & 0 deletions packages/grid/_modules_/grid/components/cell/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './GridCell';
export * from './GridEditInputCell';
export * from './GridEmptyCell';
export * from './GridRowCells';
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useGridSelector } from '../../hooks/features/core/useGridSelector';
import { renderStateSelector } from '../../hooks/features/virtualization/renderingStateSelector';
import { optionsSelector } from '../../hooks/utils/optionsSelector';
import { GridApiContext } from '../GridApiContext';
import { GridEmptyCell } from '../GridEmptyCell';
import { GridEmptyCell } from '../cell/GridEmptyCell';
import { GridScrollArea } from '../GridScrollArea';
import { GridColumnHeadersItemCollection } from './GridColumnHeadersItemCollection';
import { gridDensityHeaderHeightSelector } from '../../hooks/features/density/densitySelector';
Expand Down
5 changes: 2 additions & 3 deletions packages/grid/_modules_/grid/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
export * from './cell';
export * from './containers';
export * from './columnHeaders';
export * from './icons';
export * from './menu';
export * from './panel';
export * from './toolbar';

export * from './GridApiContext';
export * from './GridAutoSizer';
export * from './GridCell';
export * from './GridCheckboxRenderer';
export * from './GridFooter';
export * from './GridHeader';
export * from './GridLoadingOverlay';
export * from './GridNoRowsOverlay';
export * from './GridPagination';
export * from './GridRenderingZone';
export * from './GridRowCells';
export * from './GridRowCount';
export * from './GridRow';
export * from './GridSelectedRowCount';
export * from './GridStickyContainer';
export * from './GridViewport';
export * from './Watermark';
export * from './GridScrollArea';
export * from './GridEmptyCell';
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import MenuList from '@material-ui/core/MenuList';
import { GridColDef } from '../../../models/colDef/gridColDef';
import { isHideMenuKey, isTabKey } from '../../../utils/keyboardUtils';
import { GridColumnsMenuItem } from './GridColumnsMenuItem';
import { GridFilterMenuItem } from './GridFilterMenuItem';
import { HideGridColMenuItem } from './HideGridColMenuItem';
Expand All @@ -18,10 +19,10 @@ export function GridColumnMenu(props: GridColumnMenuProps) {
const { hideMenu, currentColumn, open, id, labelledby } = props;
const handleListKeyDown = React.useCallback(
(event: React.KeyboardEvent) => {
if (event.key === 'Tab') {
if (isTabKey(event.key)) {
event.preventDefault();
}
if (event.key === 'Tab' || event.key === 'Escape') {
if (isHideMenuKey(event.key)) {
hideMenu();
}
},
Expand Down
6 changes: 3 additions & 3 deletions packages/grid/_modules_/grid/components/panel/GridPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import Paper from '@material-ui/core/Paper';
import Popper from '@material-ui/core/Popper';
import { GridApiContext } from '../GridApiContext';
import { isMuiV5 } from '../../utils';
import { isEscapeKey, isMuiV5 } from '../../utils';

export interface GridPanelProps {
children?: React.ReactNode;
Expand Down Expand Up @@ -50,8 +50,8 @@ export function GridPanel(props: GridPanelProps) {
}, [apiRef]);

const handleKeyDown = React.useCallback(
(event) => {
if (event.key === 'Escape') {
(event: React.KeyboardEvent) => {
if (isEscapeKey(event.key)) {
apiRef!.current.hidePreferences();
}
},
Expand Down
Loading

0 comments on commit 2cf4818

Please sign in to comment.