Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Implement base foundation for editing a cell #1025

Merged
merged 34 commits into from Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3a38366
WIP implementation of edit cell with validation and serverside callback
dtassone Feb 10, 2021
b16062a
fix rebase issues
dtassone Feb 22, 2021
53d7c90
fix date edit and refactoring
dtassone Feb 23, 2021
9f066f7
fixes and refactoring
dtassone Feb 24, 2021
84dbf55
fix client edit rows
dtassone Feb 24, 2021
a0642b9
format
dtassone Feb 24, 2021
11e7da0
add todo
dtassone Feb 24, 2021
03debae
fix edit click
dtassone Feb 24, 2021
cabe172
add missing import
dtassone Feb 24, 2021
699e8d3
fix imports
dtassone Feb 24, 2021
1283d4e
Merge branch 'master' of github.com:mui-org/material-ui-x into editMode
dtassone Feb 25, 2021
c15dfc2
refactor edit row state, and api
dtassone Feb 25, 2021
67451b9
add gridEditCellParams
dtassone Feb 25, 2021
357f385
fix event publish and add api desc
dtassone Feb 25, 2021
72a9863
fix import order
dtassone Feb 25, 2021
7d7d361
add basic test to switch cell mode
dtassone Feb 26, 2021
77a17b3
sort asc
oliviertassinari Feb 27, 2021
d84a10a
sort asc
oliviertassinari Feb 27, 2021
5e7dd29
blank line convention
oliviertassinari Feb 27, 2021
142d693
blank line convention
oliviertassinari Feb 27, 2021
2b1acc3
prefer assertion with a potentially better DX
oliviertassinari Feb 27, 2021
0d03941
blank line convention
oliviertassinari Feb 27, 2021
d8e117b
blank line convention
oliviertassinari Feb 27, 2021
d9b461f
Api description improvements
dtassone Mar 1, 2021
df4ee43
small refactoring
dtassone Mar 1, 2021
629e2a5
more small refactoring
dtassone Mar 1, 2021
c067a32
cleanup and prettier
dtassone Mar 1, 2021
19ab929
fix edit input ux, rm caret
dtassone Mar 1, 2021
ee320e8
prettier lint
dtassone Mar 1, 2021
30eea3e
Merge branch 'master' into editMode
dtassone Mar 2, 2021
c27e5d1
fix package.json syntax
dtassone Mar 2, 2021
df0e557
small fixes
dtassone Mar 2, 2021
cf65266
add methods in api pages
dtassone Mar 2, 2021
6dca60e
fix update rows
dtassone Mar 2, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/GridComponent.tsx
Expand Up @@ -22,6 +22,7 @@ import { useGridState } from './hooks/features/core/useGridState';
import { useGridPagination } from './hooks/features/pagination/useGridPagination';
import { useGridPreferencesPanel } from './hooks/features/preferencesPanel/useGridPreferencesPanel';
import { useGridRows } from './hooks/features/rows/useGridRows';
import { useGridEditRows } from './hooks/features/rows/useGridEditRows';
import { useGridSorting } from './hooks/features/sorting/useGridSorting';
import { useGridApiRef } from './hooks/features/useGridApiRef';
import { useGridColumnReorder } from './hooks/features/columnReorder';
Expand Down Expand Up @@ -75,6 +76,7 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps

useGridColumns(props.columns, apiRef);
useGridRows(apiRef, props.rows, props.getRowId);
useGridEditRows(apiRef);
useGridKeyboard(rootContainerRef, apiRef);
useGridSelection(apiRef);
useGridSorting(apiRef, props.rows);
Expand Down
22 changes: 13 additions & 9 deletions packages/grid/_modules_/grid/components/GridCell.tsx
Expand Up @@ -5,18 +5,19 @@ import { GRID_CELL_CSS_CLASS } from '../constants/cssClassesConstants';
import { classnames } from '../utils';

export interface GridCellProps {
align: GridAlignment;
colIndex?: number;
cssClass?: string;
field?: string;
value?: GridCellValue;
formattedValue?: GridCellValue;
width: number;
hasFocus?: boolean;
height: number;
isEditable?: boolean;
rowIndex?: number;
showRightBorder?: boolean;
hasFocus?: boolean;
align: GridAlignment;
cssClass?: string;
tabIndex?: number;
colIndex?: number;
rowIndex?: number;
value?: GridCellValue;
width: number;
}

export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
Expand All @@ -25,15 +26,16 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
children,
colIndex,
cssClass,
hasFocus,
field,
formattedValue,
hasFocus,
height,
isEditable,
rowIndex,
showRightBorder,
tabIndex,
value,
width,
height,
} = props;

const valueToRender = formattedValue || value;
Expand All @@ -50,11 +52,13 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
ref={cellRef}
className={classnames(GRID_CELL_CSS_CLASS, cssClass, `MuiDataGrid-cell${capitalize(align)}`, {
'MuiDataGrid-withBorder': showRightBorder,
'MuiDataGrid-cellEditable': isEditable,
})}
role="cell"
data-value={value}
data-field={field}
data-rowindex={rowIndex}
data-editable={isEditable}
aria-colindex={colIndex}
style={{
minWidth: width,
Expand Down
21 changes: 17 additions & 4 deletions packages/grid/_modules_/grid/components/GridRowCells.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
import { gridEditRowsStateSelector } from '../hooks/features/rows/gridEditRowsSelector';
import {
GridCellClassParams,
GridColumns,
Expand Down Expand Up @@ -51,6 +52,7 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
} = props;
const api = React.useContext(GridApiContext);
const rowHeight = useGridSelector(api, gridDensityRowHeightSelector);
const editRowsState = useGridSelector(api, gridEditRowsStateSelector);

const cellsProps = columns.slice(firstColIdx, lastColIdx + 1).map((column, colIdx) => {
const isLastColumn = firstColIdx + colIdx === columns.length - 1;
Expand All @@ -66,6 +68,7 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
rowModel: row,
colDef: column,
rowIndex,
colIndex: colIdx,
value,
api: api!.current!,
});
Expand All @@ -84,11 +87,8 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
cssClassProp = { cssClass: `${cssClassProp.cssClass} ${cssClass}` };
}

const editCellState = editRowsState[row.id] && editRowsState[row.id][column.field];
let cellComponent: React.ReactElement | null = null;
if (column.renderCell) {
cellComponent = column.renderCell(cellParams);
cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellWithRenderer` };
}

if (column.valueGetter) {
// Value getter override the original value
Expand All @@ -98,9 +98,21 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {

let formattedValueProp = {};
if (column.valueFormatter) {
// TODO add formatted value to cellParams?
formattedValueProp = { formattedValue: column.valueFormatter(cellParams) };
}

if (editCellState == null && column.renderCell) {
cellComponent = column.renderCell(cellParams);
cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellWithRenderer` };
}

if (editCellState != null && column.renderEditCell) {
const params = { ...cellParams, ...editCellState };
cellComponent = column.renderEditCell(params);
cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellEditing` };
}

const cellProps: GridCellProps & { children: any } = {
value,
field: column.field,
Expand All @@ -114,6 +126,7 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
rowIndex,
colIndex: colIdx + firstColIdx,
children: cellComponent,
isEditable: cellParams.isEditable,
hasFocus:
cellFocus !== null &&
cellFocus.rowIndex === rowIndex &&
Expand Down
Expand Up @@ -230,6 +230,17 @@ export const useStyles = makeStyles(
whiteSpace: 'nowrap',
borderBottom: `1px solid ${borderColor}`,
},
'& .MuiDataGrid-editCellInputBase': {
...theme.typography.body2,
border: `1px solid ${borderColor}`,
borderRadius: theme.shape.borderRadius,
'& :focus': {
borderColor: theme.palette.text.primary,
outline: 0,
// boxShadow: getThemePaletteMode(theme.palette) === 'light' ? null : '0 0 0 100px #266798 inset',
boxShadow: 'inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%)',
dtassone marked this conversation as resolved.
Show resolved Hide resolved
},
},
// The very last cell
'& .MuiDataGrid-colCellWrapper .MuiDataGrid-cell': {
borderBottom: 'none',
Expand Down
77 changes: 77 additions & 0 deletions packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx
@@ -0,0 +1,77 @@
import * as React from 'react';
import InputBase, { InputBaseProps } from '@material-ui/core/InputBase';
import { GridCellParams } from '../../models/params/gridCellParams';
import { formatDateToLocalInputDate, isDate, mapColDefTypeToInputType } from '../../utils/utils';
import { GridEditRowUpdate } from '../../models/gridEditRowModel';
import { GridEditRowApi } from '../../models/api/gridEditRowApi';

export function EditInputCell(props: GridCellParams & InputBaseProps) {
const {
value,
api,
field,
row,
colDef,
getValue,
rowIndex,
colIndex,
isEditable,
...inputBaseProps
} = props;

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

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

const onKeyDown = 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');
}
},
[inputBaseProps.error, row.id, field, value, editRowApi],
);

const inputType = mapColDefTypeToInputType(colDef.type);
const formattedValue =
valueState && isDate(valueState)
? formatDateToLocalInputDate({ value: valueState, withTime: colDef.type === 'dateTime' })
: valueState;

React.useEffect(() => {
// The value props takes priority over state in case it is overwritten externally. Then we override the state value.
setValueState((prev) => (value !== prev ? value : prev));
dtassone marked this conversation as resolved.
Show resolved Hide resolved
}, [value]);

return (
<InputBase
autoFocus
fullWidth
className="MuiDataGrid-editCellInputBase"
onKeyDown={onKeyDown}
value={formattedValue}
onChange={onValueChange}
type={inputType}
{...inputBaseProps}
/>
);
}
export const renderEditInputCell = (params) => <EditInputCell {...params} />;
9 changes: 8 additions & 1 deletion packages/grid/_modules_/grid/constants/eventsConstants.ts
@@ -1,20 +1,27 @@
// Web standard events
export const GRID_RESIZE = 'resize';
export const GRID_CLICK = 'click';
export const GRID_DOUBLE_CLICK = 'dblclick';
export const GRID_MOUSE_HOVER = 'mouseover';
export const GRID_FOCUS_OUT = 'focusout';
export const GRID_KEYDOWN = 'keydown';
export const GRID_KEYUP = 'keyup';
export const GRID_SCROLL = 'scroll';
export const GRID_DRAGEND = 'dragend';

// XGRID events
// GRID events
export const GRID_CELL_CHANGE = 'cellChange';
export const GRID_CELL_CHANGE_COMMITTED = 'cellChangeCommitted';
export const GRID_CELL_MODE_CHANGE = 'cellModeChange';
export const GRID_EDIT_ROW_MODEL_CHANGE = 'editRowModelChange';
export const GRID_COMPONENT_ERROR = 'componentError';
export const GRID_UNMOUNT = 'unmount';
export const GRID_ELEMENT_FOCUS_OUT = 'gridFocusOut';
export const GRID_CELL_CLICK = 'cellClick';
export const GRID_DOUBLE_CELL_CLICK = 'doubleCellClick';
export const GRID_CELL_HOVER = 'cellHover';
export const GRID_ROW_CLICK = 'rowClick';
export const GRID_DOUBLE_ROW_CLICK = 'doubleRowClick';
export const GRID_ROW_HOVER = 'rowHover';
export const GRID_ROW_SELECTED = 'rowSelected';
export const GRID_SELECTION_CHANGED = 'selectionChange';
Expand Down
3 changes: 3 additions & 0 deletions packages/grid/_modules_/grid/hooks/features/core/gridState.ts
Expand Up @@ -4,6 +4,7 @@ import {
GridScrollBarState,
GridViewportSizeState,
} from '../../../models/gridContainerProps';
import { GridEditRowsModel } from '../../../models/gridEditRowModel';
import { DEFAULT_GRID_OPTIONS, GridOptions } from '../../../models/gridOptions';
import { ColumnMenuState } from '../columnMenu/columnMenuState';
import {
Expand Down Expand Up @@ -32,6 +33,7 @@ import {

export interface GridState {
rows: InternalGridRowsState;
editRows: GridEditRowsModel;
pagination: PaginationState;
options: GridOptions;
isScrolling: boolean;
Expand All @@ -53,6 +55,7 @@ export interface GridState {

export const getInitialGridState: () => GridState = () => ({
rows: getInitialGridRowState(),
editRows: {},
dtassone marked this conversation as resolved.
Show resolved Hide resolved
pagination: GRID_INITIAL_PAGINATION_STATE,
options: DEFAULT_GRID_OPTIONS,
isScrolling: false,
Expand Down
Expand Up @@ -12,6 +12,7 @@ import { GridCellIndexCoordinates } from '../../../models/gridCell';
import {
findParentElementFromClassName,
getIdFromRowElem,
getRowEl,
isGridCellRoot,
} from '../../../utils/domUtils';
import {
Expand Down Expand Up @@ -211,10 +212,7 @@ export const useGridKeyboard = (
);

const handleCopy = React.useCallback(() => {
const rowEl = findParentElementFromClassName(
document.activeElement as HTMLDivElement,
GRID_ROW_CSS_CLASS,
)! as HTMLElement;
const rowEl = getRowEl(document.activeElement)!;
const rowId = getIdFromRowElem(rowEl);
const isRowSelected = selectionState[rowId];

Expand Down
@@ -0,0 +1,3 @@
import { GridState } from '../core/gridState';

export const gridEditRowsStateSelector = (state: GridState) => state.editRows;
dtassone marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/hooks/features/rows/index.ts
@@ -1,3 +1,5 @@
export * from './gridRowsSelector';
export * from './gridRowsState';
export * from './useGridRows';
export * from './gridEditRowsSelector';
export * from './useGridEditRows';