From 3a3836630c48712886d4b49f4d87db3b1ea94166 Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 10 Feb 2021 18:59:20 +0100 Subject: [PATCH 01/32] WIP implementation of edit cell with validation and serverside callback --- .../grid/_modules_/grid/GridComponent.tsx | 1 + .../_modules_/grid/components/GridCell.tsx | 4 + .../grid/components/GridRowCells.tsx | 20 +- .../components/editCell/StringEditCell.tsx | 92 ++++++++ .../grid/constants/eventsConstants.ts | 5 + .../grid/hooks/features/core/gridState.ts | 2 + .../grid/hooks/features/rows/useEditRows.ts | 154 +++++++++++++ .../_modules_/grid/hooks/root/useEvents.ts | 26 +++ packages/grid/_modules_/grid/locales/bgBG.ts | 1 + .../grid/_modules_/grid/models/api/gridApi.ts | 3 +- .../_modules_/grid/models/api/gridRowApi.ts | 12 + .../grid/models/colDef/gridColDef.ts | 10 + .../grid/models/colDef/numericColDef.ts | 17 ++ .../grid/models/colDef/stringColDef.ts | 17 ++ .../grid/_modules_/grid/models/gridCell.ts | 5 + .../_modules_/grid/models/gridOptions.tsx | 22 ++ .../grid/models/params/gridCellParams.ts | 9 + .../grid/_modules_/grid/utils/EventEmitter.ts | 4 + .../grid/_modules_/grid/utils/paramsUtils.ts | 10 +- .../src/stories/grid-rows.stories.tsx | 215 ++++++++++++++++++ 20 files changed, 623 insertions(+), 6 deletions(-) create mode 100644 packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx create mode 100644 packages/grid/_modules_/grid/hooks/features/rows/useEditRows.ts create mode 100644 packages/grid/_modules_/grid/models/colDef/numericColDef.ts create mode 100644 packages/grid/_modules_/grid/models/colDef/stringColDef.ts diff --git a/packages/grid/_modules_/grid/GridComponent.tsx b/packages/grid/_modules_/grid/GridComponent.tsx index 2e76e5da0489..cd34bae3703e 100644 --- a/packages/grid/_modules_/grid/GridComponent.tsx +++ b/packages/grid/_modules_/grid/GridComponent.tsx @@ -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 { useEditRows } from './hooks/features/rows/useEditRows'; import { useGridSorting } from './hooks/features/sorting/useGridSorting'; import { useGridApiRef } from './hooks/features/useGridApiRef'; import { useGridColumnReorder } from './hooks/features/columnReorder'; diff --git a/packages/grid/_modules_/grid/components/GridCell.tsx b/packages/grid/_modules_/grid/components/GridCell.tsx index dce91317c0db..aec744ff534f 100644 --- a/packages/grid/_modules_/grid/components/GridCell.tsx +++ b/packages/grid/_modules_/grid/components/GridCell.tsx @@ -11,6 +11,7 @@ export interface GridCellProps { width: number; height: number; showRightBorder?: boolean; + isEditable?: boolean; hasFocus?: boolean; align: GridAlignment; cssClass?: string; @@ -26,6 +27,7 @@ export const GridCell: React.FC = React.memo((props) => { colIndex, cssClass, hasFocus, + isEditable, field, formattedValue, rowIndex, @@ -50,11 +52,13 @@ export const GridCell: React.FC = 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, diff --git a/packages/grid/_modules_/grid/components/GridRowCells.tsx b/packages/grid/_modules_/grid/components/GridRowCells.tsx index 7c24c22cc368..9020c9f64009 100644 --- a/packages/grid/_modules_/grid/components/GridRowCells.tsx +++ b/packages/grid/_modules_/grid/components/GridRowCells.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { editRowsStateSelector } from '../hooks/features/rows/useEditRows'; import { GridCellClassParams, GridColumns, @@ -51,6 +52,7 @@ export const GridRowCells: React.FC = React.memo((props) => { } = props; const api = React.useContext(GridApiContext); const rowHeight = useGridSelector(api, gridDensityRowHeightSelector); + const editRowsState = useGridSelector(api, editRowsStateSelector); const cellsProps = columns.slice(firstColIdx, lastColIdx + 1).map((column, colIdx) => { const isLastColumn = firstColIdx + colIdx === columns.length - 1; @@ -66,6 +68,7 @@ export const GridRowCells: React.FC = React.memo((props) => { rowModel: row, colDef: column, rowIndex, + colIndex: colIdx, value, api: api!.current!, }); @@ -84,11 +87,8 @@ export const GridRowCells: React.FC = 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 @@ -98,9 +98,20 @@ export const GridRowCells: React.FC = React.memo((props) => { let formattedValueProp = {}; if (column.valueFormatter) { + //TODO add formatted value to cellParams? formattedValueProp = { formattedValue: column.valueFormatter(cellParams) }; } + if (!editCellState && column.renderCell) { + cellComponent = column.renderCell(cellParams); + cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellWithRenderer` }; + } + if (editCellState && column.renderEditCell) { + const params = editCellState === true ? cellParams : { ...cellParams, ...editCellState }; + cellComponent = column.renderEditCell(params); + cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellEditing` }; + } + const cellProps: GridCellProps & { children: any } = { value, field: column.field, @@ -114,6 +125,7 @@ export const GridRowCells: React.FC = React.memo((props) => { rowIndex, colIndex: colIdx + firstColIdx, children: cellComponent, + isEditable: cellParams.isEditable, hasFocus: cellFocus !== null && cellFocus.rowIndex === rowIndex && diff --git a/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx b/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx new file mode 100644 index 000000000000..1f21da497bdf --- /dev/null +++ b/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx @@ -0,0 +1,92 @@ +import * as React from 'react'; +import TextField, { TextFieldProps } from '@material-ui/core/TextField'; +import { CellParams } from '../../models/params/cellParams'; +import { isDate } from '../../utils/utils'; + +// export interface StringEditCellProps extends CellParams { +// isValid?: boolean; +// } +function mapColDefTypeToInputType(type: string) { + switch (type) { + case 'string': + return 'text'; + case 'number': + case 'date': + return type; + case 'dateTime': + return 'datetime-local'; + default: + return 'text'; + } +} +export function StringEditCell(props: CellParams & TextFieldProps) { + const { + value, + api, + field, + row, + colDef, + getValue, + rowIndex, + colIndex, + isEditable, + ...textFieldProps + } = props; + const [inputValueState, setInputValueState] = React.useState(value || ''); + + const onValueChange = React.useCallback( + (event) => { + const newValue = event.target.value; + //TODO consider removing local state, and just use gridState + setInputValueState(newValue); + + const update = { id: row.id }; + update[field] = newValue; + api.setEditCellValue(update); + }, + [api, field, row.id], + ); + + const onKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (!textFieldProps.error && event.key === 'Enter') { + const update = { id: row.id }; + update[field] = inputValueState; + api.commitCellValueChanges(update); + } + + if (event.key === 'Escape') { + api.setCellMode(row.id, field, 'view'); + } + }, + [api, field, inputValueState, textFieldProps, row.id], + ); + + React.useEffect(() => { + setInputValueState(value || ''); + }, [value]); + + const inputType = mapColDefTypeToInputType(colDef.type); + let formattedValue = inputValueState; + if (isDate(value)) { + //TODO fix issue with local date as 00:00 time returns -1 day + formattedValue = value.toISOString().substr(0, colDef.type === 'dateTime' ? 16 : 10); + } + + return ( + + ); +} +export const renderEditStringCell = (params) => ; diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index cc33f5db1be3..fe73e3d17f7c 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -1,6 +1,7 @@ // 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'; @@ -9,6 +10,10 @@ export const GRID_SCROLL = 'scroll'; export const GRID_DRAGEND = 'dragend'; // XGRID events +export const GRID_CELL_VALUE_CHANGE = 'cellValueChange'; +export const GRID_CELL_VALUE_CHANGE_COMMITTED = 'cellValueChangeCommitted'; +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'; diff --git a/packages/grid/_modules_/grid/hooks/features/core/gridState.ts b/packages/grid/_modules_/grid/hooks/features/core/gridState.ts index 7aefe4a31280..54da20c1186f 100644 --- a/packages/grid/_modules_/grid/hooks/features/core/gridState.ts +++ b/packages/grid/_modules_/grid/hooks/features/core/gridState.ts @@ -32,6 +32,7 @@ import { export interface GridState { rows: InternalGridRowsState; + editRows: EditRowsModel; pagination: PaginationState; options: GridOptions; isScrolling: boolean; @@ -53,6 +54,7 @@ export interface GridState { export const getInitialGridState: () => GridState = () => ({ rows: getInitialGridRowState(), + editRows: {}, pagination: GRID_INITIAL_PAGINATION_STATE, options: DEFAULT_GRID_OPTIONS, isScrolling: false, diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useEditRows.ts new file mode 100644 index 000000000000..9b50ed12e017 --- /dev/null +++ b/packages/grid/_modules_/grid/hooks/features/rows/useEditRows.ts @@ -0,0 +1,154 @@ +import * as React from 'react'; +import { + CELL_MODE_CHANGE, + CELL_VALUE_CHANGE, + CELL_VALUE_CHANGE_COMMITTED, + EDIT_ROW_MODEL_CHANGE, +} from '../../../constants/eventsConstants'; +import { ApiRef } from '../../../models/api/apiRef'; +import { EditRowApi } from '../../../models/api/rowApi'; +import { CellMode, CellValue } from '../../../models/cell'; +import { CellParams } from '../../../models/params/cellParams'; +import { RowModelUpdate } from '../../../models/rows'; +import { useApiEventHandler } from '../../root/useApiEventHandler'; +import { useApiMethod } from '../../root/useApiMethod'; +import { optionsSelector } from '../../utils/optionsSelector'; +import { GridState } from '../core/gridState'; +import { useGridSelector } from '../core/useGridSelector'; +import { useGridState } from '../core/useGridState'; + +export interface EditCellProps { + value: CellValue; + [prop: string]: any; +} + +export type EditRow = { [field: string]: true | EditCellProps }; +export type EditRowsModel = { [rowId: string]: EditRow }; + +export const editRowsStateSelector = (state: GridState) => state.editRows; + +export function useEditRows(apiRef: ApiRef) { + const [, setGridState, forceUpdate] = useGridState(apiRef); + const options = useGridSelector(apiRef, optionsSelector); + + const setCellEditMode = React.useCallback( + (id, field) => { + setGridState((state) => { + const currentCellEditState: EditRowsModel = { ...state.editRows }; + currentCellEditState[id] = currentCellEditState[id] || {}; + currentCellEditState[id][field] = true; + + const newEditRowsState = { ...state.editRows, ...currentCellEditState }; + + apiRef.current.publishEvent(EDIT_ROW_MODEL_CHANGE, newEditRowsState); + return { ...state, editRows: newEditRowsState }; + }); + apiRef.current.publishEvent(CELL_MODE_CHANGE, { + id, + field, + mode: 'edit', + api: apiRef.current, + }); + forceUpdate(); + }, + [apiRef, forceUpdate, setGridState], + ); + + const setCellViewMode = React.useCallback( + (id, field) => { + setGridState((state) => { + const newEditRowsState: EditRowsModel = { ...state.editRows }; + + if (!newEditRowsState[id]) { + return state; + } + + if (newEditRowsState[id][field]) { + delete newEditRowsState[id][field]; + if (!Object.keys(newEditRowsState[id]).length) { + delete newEditRowsState[id]; + } + } + apiRef.current.publishEvent(EDIT_ROW_MODEL_CHANGE, newEditRowsState); + + return { ...state, editRows: newEditRowsState }; + }); + apiRef.current.publishEvent(CELL_MODE_CHANGE, { + id, + field, + mode: 'view', + api: apiRef.current, + }); + forceUpdate(); + }, + [apiRef, forceUpdate, setGridState], + ); + + const setCellMode = React.useCallback( + (id, field, mode: CellMode) => { + if (mode === 'edit') { + setCellEditMode(id, field); + } else { + setCellViewMode(id, field); + } + }, + [setCellEditMode, setCellViewMode], + ); + + const isCellEditable = React.useCallback( + (params: CellParams) => { + return params.colDef.editable && (!options.isCellEditable || options.isCellEditable(params)); + }, + [options.isCellEditable], + ); + + const commitCellValueChanges = React.useCallback( + (update: RowModelUpdate) => { + if (apiRef.current.hasListener(CELL_VALUE_CHANGE_COMMITTED)) { + apiRef.current.publishEvent(CELL_VALUE_CHANGE_COMMITTED, { update, api: apiRef.current }); + return; + } + //TODO don't update when it's in server mode + //How should we turn server mode? featureMode === 'server' ? + + apiRef.current.updateRows([update]); + const field = Object.keys(update).find((key) => key !== 'id')!; + apiRef.current.setCellMode(update.id, field, 'view'); + }, + [apiRef], + ); + + const setEditCellValue = React.useCallback( + (update: RowModelUpdate) => { + apiRef.current.publishEvent(CELL_VALUE_CHANGE, { update, api: apiRef.current }); + }, + [apiRef], + ); + + const setEditRowsModel = React.useCallback( + (editRows: EditRowsModel) => { + setGridState((state) => { + const newState = { ...state, editRows }; + return newState; + }); + forceUpdate(); + }, + [forceUpdate, setGridState], + ); + + //TODO add those options.handlers on apiRef + useApiEventHandler(apiRef, CELL_VALUE_CHANGE, options.onEditCellValueChange); + useApiEventHandler(apiRef, CELL_VALUE_CHANGE_COMMITTED, options.onEditCellValueChangeCommitted); + useApiEventHandler(apiRef, CELL_MODE_CHANGE, options.onCellModeChange); + useApiEventHandler(apiRef, EDIT_ROW_MODEL_CHANGE, options.onEditRowModelChange); + + useApiMethod( + apiRef, + { setCellMode, isCellEditable, commitCellValueChanges, setEditCellValue, setEditRowsModel }, + 'EditRowApi', + ); + + React.useEffect(() => { + apiRef.current.setEditRowsModel(options.editRowsModel || {}); + }, [apiRef, forceUpdate, options.editRowsModel]); +} diff --git a/packages/grid/_modules_/grid/hooks/root/useEvents.ts b/packages/grid/_modules_/grid/hooks/root/useEvents.ts index f5e5c2fe93b7..5f88fc40bc06 100644 --- a/packages/grid/_modules_/grid/hooks/root/useEvents.ts +++ b/packages/grid/_modules_/grid/hooks/root/useEvents.ts @@ -25,6 +25,9 @@ import { GRID_ELEMENT_FOCUS_OUT, GRID_COMPONENT_ERROR, GRID_STATE_CHANGE, + DOUBLE_CELL_CLICK, + DOUBLE_ROW_CLICK, + DOUBLE_CLICK, } from '../../constants/eventsConstants'; import { GRID_CELL_CSS_CLASS, GRID_ROW_CSS_CLASS } from '../../constants/cssClassesConstants'; import { findParentElementFromClassName, getIdFromRowElem, isGridCell } from '../../utils/domUtils'; @@ -113,6 +116,24 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: [apiRef, getEventParams], ); + const onDoubleClickHandler = React.useCallback( + (event: MouseEvent) => { + const eventParams = getEventParams(event); + + if (!eventParams) { + return; + } + + if (eventParams.cell) { + apiRef.current.publishEvent(DOUBLE_CELL_CLICK, eventParams.cell); + } + if (eventParams.row) { + apiRef.current.publishEvent(DOUBLE_ROW_CLICK, eventParams.row); + } + }, + [apiRef, getEventParams], + ); + const onHoverHandler = React.useCallback( (event: MouseEvent) => { const eventParams = getEventParams(event); @@ -175,6 +196,9 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: useGridApiEventHandler(apiRef, GRID_COLUMN_HEADER_CLICK, options.onColumnHeaderClick); useGridApiEventHandler(apiRef, GRID_CELL_CLICK, options.onCellClick); useGridApiEventHandler(apiRef, GRID_ROW_CLICK, options.onRowClick); + useGridApiEventHandler(apiRef, GRID_DOUBLE_CELL_CLICK, options.onCellDoubleClick); + useGridApiEventHandler(apiRef, GRID_DOUBLE_ROW_CLICK, options.onRowDoubleClick); + useGridApiEventHandler(apiRef, GRID_CELL_HOVER, options.onCellHover); useGridApiEventHandler(apiRef, GRID_ROW_HOVER, options.onRowHover); useGridApiEventHandler(apiRef, GRID_COMPONENT_ERROR, options.onError); @@ -188,6 +212,7 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: const gridRootElem = gridRootRef.current; gridRootElem.addEventListener(GRID_CLICK, onClickHandler, { capture: true }); + gridRootElem.addEventListener(DOUBLE_CLICK, onDoubleClickHandler, { capture: true }); gridRootElem.addEventListener(GRID_MOUSE_HOVER, onHoverHandler, { capture: true }); gridRootElem.addEventListener(GRID_FOCUS_OUT, onFocusOutHandler); @@ -214,6 +239,7 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: getHandler, logger, onClickHandler, + onDoubleClickHandler, onHoverHandler, onFocusOutHandler, apiRef, diff --git a/packages/grid/_modules_/grid/locales/bgBG.ts b/packages/grid/_modules_/grid/locales/bgBG.ts index bf8202532579..6ac971987b10 100644 --- a/packages/grid/_modules_/grid/locales/bgBG.ts +++ b/packages/grid/_modules_/grid/locales/bgBG.ts @@ -48,6 +48,7 @@ export const bgBG: Localization = getGridLocalization({ filterOperatorNot: 'не е', filterOperatorAfter: 'е след', filterOperatorOnOrAfter: 'е на или след', + filterOperatorAfter: 'е след', filterOperatorBefore: 'е преди', filterOperatorOnOrBefore: 'е на или преди', filterPanelInputLabel: 'Стойност', diff --git a/packages/grid/_modules_/grid/models/api/gridApi.ts b/packages/grid/_modules_/grid/models/api/gridApi.ts index a9ec556108d3..6259322604c1 100644 --- a/packages/grid/_modules_/grid/models/api/gridApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridApi.ts @@ -3,7 +3,7 @@ import { ColumnResizeApi } from './columnResizeApi'; import { ComponentsApi } from './gridComponentsApi'; import { FilterApi } from './filterApi'; import { PreferencesPanelApi } from './preferencesPanelApi'; -import { GridRowApi } from './gridRowApi'; +import { GridRowApi, EditRowApi } from './gridRowApi'; import { GridColumnApi } from './gridColumnApi'; import { ColumnReorderApi } from './columnReorderApi'; import { GridSelectionApi } from './gridSelectionApi'; @@ -26,6 +26,7 @@ export type GridApi = GridCoreApi & GridDensityApi & GridEventsApi & GridRowApi & + EditRowApi & GridColumnApi & ColumnReorderApi & GridSelectionApi & diff --git a/packages/grid/_modules_/grid/models/api/gridRowApi.ts b/packages/grid/_modules_/grid/models/api/gridRowApi.ts index 27c1cb74123d..e70f75b3e4d0 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowApi.ts @@ -1,4 +1,8 @@ import { GridRowModel, GridRowId, RowModelUpdate } from '../gridRows'; +import { EditRowsModel } from '../../hooks/features/rows/useEditRows'; +import { CellMode } from '../cell'; +import { CellParams } from '../params/cellParams'; +import { RowModel, RowId, RowModelUpdate } from '../rows'; /** * The Row API interface that is available in the grid [[apiRef]]. @@ -43,3 +47,11 @@ export interface GridRowApi { */ getRowFromId: (id: GridRowId) => GridRowModel; } + +export interface EditRowApi { + setEditRowsModel: (model: EditRowsModel) => void; + setCellMode: (rowId: RowId, field: string, mode: CellMode) => void; + isCellEditable: (params: CellParams) => boolean; + setEditCellValue: (update: RowModelUpdate) => void; + commitCellValueChanges: (update: RowModelUpdate) => void; +} diff --git a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts index 4a0823ef24ec..37b06e333ffe 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts @@ -52,6 +52,11 @@ export interface GridColDef { * @default true */ resizable?: boolean; + /** + * If `true`, the column cell would be editable. + * @default true + */ + editable?: boolean; /** * A comparator function used to sort rows. */ @@ -88,6 +93,11 @@ export interface GridColDef { * @param params */ renderCell?: (params: GridCellParams) => React.ReactElement; + /** + * Allows to override the component rendered in edit cell mode for this column. + * @param params + */ + renderEditCell?: (params: CellParams) => React.ReactElement; /** * Class name that will be added in the column header cell. */ diff --git a/packages/grid/_modules_/grid/models/colDef/numericColDef.ts b/packages/grid/_modules_/grid/models/colDef/numericColDef.ts new file mode 100644 index 000000000000..5773619dd54a --- /dev/null +++ b/packages/grid/_modules_/grid/models/colDef/numericColDef.ts @@ -0,0 +1,17 @@ +import { renderEditStringCell } from '../../components/editCell/StringEditCell'; +import { numberComparer } from '../../utils/sortingUtils'; +import { isNumber } from '../../utils/utils'; +import { getNumericColumnOperators } from './numericOperators'; +import { STRING_COL_DEF } from './stringColDef'; +import { ColTypeDef } from './colDef'; + +export const NUMERIC_COL_DEF: ColTypeDef = { + ...STRING_COL_DEF, + type: 'number', + align: 'right', + headerAlign: 'right', + sortComparator: numberComparer, + valueFormatter: ({ value }) => (value && isNumber(value) && value.toLocaleString()) || value, + filterOperators: getNumericColumnOperators(), + renderEditCell: renderEditStringCell, +}; diff --git a/packages/grid/_modules_/grid/models/colDef/stringColDef.ts b/packages/grid/_modules_/grid/models/colDef/stringColDef.ts new file mode 100644 index 000000000000..4749283e7718 --- /dev/null +++ b/packages/grid/_modules_/grid/models/colDef/stringColDef.ts @@ -0,0 +1,17 @@ +import { renderEditStringCell } from '../../components/editCell/StringEditCell'; +import { stringNumberComparer } from '../../utils/sortingUtils'; +import { ColTypeDef } from './colDef'; +import { getStringOperators } from './stringOperators'; + +export const STRING_COL_DEF: ColTypeDef = { + width: 100, + hide: false, + sortable: true, + resizable: true, + filterable: true, + sortComparator: stringNumberComparer, + type: 'string', + align: 'left', + filterOperators: getStringOperators(), + renderEditCell: renderEditStringCell, +}; diff --git a/packages/grid/_modules_/grid/models/gridCell.ts b/packages/grid/_modules_/grid/models/gridCell.ts index 84455990dc81..7c717180f15d 100644 --- a/packages/grid/_modules_/grid/models/gridCell.ts +++ b/packages/grid/_modules_/grid/models/gridCell.ts @@ -1,3 +1,8 @@ +/** + * The Cell mode values. + */ +export type CellMode = 'edit' | 'view'; + /** * The cell value type. */ diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 329d97e2ae3b..a1371d58bcce 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -1,5 +1,6 @@ import { GRID_DEFAULT_LOCALE_TEXT } from '../constants/localeTextConstants'; import { FilterModel } from '../hooks/features/filter/FilterModelState'; +import { EditRowsModel } from '../hooks/features/rows/useEditRows'; import { Logger } from '../hooks/utils/useLogger'; import { GridLocaleText } from './api/gridLocaleTextApi'; import { GridColumnTypesRecord } from './colDef/gridColTypeDef'; @@ -215,6 +216,11 @@ export interface GridOptions { * @param param With all properties from [[GridCellParams]]. */ onCellClick?: (param: GridCellParams) => void; + /** + * Callback fired when a double click event comes from a cell element. + * @param param With all properties from [[CellParams]]. + */ + onCellDoubleClick?: (param: CellParams) => void; /** * Callback fired when a hover event comes from a cell element. * @param param With all properties from [[GridCellParams]]. @@ -225,6 +231,11 @@ export interface GridOptions { * @param param With all properties from [[GridRowParams]]. */ onRowClick?: (param: GridRowParams) => void; + /** + * Callback fired when a click event comes from a row container element. + * @param param With all properties from [[RowParams]]. + */ + onRowDoubleClick?: (param: RowParams) => void; /** * Callback fired when a hover event comes from a row container element. * @param param With all properties from [[GridRowParams]]. @@ -273,6 +284,17 @@ export interface GridOptions { * Callback fired when the state of the grid is updated. */ onStateChange?: (params: any) => void; + + editRowsModel?: EditRowsModel; + /** + * Callback fired when the cell is rendered. + */ + isCellEditable?: (params: CellParams) => boolean; + onCellModeChange?: ({ id: RowId, field: string, api: any, mode: CellMode }) => void; + onEditCellValueChange?: (params: { api: any; update: RowModelUpdate }) => void; + onEditCellValueChangeCommitted?: (params: { api: any; update: RowModelUpdate }) => void; + onEditRowModelChange?: (model: EditRowsModel) => void; + /** * Extend native column types with your new column types. */ diff --git a/packages/grid/_modules_/grid/models/params/gridCellParams.ts b/packages/grid/_modules_/grid/models/params/gridCellParams.ts index a07889eb4162..d7e55516c610 100644 --- a/packages/grid/_modules_/grid/models/params/gridCellParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridCellParams.ts @@ -36,8 +36,17 @@ export interface GridCellParams { rowIndex?: number; /** * GridApiRef that let you manipulate the grid. + * The column index that the current cell belongs to. + */ + colIndex?: number; + /** + * GridApi that let you manipulate the grid. */ api: any; + /** + * true: if the cell is editable + */ + isEditable?: boolean; } /** diff --git a/packages/grid/_modules_/grid/utils/EventEmitter.ts b/packages/grid/_modules_/grid/utils/EventEmitter.ts index c6eed5221116..ba6d7b90ec4a 100644 --- a/packages/grid/_modules_/grid/utils/EventEmitter.ts +++ b/packages/grid/_modules_/grid/utils/EventEmitter.ts @@ -10,6 +10,10 @@ export class EventEmitter { events: { [key: string]: Listener[] } = {}; + hasListener(eventName: string): boolean { + return this.events[eventName].length > 0; + } + on(eventName: string, listener: Listener): void { if (!Array.isArray(this.events[eventName])) { this.events[eventName] = []; diff --git a/packages/grid/_modules_/grid/utils/paramsUtils.ts b/packages/grid/_modules_/grid/utils/paramsUtils.ts index 5f959be73798..2986b51fa05b 100644 --- a/packages/grid/_modules_/grid/utils/paramsUtils.ts +++ b/packages/grid/_modules_/grid/utils/paramsUtils.ts @@ -11,6 +11,7 @@ export function buildGridCellParams({ element, value, rowIndex, + colIndex, rowModel, colDef, api, @@ -18,11 +19,12 @@ export function buildGridCellParams({ rowModel: GridRowModel; colDef: GridColDef; rowIndex?: number; + colIndex?: number; value: GridCellValue; api: GridApi; element?: HTMLElement; }): GridCellParams { - return { + const params: CellParams = { element, value, field: colDef?.field, @@ -60,8 +62,14 @@ export function buildGridCellParams({ row: rowModel, colDef, rowIndex, + colIndex: colIndex || (colDef && api.getColumnIndex(colDef.field, true)), api, }; + const isEditableAttr = element && element.getAttribute('data-editable'); + params.isEditable = + isEditableAttr != null ? isEditableAttr === 'true' : colDef && api.isCellEditable(params); + + return params; } export function buildGridRowParams({ diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 507c90084897..c51ddcd72ff9 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -1,7 +1,13 @@ +import InputAdornment from '@material-ui/core/InputAdornment'; +import { makeStyles } from '@material-ui/core/styles'; import * as React from 'react'; import Button from '@material-ui/core/Button'; import { GridRowData, useGridApiRef, XGrid } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; +import { + EditCellProps, + EditRowsModel, +} from '../../../grid/_modules_/grid/hooks/features/rows/useEditRows'; import { randomInt } from '../data/random-generator'; export default { @@ -192,3 +198,212 @@ export function ScrollIssue() { ); } + +// Requirements +/* +- Turn edit mode, using a button or events such as double click... +- Expose double click cell +- Be able to edit rows as well as individual cell +- Validate the value of a cell +- render different input component according to the type of value to edit +- fix issue with number as IDs +- Provide a basic Edit UX out of the box +- Customise the edit for a particular cell +- Some columns should not be editable +- Some rows should not be editable + +colDef.renderEditCell + + boolean} + onCellModeChange + onRowModeChange + onCellValueChange=??? => while typing, allows to validate? Or feedback user... + onCellValueChangeCommitted => pressing enter? What happens when you press ESC? + /> + + */ +// TODO demo with Cell edit with value getter +// Todo demo with cell not editable according to value +// demo with cell edit validation +// demo with cell edit validation serverside ie username +// demo with cell edit client and serverside ie username + +// TODO create inputs for each col types + +const baselineEditProps = { + rows: [ + { + id: 0, + firstname: 'Damien', + lastname: 'Tassone', + email: 'damien@material-ui.com', + username: 'Damo', + lastLogin: new Date(), + age: 25, + DOB: new Date(1996, 10, 2), + meetup: new Date(2020, 2, 25, 10, 50, 0), + }, + { + id: 1, + firstname: 'Jon', + lastname: 'Wood', + email: 'jon@material-ui.com', + username: 'jon', + lastLogin: new Date(), + age: 25, + DOB: new Date(1992, 1, 20), + meetup: new Date(2020, 4, 15, 10, 50, 0), + }, + { + id: 2, + firstname: 'James', + lastname: 'Smith', + email: 'james@material-ui.com', + username: 'smithhhh', + lastLogin: new Date(), + age: 25, + DOB: new Date(1986, 0, 12), + meetup: new Date(2020, 3, 5, 10, 50, 0), + }, + ], + columns: [ + { field: 'firstname', editable: true }, + { field: 'lastname', editable: true }, + { + field: 'fullname', + editable: true, + valueGetter: ({ row }) => `${row.firstname} ${row.lastname}`, + }, //needs special field, value getter... + { field: 'username', editable: true }, //TODO implement server validation + { field: 'email', editable: true, width: 150 }, //needs validation + { field: 'age', width: 50, type: 'number', editable: true }, + { field: 'DOB', width: 120, type: 'date', editable: true }, + { field: 'meetup', width: 180, type: 'dateTime', editable: true }, + { field: 'lastLogin', width: 180, type: 'dateTime', editable: false }, + ], +}; +function validateEmail(email) { + const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); +} + +//MuiDataGrid-cellEditing +const useStyles = makeStyles({ + root: { + '& .MuiDataGrid-cellEditable': { + backgroundColor: 'rgba(184,250,158,0.19)', + color: '#1a3e72', + }, + '& .MuiDataGrid-cellEditing': { + backgroundColor: 'rgb(255,215,115, 0.19)', + color: '#1a3e72', + }, + }, +}); + +export function EditRowsPoc() { + const apiRef = useApiRef(); + const classes = useStyles(); + + const [selectedCell, setSelectedCell] = React.useState<[string, string] | null>(null); + const [isEditable, setIsEditable] = React.useState(false); + const [editRowsModel, setEditRowsModel] = React.useState({}); + + const editRow = React.useCallback(() => { + if (!selectedCell) { + return; + } + + setEditRowsModel((state) => { + const editRowState: EditRowsModel = { ...state }; + editRowState[selectedCell[0]] = editRowState[selectedCell[0]] + ? editRowState[selectedCell[0]] + : {}; + editRowState[selectedCell[0]][selectedCell[1]] = true; + + return { ...state, ...editRowState }; + }); + }, [selectedCell]); + + const onCellClick = React.useCallback((params: CellParams) => { + setSelectedCell([params.row.id!.toString(), params.field]); + setIsEditable(!!params.isEditable); + }, []); + + const onCellDoubleClick = React.useCallback( + (params: CellParams) => { + if (params.isEditable) { + apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); + } + }, + [apiRef], + ); + + const isCellEditable = React.useCallback((params: CellParams) => params.row.id !== 0, []); + + const onEditCellValueChange = React.useCallback(({ update }) => { + if (update.email) { + const isValid = validateEmail(update.email); + // how do we feedback users? + // how do we prevent committing? + const newState = {}; + newState[update.id] = { email: { value: update.email, error: !isValid } }; + setEditRowsModel((state) => ({ ...state, ...newState })); + } + }, []); + + const onEditCellValueChangeCommitted = React.useCallback( + ({ update, api }) => { + const field = Object.keys(update).find((key) => key !== 'id')!; + if (update.email) { + const newState = {}; + const componentProps = { + InputProps: { endAdornment: }, + }; + newState[update.id] = {}; + newState[update.id][field] = { value: update.email, ...componentProps }; + setEditRowsModel((state) => ({ ...state, ...newState })); + setTimeout(() => { + apiRef.current.updateRows([update]); + apiRef.current.setCellMode(update.id, field, 'view'); + }, 2000); + } else if (update.fullname) { + const [firstname, lastname] = update.fullname.split(' '); + apiRef.current.updateRows([{ id: update.id, firstname, lastname }]); + apiRef.current.setCellMode(update.id, field, 'view'); + } else { + apiRef.current.updateRows([update]); + apiRef.current.setCellMode(update.id, field, 'view'); + } + }, + [apiRef], + ); + + return ( + + Green cells are editable! Click + EDIT or Double click +
+ +
+
+ +
+
+ ); +} From b16062a040b3e17fada517cef487e22a4cb5cc14 Mon Sep 17 00:00:00 2001 From: damien Date: Mon, 22 Feb 2021 16:35:44 +0100 Subject: [PATCH 02/32] fix rebase issues --- .../grid/_modules_/grid/GridComponent.tsx | 3 +- .../grid/components/GridRowCells.tsx | 4 +- .../components/editCell/StringEditCell.tsx | 4 +- .../grid/constants/eventsConstants.ts | 2 + .../grid/hooks/features/core/gridState.ts | 3 +- .../{useEditRows.ts => useGridEditRows.ts} | 80 ++++++++++--------- .../grid/hooks/features/rows/useGridRows.ts | 4 +- .../_modules_/grid/hooks/root/useEvents.ts | 12 +-- .../grid/_modules_/grid/models/api/gridApi.ts | 4 +- .../_modules_/grid/models/api/gridRowApi.ts | 23 +++--- .../grid/models/colDef/gridColDef.ts | 2 +- .../grid/models/colDef/gridDateColDef.ts | 3 + .../grid/models/colDef/gridNumericColDef.ts | 2 + .../grid/models/colDef/gridStringColDef.ts | 2 + .../grid/models/colDef/numericColDef.ts | 17 ---- .../grid/models/colDef/stringColDef.ts | 17 ---- .../grid/_modules_/grid/models/gridCell.ts | 2 +- .../_modules_/grid/models/gridOptions.tsx | 17 ++-- .../grid/_modules_/grid/models/gridRows.ts | 2 +- .../grid/_modules_/grid/utils/paramsUtils.ts | 2 +- .../src/stories/grid-rows.stories.tsx | 28 ++++--- 21 files changed, 112 insertions(+), 121 deletions(-) rename packages/grid/_modules_/grid/hooks/features/rows/{useEditRows.ts => useGridEditRows.ts} (57%) delete mode 100644 packages/grid/_modules_/grid/models/colDef/numericColDef.ts delete mode 100644 packages/grid/_modules_/grid/models/colDef/stringColDef.ts diff --git a/packages/grid/_modules_/grid/GridComponent.tsx b/packages/grid/_modules_/grid/GridComponent.tsx index cd34bae3703e..15489cdfe0f2 100644 --- a/packages/grid/_modules_/grid/GridComponent.tsx +++ b/packages/grid/_modules_/grid/GridComponent.tsx @@ -22,7 +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 { useEditRows } from './hooks/features/rows/useEditRows'; +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'; @@ -76,6 +76,7 @@ export const GridComponent = React.forwardRef = React.memo((props) => { } = props; const api = React.useContext(GridApiContext); const rowHeight = useGridSelector(api, gridDensityRowHeightSelector); - const editRowsState = useGridSelector(api, editRowsStateSelector); + const editRowsState = useGridSelector(api, gridEditRowsStateSelector); const cellsProps = columns.slice(firstColIdx, lastColIdx + 1).map((column, colIdx) => { const isLastColumn = firstColIdx + colIdx === columns.length - 1; diff --git a/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx b/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx index 1f21da497bdf..36693a2735a9 100644 --- a/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx +++ b/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import TextField, { TextFieldProps } from '@material-ui/core/TextField'; -import { CellParams } from '../../models/params/cellParams'; +import { GridCellParams } from '../../models/params/gridCellParams'; import { isDate } from '../../utils/utils'; // export interface StringEditCellProps extends CellParams { @@ -19,7 +19,7 @@ function mapColDefTypeToInputType(type: string) { return 'text'; } } -export function StringEditCell(props: CellParams & TextFieldProps) { +export function StringEditCell(props: GridCellParams & TextFieldProps) { const { value, api, diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index fe73e3d17f7c..59f847e53bdf 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -18,8 +18,10 @@ 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'; diff --git a/packages/grid/_modules_/grid/hooks/features/core/gridState.ts b/packages/grid/_modules_/grid/hooks/features/core/gridState.ts index 54da20c1186f..7aa79fb14760 100644 --- a/packages/grid/_modules_/grid/hooks/features/core/gridState.ts +++ b/packages/grid/_modules_/grid/hooks/features/core/gridState.ts @@ -23,6 +23,7 @@ import { } from '../pagination/gridPaginationReducer'; import { GridPreferencePanelState } from '../preferencesPanel/gridPreferencePanelState'; import { getInitialGridRowState, InternalGridRowsState } from '../rows/gridRowsState'; +import { GridEditRowsModel } from '../rows/useGridEditRows'; import { GridSelectionState } from '../selection/gridSelectionState'; import { getInitialGridSortingState, GridSortingState } from '../sorting/gridSortingState'; import { @@ -32,7 +33,7 @@ import { export interface GridState { rows: InternalGridRowsState; - editRows: EditRowsModel; + editRows: GridEditRowsModel; pagination: PaginationState; options: GridOptions; isScrolling: boolean; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts similarity index 57% rename from packages/grid/_modules_/grid/hooks/features/rows/useEditRows.ts rename to packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index 9b50ed12e017..73c25bcc92a6 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -1,49 +1,50 @@ import * as React from 'react'; import { - CELL_MODE_CHANGE, - CELL_VALUE_CHANGE, - CELL_VALUE_CHANGE_COMMITTED, - EDIT_ROW_MODEL_CHANGE, + GRID_CELL_MODE_CHANGE, + GRID_CELL_VALUE_CHANGE, + GRID_CELL_VALUE_CHANGE_COMMITTED, + GRID_EDIT_ROW_MODEL_CHANGE, } from '../../../constants/eventsConstants'; -import { ApiRef } from '../../../models/api/apiRef'; -import { EditRowApi } from '../../../models/api/rowApi'; -import { CellMode, CellValue } from '../../../models/cell'; -import { CellParams } from '../../../models/params/cellParams'; -import { RowModelUpdate } from '../../../models/rows'; -import { useApiEventHandler } from '../../root/useApiEventHandler'; -import { useApiMethod } from '../../root/useApiMethod'; +import { GridApiRef } from '../../../models/api/gridApiRef'; +import { GridEditRowApi } from '../../../models/api/gridRowApi'; +import { GridCellMode, GridCellValue } from '../../../models/gridCell'; +import { GridRowModelUpdate } from '../../../models/gridRows'; +import { GridCellParams } from '../../../models/params/gridCellParams'; +import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; +import { useGridApiMethod } from '../../root/useGridApiMethod'; + import { optionsSelector } from '../../utils/optionsSelector'; import { GridState } from '../core/gridState'; import { useGridSelector } from '../core/useGridSelector'; import { useGridState } from '../core/useGridState'; -export interface EditCellProps { - value: CellValue; +export interface GridEditCellProps { + value: GridCellValue; [prop: string]: any; } -export type EditRow = { [field: string]: true | EditCellProps }; -export type EditRowsModel = { [rowId: string]: EditRow }; +export type GridEditRow = { [field: string]: true | GridEditCellProps }; +export type GridEditRowsModel = { [rowId: string]: GridEditRow }; -export const editRowsStateSelector = (state: GridState) => state.editRows; +export const gridEditRowsStateSelector = (state: GridState) => state.editRows; -export function useEditRows(apiRef: ApiRef) { +export function useGridEditRows(apiRef: GridApiRef) { const [, setGridState, forceUpdate] = useGridState(apiRef); const options = useGridSelector(apiRef, optionsSelector); const setCellEditMode = React.useCallback( (id, field) => { setGridState((state) => { - const currentCellEditState: EditRowsModel = { ...state.editRows }; + const currentCellEditState: GridEditRowsModel = { ...state.editRows }; currentCellEditState[id] = currentCellEditState[id] || {}; currentCellEditState[id][field] = true; const newEditRowsState = { ...state.editRows, ...currentCellEditState }; - apiRef.current.publishEvent(EDIT_ROW_MODEL_CHANGE, newEditRowsState); + apiRef.current.publishEvent(GRID_EDIT_ROW_MODEL_CHANGE, newEditRowsState); return { ...state, editRows: newEditRowsState }; }); - apiRef.current.publishEvent(CELL_MODE_CHANGE, { + apiRef.current.publishEvent(GRID_CELL_MODE_CHANGE, { id, field, mode: 'edit', @@ -57,7 +58,7 @@ export function useEditRows(apiRef: ApiRef) { const setCellViewMode = React.useCallback( (id, field) => { setGridState((state) => { - const newEditRowsState: EditRowsModel = { ...state.editRows }; + const newEditRowsState: GridEditRowsModel = { ...state.editRows }; if (!newEditRowsState[id]) { return state; @@ -69,11 +70,11 @@ export function useEditRows(apiRef: ApiRef) { delete newEditRowsState[id]; } } - apiRef.current.publishEvent(EDIT_ROW_MODEL_CHANGE, newEditRowsState); + apiRef.current.publishEvent(GRID_EDIT_ROW_MODEL_CHANGE, newEditRowsState); return { ...state, editRows: newEditRowsState }; }); - apiRef.current.publishEvent(CELL_MODE_CHANGE, { + apiRef.current.publishEvent(GRID_CELL_MODE_CHANGE, { id, field, mode: 'view', @@ -85,7 +86,7 @@ export function useEditRows(apiRef: ApiRef) { ); const setCellMode = React.useCallback( - (id, field, mode: CellMode) => { + (id, field, mode: GridCellMode) => { if (mode === 'edit') { setCellEditMode(id, field); } else { @@ -96,16 +97,19 @@ export function useEditRows(apiRef: ApiRef) { ); const isCellEditable = React.useCallback( - (params: CellParams) => { + (params: GridCellParams) => { return params.colDef.editable && (!options.isCellEditable || options.isCellEditable(params)); }, [options.isCellEditable], ); const commitCellValueChanges = React.useCallback( - (update: RowModelUpdate) => { - if (apiRef.current.hasListener(CELL_VALUE_CHANGE_COMMITTED)) { - apiRef.current.publishEvent(CELL_VALUE_CHANGE_COMMITTED, { update, api: apiRef.current }); + (update: GridRowModelUpdate) => { + if (apiRef.current.hasListener(GRID_CELL_VALUE_CHANGE_COMMITTED)) { + apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, { + update, + api: apiRef.current, + }); return; } //TODO don't update when it's in server mode @@ -119,14 +123,14 @@ export function useEditRows(apiRef: ApiRef) { ); const setEditCellValue = React.useCallback( - (update: RowModelUpdate) => { - apiRef.current.publishEvent(CELL_VALUE_CHANGE, { update, api: apiRef.current }); + (update: GridRowModelUpdate) => { + apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE, { update, api: apiRef.current }); }, [apiRef], ); const setEditRowsModel = React.useCallback( - (editRows: EditRowsModel) => { + (editRows: GridEditRowsModel) => { setGridState((state) => { const newState = { ...state, editRows }; return newState; @@ -137,12 +141,16 @@ export function useEditRows(apiRef: ApiRef) { ); //TODO add those options.handlers on apiRef - useApiEventHandler(apiRef, CELL_VALUE_CHANGE, options.onEditCellValueChange); - useApiEventHandler(apiRef, CELL_VALUE_CHANGE_COMMITTED, options.onEditCellValueChangeCommitted); - useApiEventHandler(apiRef, CELL_MODE_CHANGE, options.onCellModeChange); - useApiEventHandler(apiRef, EDIT_ROW_MODEL_CHANGE, options.onEditRowModelChange); + useGridApiEventHandler(apiRef, GRID_CELL_VALUE_CHANGE, options.onEditCellValueChange); + useGridApiEventHandler( + apiRef, + GRID_CELL_VALUE_CHANGE_COMMITTED, + options.onEditCellValueChangeCommitted, + ); + useGridApiEventHandler(apiRef, GRID_CELL_MODE_CHANGE, options.onCellModeChange); + useGridApiEventHandler(apiRef, GRID_EDIT_ROW_MODEL_CHANGE, options.onEditRowModelChange); - useApiMethod( + useGridApiMethod( apiRef, { setCellMode, isCellEditable, commitCellValueChanges, setEditCellValue, setEditRowsModel }, 'EditRowApi', diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index f04f44c429a6..376c3ec93b23 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -9,7 +9,7 @@ import { GridRowApi } from '../../../models/api/gridRowApi'; import { checkGridRowHasId, GridRowModel, - RowModelUpdate, + GridRowModelUpdate, GridRowId, GridRowsProp, GridRowIdGetter, @@ -133,7 +133,7 @@ export const useGridRows = ( ); const updateRows = React.useCallback( - (updates: RowModelUpdate[]) => { + (updates: GridRowModelUpdate[]) => { // we removes duplicate updates. A server can batch updates, and send several updates for the same row in one fn call. const uniqUpdates = updates.reduce((uniq, update) => { const udpateWithId = addGridRowId(update, getRowIdProp); diff --git a/packages/grid/_modules_/grid/hooks/root/useEvents.ts b/packages/grid/_modules_/grid/hooks/root/useEvents.ts index 5f88fc40bc06..2a10d3416583 100644 --- a/packages/grid/_modules_/grid/hooks/root/useEvents.ts +++ b/packages/grid/_modules_/grid/hooks/root/useEvents.ts @@ -25,9 +25,9 @@ import { GRID_ELEMENT_FOCUS_OUT, GRID_COMPONENT_ERROR, GRID_STATE_CHANGE, - DOUBLE_CELL_CLICK, - DOUBLE_ROW_CLICK, - DOUBLE_CLICK, + GRID_DOUBLE_CELL_CLICK, + GRID_DOUBLE_ROW_CLICK, + GRID_DOUBLE_CLICK, } from '../../constants/eventsConstants'; import { GRID_CELL_CSS_CLASS, GRID_ROW_CSS_CLASS } from '../../constants/cssClassesConstants'; import { findParentElementFromClassName, getIdFromRowElem, isGridCell } from '../../utils/domUtils'; @@ -125,10 +125,10 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: } if (eventParams.cell) { - apiRef.current.publishEvent(DOUBLE_CELL_CLICK, eventParams.cell); + apiRef.current.publishEvent(GRID_DOUBLE_CELL_CLICK, eventParams.cell); } if (eventParams.row) { - apiRef.current.publishEvent(DOUBLE_ROW_CLICK, eventParams.row); + apiRef.current.publishEvent(GRID_DOUBLE_ROW_CLICK, eventParams.row); } }, [apiRef, getEventParams], @@ -212,7 +212,7 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: const gridRootElem = gridRootRef.current; gridRootElem.addEventListener(GRID_CLICK, onClickHandler, { capture: true }); - gridRootElem.addEventListener(DOUBLE_CLICK, onDoubleClickHandler, { capture: true }); + gridRootElem.addEventListener(GRID_DOUBLE_CLICK, onDoubleClickHandler, { capture: true }); gridRootElem.addEventListener(GRID_MOUSE_HOVER, onHoverHandler, { capture: true }); gridRootElem.addEventListener(GRID_FOCUS_OUT, onFocusOutHandler); diff --git a/packages/grid/_modules_/grid/models/api/gridApi.ts b/packages/grid/_modules_/grid/models/api/gridApi.ts index 6259322604c1..b0c898dee032 100644 --- a/packages/grid/_modules_/grid/models/api/gridApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridApi.ts @@ -3,7 +3,7 @@ import { ColumnResizeApi } from './columnResizeApi'; import { ComponentsApi } from './gridComponentsApi'; import { FilterApi } from './filterApi'; import { PreferencesPanelApi } from './preferencesPanelApi'; -import { GridRowApi, EditRowApi } from './gridRowApi'; +import { GridRowApi, GridEditRowApi } from './gridRowApi'; import { GridColumnApi } from './gridColumnApi'; import { ColumnReorderApi } from './columnReorderApi'; import { GridSelectionApi } from './gridSelectionApi'; @@ -26,7 +26,7 @@ export type GridApi = GridCoreApi & GridDensityApi & GridEventsApi & GridRowApi & - EditRowApi & + GridEditRowApi & GridColumnApi & ColumnReorderApi & GridSelectionApi & diff --git a/packages/grid/_modules_/grid/models/api/gridRowApi.ts b/packages/grid/_modules_/grid/models/api/gridRowApi.ts index e70f75b3e4d0..3c50bffb7a57 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowApi.ts @@ -1,8 +1,7 @@ -import { GridRowModel, GridRowId, RowModelUpdate } from '../gridRows'; -import { EditRowsModel } from '../../hooks/features/rows/useEditRows'; -import { CellMode } from '../cell'; -import { CellParams } from '../params/cellParams'; -import { RowModel, RowId, RowModelUpdate } from '../rows'; +import { GridCellMode } from '../gridCell'; +import { GridRowModel, GridRowId, GridRowModelUpdate } from '../gridRows'; +import { GridEditRowsModel } from '../../hooks/features/rows/useGridEditRows'; +import { GridCellParams } from '../params/gridCellParams'; /** * The Row API interface that is available in the grid [[apiRef]]. @@ -30,7 +29,7 @@ export interface GridRowApi { * Update any properties of the current set of GridRowData[]. * @param updates */ - updateRows: (updates: RowModelUpdate[]) => void; + updateRows: (updates: GridRowModelUpdate[]) => void; /** * Get the GridRowId of a row at a specific position. * @param index @@ -48,10 +47,10 @@ export interface GridRowApi { getRowFromId: (id: GridRowId) => GridRowModel; } -export interface EditRowApi { - setEditRowsModel: (model: EditRowsModel) => void; - setCellMode: (rowId: RowId, field: string, mode: CellMode) => void; - isCellEditable: (params: CellParams) => boolean; - setEditCellValue: (update: RowModelUpdate) => void; - commitCellValueChanges: (update: RowModelUpdate) => void; +export interface GridEditRowApi { + setEditRowsModel: (model: GridEditRowsModel) => void; + setCellMode: (rowId: GridRowId, field: string, mode: GridCellMode) => void; + isCellEditable: (params: GridCellParams) => boolean; + setEditCellValue: (update: GridRowModelUpdate) => void; + commitCellValueChanges: (update: GridRowModelUpdate) => void; } diff --git a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts index 37b06e333ffe..421fb3c9ded6 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts @@ -97,7 +97,7 @@ export interface GridColDef { * Allows to override the component rendered in edit cell mode for this column. * @param params */ - renderEditCell?: (params: CellParams) => React.ReactElement; + renderEditCell?: (params: GridCellParams) => React.ReactElement; /** * Class name that will be added in the column header cell. */ diff --git a/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts index b0bc947a885d..30644572de02 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts @@ -1,3 +1,4 @@ +import { renderEditStringCell } from '../../components/editCell/StringEditCell'; import { isDate } from '../../utils/utils'; import { gridDateComparer } from '../../utils/sortingUtils'; import { GridCellValue } from '../gridCell'; @@ -25,6 +26,7 @@ export const GRID_DATE_COL_DEF: GridColTypeDef = { sortComparator: gridDateComparer, valueFormatter: gridDateFormatter, filterOperators: getGridDateOperators(), + renderEditCell: renderEditStringCell, }; export const GRID_DATETIME_COL_DEF: GridColTypeDef = { @@ -33,4 +35,5 @@ export const GRID_DATETIME_COL_DEF: GridColTypeDef = { sortComparator: gridDateComparer, valueFormatter: gridDateTimeFormatter, filterOperators: getGridDateOperators(true), + renderEditCell: renderEditStringCell, }; diff --git a/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts index 87c2c00debbf..190043b076a5 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts @@ -1,3 +1,4 @@ +import { renderEditStringCell } from '../../components/editCell/StringEditCell'; import { gridNumberComparer } from '../../utils/sortingUtils'; import { isNumber } from '../../utils/utils'; import { getGridNumericColumnOperators } from './gridNumericOperators'; @@ -12,4 +13,5 @@ export const GRID_NUMERIC_COL_DEF: GridColTypeDef = { sortComparator: gridNumberComparer, valueFormatter: ({ value }) => (value && isNumber(value) && value.toLocaleString()) || value, filterOperators: getGridNumericColumnOperators(), + renderEditCell: renderEditStringCell, }; diff --git a/packages/grid/_modules_/grid/models/colDef/gridStringColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridStringColDef.ts index aa9913fc558c..5d9a000357a4 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridStringColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridStringColDef.ts @@ -1,3 +1,4 @@ +import { renderEditStringCell } from '../../components/editCell/StringEditCell'; import { gridStringNumberComparer } from '../../utils/sortingUtils'; import { GridColTypeDef } from './gridColDef'; import { getGridStringOperators } from './gridStringOperators'; @@ -12,4 +13,5 @@ export const GRID_STRING_COL_DEF: GridColTypeDef = { type: 'string', align: 'left', filterOperators: getGridStringOperators(), + renderEditCell: renderEditStringCell, }; diff --git a/packages/grid/_modules_/grid/models/colDef/numericColDef.ts b/packages/grid/_modules_/grid/models/colDef/numericColDef.ts deleted file mode 100644 index 5773619dd54a..000000000000 --- a/packages/grid/_modules_/grid/models/colDef/numericColDef.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { renderEditStringCell } from '../../components/editCell/StringEditCell'; -import { numberComparer } from '../../utils/sortingUtils'; -import { isNumber } from '../../utils/utils'; -import { getNumericColumnOperators } from './numericOperators'; -import { STRING_COL_DEF } from './stringColDef'; -import { ColTypeDef } from './colDef'; - -export const NUMERIC_COL_DEF: ColTypeDef = { - ...STRING_COL_DEF, - type: 'number', - align: 'right', - headerAlign: 'right', - sortComparator: numberComparer, - valueFormatter: ({ value }) => (value && isNumber(value) && value.toLocaleString()) || value, - filterOperators: getNumericColumnOperators(), - renderEditCell: renderEditStringCell, -}; diff --git a/packages/grid/_modules_/grid/models/colDef/stringColDef.ts b/packages/grid/_modules_/grid/models/colDef/stringColDef.ts deleted file mode 100644 index 4749283e7718..000000000000 --- a/packages/grid/_modules_/grid/models/colDef/stringColDef.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { renderEditStringCell } from '../../components/editCell/StringEditCell'; -import { stringNumberComparer } from '../../utils/sortingUtils'; -import { ColTypeDef } from './colDef'; -import { getStringOperators } from './stringOperators'; - -export const STRING_COL_DEF: ColTypeDef = { - width: 100, - hide: false, - sortable: true, - resizable: true, - filterable: true, - sortComparator: stringNumberComparer, - type: 'string', - align: 'left', - filterOperators: getStringOperators(), - renderEditCell: renderEditStringCell, -}; diff --git a/packages/grid/_modules_/grid/models/gridCell.ts b/packages/grid/_modules_/grid/models/gridCell.ts index 7c717180f15d..6e19aa4c53e7 100644 --- a/packages/grid/_modules_/grid/models/gridCell.ts +++ b/packages/grid/_modules_/grid/models/gridCell.ts @@ -1,7 +1,7 @@ /** * The Cell mode values. */ -export type CellMode = 'edit' | 'view'; +export type GridCellMode = 'edit' | 'view'; /** * The cell value type. diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index a1371d58bcce..04553a71ca25 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -1,12 +1,13 @@ import { GRID_DEFAULT_LOCALE_TEXT } from '../constants/localeTextConstants'; import { FilterModel } from '../hooks/features/filter/FilterModelState'; -import { EditRowsModel } from '../hooks/features/rows/useEditRows'; +import { GridEditRowsModel } from '../hooks/features/rows/useGridEditRows'; import { Logger } from '../hooks/utils/useLogger'; import { GridLocaleText } from './api/gridLocaleTextApi'; import { GridColumnTypesRecord } from './colDef/gridColTypeDef'; import { getGridDefaultColumnTypes } from './colDef/gridDefaultColumnTypes'; import { GridDensity, GridDensityTypes } from './gridDensity'; import { GridFeatureMode, GridFeatureModeConstant } from './gridFeatureMode'; +import { GridRowModelUpdate } from './gridRows'; import { GridCellParams } from './params/gridCellParams'; import { GridColParams } from './params/gridColParams'; import { GridFilterModelParams } from './params/gridFilterModelParams'; @@ -220,7 +221,7 @@ export interface GridOptions { * Callback fired when a double click event comes from a cell element. * @param param With all properties from [[CellParams]]. */ - onCellDoubleClick?: (param: CellParams) => void; + onCellDoubleClick?: (param: GridCellParams) => void; /** * Callback fired when a hover event comes from a cell element. * @param param With all properties from [[GridCellParams]]. @@ -235,7 +236,7 @@ export interface GridOptions { * Callback fired when a click event comes from a row container element. * @param param With all properties from [[RowParams]]. */ - onRowDoubleClick?: (param: RowParams) => void; + onRowDoubleClick?: (param: GridRowParams) => void; /** * Callback fired when a hover event comes from a row container element. * @param param With all properties from [[GridRowParams]]. @@ -285,15 +286,15 @@ export interface GridOptions { */ onStateChange?: (params: any) => void; - editRowsModel?: EditRowsModel; + editRowsModel?: GridEditRowsModel; /** * Callback fired when the cell is rendered. */ - isCellEditable?: (params: CellParams) => boolean; + isCellEditable?: (params: GridCellParams) => boolean; onCellModeChange?: ({ id: RowId, field: string, api: any, mode: CellMode }) => void; - onEditCellValueChange?: (params: { api: any; update: RowModelUpdate }) => void; - onEditCellValueChangeCommitted?: (params: { api: any; update: RowModelUpdate }) => void; - onEditRowModelChange?: (model: EditRowsModel) => void; + onEditCellValueChange?: (params: { api: any; update: GridRowModelUpdate }) => void; + onEditCellValueChangeCommitted?: (params: { api: any; update: GridRowModelUpdate }) => void; + onEditRowModelChange?: (model: GridEditRowsModel) => void; /** * Extend native column types with your new column types. diff --git a/packages/grid/_modules_/grid/models/gridRows.ts b/packages/grid/_modules_/grid/models/gridRows.ts index 7b9ec3b1b13d..dc64e8a7a1ba 100644 --- a/packages/grid/_modules_/grid/models/gridRows.ts +++ b/packages/grid/_modules_/grid/models/gridRows.ts @@ -8,7 +8,7 @@ export type GridRowModel = ObjectWithId & GridRowData; export type GridUpdateAction = 'delete'; -export interface RowModelUpdate extends GridRowData { +export interface GridRowModelUpdate extends GridRowData { _action?: GridUpdateAction; } diff --git a/packages/grid/_modules_/grid/utils/paramsUtils.ts b/packages/grid/_modules_/grid/utils/paramsUtils.ts index 2986b51fa05b..f2430026b1e0 100644 --- a/packages/grid/_modules_/grid/utils/paramsUtils.ts +++ b/packages/grid/_modules_/grid/utils/paramsUtils.ts @@ -24,7 +24,7 @@ export function buildGridCellParams({ api: GridApi; element?: HTMLElement; }): GridCellParams { - const params: CellParams = { + const params: GridCellParams = { element, value, field: colDef?.field, diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index c51ddcd72ff9..54c4de726f7c 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -2,12 +2,18 @@ import InputAdornment from '@material-ui/core/InputAdornment'; import { makeStyles } from '@material-ui/core/styles'; import * as React from 'react'; import Button from '@material-ui/core/Button'; -import { GridRowData, useGridApiRef, XGrid } from '@material-ui/x-grid'; +import { + GridCellParams, + GridLoadIcon, + GridRowData, + useGridApiRef, + XGrid, +} from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; import { - EditCellProps, - EditRowsModel, -} from '../../../grid/_modules_/grid/hooks/features/rows/useEditRows'; + GridEditCellProps, + GridEditRowsModel, +} from '../../../grid/_modules_/grid/hooks/features/rows/useGridEditRows'; import { randomInt } from '../data/random-generator'; export default { @@ -306,12 +312,12 @@ const useStyles = makeStyles({ }); export function EditRowsPoc() { - const apiRef = useApiRef(); + const apiRef = useGridApiRef(); const classes = useStyles(); const [selectedCell, setSelectedCell] = React.useState<[string, string] | null>(null); const [isEditable, setIsEditable] = React.useState(false); - const [editRowsModel, setEditRowsModel] = React.useState({}); + const [editRowsModel, setEditRowsModel] = React.useState({}); const editRow = React.useCallback(() => { if (!selectedCell) { @@ -319,7 +325,7 @@ export function EditRowsPoc() { } setEditRowsModel((state) => { - const editRowState: EditRowsModel = { ...state }; + const editRowState: GridEditRowsModel = { ...state }; editRowState[selectedCell[0]] = editRowState[selectedCell[0]] ? editRowState[selectedCell[0]] : {}; @@ -329,13 +335,13 @@ export function EditRowsPoc() { }); }, [selectedCell]); - const onCellClick = React.useCallback((params: CellParams) => { + const onCellClick = React.useCallback((params: GridCellParams) => { setSelectedCell([params.row.id!.toString(), params.field]); setIsEditable(!!params.isEditable); }, []); const onCellDoubleClick = React.useCallback( - (params: CellParams) => { + (params: GridCellParams) => { if (params.isEditable) { apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); } @@ -343,7 +349,7 @@ export function EditRowsPoc() { [apiRef], ); - const isCellEditable = React.useCallback((params: CellParams) => params.row.id !== 0, []); + const isCellEditable = React.useCallback((params: GridCellParams) => params.row.id !== 0, []); const onEditCellValueChange = React.useCallback(({ update }) => { if (update.email) { @@ -362,7 +368,7 @@ export function EditRowsPoc() { if (update.email) { const newState = {}; const componentProps = { - InputProps: { endAdornment: }, + InputProps: { endAdornment: }, }; newState[update.id] = {}; newState[update.id][field] = { value: update.email, ...componentProps }; From 53d7c9034b5ff520fd78c4a4f395e9a1e19bb15f Mon Sep 17 00:00:00 2001 From: damien Date: Tue, 23 Feb 2021 18:44:05 +0100 Subject: [PATCH 03/32] fix date edit and refactoring --- .../grid/components/GridRowCells.tsx | 4 +- .../components/editCell/EditInputCell.tsx | 82 +++++++++++++++++ .../components/editCell/StringEditCell.tsx | 92 ------------------- .../grid/hooks/features/core/gridState.ts | 2 +- .../features/rows/gridEditRowsSelector.ts | 3 + .../hooks/features/rows/useGridEditRows.ts | 29 +++--- packages/grid/_modules_/grid/locales/bgBG.ts | 1 - .../grid/_modules_/grid/models/api/gridApi.ts | 3 +- .../grid/models/api/gridEditRowApi.ts | 12 +++ .../_modules_/grid/models/api/gridRowApi.ts | 11 --- .../grid/_modules_/grid/models/api/index.ts | 1 + .../grid/models/colDef/gridDateColDef.ts | 6 +- .../grid/models/colDef/gridNumericColDef.ts | 4 +- .../grid/models/colDef/gridStringColDef.ts | 4 +- .../_modules_/grid/models/gridEditRowModel.ts | 10 ++ .../_modules_/grid/models/gridOptions.tsx | 2 +- packages/grid/_modules_/grid/utils/utils.ts | 29 +++++- .../src/stories/grid-rows.stories.tsx | 54 ++++++----- 18 files changed, 194 insertions(+), 155 deletions(-) create mode 100644 packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx delete mode 100644 packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx create mode 100644 packages/grid/_modules_/grid/hooks/features/rows/gridEditRowsSelector.ts create mode 100644 packages/grid/_modules_/grid/models/api/gridEditRowApi.ts create mode 100644 packages/grid/_modules_/grid/models/gridEditRowModel.ts diff --git a/packages/grid/_modules_/grid/components/GridRowCells.tsx b/packages/grid/_modules_/grid/components/GridRowCells.tsx index 584d030b44fd..c3c3c3c2500b 100644 --- a/packages/grid/_modules_/grid/components/GridRowCells.tsx +++ b/packages/grid/_modules_/grid/components/GridRowCells.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { gridEditRowsStateSelector } from '../hooks/features/rows/useGridEditRows'; +import { gridEditRowsStateSelector } from '../hooks/features/rows/gridEditRowsSelector'; import { GridCellClassParams, GridColumns, @@ -98,7 +98,7 @@ export const GridRowCells: React.FC = React.memo((props) => { let formattedValueProp = {}; if (column.valueFormatter) { - //TODO add formatted value to cellParams? + // TODO add formatted value to cellParams? formattedValueProp = { formattedValue: column.valueFormatter(cellParams) }; } diff --git a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx new file mode 100644 index 000000000000..bdfa78c6146b --- /dev/null +++ b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx @@ -0,0 +1,82 @@ +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'; + +export function EditInputCell(props: GridCellParams & InputBaseProps) { + const { + value, + api, + field, + row, + colDef, + getValue, + rowIndex, + colIndex, + isEditable, + ...inputBaseProps + } = props; + + const caretRafRef = React.useRef(0); + React.useEffect(() => { + return () => { + cancelAnimationFrame(caretRafRef.current); + }; + }, []); + + const keepCaretPosition = React.useCallback((event) => { + // Fix caret from jumping to the end of the input + const caret = event.target.selectionStart; + const element = event.target; + caretRafRef.current = window.requestAnimationFrame(() => { + element.selectionStart = caret; + element.selectionEnd = caret; + }); + }, []); + + const onValueChange = React.useCallback( + (event) => { + keepCaretPosition(event); + + const newValue = event.target.value; + const update = { id: row.id }; + update[field] = newValue; + api.setEditCellValue(update); + }, + [api, field, keepCaretPosition, row.id], + ); + + const onKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (!inputBaseProps.error && event.key === 'Enter') { + const update = { id: row.id }; + update[field] = value; + api.commitCellValueChanges(update); + } + + if (event.key === 'Escape') { + api.setCellMode(row.id, field, 'view'); + } + }, + [inputBaseProps.error, row.id, field, value, api], + ); + + const inputType = mapColDefTypeToInputType(colDef.type); + const formattedValue = + value && isDate(value) + ? formatDateToLocalInputDate({ value, withTime: colDef.type === 'dateTime' }) + : value; + + return ( + + ); +} +export const renderEditInputCell = (params) => ; diff --git a/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx b/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx deleted file mode 100644 index 36693a2735a9..000000000000 --- a/packages/grid/_modules_/grid/components/editCell/StringEditCell.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import * as React from 'react'; -import TextField, { TextFieldProps } from '@material-ui/core/TextField'; -import { GridCellParams } from '../../models/params/gridCellParams'; -import { isDate } from '../../utils/utils'; - -// export interface StringEditCellProps extends CellParams { -// isValid?: boolean; -// } -function mapColDefTypeToInputType(type: string) { - switch (type) { - case 'string': - return 'text'; - case 'number': - case 'date': - return type; - case 'dateTime': - return 'datetime-local'; - default: - return 'text'; - } -} -export function StringEditCell(props: GridCellParams & TextFieldProps) { - const { - value, - api, - field, - row, - colDef, - getValue, - rowIndex, - colIndex, - isEditable, - ...textFieldProps - } = props; - const [inputValueState, setInputValueState] = React.useState(value || ''); - - const onValueChange = React.useCallback( - (event) => { - const newValue = event.target.value; - //TODO consider removing local state, and just use gridState - setInputValueState(newValue); - - const update = { id: row.id }; - update[field] = newValue; - api.setEditCellValue(update); - }, - [api, field, row.id], - ); - - const onKeyDown = React.useCallback( - (event: React.KeyboardEvent) => { - if (!textFieldProps.error && event.key === 'Enter') { - const update = { id: row.id }; - update[field] = inputValueState; - api.commitCellValueChanges(update); - } - - if (event.key === 'Escape') { - api.setCellMode(row.id, field, 'view'); - } - }, - [api, field, inputValueState, textFieldProps, row.id], - ); - - React.useEffect(() => { - setInputValueState(value || ''); - }, [value]); - - const inputType = mapColDefTypeToInputType(colDef.type); - let formattedValue = inputValueState; - if (isDate(value)) { - //TODO fix issue with local date as 00:00 time returns -1 day - formattedValue = value.toISOString().substr(0, colDef.type === 'dateTime' ? 16 : 10); - } - - return ( - - ); -} -export const renderEditStringCell = (params) => ; diff --git a/packages/grid/_modules_/grid/hooks/features/core/gridState.ts b/packages/grid/_modules_/grid/hooks/features/core/gridState.ts index 7aa79fb14760..7105fc2f9497 100644 --- a/packages/grid/_modules_/grid/hooks/features/core/gridState.ts +++ b/packages/grid/_modules_/grid/hooks/features/core/gridState.ts @@ -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 { @@ -23,7 +24,6 @@ import { } from '../pagination/gridPaginationReducer'; import { GridPreferencePanelState } from '../preferencesPanel/gridPreferencePanelState'; import { getInitialGridRowState, InternalGridRowsState } from '../rows/gridRowsState'; -import { GridEditRowsModel } from '../rows/useGridEditRows'; import { GridSelectionState } from '../selection/gridSelectionState'; import { getInitialGridSortingState, GridSortingState } from '../sorting/gridSortingState'; import { diff --git a/packages/grid/_modules_/grid/hooks/features/rows/gridEditRowsSelector.ts b/packages/grid/_modules_/grid/hooks/features/rows/gridEditRowsSelector.ts new file mode 100644 index 000000000000..a0c5a9bd9968 --- /dev/null +++ b/packages/grid/_modules_/grid/hooks/features/rows/gridEditRowsSelector.ts @@ -0,0 +1,3 @@ +import { GridState } from '../core/gridState'; + +export const gridEditRowsStateSelector = (state: GridState) => state.editRows; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index 73c25bcc92a6..484cec69f5d9 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -6,28 +6,18 @@ import { GRID_EDIT_ROW_MODEL_CHANGE, } from '../../../constants/eventsConstants'; import { GridApiRef } from '../../../models/api/gridApiRef'; -import { GridEditRowApi } from '../../../models/api/gridRowApi'; -import { GridCellMode, GridCellValue } from '../../../models/gridCell'; +import { GridEditRowApi } from '../../../models/api/gridEditRowApi'; +import { GridCellMode } from '../../../models/gridCell'; +import { GridEditRowsModel } from '../../../models/gridEditRowModel'; import { GridRowModelUpdate } from '../../../models/gridRows'; import { GridCellParams } from '../../../models/params/gridCellParams'; import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; import { useGridApiMethod } from '../../root/useGridApiMethod'; import { optionsSelector } from '../../utils/optionsSelector'; -import { GridState } from '../core/gridState'; import { useGridSelector } from '../core/useGridSelector'; import { useGridState } from '../core/useGridState'; -export interface GridEditCellProps { - value: GridCellValue; - [prop: string]: any; -} - -export type GridEditRow = { [field: string]: true | GridEditCellProps }; -export type GridEditRowsModel = { [rowId: string]: GridEditRow }; - -export const gridEditRowsStateSelector = (state: GridState) => state.editRows; - export function useGridEditRows(apiRef: GridApiRef) { const [, setGridState, forceUpdate] = useGridState(apiRef); const options = useGridSelector(apiRef, optionsSelector); @@ -35,6 +25,10 @@ export function useGridEditRows(apiRef: GridApiRef) { const setCellEditMode = React.useCallback( (id, field) => { setGridState((state) => { + if (state.editRows[id] && state.editRows[id][field]) { + return state; + } + const currentCellEditState: GridEditRowsModel = { ...state.editRows }; currentCellEditState[id] = currentCellEditState[id] || {}; currentCellEditState[id][field] = true; @@ -60,7 +54,7 @@ export function useGridEditRows(apiRef: GridApiRef) { setGridState((state) => { const newEditRowsState: GridEditRowsModel = { ...state.editRows }; - if (!newEditRowsState[id]) { + if (!newEditRowsState[id] || !newEditRowsState[id][field]) { return state; } @@ -100,6 +94,7 @@ export function useGridEditRows(apiRef: GridApiRef) { (params: GridCellParams) => { return params.colDef.editable && (!options.isCellEditable || options.isCellEditable(params)); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [options.isCellEditable], ); @@ -112,8 +107,8 @@ export function useGridEditRows(apiRef: GridApiRef) { }); return; } - //TODO don't update when it's in server mode - //How should we turn server mode? featureMode === 'server' ? + // TODO don't update when it's in server mode + // How should we turn server mode? featureMode === 'server' ? apiRef.current.updateRows([update]); const field = Object.keys(update).find((key) => key !== 'id')!; @@ -140,7 +135,7 @@ export function useGridEditRows(apiRef: GridApiRef) { [forceUpdate, setGridState], ); - //TODO add those options.handlers on apiRef + // TODO add those options.handlers on apiRef useGridApiEventHandler(apiRef, GRID_CELL_VALUE_CHANGE, options.onEditCellValueChange); useGridApiEventHandler( apiRef, diff --git a/packages/grid/_modules_/grid/locales/bgBG.ts b/packages/grid/_modules_/grid/locales/bgBG.ts index 6ac971987b10..bf8202532579 100644 --- a/packages/grid/_modules_/grid/locales/bgBG.ts +++ b/packages/grid/_modules_/grid/locales/bgBG.ts @@ -48,7 +48,6 @@ export const bgBG: Localization = getGridLocalization({ filterOperatorNot: 'не е', filterOperatorAfter: 'е след', filterOperatorOnOrAfter: 'е на или след', - filterOperatorAfter: 'е след', filterOperatorBefore: 'е преди', filterOperatorOnOrBefore: 'е на или преди', filterPanelInputLabel: 'Стойност', diff --git a/packages/grid/_modules_/grid/models/api/gridApi.ts b/packages/grid/_modules_/grid/models/api/gridApi.ts index b0c898dee032..9204c3997af6 100644 --- a/packages/grid/_modules_/grid/models/api/gridApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridApi.ts @@ -2,8 +2,9 @@ import { ColumnMenuApi } from './columnMenuApi'; import { ColumnResizeApi } from './columnResizeApi'; import { ComponentsApi } from './gridComponentsApi'; import { FilterApi } from './filterApi'; +import { GridEditRowApi } from './gridEditRowApi'; import { PreferencesPanelApi } from './preferencesPanelApi'; -import { GridRowApi, GridEditRowApi } from './gridRowApi'; +import { GridRowApi } from './gridRowApi'; import { GridColumnApi } from './gridColumnApi'; import { ColumnReorderApi } from './columnReorderApi'; import { GridSelectionApi } from './gridSelectionApi'; diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts new file mode 100644 index 000000000000..89f8441ddefe --- /dev/null +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -0,0 +1,12 @@ +import { GridCellMode } from '../gridCell'; +import { GridEditRowsModel } from '../gridEditRowModel'; +import { GridRowId, GridRowModelUpdate } from '../gridRows'; +import { GridCellParams } from '../params/gridCellParams'; + +export interface GridEditRowApi { + setEditRowsModel: (model: GridEditRowsModel) => void; + setCellMode: (rowId: GridRowId, field: string, mode: GridCellMode) => void; + isCellEditable: (params: GridCellParams) => boolean; + setEditCellValue: (update: GridRowModelUpdate) => void; + commitCellValueChanges: (update: GridRowModelUpdate) => void; +} diff --git a/packages/grid/_modules_/grid/models/api/gridRowApi.ts b/packages/grid/_modules_/grid/models/api/gridRowApi.ts index 3c50bffb7a57..8b0cbe2f00d9 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowApi.ts @@ -1,7 +1,4 @@ -import { GridCellMode } from '../gridCell'; import { GridRowModel, GridRowId, GridRowModelUpdate } from '../gridRows'; -import { GridEditRowsModel } from '../../hooks/features/rows/useGridEditRows'; -import { GridCellParams } from '../params/gridCellParams'; /** * The Row API interface that is available in the grid [[apiRef]]. @@ -46,11 +43,3 @@ export interface GridRowApi { */ getRowFromId: (id: GridRowId) => GridRowModel; } - -export interface GridEditRowApi { - setEditRowsModel: (model: GridEditRowsModel) => void; - setCellMode: (rowId: GridRowId, field: string, mode: GridCellMode) => void; - isCellEditable: (params: GridCellParams) => boolean; - setEditCellValue: (update: GridRowModelUpdate) => void; - commitCellValueChanges: (update: GridRowModelUpdate) => void; -} diff --git a/packages/grid/_modules_/grid/models/api/index.ts b/packages/grid/_modules_/grid/models/api/index.ts index 884b9d0db946..032328ea976f 100644 --- a/packages/grid/_modules_/grid/models/api/index.ts +++ b/packages/grid/_modules_/grid/models/api/index.ts @@ -4,6 +4,7 @@ export * from './gridColumnApi'; export * from './gridComponentsApi'; export * from './gridDensityApi'; export * from './gridEventsApi'; +export * from './gridEditRowApi'; export * from './gridApi'; export * from './gridPaginationApi'; export * from './gridRowApi'; diff --git a/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts index 30644572de02..7cbf6416ba55 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts @@ -1,4 +1,4 @@ -import { renderEditStringCell } from '../../components/editCell/StringEditCell'; +import { renderEditInputCell } from '../../components/editCell/EditInputCell'; import { isDate } from '../../utils/utils'; import { gridDateComparer } from '../../utils/sortingUtils'; import { GridCellValue } from '../gridCell'; @@ -26,7 +26,7 @@ export const GRID_DATE_COL_DEF: GridColTypeDef = { sortComparator: gridDateComparer, valueFormatter: gridDateFormatter, filterOperators: getGridDateOperators(), - renderEditCell: renderEditStringCell, + renderEditCell: renderEditInputCell, }; export const GRID_DATETIME_COL_DEF: GridColTypeDef = { @@ -35,5 +35,5 @@ export const GRID_DATETIME_COL_DEF: GridColTypeDef = { sortComparator: gridDateComparer, valueFormatter: gridDateTimeFormatter, filterOperators: getGridDateOperators(true), - renderEditCell: renderEditStringCell, + renderEditCell: renderEditInputCell, }; diff --git a/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts index 190043b076a5..83004b1d8a7e 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts @@ -1,4 +1,4 @@ -import { renderEditStringCell } from '../../components/editCell/StringEditCell'; +import { renderEditInputCell } from '../../components/editCell/EditInputCell'; import { gridNumberComparer } from '../../utils/sortingUtils'; import { isNumber } from '../../utils/utils'; import { getGridNumericColumnOperators } from './gridNumericOperators'; @@ -13,5 +13,5 @@ export const GRID_NUMERIC_COL_DEF: GridColTypeDef = { sortComparator: gridNumberComparer, valueFormatter: ({ value }) => (value && isNumber(value) && value.toLocaleString()) || value, filterOperators: getGridNumericColumnOperators(), - renderEditCell: renderEditStringCell, + renderEditCell: renderEditInputCell, }; diff --git a/packages/grid/_modules_/grid/models/colDef/gridStringColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridStringColDef.ts index 5d9a000357a4..29fa09b27717 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridStringColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridStringColDef.ts @@ -1,4 +1,4 @@ -import { renderEditStringCell } from '../../components/editCell/StringEditCell'; +import { renderEditInputCell } from '../../components/editCell/EditInputCell'; import { gridStringNumberComparer } from '../../utils/sortingUtils'; import { GridColTypeDef } from './gridColDef'; import { getGridStringOperators } from './gridStringOperators'; @@ -13,5 +13,5 @@ export const GRID_STRING_COL_DEF: GridColTypeDef = { type: 'string', align: 'left', filterOperators: getGridStringOperators(), - renderEditCell: renderEditStringCell, + renderEditCell: renderEditInputCell, }; diff --git a/packages/grid/_modules_/grid/models/gridEditRowModel.ts b/packages/grid/_modules_/grid/models/gridEditRowModel.ts new file mode 100644 index 000000000000..e0e4fe8d1c45 --- /dev/null +++ b/packages/grid/_modules_/grid/models/gridEditRowModel.ts @@ -0,0 +1,10 @@ +import { GridCellValue } from './gridCell'; + +export interface GridEditCellProps { + value: GridCellValue; + + [prop: string]: any; +} + +export type GridEditRow = { [field: string]: true | GridEditCellProps }; +export type GridEditRowsModel = { [rowId: string]: GridEditRow }; diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 04553a71ca25..27b7f9591280 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -1,11 +1,11 @@ import { GRID_DEFAULT_LOCALE_TEXT } from '../constants/localeTextConstants'; import { FilterModel } from '../hooks/features/filter/FilterModelState'; -import { GridEditRowsModel } from '../hooks/features/rows/useGridEditRows'; import { Logger } from '../hooks/utils/useLogger'; import { GridLocaleText } from './api/gridLocaleTextApi'; import { GridColumnTypesRecord } from './colDef/gridColTypeDef'; import { getGridDefaultColumnTypes } from './colDef/gridDefaultColumnTypes'; import { GridDensity, GridDensityTypes } from './gridDensity'; +import { GridEditRowsModel } from './gridEditRowModel'; import { GridFeatureMode, GridFeatureModeConstant } from './gridFeatureMode'; import { GridRowModelUpdate } from './gridRows'; import { GridCellParams } from './params/gridCellParams'; diff --git a/packages/grid/_modules_/grid/utils/utils.ts b/packages/grid/_modules_/grid/utils/utils.ts index 8d7f3c62d7d6..4f5e714b71eb 100644 --- a/packages/grid/_modules_/grid/utils/utils.ts +++ b/packages/grid/_modules_/grid/utils/utils.ts @@ -1,5 +1,6 @@ import * as styles from '@material-ui/core/styles'; import isDeepEqual from '../lib/lodash/isDeepEqual'; +import { GridCellValue } from '../models/gridCell'; export { isDeepEqual }; @@ -11,7 +12,20 @@ export interface DebouncedFunction extends Function { export function isDate(value: any): value is Date { return value instanceof Date; } - +export function formatDateToLocalInputDate({ + value, + withTime, +}: { + value: GridCellValue; + withTime: boolean; +}) { + if (isDate(value)) { + const offset = value.getTimezoneOffset(); + const localDate = new Date(value.getTime() - offset * 60 * 1000); + return localDate.toISOString().substr(0, withTime ? 16 : 10); + } + return value; +} export function isArray(value: any): value is Array { return Array.isArray(value); } @@ -60,3 +74,16 @@ export function localStorageAvailable() { return false; } } +export function mapColDefTypeToInputType(type: string) { + switch (type) { + case 'string': + return 'text'; + case 'number': + case 'date': + return type; + case 'dateTime': + return 'datetime-local'; + default: + return 'text'; + } +} diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 54c4de726f7c..43770abac84c 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -1,4 +1,3 @@ -import InputAdornment from '@material-ui/core/InputAdornment'; import { makeStyles } from '@material-ui/core/styles'; import * as React from 'react'; import Button from '@material-ui/core/Button'; @@ -10,10 +9,7 @@ import { XGrid, } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; -import { - GridEditCellProps, - GridEditRowsModel, -} from '../../../grid/_modules_/grid/hooks/features/rows/useGridEditRows'; +import { GridEditRowsModel } from '../../../grid/_modules_/grid/models/gridEditRowModel'; import { randomInt } from '../data/random-generator'; export default { @@ -283,9 +279,9 @@ const baselineEditProps = { field: 'fullname', editable: true, valueGetter: ({ row }) => `${row.firstname} ${row.lastname}`, - }, //needs special field, value getter... - { field: 'username', editable: true }, //TODO implement server validation - { field: 'email', editable: true, width: 150 }, //needs validation + }, + { field: 'username', editable: true }, + { field: 'email', editable: true, width: 150 }, { field: 'age', width: 50, type: 'number', editable: true }, { field: 'DOB', width: 120, type: 'date', editable: true }, { field: 'meetup', width: 180, type: 'dateTime', editable: true }, @@ -297,7 +293,6 @@ function validateEmail(email) { return re.test(String(email).toLowerCase()); } -//MuiDataGrid-cellEditing const useStyles = makeStyles({ root: { '& .MuiDataGrid-cellEditable': { @@ -311,7 +306,7 @@ const useStyles = makeStyles({ }, }); -export function EditRowsPoc() { +export function EditRows() { const apiRef = useGridApiRef(); const classes = useStyles(); @@ -351,19 +346,36 @@ export function EditRowsPoc() { const isCellEditable = React.useCallback((params: GridCellParams) => params.row.id !== 0, []); - const onEditCellValueChange = React.useCallback(({ update }) => { - if (update.email) { - const isValid = validateEmail(update.email); - // how do we feedback users? - // how do we prevent committing? - const newState = {}; - newState[update.id] = { email: { value: update.email, error: !isValid } }; - setEditRowsModel((state) => ({ ...state, ...newState })); - } - }, []); + const onEditCellValueChange = React.useCallback( + ({ update }) => { + if (update.email) { + const isValid = validateEmail(update.email); + const newState = {}; + newState[update.id] = { + ...editRowsModel[update.id], + email: { value: update.email, error: !isValid }, + }; + setEditRowsModel((state) => ({ ...state, ...newState })); + } + if (update.DOB) { + const newState = {}; + newState[update.id] = { ...editRowsModel[update.id], DOB: { value: new Date(update.DOB) } }; + setEditRowsModel((state) => ({ ...state, ...newState })); + } + if (update.meetup) { + const newState = {}; + newState[update.id] = { + ...editRowsModel[update.id], + meetup: { value: new Date(update.meetup) }, + }; + setEditRowsModel((state) => ({ ...state, ...newState })); + } + }, + [editRowsModel], + ); const onEditCellValueChangeCommitted = React.useCallback( - ({ update, api }) => { + ({ update }) => { const field = Object.keys(update).find((key) => key !== 'id')!; if (update.email) { const newState = {}; From 9f066f7386d57956f46a1c3e1c4d2905d5bc629d Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 24 Feb 2021 16:39:27 +0100 Subject: [PATCH 04/32] fixes and refactoring --- .../components/editCell/EditInputCell.tsx | 2 +- .../features/keyboard/useGridKeyboard.ts | 6 +-- .../grid/hooks/features/rows/index.ts | 2 + .../hooks/features/rows/useGridEditRows.ts | 46 ++++++++++++++++--- .../grid/models/api/gridEditRowApi.ts | 7 +++ .../_modules_/grid/models/gridOptions.tsx | 6 +++ .../grid/_modules_/grid/utils/EventEmitter.ts | 4 -- .../grid/_modules_/grid/utils/domUtils.ts | 11 +++++ .../src/stories/grid-rows.stories.tsx | 5 ++ 9 files changed, 73 insertions(+), 16 deletions(-) diff --git a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx index bdfa78c6146b..e8905b43d165 100644 --- a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx +++ b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx @@ -75,7 +75,7 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { onChange={onValueChange} type={inputType} style={{ width: '100%' }} - error + {...inputBaseProps} /> ); } diff --git a/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboard.ts b/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboard.ts index db58b0a7e9c9..edfd643e2ec1 100644 --- a/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboard.ts +++ b/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboard.ts @@ -12,6 +12,7 @@ import { GridCellIndexCoordinates } from '../../../models/gridCell'; import { findParentElementFromClassName, getIdFromRowElem, + getRowEl, isGridCellRoot, } from '../../../utils/domUtils'; import { @@ -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]; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/index.ts b/packages/grid/_modules_/grid/hooks/features/rows/index.ts index decfdced5b42..e2f92894b076 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/index.ts +++ b/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'; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index 484cec69f5d9..afeeceeb1ac9 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -9,6 +9,7 @@ import { GridApiRef } from '../../../models/api/gridApiRef'; import { GridEditRowApi } from '../../../models/api/gridEditRowApi'; import { GridCellMode } from '../../../models/gridCell'; import { GridEditRowsModel } from '../../../models/gridEditRowModel'; +import { GridFeatureModeConstant } from '../../../models/gridFeatureMode'; import { GridRowModelUpdate } from '../../../models/gridRows'; import { GridCellParams } from '../../../models/params/gridCellParams'; import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; @@ -100,21 +101,18 @@ export function useGridEditRows(apiRef: GridApiRef) { const commitCellValueChanges = React.useCallback( (update: GridRowModelUpdate) => { - if (apiRef.current.hasListener(GRID_CELL_VALUE_CHANGE_COMMITTED)) { + if (options.editMode === GridFeatureModeConstant.server) { apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, { update, api: apiRef.current, }); return; } - // TODO don't update when it's in server mode - // How should we turn server mode? featureMode === 'server' ? - apiRef.current.updateRows([update]); const field = Object.keys(update).find((key) => key !== 'id')!; apiRef.current.setCellMode(update.id, field, 'view'); }, - [apiRef], + [apiRef, options.editMode], ); const setEditCellValue = React.useCallback( @@ -134,7 +132,31 @@ export function useGridEditRows(apiRef: GridApiRef) { }, [forceUpdate, setGridState], ); - + // TODO cleanup params What should we put? + const onEditCellValueChange = React.useCallback( + (handler: (param: { update: GridRowModelUpdate }) => void): (() => void) => { + return apiRef.current.subscribeEvent(GRID_CELL_VALUE_CHANGE, handler); + }, + [apiRef], + ); + const onEditCellValueChangeCommitted = React.useCallback( + (handler: (param: { update: GridRowModelUpdate }) => void): (() => void) => { + return apiRef.current.subscribeEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, handler); + }, + [apiRef], + ); + const onCellModeChange = React.useCallback( + (handler: (param: { update: GridRowModelUpdate }) => void): (() => void) => { + return apiRef.current.subscribeEvent(GRID_CELL_MODE_CHANGE, handler); + }, + [apiRef], + ); + const onEditRowModelChange = React.useCallback( + (handler: (param: { update: GridRowModelUpdate }) => void): (() => void) => { + return apiRef.current.subscribeEvent(GRID_EDIT_ROW_MODEL_CHANGE, handler); + }, + [apiRef], + ); // TODO add those options.handlers on apiRef useGridApiEventHandler(apiRef, GRID_CELL_VALUE_CHANGE, options.onEditCellValueChange); useGridApiEventHandler( @@ -147,7 +169,17 @@ export function useGridEditRows(apiRef: GridApiRef) { useGridApiMethod( apiRef, - { setCellMode, isCellEditable, commitCellValueChanges, setEditCellValue, setEditRowsModel }, + { + setCellMode, + onEditRowModelChange, + onCellModeChange, + onEditCellValueChangeCommitted, + onEditCellValueChange, + isCellEditable, + commitCellValueChanges, + setEditCellValue, + setEditRowsModel, + }, 'EditRowApi', ); diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts index 89f8441ddefe..589be8ec71a8 100644 --- a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -9,4 +9,11 @@ export interface GridEditRowApi { isCellEditable: (params: GridCellParams) => boolean; setEditCellValue: (update: GridRowModelUpdate) => void; commitCellValueChanges: (update: GridRowModelUpdate) => void; + + onEditRowModelChange: (handler: (param: { update: GridRowModelUpdate }) => void) => void; + onCellModeChange: (handler: (param: { update: GridRowModelUpdate }) => void) => void; + onEditCellValueChangeCommitted: ( + handler: (param: { update: GridRowModelUpdate }) => void, + ) => void; + onEditCellValueChange: (handler: (param: { update: GridRowModelUpdate }) => void) => void; } diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 27b7f9591280..1717bd303509 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -160,6 +160,12 @@ export interface GridOptions { * Set it to 'server' if you would like to handle filtering on the server-side. */ filterMode?: GridFeatureMode; + /** + * Edit cell or rows can be processed on the server or client-side. + * Set it to 'client' if you would like to handle editing on the client-side. + * Set it to 'server' if you would like to handle editing on the server-side. + */ + editMode?: GridFeatureMode; /** * If `true`, the footer component is hidden. * @default false diff --git a/packages/grid/_modules_/grid/utils/EventEmitter.ts b/packages/grid/_modules_/grid/utils/EventEmitter.ts index ba6d7b90ec4a..c6eed5221116 100644 --- a/packages/grid/_modules_/grid/utils/EventEmitter.ts +++ b/packages/grid/_modules_/grid/utils/EventEmitter.ts @@ -10,10 +10,6 @@ export class EventEmitter { events: { [key: string]: Listener[] } = {}; - hasListener(eventName: string): boolean { - return this.events[eventName].length > 0; - } - on(eventName: string, listener: Listener): void { if (!Array.isArray(this.events[eventName])) { this.events[eventName] = []; diff --git a/packages/grid/_modules_/grid/utils/domUtils.ts b/packages/grid/_modules_/grid/utils/domUtils.ts index f28bfdd2a1ed..5b12f3d84b8a 100644 --- a/packages/grid/_modules_/grid/utils/domUtils.ts +++ b/packages/grid/_modules_/grid/utils/domUtils.ts @@ -2,6 +2,7 @@ import { GRID_CELL_CSS_CLASS, GRID_DATA_CONTAINER_CSS_CLASS, GRID_HEADER_CELL_TITLE_CSS_CLASS, + GRID_ROW_CSS_CLASS, } from '../constants/cssClassesConstants'; import { GridCellIndexCoordinates } from '../models/gridCell'; @@ -13,6 +14,13 @@ export function findParentElementFromClassName(elem: Element, className: string) return elem.closest(`.${className}`); } +export function getRowEl(cell?: Element | null): HTMLElement | null { + if (!cell) { + return null; + } + return findParentElementFromClassName(cell as HTMLDivElement, GRID_ROW_CSS_CLASS)! as HTMLElement; +} + export function isGridCellRoot(elem: Element | null): boolean { return elem != null && elem.classList.contains(GRID_CELL_CSS_CLASS); } @@ -31,6 +39,9 @@ export function isGridHeaderTitleContainer(elem: Element): boolean { export function getIdFromRowElem(rowEl: Element): string { return rowEl.getAttribute('data-id')!; } +export function getFieldFromCellElem(cellEl: Element): string { + return cellEl.getAttribute('data-field')!; +} export function getFieldFromHeaderElem(colCellEl: Element): string { return colCellEl.getAttribute('data-field')!; diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 43770abac84c..b2d380e52f90 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -303,6 +303,10 @@ const useStyles = makeStyles({ backgroundColor: 'rgb(255,215,115, 0.19)', color: '#1a3e72', }, + '& .Mui-error': { + backgroundColor: 'rgb(126,10,15, 0.1)', + color: '#750f0f', + }, }, }); @@ -420,6 +424,7 @@ export function EditRows() { onEditCellValueChange={onEditCellValueChange} onEditCellValueChangeCommitted={onEditCellValueChangeCommitted} editRowsModel={editRowsModel} + editMode="server" /> From 84dbf551c825e1be23738317e86b744a49dc5c89 Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 24 Feb 2021 19:43:34 +0100 Subject: [PATCH 05/32] fix client edit rows --- .../grid/components/GridRowCells.tsx | 12 +- .../hooks/features/rows/useGridEditRows.ts | 103 ++- .../grid/models/api/gridEditRowApi.ts | 44 +- .../_modules_/grid/models/gridEditRowModel.ts | 8 +- .../src/stories/grid-rows.stories.tsx | 730 +++++++++--------- 5 files changed, 499 insertions(+), 398 deletions(-) diff --git a/packages/grid/_modules_/grid/components/GridRowCells.tsx b/packages/grid/_modules_/grid/components/GridRowCells.tsx index c3c3c3c2500b..a8a5efc8501d 100644 --- a/packages/grid/_modules_/grid/components/GridRowCells.tsx +++ b/packages/grid/_modules_/grid/components/GridRowCells.tsx @@ -89,7 +89,7 @@ export const GridRowCells: React.FC = React.memo((props) => { const editCellState = editRowsState[row.id] && editRowsState[row.id][column.field]; let cellComponent: React.ReactElement | null = null; - + if (column.valueGetter) { // Value getter override the original value value = column.valueGetter(cellParams); @@ -102,12 +102,16 @@ export const GridRowCells: React.FC = React.memo((props) => { formattedValueProp = { formattedValue: column.valueFormatter(cellParams) }; } - if (!editCellState && column.renderCell) { + if (editCellState == null && column.renderCell) { cellComponent = column.renderCell(cellParams); cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellWithRenderer` }; } - if (editCellState && column.renderEditCell) { - const params = editCellState === true ? cellParams : { ...cellParams, ...editCellState }; + + if (editCellState != null && column.renderEditCell) { + const params = editCellState === true || typeof editCellState !== 'object' ? cellParams : { ...cellParams, ...editCellState }; + if(editCellState !== true && typeof editCellState !== 'object') { + params.value = editCellState; + } cellComponent = column.renderEditCell(params); cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellEditing` }; } diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index afeeceeb1ac9..3b62813df116 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -3,15 +3,16 @@ import { GRID_CELL_MODE_CHANGE, GRID_CELL_VALUE_CHANGE, GRID_CELL_VALUE_CHANGE_COMMITTED, - GRID_EDIT_ROW_MODEL_CHANGE, + GRID_EDIT_ROW_MODEL_CHANGE } from '../../../constants/eventsConstants'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { GridEditRowApi } from '../../../models/api/gridEditRowApi'; import { GridCellMode } from '../../../models/gridCell'; -import { GridEditRowsModel } from '../../../models/gridEditRowModel'; +import { GridEditRowsModel, GridEditRowUpdate } from '../../../models/gridEditRowModel'; import { GridFeatureModeConstant } from '../../../models/gridFeatureMode'; -import { GridRowModelUpdate } from '../../../models/gridRows'; +import { GridRowId, GridRowModelUpdate } from '../../../models/gridRows'; import { GridCellParams } from '../../../models/params/gridCellParams'; +import { buildGridCellParams } from '../../../utils/paramsUtils'; import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; import { useGridApiMethod } from '../../root/useGridApiMethod'; @@ -23,6 +24,27 @@ export function useGridEditRows(apiRef: GridApiRef) { const [, setGridState, forceUpdate] = useGridState(apiRef); const options = useGridSelector(apiRef, optionsSelector); + const getCellValue = React.useCallback( + (id: GridRowId, field: string) => { + const colDef = apiRef.current.getColumnFromField(field); + const rowModel = apiRef.current.getRowFromId(id); + + if (!colDef || !colDef.valueGetter) { + return rowModel[field]; + } + + return colDef.valueGetter( + buildGridCellParams({ + value: rowModel[field], + colDef, + rowModel, + api: apiRef.current + }) + ); + }, + [apiRef] + ); + const setCellEditMode = React.useCallback( (id, field) => { setGridState((state) => { @@ -32,7 +54,7 @@ export function useGridEditRows(apiRef: GridApiRef) { const currentCellEditState: GridEditRowsModel = { ...state.editRows }; currentCellEditState[id] = currentCellEditState[id] || {}; - currentCellEditState[id][field] = true; + currentCellEditState[id][field] = { value: getCellValue(id, field) }; const newEditRowsState = { ...state.editRows, ...currentCellEditState }; @@ -43,11 +65,11 @@ export function useGridEditRows(apiRef: GridApiRef) { id, field, mode: 'edit', - api: apiRef.current, + api: apiRef.current }); forceUpdate(); }, - [apiRef, forceUpdate, setGridState], + [apiRef, forceUpdate, getCellValue, setGridState] ); const setCellViewMode = React.useCallback( @@ -73,11 +95,11 @@ export function useGridEditRows(apiRef: GridApiRef) { id, field, mode: 'view', - api: apiRef.current, + api: apiRef.current }); forceUpdate(); }, - [apiRef, forceUpdate, setGridState], + [apiRef, forceUpdate, setGridState] ); const setCellMode = React.useCallback( @@ -88,7 +110,7 @@ export function useGridEditRows(apiRef: GridApiRef) { setCellViewMode(id, field); } }, - [setCellEditMode, setCellViewMode], + [setCellEditMode, setCellViewMode] ); const isCellEditable = React.useCallback( @@ -96,7 +118,7 @@ export function useGridEditRows(apiRef: GridApiRef) { return params.colDef.editable && (!options.isCellEditable || options.isCellEditable(params)); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [options.isCellEditable], + [options.isCellEditable] ); const commitCellValueChanges = React.useCallback( @@ -104,7 +126,7 @@ export function useGridEditRows(apiRef: GridApiRef) { if (options.editMode === GridFeatureModeConstant.server) { apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, { update, - api: apiRef.current, + api: apiRef.current }); return; } @@ -112,14 +134,27 @@ export function useGridEditRows(apiRef: GridApiRef) { const field = Object.keys(update).find((key) => key !== 'id')!; apiRef.current.setCellMode(update.id, field, 'view'); }, - [apiRef, options.editMode], + [apiRef, options.editMode] ); const setEditCellValue = React.useCallback( - (update: GridRowModelUpdate) => { - apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE, { update, api: apiRef.current }); + (update: GridEditRowUpdate) => { + if (options.editMode === GridFeatureModeConstant.server) { + apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE, { update, api: apiRef.current }); + return; + } + setGridState((state) => { + const newState = { ...state.editRows }; + newState[update.id] = { + ...state.editRows[update.id], + ...update + }; + + return { ...state, editRows: newState }; + }); + forceUpdate(); }, - [apiRef], + [apiRef, forceUpdate, options.editMode, setGridState] ); const setEditRowsModel = React.useCallback( @@ -130,46 +165,43 @@ export function useGridEditRows(apiRef: GridApiRef) { }); forceUpdate(); }, - [forceUpdate, setGridState], + [forceUpdate, setGridState] ); // TODO cleanup params What should we put? const onEditCellValueChange = React.useCallback( - (handler: (param: { update: GridRowModelUpdate }) => void): (() => void) => { + (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { return apiRef.current.subscribeEvent(GRID_CELL_VALUE_CHANGE, handler); }, - [apiRef], + [apiRef] ); const onEditCellValueChangeCommitted = React.useCallback( - (handler: (param: { update: GridRowModelUpdate }) => void): (() => void) => { + (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { return apiRef.current.subscribeEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, handler); }, - [apiRef], + [apiRef] ); const onCellModeChange = React.useCallback( - (handler: (param: { update: GridRowModelUpdate }) => void): (() => void) => { + (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { return apiRef.current.subscribeEvent(GRID_CELL_MODE_CHANGE, handler); }, - [apiRef], + [apiRef] ); const onEditRowModelChange = React.useCallback( - (handler: (param: { update: GridRowModelUpdate }) => void): (() => void) => { + (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { return apiRef.current.subscribeEvent(GRID_EDIT_ROW_MODEL_CHANGE, handler); }, - [apiRef], + [apiRef] ); // TODO add those options.handlers on apiRef useGridApiEventHandler(apiRef, GRID_CELL_VALUE_CHANGE, options.onEditCellValueChange); - useGridApiEventHandler( - apiRef, - GRID_CELL_VALUE_CHANGE_COMMITTED, - options.onEditCellValueChangeCommitted, - ); + useGridApiEventHandler(apiRef, GRID_CELL_VALUE_CHANGE_COMMITTED, options.onEditCellValueChangeCommitted); useGridApiEventHandler(apiRef, GRID_CELL_MODE_CHANGE, options.onCellModeChange); useGridApiEventHandler(apiRef, GRID_EDIT_ROW_MODEL_CHANGE, options.onEditRowModelChange); useGridApiMethod( apiRef, { + getCellValue, setCellMode, onEditRowModelChange, onCellModeChange, @@ -178,12 +210,15 @@ export function useGridEditRows(apiRef: GridApiRef) { isCellEditable, commitCellValueChanges, setEditCellValue, - setEditRowsModel, + setEditRowsModel }, - 'EditRowApi', + 'EditRowApi' ); - React.useEffect(() => { - apiRef.current.setEditRowsModel(options.editRowsModel || {}); - }, [apiRef, forceUpdate, options.editRowsModel]); + React.useEffect( + () => { + apiRef.current.setEditRowsModel(options.editRowsModel || {}); + }, + [apiRef, forceUpdate, options.editRowsModel] + ); } diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts index 589be8ec71a8..546c4c8b1b1f 100644 --- a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -1,19 +1,47 @@ -import { GridCellMode } from '../gridCell'; -import { GridEditRowsModel } from '../gridEditRowModel'; +import { GridCellMode, GridCellValue } from '../gridCell'; +import { GridEditRowsModel, GridEditRowUpdate } from '../gridEditRowModel'; import { GridRowId, GridRowModelUpdate } from '../gridRows'; import { GridCellParams } from '../params/gridCellParams'; export interface GridEditRowApi { + /** + * Set the edit rows model of the grid + * @param GridEditRowsModel + */ setEditRowsModel: (model: GridEditRowsModel) => void; + /** + * Set the cell mode of a cell + * @param GridRowId + * @param string + * @param 'edit' | 'view' + */ setCellMode: (rowId: GridRowId, field: string, mode: GridCellMode) => void; + /** + * Returns true if the cell is editable + * @param params + */ isCellEditable: (params: GridCellParams) => boolean; - setEditCellValue: (update: GridRowModelUpdate) => void; - commitCellValueChanges: (update: GridRowModelUpdate) => void; + /** + * Set the edit cell input props + * @param update + */ + setEditCellValue: (update: GridEditRowUpdate) => void; + /** + * commit the cell value changes to update the cell value. + * @param update + */ + commitCellValueChanges: (update: GridEditRowUpdate) => void; + /** + * get the cell value of a row and field + * @param id + * @param field + */ + getCellValue: (id: GridRowId, field: string)=> GridCellValue; - onEditRowModelChange: (handler: (param: { update: GridRowModelUpdate }) => void) => void; - onCellModeChange: (handler: (param: { update: GridRowModelUpdate }) => void) => void; + onEditRowModelChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; + onCellModeChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; onEditCellValueChangeCommitted: ( - handler: (param: { update: GridRowModelUpdate }) => void, + handler: (param: { update: GridEditRowUpdate }) => void, ) => void; - onEditCellValueChange: (handler: (param: { update: GridRowModelUpdate }) => void) => void; + onEditCellValueChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; } diff --git a/packages/grid/_modules_/grid/models/gridEditRowModel.ts b/packages/grid/_modules_/grid/models/gridEditRowModel.ts index e0e4fe8d1c45..b1b0b0cfea5f 100644 --- a/packages/grid/_modules_/grid/models/gridEditRowModel.ts +++ b/packages/grid/_modules_/grid/models/gridEditRowModel.ts @@ -1,10 +1,14 @@ import { GridCellValue } from './gridCell'; +import { GridRowId } from './gridRows'; export interface GridEditCellProps { value: GridCellValue; [prop: string]: any; } +export interface GridEditRowUpdate { + id: GridRowId; + [prop: string]: GridCellValue | GridEditCellProps; +} -export type GridEditRow = { [field: string]: true | GridEditCellProps }; -export type GridEditRowsModel = { [rowId: string]: GridEditRow }; +export type GridEditRowsModel = { [rowId: string]: GridEditRowUpdate }; \ No newline at end of file diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index b2d380e52f90..cbf75644808e 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -1,204 +1,203 @@ import { makeStyles } from '@material-ui/core/styles'; import * as React from 'react'; import Button from '@material-ui/core/Button'; -import { - GridCellParams, - GridLoadIcon, - GridRowData, - useGridApiRef, - XGrid, -} from '@material-ui/x-grid'; +import { GridCellParams, GridLoadIcon, GridRowData, useGridApiRef, XGrid } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; import { GridEditRowsModel } from '../../../grid/_modules_/grid/models/gridEditRowModel'; import { randomInt } from '../data/random-generator'; export default { - title: 'X-Grid Tests/Rows', - component: XGrid, - parameters: { - options: { selectedPanel: 'storybook/storysource/panel' }, - docs: { - page: null, - }, - }, + title: 'X-Grid Tests/Rows', + component: XGrid, + parameters: { + options: { selectedPanel: 'storybook/storysource/panel' }, + docs: { + page: null + } + } }; const newRows = [ - { - id: 3, - brand: 'Asics', - }, + { + id: 3, + brand: 'Asics' + } ]; const baselineProps = { - rows: [ - { - id: 0, - brand: 'Nike', - }, - { - id: 1, - brand: 'Adidas', - }, - { - id: 2, - brand: 'Puma', - }, - ], - columns: [{ field: 'brand' }], + rows: [ + { + id: 0, + brand: 'Nike' + }, + { + id: 1, + brand: 'Adidas' + }, + { + id: 2, + brand: 'Puma' + } + ], + columns: [ { field: 'brand' } ] }; function getStoryRowId(row) { - return row.brand; + return row.brand; } export function NoId() { - const [rows] = React.useState([ - { - brand: 'Nike', - }, - { - brand: 'Adidas', - }, - { - brand: 'Puma', - }, - ]); - - return ( -
- -
- ); + const [ rows ] = React.useState([ + { + brand: 'Nike' + }, + { + brand: 'Adidas' + }, + { + brand: 'Puma' + } + ]); + + return ( +
+ +
+ ); } export function CommodityNewRowId() { - const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); - const getRowId = React.useCallback((row: GridRowData) => `${row.desk}-${row.commodity}`, []); - return ( -
- c.field !== 'id')} - getRowId={getRowId} - /> -
- ); + const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); + const getRowId = React.useCallback((row: GridRowData) => `${row.desk}-${row.commodity}`, []); + return ( +
+ c.field !== 'id')} getRowId={getRowId} /> +
+ ); } export function SetRowsViaApi() { - const apiRef = useGridApiRef(); - - const setNewRows = React.useCallback(() => { - apiRef.current.setRows(newRows); - }, [apiRef]); - - return ( - -
- -
-
- -
-
- ); + const apiRef = useGridApiRef(); + + const setNewRows = React.useCallback( + () => { + apiRef.current.setRows(newRows); + }, + [ apiRef ] + ); + + return ( + +
+ +
+
+ +
+
+ ); } export function SetCommodityRowsViaApi() { - const apiRef = useGridApiRef(); - const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); - const apiDemoData = useDemoData({ dataSet: 'Commodity', rowLength: 150 }); - - const setNewRows = React.useCallback(() => { - apiDemoData.setRowLength(randomInt(100, 500)); - apiDemoData.loadNewData(); - apiRef.current.setRows(apiDemoData.data.rows); - }, [apiDemoData, apiRef]); - - return ( - -
- -
-
- -
-
- ); + const apiRef = useGridApiRef(); + const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); + const apiDemoData = useDemoData({ dataSet: 'Commodity', rowLength: 150 }); + + const setNewRows = React.useCallback( + () => { + apiDemoData.setRowLength(randomInt(100, 500)); + apiDemoData.loadNewData(); + apiRef.current.setRows(apiDemoData.data.rows); + }, + [ apiDemoData, apiRef ] + ); + + return ( + +
+ +
+
+ +
+
+ ); } export function ChangeRowsAndColumns() { - const [rows, setRows] = React.useState(baselineProps.rows); - const [cols, setCols] = React.useState(baselineProps.columns); - - const changeDataSet = React.useCallback(() => { - const newData = { - rows: [ - { - id: 0, - country: 'France', - }, - { - id: 1, - country: 'UK', - }, - { - id: 12, - country: 'US', - }, - ], - columns: [{ field: 'country' }], - }; - - setRows(newData.rows); - setCols(newData.columns); - }, []); - - return ( - -
- -
-
- -
-
- ); + const [ rows, setRows ] = React.useState(baselineProps.rows); + const [ cols, setCols ] = React.useState(baselineProps.columns); + + const changeDataSet = React.useCallback(() => { + const newData = { + rows: [ + { + id: 0, + country: 'France' + }, + { + id: 1, + country: 'UK' + }, + { + id: 12, + country: 'US' + } + ], + columns: [ { field: 'country' } ] + }; + + setRows(newData.rows); + setCols(newData.columns); + }, []); + + return ( + +
+ +
+
+ +
+
+ ); } const rows: any = [ - { id: 1, col1: 'Hello', col2: 'World' }, - { id: 2, col1: 'XGrid', col2: 'is Awesome' }, - { id: 3, col1: 'Material-UI', col2: 'is Amazing' }, - { id: 4, col1: 'Hello', col2: 'World' }, - { id: 5, col1: 'XGrid', col2: 'is Awesome' }, - { id: 6, col1: 'Material-UI', col2: 'is Amazing' }, + { id: 1, col1: 'Hello', col2: 'World' }, + { id: 2, col1: 'XGrid', col2: 'is Awesome' }, + { id: 3, col1: 'Material-UI', col2: 'is Amazing' }, + { id: 4, col1: 'Hello', col2: 'World' }, + { id: 5, col1: 'XGrid', col2: 'is Awesome' }, + { id: 6, col1: 'Material-UI', col2: 'is Amazing' } ]; const columns: any[] = [ - { field: 'id', hide: true }, - { field: 'col1', headerName: 'Column 1', width: 150 }, - { field: 'col2', headerName: 'Column 2', width: 150 }, + { field: 'id', hide: true }, + { field: 'col1', headerName: 'Column 1', width: 150 }, + { field: 'col2', headerName: 'Column 2', width: 150 } ]; export function ScrollIssue() { - const apiRef = useGridApiRef(); - - React.useEffect(() => { - const timeout = setTimeout(() => { - apiRef.current.scrollToIndexes({ colIndex: 0, rowIndex: 6 }); - }, 0); - - return () => clearTimeout(timeout); - }, [apiRef]); - - return ( -
- -
- ); + const apiRef = useGridApiRef(); + + React.useEffect( + () => { + const timeout = setTimeout(() => { + apiRef.current.scrollToIndexes({ colIndex: 0, rowIndex: 6 }); + }, 0); + + return () => clearTimeout(timeout); + }, + [ apiRef ] + ); + + return ( +
+ +
+ ); } // Requirements @@ -237,196 +236,227 @@ colDef.renderEditCell // TODO create inputs for each col types const baselineEditProps = { - rows: [ - { - id: 0, - firstname: 'Damien', - lastname: 'Tassone', - email: 'damien@material-ui.com', - username: 'Damo', - lastLogin: new Date(), - age: 25, - DOB: new Date(1996, 10, 2), - meetup: new Date(2020, 2, 25, 10, 50, 0), - }, - { - id: 1, - firstname: 'Jon', - lastname: 'Wood', - email: 'jon@material-ui.com', - username: 'jon', - lastLogin: new Date(), - age: 25, - DOB: new Date(1992, 1, 20), - meetup: new Date(2020, 4, 15, 10, 50, 0), - }, - { - id: 2, - firstname: 'James', - lastname: 'Smith', - email: 'james@material-ui.com', - username: 'smithhhh', - lastLogin: new Date(), - age: 25, - DOB: new Date(1986, 0, 12), - meetup: new Date(2020, 3, 5, 10, 50, 0), - }, - ], - columns: [ - { field: 'firstname', editable: true }, - { field: 'lastname', editable: true }, - { - field: 'fullname', - editable: true, - valueGetter: ({ row }) => `${row.firstname} ${row.lastname}`, - }, - { field: 'username', editable: true }, - { field: 'email', editable: true, width: 150 }, - { field: 'age', width: 50, type: 'number', editable: true }, - { field: 'DOB', width: 120, type: 'date', editable: true }, - { field: 'meetup', width: 180, type: 'dateTime', editable: true }, - { field: 'lastLogin', width: 180, type: 'dateTime', editable: false }, - ], + rows: [ + { + id: 0, + firstname: 'Damien', + lastname: 'Tassone', + email: 'damien@material-ui.com', + username: 'Damo', + lastLogin: new Date(), + age: 25, + DOB: new Date(1996, 10, 2), + meetup: new Date(2020, 2, 25, 10, 50, 0) + }, + { + id: 1, + firstname: 'Jon', + lastname: 'Wood', + email: 'jon@material-ui.com', + username: 'jon', + lastLogin: new Date(), + age: 25, + DOB: new Date(1992, 1, 20), + meetup: new Date(2020, 4, 15, 10, 50, 0) + }, + { + id: 2, + firstname: 'James', + lastname: 'Smith', + email: 'james@material-ui.com', + username: 'smithhhh', + lastLogin: new Date(), + age: 25, + DOB: new Date(1986, 0, 12), + meetup: new Date(2020, 3, 5, 10, 50, 0) + } + ], + columns: [ + { field: 'firstname', editable: true }, + { field: 'lastname', editable: true }, + { + field: 'fullname', + editable: true, + valueGetter: ({ row }) => `${row.firstname} ${row.lastname}` + }, + { field: 'username', editable: true }, + { field: 'email', editable: true, width: 150 }, + { field: 'age', width: 50, type: 'number', editable: true }, + { field: 'DOB', width: 120, type: 'date', editable: true }, + { field: 'meetup', width: 180, type: 'dateTime', editable: true }, + { field: 'lastLogin', width: 180, type: 'dateTime', editable: false } + ] }; function validateEmail(email) { - const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(String(email).toLowerCase()); + const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); } const useStyles = makeStyles({ - root: { - '& .MuiDataGrid-cellEditable': { - backgroundColor: 'rgba(184,250,158,0.19)', - color: '#1a3e72', - }, - '& .MuiDataGrid-cellEditing': { - backgroundColor: 'rgb(255,215,115, 0.19)', - color: '#1a3e72', - }, - '& .Mui-error': { - backgroundColor: 'rgb(126,10,15, 0.1)', - color: '#750f0f', - }, - }, + root: { + '& .MuiDataGrid-cellEditable': { + backgroundColor: 'rgba(184,250,158,0.19)', + color: '#1a3e72' + }, + '& .MuiDataGrid-cellEditing': { + backgroundColor: 'rgb(255,215,115, 0.19)', + color: '#1a3e72' + }, + '& .Mui-error': { + backgroundColor: 'rgb(126,10,15, 0.1)', + color: '#750f0f' + } + } }); -export function EditRows() { - const apiRef = useGridApiRef(); - const classes = useStyles(); - - const [selectedCell, setSelectedCell] = React.useState<[string, string] | null>(null); - const [isEditable, setIsEditable] = React.useState(false); - const [editRowsModel, setEditRowsModel] = React.useState({}); - - const editRow = React.useCallback(() => { - if (!selectedCell) { - return; - } - - setEditRowsModel((state) => { - const editRowState: GridEditRowsModel = { ...state }; - editRowState[selectedCell[0]] = editRowState[selectedCell[0]] - ? editRowState[selectedCell[0]] - : {}; - editRowState[selectedCell[0]][selectedCell[1]] = true; - - return { ...state, ...editRowState }; - }); - }, [selectedCell]); - - const onCellClick = React.useCallback((params: GridCellParams) => { - setSelectedCell([params.row.id!.toString(), params.field]); - setIsEditable(!!params.isEditable); - }, []); - - const onCellDoubleClick = React.useCallback( - (params: GridCellParams) => { - if (params.isEditable) { - apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); - } - }, - [apiRef], - ); - - const isCellEditable = React.useCallback((params: GridCellParams) => params.row.id !== 0, []); - - const onEditCellValueChange = React.useCallback( - ({ update }) => { - if (update.email) { - const isValid = validateEmail(update.email); - const newState = {}; - newState[update.id] = { - ...editRowsModel[update.id], - email: { value: update.email, error: !isValid }, - }; - setEditRowsModel((state) => ({ ...state, ...newState })); - } - if (update.DOB) { - const newState = {}; - newState[update.id] = { ...editRowsModel[update.id], DOB: { value: new Date(update.DOB) } }; - setEditRowsModel((state) => ({ ...state, ...newState })); - } - if (update.meetup) { - const newState = {}; - newState[update.id] = { - ...editRowsModel[update.id], - meetup: { value: new Date(update.meetup) }, - }; - setEditRowsModel((state) => ({ ...state, ...newState })); - } - }, - [editRowsModel], - ); - - const onEditCellValueChangeCommitted = React.useCallback( - ({ update }) => { - const field = Object.keys(update).find((key) => key !== 'id')!; - if (update.email) { - const newState = {}; - const componentProps = { - InputProps: { endAdornment: }, - }; - newState[update.id] = {}; - newState[update.id][field] = { value: update.email, ...componentProps }; - setEditRowsModel((state) => ({ ...state, ...newState })); - setTimeout(() => { - apiRef.current.updateRows([update]); - apiRef.current.setCellMode(update.id, field, 'view'); - }, 2000); - } else if (update.fullname) { - const [firstname, lastname] = update.fullname.split(' '); - apiRef.current.updateRows([{ id: update.id, firstname, lastname }]); - apiRef.current.setCellMode(update.id, field, 'view'); - } else { - apiRef.current.updateRows([update]); - apiRef.current.setCellMode(update.id, field, 'view'); - } - }, - [apiRef], - ); - - return ( - - Green cells are editable! Click + EDIT or Double click -
- -
-
- -
-
- ); +export function EditRowsControl() { + const apiRef = useGridApiRef(); + const classes = useStyles(); + + const [ selectedCell, setSelectedCell ] = React.useState<[string, string] | null>(null); + const [ isEditable, setIsEditable ] = React.useState(false); + const [ editRowsModel, setEditRowsModel ] = React.useState({}); + + const editRow = React.useCallback( + () => { + if (!selectedCell) { + return; + } + + setEditRowsModel((state) => { + const editRowState: GridEditRowsModel = { ...state }; + editRowState[selectedCell[0]] = editRowState[selectedCell[0]] ? editRowState[selectedCell[0]] : {}; + editRowState[selectedCell[0]][selectedCell[1]] = true; + + return { ...state, ...editRowState }; + }); + }, + [ selectedCell ] + ); + + const onCellClick = React.useCallback((params: GridCellParams) => { + setSelectedCell([ params.row.id!.toString(), params.field ]); + setIsEditable(!!params.isEditable); + }, []); + + const onCellDoubleClick = React.useCallback( + (params: GridCellParams) => { + if (params.isEditable) { + apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); + } + }, + [ apiRef ] + ); + + const isCellEditable = React.useCallback((params: GridCellParams) => params.row.id !== 0, []); + + const onEditCellValueChange = React.useCallback( + ({ update }) => { + if (update.email) { + const isValid = validateEmail(update.email); + const newState = {}; + newState[update.id] = { + ...editRowsModel[update.id], + email: { value: update.email, error: !isValid } + }; + setEditRowsModel((state) => ({ ...state, ...newState })); + return; + } + if (update.DOB) { + const newState = {}; + newState[update.id] = { ...editRowsModel[update.id], DOB: { value: new Date(update.DOB) } }; + setEditRowsModel((state) => ({ ...state, ...newState })); + return; + } + if (update.meetup) { + const newState = {}; + newState[update.id] = { + ...editRowsModel[update.id], + meetup: { value: new Date(update.meetup) } + }; + setEditRowsModel((state) => ({ ...state, ...newState })); + return; + } + const newState = {}; + newState[update.id] = { + ...editRowsModel[update.id], + ...update + }; + setEditRowsModel((state) => ({ ...state, ...newState })); + }, + [ editRowsModel ] + ); + + const onEditCellValueChangeCommitted = React.useCallback( + ({ update }) => { + const field = Object.keys(update).find((key) => key !== 'id')!; + if (update.email) { + const newState = {}; + const componentProps = { + InputProps: { endAdornment: } + }; + newState[update.id] = {}; + newState[update.id][field] = { value: update.email, ...componentProps }; + setEditRowsModel((state) => ({ ...state, ...newState })); + setTimeout(() => { + apiRef.current.updateRows([ update ]); + apiRef.current.setCellMode(update.id, field, 'view'); + }, 2000); + } else if (update.fullname) { + const [ firstname, lastname ] = update.fullname.split(' '); + apiRef.current.updateRows([ { id: update.id, firstname, lastname } ]); + apiRef.current.setCellMode(update.id, field, 'view'); + } else { + apiRef.current.updateRows([ update ]); + apiRef.current.setCellMode(update.id, field, 'view'); + } + }, + [ apiRef ] + ); + + return ( + + Green cells are editable! Click + EDIT or Double click +
+ +
+
+ +
+
+ ); +} +export function EditRowsBasic() { + const apiRef = useGridApiRef(); + + const onCellDoubleClick = React.useCallback( + (params: GridCellParams) => { + if (params.isEditable) { + apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); + } + }, + [ apiRef ] + ); + + return ( + + Green cells are editable! Double click +
+ +
+
+ ); } From a0642b9f1ab09d4de8d13efc0ac60c713ccfcc74 Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 24 Feb 2021 19:45:14 +0100 Subject: [PATCH 06/32] format --- .../grid/components/GridRowCells.tsx | 9 +- .../hooks/features/rows/useGridEditRows.ts | 57 +- .../grid/models/api/gridEditRowApi.ts | 10 +- .../_modules_/grid/models/gridEditRowModel.ts | 2 +- .../src/stories/grid-rows.stories.tsx | 754 +++++++++--------- 5 files changed, 417 insertions(+), 415 deletions(-) diff --git a/packages/grid/_modules_/grid/components/GridRowCells.tsx b/packages/grid/_modules_/grid/components/GridRowCells.tsx index a8a5efc8501d..6cdbca6fedd7 100644 --- a/packages/grid/_modules_/grid/components/GridRowCells.tsx +++ b/packages/grid/_modules_/grid/components/GridRowCells.tsx @@ -89,7 +89,7 @@ export const GridRowCells: React.FC = React.memo((props) => { const editCellState = editRowsState[row.id] && editRowsState[row.id][column.field]; let cellComponent: React.ReactElement | null = null; - + if (column.valueGetter) { // Value getter override the original value value = column.valueGetter(cellParams); @@ -108,8 +108,11 @@ export const GridRowCells: React.FC = React.memo((props) => { } if (editCellState != null && column.renderEditCell) { - const params = editCellState === true || typeof editCellState !== 'object' ? cellParams : { ...cellParams, ...editCellState }; - if(editCellState !== true && typeof editCellState !== 'object') { + const params = + editCellState === true || typeof editCellState !== 'object' + ? cellParams + : { ...cellParams, ...editCellState }; + if (editCellState !== true && typeof editCellState !== 'object') { params.value = editCellState; } cellComponent = column.renderEditCell(params); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index 3b62813df116..b3b786c2ca43 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -3,7 +3,7 @@ import { GRID_CELL_MODE_CHANGE, GRID_CELL_VALUE_CHANGE, GRID_CELL_VALUE_CHANGE_COMMITTED, - GRID_EDIT_ROW_MODEL_CHANGE + GRID_EDIT_ROW_MODEL_CHANGE, } from '../../../constants/eventsConstants'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { GridEditRowApi } from '../../../models/api/gridEditRowApi'; @@ -38,11 +38,11 @@ export function useGridEditRows(apiRef: GridApiRef) { value: rowModel[field], colDef, rowModel, - api: apiRef.current - }) + api: apiRef.current, + }), ); }, - [apiRef] + [apiRef], ); const setCellEditMode = React.useCallback( @@ -65,11 +65,11 @@ export function useGridEditRows(apiRef: GridApiRef) { id, field, mode: 'edit', - api: apiRef.current + api: apiRef.current, }); forceUpdate(); }, - [apiRef, forceUpdate, getCellValue, setGridState] + [apiRef, forceUpdate, getCellValue, setGridState], ); const setCellViewMode = React.useCallback( @@ -95,11 +95,11 @@ export function useGridEditRows(apiRef: GridApiRef) { id, field, mode: 'view', - api: apiRef.current + api: apiRef.current, }); forceUpdate(); }, - [apiRef, forceUpdate, setGridState] + [apiRef, forceUpdate, setGridState], ); const setCellMode = React.useCallback( @@ -110,7 +110,7 @@ export function useGridEditRows(apiRef: GridApiRef) { setCellViewMode(id, field); } }, - [setCellEditMode, setCellViewMode] + [setCellEditMode, setCellViewMode], ); const isCellEditable = React.useCallback( @@ -118,7 +118,7 @@ export function useGridEditRows(apiRef: GridApiRef) { return params.colDef.editable && (!options.isCellEditable || options.isCellEditable(params)); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [options.isCellEditable] + [options.isCellEditable], ); const commitCellValueChanges = React.useCallback( @@ -126,7 +126,7 @@ export function useGridEditRows(apiRef: GridApiRef) { if (options.editMode === GridFeatureModeConstant.server) { apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, { update, - api: apiRef.current + api: apiRef.current, }); return; } @@ -134,7 +134,7 @@ export function useGridEditRows(apiRef: GridApiRef) { const field = Object.keys(update).find((key) => key !== 'id')!; apiRef.current.setCellMode(update.id, field, 'view'); }, - [apiRef, options.editMode] + [apiRef, options.editMode], ); const setEditCellValue = React.useCallback( @@ -147,14 +147,14 @@ export function useGridEditRows(apiRef: GridApiRef) { const newState = { ...state.editRows }; newState[update.id] = { ...state.editRows[update.id], - ...update + ...update, }; return { ...state, editRows: newState }; }); forceUpdate(); }, - [apiRef, forceUpdate, options.editMode, setGridState] + [apiRef, forceUpdate, options.editMode, setGridState], ); const setEditRowsModel = React.useCallback( @@ -165,36 +165,40 @@ export function useGridEditRows(apiRef: GridApiRef) { }); forceUpdate(); }, - [forceUpdate, setGridState] + [forceUpdate, setGridState], ); // TODO cleanup params What should we put? const onEditCellValueChange = React.useCallback( (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { return apiRef.current.subscribeEvent(GRID_CELL_VALUE_CHANGE, handler); }, - [apiRef] + [apiRef], ); const onEditCellValueChangeCommitted = React.useCallback( (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { return apiRef.current.subscribeEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, handler); }, - [apiRef] + [apiRef], ); const onCellModeChange = React.useCallback( (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { return apiRef.current.subscribeEvent(GRID_CELL_MODE_CHANGE, handler); }, - [apiRef] + [apiRef], ); const onEditRowModelChange = React.useCallback( (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { return apiRef.current.subscribeEvent(GRID_EDIT_ROW_MODEL_CHANGE, handler); }, - [apiRef] + [apiRef], ); // TODO add those options.handlers on apiRef useGridApiEventHandler(apiRef, GRID_CELL_VALUE_CHANGE, options.onEditCellValueChange); - useGridApiEventHandler(apiRef, GRID_CELL_VALUE_CHANGE_COMMITTED, options.onEditCellValueChangeCommitted); + useGridApiEventHandler( + apiRef, + GRID_CELL_VALUE_CHANGE_COMMITTED, + options.onEditCellValueChangeCommitted, + ); useGridApiEventHandler(apiRef, GRID_CELL_MODE_CHANGE, options.onCellModeChange); useGridApiEventHandler(apiRef, GRID_EDIT_ROW_MODEL_CHANGE, options.onEditRowModelChange); @@ -210,15 +214,12 @@ export function useGridEditRows(apiRef: GridApiRef) { isCellEditable, commitCellValueChanges, setEditCellValue, - setEditRowsModel + setEditRowsModel, }, - 'EditRowApi' + 'EditRowApi', ); - React.useEffect( - () => { - apiRef.current.setEditRowsModel(options.editRowsModel || {}); - }, - [apiRef, forceUpdate, options.editRowsModel] - ); + React.useEffect(() => { + apiRef.current.setEditRowsModel(options.editRowsModel || {}); + }, [apiRef, forceUpdate, options.editRowsModel]); } diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts index 546c4c8b1b1f..ce4db5def6ac 100644 --- a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -1,6 +1,6 @@ import { GridCellMode, GridCellValue } from '../gridCell'; import { GridEditRowsModel, GridEditRowUpdate } from '../gridEditRowModel'; -import { GridRowId, GridRowModelUpdate } from '../gridRows'; +import { GridRowId } from '../gridRows'; import { GridCellParams } from '../params/gridCellParams'; export interface GridEditRowApi { @@ -32,16 +32,14 @@ export interface GridEditRowApi { */ commitCellValueChanges: (update: GridEditRowUpdate) => void; /** - * get the cell value of a row and field + * get the cell value of a row and field * @param id * @param field */ - getCellValue: (id: GridRowId, field: string)=> GridCellValue; + getCellValue: (id: GridRowId, field: string) => GridCellValue; onEditRowModelChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; onCellModeChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; - onEditCellValueChangeCommitted: ( - handler: (param: { update: GridEditRowUpdate }) => void, - ) => void; + onEditCellValueChangeCommitted: (handler: (param: { update: GridEditRowUpdate }) => void) => void; onEditCellValueChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; } diff --git a/packages/grid/_modules_/grid/models/gridEditRowModel.ts b/packages/grid/_modules_/grid/models/gridEditRowModel.ts index b1b0b0cfea5f..64e809f86c01 100644 --- a/packages/grid/_modules_/grid/models/gridEditRowModel.ts +++ b/packages/grid/_modules_/grid/models/gridEditRowModel.ts @@ -11,4 +11,4 @@ export interface GridEditRowUpdate { [prop: string]: GridCellValue | GridEditCellProps; } -export type GridEditRowsModel = { [rowId: string]: GridEditRowUpdate }; \ No newline at end of file +export type GridEditRowsModel = { [rowId: string]: GridEditRowUpdate }; diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index cbf75644808e..47955ed18ee8 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -1,203 +1,204 @@ import { makeStyles } from '@material-ui/core/styles'; import * as React from 'react'; import Button from '@material-ui/core/Button'; -import { GridCellParams, GridLoadIcon, GridRowData, useGridApiRef, XGrid } from '@material-ui/x-grid'; +import { + GridCellParams, + GridLoadIcon, + GridRowData, + useGridApiRef, + XGrid, +} from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; import { GridEditRowsModel } from '../../../grid/_modules_/grid/models/gridEditRowModel'; import { randomInt } from '../data/random-generator'; export default { - title: 'X-Grid Tests/Rows', - component: XGrid, - parameters: { - options: { selectedPanel: 'storybook/storysource/panel' }, - docs: { - page: null - } - } + title: 'X-Grid Tests/Rows', + component: XGrid, + parameters: { + options: { selectedPanel: 'storybook/storysource/panel' }, + docs: { + page: null, + }, + }, }; const newRows = [ - { - id: 3, - brand: 'Asics' - } + { + id: 3, + brand: 'Asics', + }, ]; const baselineProps = { - rows: [ - { - id: 0, - brand: 'Nike' - }, - { - id: 1, - brand: 'Adidas' - }, - { - id: 2, - brand: 'Puma' - } - ], - columns: [ { field: 'brand' } ] + rows: [ + { + id: 0, + brand: 'Nike', + }, + { + id: 1, + brand: 'Adidas', + }, + { + id: 2, + brand: 'Puma', + }, + ], + columns: [{ field: 'brand' }], }; function getStoryRowId(row) { - return row.brand; + return row.brand; } export function NoId() { - const [ rows ] = React.useState([ - { - brand: 'Nike' - }, - { - brand: 'Adidas' - }, - { - brand: 'Puma' - } - ]); - - return ( -
- -
- ); + const [rows] = React.useState([ + { + brand: 'Nike', + }, + { + brand: 'Adidas', + }, + { + brand: 'Puma', + }, + ]); + + return ( +
+ +
+ ); } export function CommodityNewRowId() { - const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); - const getRowId = React.useCallback((row: GridRowData) => `${row.desk}-${row.commodity}`, []); - return ( -
- c.field !== 'id')} getRowId={getRowId} /> -
- ); + const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); + const getRowId = React.useCallback((row: GridRowData) => `${row.desk}-${row.commodity}`, []); + return ( +
+ c.field !== 'id')} + getRowId={getRowId} + /> +
+ ); } export function SetRowsViaApi() { - const apiRef = useGridApiRef(); - - const setNewRows = React.useCallback( - () => { - apiRef.current.setRows(newRows); - }, - [ apiRef ] - ); - - return ( - -
- -
-
- -
-
- ); + const apiRef = useGridApiRef(); + + const setNewRows = React.useCallback(() => { + apiRef.current.setRows(newRows); + }, [apiRef]); + + return ( + +
+ +
+
+ +
+
+ ); } export function SetCommodityRowsViaApi() { - const apiRef = useGridApiRef(); - const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); - const apiDemoData = useDemoData({ dataSet: 'Commodity', rowLength: 150 }); - - const setNewRows = React.useCallback( - () => { - apiDemoData.setRowLength(randomInt(100, 500)); - apiDemoData.loadNewData(); - apiRef.current.setRows(apiDemoData.data.rows); - }, - [ apiDemoData, apiRef ] - ); - - return ( - -
- -
-
- -
-
- ); + const apiRef = useGridApiRef(); + const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); + const apiDemoData = useDemoData({ dataSet: 'Commodity', rowLength: 150 }); + + const setNewRows = React.useCallback(() => { + apiDemoData.setRowLength(randomInt(100, 500)); + apiDemoData.loadNewData(); + apiRef.current.setRows(apiDemoData.data.rows); + }, [apiDemoData, apiRef]); + + return ( + +
+ +
+
+ +
+
+ ); } export function ChangeRowsAndColumns() { - const [ rows, setRows ] = React.useState(baselineProps.rows); - const [ cols, setCols ] = React.useState(baselineProps.columns); - - const changeDataSet = React.useCallback(() => { - const newData = { - rows: [ - { - id: 0, - country: 'France' - }, - { - id: 1, - country: 'UK' - }, - { - id: 12, - country: 'US' - } - ], - columns: [ { field: 'country' } ] - }; - - setRows(newData.rows); - setCols(newData.columns); - }, []); - - return ( - -
- -
-
- -
-
- ); + const [rows, setRows] = React.useState(baselineProps.rows); + const [cols, setCols] = React.useState(baselineProps.columns); + + const changeDataSet = React.useCallback(() => { + const newData = { + rows: [ + { + id: 0, + country: 'France', + }, + { + id: 1, + country: 'UK', + }, + { + id: 12, + country: 'US', + }, + ], + columns: [{ field: 'country' }], + }; + + setRows(newData.rows); + setCols(newData.columns); + }, []); + + return ( + +
+ +
+
+ +
+
+ ); } const rows: any = [ - { id: 1, col1: 'Hello', col2: 'World' }, - { id: 2, col1: 'XGrid', col2: 'is Awesome' }, - { id: 3, col1: 'Material-UI', col2: 'is Amazing' }, - { id: 4, col1: 'Hello', col2: 'World' }, - { id: 5, col1: 'XGrid', col2: 'is Awesome' }, - { id: 6, col1: 'Material-UI', col2: 'is Amazing' } + { id: 1, col1: 'Hello', col2: 'World' }, + { id: 2, col1: 'XGrid', col2: 'is Awesome' }, + { id: 3, col1: 'Material-UI', col2: 'is Amazing' }, + { id: 4, col1: 'Hello', col2: 'World' }, + { id: 5, col1: 'XGrid', col2: 'is Awesome' }, + { id: 6, col1: 'Material-UI', col2: 'is Amazing' }, ]; const columns: any[] = [ - { field: 'id', hide: true }, - { field: 'col1', headerName: 'Column 1', width: 150 }, - { field: 'col2', headerName: 'Column 2', width: 150 } + { field: 'id', hide: true }, + { field: 'col1', headerName: 'Column 1', width: 150 }, + { field: 'col2', headerName: 'Column 2', width: 150 }, ]; export function ScrollIssue() { - const apiRef = useGridApiRef(); - - React.useEffect( - () => { - const timeout = setTimeout(() => { - apiRef.current.scrollToIndexes({ colIndex: 0, rowIndex: 6 }); - }, 0); - - return () => clearTimeout(timeout); - }, - [ apiRef ] - ); - - return ( -
- -
- ); + const apiRef = useGridApiRef(); + + React.useEffect(() => { + const timeout = setTimeout(() => { + apiRef.current.scrollToIndexes({ colIndex: 0, rowIndex: 6 }); + }, 0); + + return () => clearTimeout(timeout); + }, [apiRef]); + + return ( +
+ +
+ ); } // Requirements @@ -236,227 +237,226 @@ colDef.renderEditCell // TODO create inputs for each col types const baselineEditProps = { - rows: [ - { - id: 0, - firstname: 'Damien', - lastname: 'Tassone', - email: 'damien@material-ui.com', - username: 'Damo', - lastLogin: new Date(), - age: 25, - DOB: new Date(1996, 10, 2), - meetup: new Date(2020, 2, 25, 10, 50, 0) - }, - { - id: 1, - firstname: 'Jon', - lastname: 'Wood', - email: 'jon@material-ui.com', - username: 'jon', - lastLogin: new Date(), - age: 25, - DOB: new Date(1992, 1, 20), - meetup: new Date(2020, 4, 15, 10, 50, 0) - }, - { - id: 2, - firstname: 'James', - lastname: 'Smith', - email: 'james@material-ui.com', - username: 'smithhhh', - lastLogin: new Date(), - age: 25, - DOB: new Date(1986, 0, 12), - meetup: new Date(2020, 3, 5, 10, 50, 0) - } - ], - columns: [ - { field: 'firstname', editable: true }, - { field: 'lastname', editable: true }, - { - field: 'fullname', - editable: true, - valueGetter: ({ row }) => `${row.firstname} ${row.lastname}` - }, - { field: 'username', editable: true }, - { field: 'email', editable: true, width: 150 }, - { field: 'age', width: 50, type: 'number', editable: true }, - { field: 'DOB', width: 120, type: 'date', editable: true }, - { field: 'meetup', width: 180, type: 'dateTime', editable: true }, - { field: 'lastLogin', width: 180, type: 'dateTime', editable: false } - ] + rows: [ + { + id: 0, + firstname: 'Damien', + lastname: 'Tassone', + email: 'damien@material-ui.com', + username: 'Damo', + lastLogin: new Date(), + age: 25, + DOB: new Date(1996, 10, 2), + meetup: new Date(2020, 2, 25, 10, 50, 0), + }, + { + id: 1, + firstname: 'Jon', + lastname: 'Wood', + email: 'jon@material-ui.com', + username: 'jon', + lastLogin: new Date(), + age: 25, + DOB: new Date(1992, 1, 20), + meetup: new Date(2020, 4, 15, 10, 50, 0), + }, + { + id: 2, + firstname: 'James', + lastname: 'Smith', + email: 'james@material-ui.com', + username: 'smithhhh', + lastLogin: new Date(), + age: 25, + DOB: new Date(1986, 0, 12), + meetup: new Date(2020, 3, 5, 10, 50, 0), + }, + ], + columns: [ + { field: 'firstname', editable: true }, + { field: 'lastname', editable: true }, + { + field: 'fullname', + editable: true, + valueGetter: ({ row }) => `${row.firstname} ${row.lastname}`, + }, + { field: 'username', editable: true }, + { field: 'email', editable: true, width: 150 }, + { field: 'age', width: 50, type: 'number', editable: true }, + { field: 'DOB', width: 120, type: 'date', editable: true }, + { field: 'meetup', width: 180, type: 'dateTime', editable: true }, + { field: 'lastLogin', width: 180, type: 'dateTime', editable: false }, + ], }; function validateEmail(email) { - const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(String(email).toLowerCase()); + const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); } const useStyles = makeStyles({ - root: { - '& .MuiDataGrid-cellEditable': { - backgroundColor: 'rgba(184,250,158,0.19)', - color: '#1a3e72' - }, - '& .MuiDataGrid-cellEditing': { - backgroundColor: 'rgb(255,215,115, 0.19)', - color: '#1a3e72' - }, - '& .Mui-error': { - backgroundColor: 'rgb(126,10,15, 0.1)', - color: '#750f0f' - } - } + root: { + '& .MuiDataGrid-cellEditable': { + backgroundColor: 'rgba(184,250,158,0.19)', + color: '#1a3e72', + }, + '& .MuiDataGrid-cellEditing': { + backgroundColor: 'rgb(255,215,115, 0.19)', + color: '#1a3e72', + }, + '& .Mui-error': { + backgroundColor: 'rgb(126,10,15, 0.1)', + color: '#750f0f', + }, + }, }); export function EditRowsControl() { - const apiRef = useGridApiRef(); - const classes = useStyles(); - - const [ selectedCell, setSelectedCell ] = React.useState<[string, string] | null>(null); - const [ isEditable, setIsEditable ] = React.useState(false); - const [ editRowsModel, setEditRowsModel ] = React.useState({}); - - const editRow = React.useCallback( - () => { - if (!selectedCell) { - return; - } - - setEditRowsModel((state) => { - const editRowState: GridEditRowsModel = { ...state }; - editRowState[selectedCell[0]] = editRowState[selectedCell[0]] ? editRowState[selectedCell[0]] : {}; - editRowState[selectedCell[0]][selectedCell[1]] = true; - - return { ...state, ...editRowState }; - }); - }, - [ selectedCell ] - ); - - const onCellClick = React.useCallback((params: GridCellParams) => { - setSelectedCell([ params.row.id!.toString(), params.field ]); - setIsEditable(!!params.isEditable); - }, []); - - const onCellDoubleClick = React.useCallback( - (params: GridCellParams) => { - if (params.isEditable) { - apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); - } - }, - [ apiRef ] - ); - - const isCellEditable = React.useCallback((params: GridCellParams) => params.row.id !== 0, []); - - const onEditCellValueChange = React.useCallback( - ({ update }) => { - if (update.email) { - const isValid = validateEmail(update.email); - const newState = {}; - newState[update.id] = { - ...editRowsModel[update.id], - email: { value: update.email, error: !isValid } - }; - setEditRowsModel((state) => ({ ...state, ...newState })); - return; - } - if (update.DOB) { - const newState = {}; - newState[update.id] = { ...editRowsModel[update.id], DOB: { value: new Date(update.DOB) } }; - setEditRowsModel((state) => ({ ...state, ...newState })); - return; - } - if (update.meetup) { - const newState = {}; - newState[update.id] = { - ...editRowsModel[update.id], - meetup: { value: new Date(update.meetup) } - }; - setEditRowsModel((state) => ({ ...state, ...newState })); - return; - } - const newState = {}; - newState[update.id] = { - ...editRowsModel[update.id], - ...update - }; - setEditRowsModel((state) => ({ ...state, ...newState })); - }, - [ editRowsModel ] - ); - - const onEditCellValueChangeCommitted = React.useCallback( - ({ update }) => { - const field = Object.keys(update).find((key) => key !== 'id')!; - if (update.email) { - const newState = {}; - const componentProps = { - InputProps: { endAdornment: } - }; - newState[update.id] = {}; - newState[update.id][field] = { value: update.email, ...componentProps }; - setEditRowsModel((state) => ({ ...state, ...newState })); - setTimeout(() => { - apiRef.current.updateRows([ update ]); - apiRef.current.setCellMode(update.id, field, 'view'); - }, 2000); - } else if (update.fullname) { - const [ firstname, lastname ] = update.fullname.split(' '); - apiRef.current.updateRows([ { id: update.id, firstname, lastname } ]); - apiRef.current.setCellMode(update.id, field, 'view'); - } else { - apiRef.current.updateRows([ update ]); - apiRef.current.setCellMode(update.id, field, 'view'); - } - }, - [ apiRef ] - ); - - return ( - - Green cells are editable! Click + EDIT or Double click -
- -
-
- -
-
- ); + const apiRef = useGridApiRef(); + const classes = useStyles(); + + const [selectedCell, setSelectedCell] = React.useState<[string, string] | null>(null); + const [isEditable, setIsEditable] = React.useState(false); + const [editRowsModel, setEditRowsModel] = React.useState({}); + + const editRow = React.useCallback(() => { + if (!selectedCell) { + return; + } + + setEditRowsModel((state) => { + const editRowState: GridEditRowsModel = { ...state }; + editRowState[selectedCell[0]] = editRowState[selectedCell[0]] + ? editRowState[selectedCell[0]] + : {}; + editRowState[selectedCell[0]][selectedCell[1]] = true; + + return { ...state, ...editRowState }; + }); + }, [selectedCell]); + + const onCellClick = React.useCallback((params: GridCellParams) => { + setSelectedCell([params.row.id!.toString(), params.field]); + setIsEditable(!!params.isEditable); + }, []); + + const onCellDoubleClick = React.useCallback( + (params: GridCellParams) => { + if (params.isEditable) { + apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); + } + }, + [apiRef], + ); + + const isCellEditable = React.useCallback((params: GridCellParams) => params.row.id !== 0, []); + + const onEditCellValueChange = React.useCallback( + ({ update }) => { + if (update.email) { + const isValid = validateEmail(update.email); + const newState = {}; + newState[update.id] = { + ...editRowsModel[update.id], + email: { value: update.email, error: !isValid }, + }; + setEditRowsModel((state) => ({ ...state, ...newState })); + return; + } + if (update.DOB) { + const newState = {}; + newState[update.id] = { ...editRowsModel[update.id], DOB: { value: new Date(update.DOB) } }; + setEditRowsModel((state) => ({ ...state, ...newState })); + return; + } + if (update.meetup) { + const newState = {}; + newState[update.id] = { + ...editRowsModel[update.id], + meetup: { value: new Date(update.meetup) }, + }; + setEditRowsModel((state) => ({ ...state, ...newState })); + return; + } + const newState = {}; + newState[update.id] = { + ...editRowsModel[update.id], + ...update, + }; + setEditRowsModel((state) => ({ ...state, ...newState })); + }, + [editRowsModel], + ); + + const onEditCellValueChangeCommitted = React.useCallback( + ({ update }) => { + const field = Object.keys(update).find((key) => key !== 'id')!; + if (update.email) { + const newState = {}; + const componentProps = { + InputProps: { endAdornment: }, + }; + newState[update.id] = {}; + newState[update.id][field] = { value: update.email, ...componentProps }; + setEditRowsModel((state) => ({ ...state, ...newState })); + setTimeout(() => { + apiRef.current.updateRows([update]); + apiRef.current.setCellMode(update.id, field, 'view'); + }, 2000); + } else if (update.fullname) { + const [firstname, lastname] = update.fullname.split(' '); + apiRef.current.updateRows([{ id: update.id, firstname, lastname }]); + apiRef.current.setCellMode(update.id, field, 'view'); + } else { + apiRef.current.updateRows([update]); + apiRef.current.setCellMode(update.id, field, 'view'); + } + }, + [apiRef], + ); + + return ( + + Green cells are editable! Click + EDIT or Double click +
+ +
+
+ +
+
+ ); } export function EditRowsBasic() { - const apiRef = useGridApiRef(); - - const onCellDoubleClick = React.useCallback( - (params: GridCellParams) => { - if (params.isEditable) { - apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); - } - }, - [ apiRef ] - ); - - return ( - - Green cells are editable! Double click -
- -
-
- ); + const apiRef = useGridApiRef(); + + const onCellDoubleClick = React.useCallback( + (params: GridCellParams) => { + if (params.isEditable) { + apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); + } + }, + [apiRef], + ); + + return ( + + Green cells are editable! Double click +
+ +
+
+ ); } From 11e7da0ffdd2286ff4b42f562e400f61fb1a4135 Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 24 Feb 2021 19:48:25 +0100 Subject: [PATCH 07/32] add todo --- packages/storybook/src/stories/grid-rows.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 47955ed18ee8..04ea06077d1a 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -362,6 +362,7 @@ export function EditRowsControl() { setEditRowsModel((state) => ({ ...state, ...newState })); return; } + // todo handle native types like date internally? if (update.DOB) { const newState = {}; newState[update.id] = { ...editRowsModel[update.id], DOB: { value: new Date(update.DOB) } }; From 03debae3ccfdba386c7f9cf734865eb86045a7c6 Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 24 Feb 2021 20:06:02 +0100 Subject: [PATCH 08/32] fix edit click --- packages/storybook/src/stories/grid-rows.stories.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 04ea06077d1a..0ab5aa4c3d56 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -314,7 +314,7 @@ export function EditRowsControl() { const apiRef = useGridApiRef(); const classes = useStyles(); - const [selectedCell, setSelectedCell] = React.useState<[string, string] | null>(null); + const [selectedCell, setSelectedCell] = React.useState<[string, string, GridCellValue] | null>(null); const [isEditable, setIsEditable] = React.useState(false); const [editRowsModel, setEditRowsModel] = React.useState({}); @@ -327,15 +327,15 @@ export function EditRowsControl() { const editRowState: GridEditRowsModel = { ...state }; editRowState[selectedCell[0]] = editRowState[selectedCell[0]] ? editRowState[selectedCell[0]] - : {}; - editRowState[selectedCell[0]][selectedCell[1]] = true; + : {id: selectedCell[0]}; + editRowState[selectedCell[0]][selectedCell[1]] = selectedCell[2]; return { ...state, ...editRowState }; }); }, [selectedCell]); const onCellClick = React.useCallback((params: GridCellParams) => { - setSelectedCell([params.row.id!.toString(), params.field]); + setSelectedCell([params.row.id!.toString(), params.field, params.value]); setIsEditable(!!params.isEditable); }, []); From cabe172e6ed854f0ac30f68314400cd084ff5278 Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 24 Feb 2021 20:07:29 +0100 Subject: [PATCH 09/32] add missing import --- packages/storybook/src/stories/grid-rows.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 0ab5aa4c3d56..de2c79b0b4f9 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -11,6 +11,7 @@ import { import { useDemoData } from '@material-ui/x-grid-data-generator'; import { GridEditRowsModel } from '../../../grid/_modules_/grid/models/gridEditRowModel'; import { randomInt } from '../data/random-generator'; +import { GridCellValue } from '@material-ui/x-grid'; export default { title: 'X-Grid Tests/Rows', From 699e8d3c95b42bba897f9b29a7f75d52aa09c672 Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 24 Feb 2021 20:13:12 +0100 Subject: [PATCH 10/32] fix imports --- packages/grid/_modules_/grid/models/index.ts | 1 + packages/storybook/src/stories/grid-rows.stories.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/grid/_modules_/grid/models/index.ts b/packages/grid/_modules_/grid/models/index.ts index 20738a83accf..4d85995ec11f 100644 --- a/packages/grid/_modules_/grid/models/index.ts +++ b/packages/grid/_modules_/grid/models/index.ts @@ -1,6 +1,7 @@ export * from './colDef'; export * from './gridContainerProps'; export * from './elementSize'; +export * from './gridEditRowModel'; export * from './gridFeatureMode'; export * from './gridFilterItem'; export * from './gridFilterOperator'; diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index de2c79b0b4f9..a4f6e4e47046 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -2,16 +2,16 @@ import { makeStyles } from '@material-ui/core/styles'; import * as React from 'react'; import Button from '@material-ui/core/Button'; import { + GridCellValue, GridCellParams, + GridEditRowsModel, GridLoadIcon, GridRowData, useGridApiRef, XGrid, } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; -import { GridEditRowsModel } from '../../../grid/_modules_/grid/models/gridEditRowModel'; import { randomInt } from '../data/random-generator'; -import { GridCellValue } from '@material-ui/x-grid'; export default { title: 'X-Grid Tests/Rows', From c15dfc29fe71396a98019daf16b6d619cd3ee2c8 Mon Sep 17 00:00:00 2001 From: damien Date: Thu, 25 Feb 2021 16:28:34 +0100 Subject: [PATCH 11/32] refactor edit row state, and api --- .../grid/components/GridRowCells.tsx | 8 +- .../components/editCell/EditInputCell.tsx | 28 ++++--- .../grid/constants/eventsConstants.ts | 4 +- .../hooks/features/rows/useGridEditRows.ts | 84 ++++++++++--------- .../grid/models/api/gridEditRowApi.ts | 19 +++-- .../_modules_/grid/models/gridEditRowModel.ts | 7 +- .../_modules_/grid/models/gridOptions.tsx | 14 ++-- .../_modules_/grid/models/params/index.ts | 1 + .../grid/_modules_/grid/utils/mergeUtils.ts | 12 ++- .../src/stories/grid-rows.stories.tsx | 79 ++++++++--------- 10 files changed, 132 insertions(+), 124 deletions(-) diff --git a/packages/grid/_modules_/grid/components/GridRowCells.tsx b/packages/grid/_modules_/grid/components/GridRowCells.tsx index 6cdbca6fedd7..d8af0d65cf69 100644 --- a/packages/grid/_modules_/grid/components/GridRowCells.tsx +++ b/packages/grid/_modules_/grid/components/GridRowCells.tsx @@ -108,13 +108,7 @@ export const GridRowCells: React.FC = React.memo((props) => { } if (editCellState != null && column.renderEditCell) { - const params = - editCellState === true || typeof editCellState !== 'object' - ? cellParams - : { ...cellParams, ...editCellState }; - if (editCellState !== true && typeof editCellState !== 'object') { - params.value = editCellState; - } + const params = { ...cellParams, ...editCellState }; cellComponent = column.renderEditCell(params); cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellEditing` }; } diff --git a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx index e8905b43d165..0c26866162f1 100644 --- a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx +++ b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx @@ -2,6 +2,8 @@ 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 { @@ -17,6 +19,7 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { ...inputBaseProps } = props; + const editRowApi = api as GridEditRowApi; const caretRafRef = React.useRef(0); React.useEffect(() => { return () => { @@ -36,29 +39,32 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { const onValueChange = React.useCallback( (event) => { - keepCaretPosition(event); - + if (colDef.type === 'string') { + keepCaretPosition(event); + } const newValue = event.target.value; - const update = { id: row.id }; - update[field] = newValue; - api.setEditCellValue(update); + const update: GridEditRowUpdate = {}; + update[field] = { + value: colDef.type === 'date' || colDef.type === 'dateTime' ? new Date(newValue) : newValue, + }; + editRowApi.setEditCellProps(row.id, update); }, - [api, field, keepCaretPosition, row.id], + [editRowApi, colDef.type, field, keepCaretPosition, row.id], ); const onKeyDown = React.useCallback( (event: React.KeyboardEvent) => { if (!inputBaseProps.error && event.key === 'Enter') { - const update = { id: row.id }; - update[field] = value; - api.commitCellValueChanges(update); + const update: GridEditRowUpdate = {}; + update[field] = { value }; + editRowApi.commitCellChange(row.id, update); } if (event.key === 'Escape') { - api.setCellMode(row.id, field, 'view'); + editRowApi.setCellMode(row.id, field, 'view'); } }, - [inputBaseProps.error, row.id, field, value, api], + [inputBaseProps.error, row.id, field, value, editRowApi], ); const inputType = mapColDefTypeToInputType(colDef.type); diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index 59f847e53bdf..8a4477e3efa3 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -10,8 +10,8 @@ export const GRID_SCROLL = 'scroll'; export const GRID_DRAGEND = 'dragend'; // XGRID events -export const GRID_CELL_VALUE_CHANGE = 'cellValueChange'; -export const GRID_CELL_VALUE_CHANGE_COMMITTED = 'cellValueChangeCommitted'; +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'; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index b3b786c2ca43..9ed3fbc34ec7 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -1,8 +1,8 @@ import * as React from 'react'; import { GRID_CELL_MODE_CHANGE, - GRID_CELL_VALUE_CHANGE, - GRID_CELL_VALUE_CHANGE_COMMITTED, + GRID_CELL_CHANGE, + GRID_CELL_CHANGE_COMMITTED, GRID_EDIT_ROW_MODEL_CHANGE, } from '../../../constants/eventsConstants'; import { GridApiRef } from '../../../models/api/gridApiRef'; @@ -10,8 +10,13 @@ import { GridEditRowApi } from '../../../models/api/gridEditRowApi'; import { GridCellMode } from '../../../models/gridCell'; import { GridEditRowsModel, GridEditRowUpdate } from '../../../models/gridEditRowModel'; import { GridFeatureModeConstant } from '../../../models/gridFeatureMode'; -import { GridRowId, GridRowModelUpdate } from '../../../models/gridRows'; +import { GridRowId } from '../../../models/gridRows'; import { GridCellParams } from '../../../models/params/gridCellParams'; +import { + GridCellModeChangeParams, + GridEditCellParams, + GridEditRowModelParams, +} from '../../../models/params/gridEditCellParams'; import { buildGridCellParams } from '../../../utils/paramsUtils'; import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; import { useGridApiMethod } from '../../root/useGridApiMethod'; @@ -91,12 +96,13 @@ export function useGridEditRows(apiRef: GridApiRef) { return { ...state, editRows: newEditRowsState }; }); - apiRef.current.publishEvent(GRID_CELL_MODE_CHANGE, { + const params: GridCellModeChangeParams = { id, field, mode: 'view', api: apiRef.current, - }); + }; + apiRef.current.publishEvent(GRID_CELL_MODE_CHANGE, params); forceUpdate(); }, [apiRef, forceUpdate, setGridState], @@ -121,32 +127,33 @@ export function useGridEditRows(apiRef: GridApiRef) { [options.isCellEditable], ); - const commitCellValueChanges = React.useCallback( - (update: GridRowModelUpdate) => { + const commitCellChange = React.useCallback( + (id: GridRowId, update: GridEditRowUpdate) => { if (options.editMode === GridFeatureModeConstant.server) { - apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, { - update, - api: apiRef.current, - }); + const params: GridEditCellParams = { api: apiRef.current, id, update }; + apiRef.current.publishEvent(GRID_CELL_CHANGE_COMMITTED, params); return; } - apiRef.current.updateRows([update]); const field = Object.keys(update).find((key) => key !== 'id')!; - apiRef.current.setCellMode(update.id, field, 'view'); + const rowUpdate = { id }; + rowUpdate[field] = update[field].value; + apiRef.current.updateRows([rowUpdate]); + apiRef.current.setCellMode(id, field, 'view'); }, [apiRef, options.editMode], ); - const setEditCellValue = React.useCallback( - (update: GridEditRowUpdate) => { + const setEditCellProps = React.useCallback( + (id: GridRowId, update: GridEditRowUpdate) => { if (options.editMode === GridFeatureModeConstant.server) { - apiRef.current.publishEvent(GRID_CELL_VALUE_CHANGE, { update, api: apiRef.current }); + const params: GridEditCellParams = { api: apiRef.current, id, update }; + apiRef.current.publishEvent(GRID_CELL_CHANGE, params); return; } setGridState((state) => { const newState = { ...state.editRows }; - newState[update.id] = { - ...state.editRows[update.id], + newState[id] = { + ...state.editRows[id], ...update, }; @@ -168,37 +175,34 @@ export function useGridEditRows(apiRef: GridApiRef) { [forceUpdate, setGridState], ); // TODO cleanup params What should we put? - const onEditCellValueChange = React.useCallback( - (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { - return apiRef.current.subscribeEvent(GRID_CELL_VALUE_CHANGE, handler); + const onEditRowModelChange = React.useCallback( + (handler: (param: GridEditRowModelParams) => void): (() => void) => { + return apiRef.current.subscribeEvent(GRID_EDIT_ROW_MODEL_CHANGE, handler); }, [apiRef], ); - const onEditCellValueChangeCommitted = React.useCallback( - (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { - return apiRef.current.subscribeEvent(GRID_CELL_VALUE_CHANGE_COMMITTED, handler); + const onCellModeChange = React.useCallback( + (handler: (param: GridCellModeChangeParams) => void): (() => void) => { + return apiRef.current.subscribeEvent(GRID_CELL_MODE_CHANGE, handler); }, [apiRef], ); - const onCellModeChange = React.useCallback( - (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { - return apiRef.current.subscribeEvent(GRID_CELL_MODE_CHANGE, handler); + const onEditCellChange = React.useCallback( + (handler: (param: GridEditCellParams) => void): (() => void) => { + return apiRef.current.subscribeEvent(GRID_CELL_CHANGE, handler); }, [apiRef], ); - const onEditRowModelChange = React.useCallback( - (handler: (param: { update: GridEditRowUpdate }) => void): (() => void) => { - return apiRef.current.subscribeEvent(GRID_EDIT_ROW_MODEL_CHANGE, handler); + const onEditCellChangeCommitted = React.useCallback( + (handler: (param: GridEditCellParams) => void): (() => void) => { + return apiRef.current.subscribeEvent(GRID_CELL_CHANGE_COMMITTED, handler); }, [apiRef], ); + // TODO add those options.handlers on apiRef - useGridApiEventHandler(apiRef, GRID_CELL_VALUE_CHANGE, options.onEditCellValueChange); - useGridApiEventHandler( - apiRef, - GRID_CELL_VALUE_CHANGE_COMMITTED, - options.onEditCellValueChangeCommitted, - ); + useGridApiEventHandler(apiRef, GRID_CELL_CHANGE, options.onEditCellChange); + useGridApiEventHandler(apiRef, GRID_CELL_CHANGE_COMMITTED, options.onEditCellChangeCommitted); useGridApiEventHandler(apiRef, GRID_CELL_MODE_CHANGE, options.onCellModeChange); useGridApiEventHandler(apiRef, GRID_EDIT_ROW_MODEL_CHANGE, options.onEditRowModelChange); @@ -209,11 +213,11 @@ export function useGridEditRows(apiRef: GridApiRef) { setCellMode, onEditRowModelChange, onCellModeChange, - onEditCellValueChangeCommitted, - onEditCellValueChange, + onEditCellChangeCommitted, + onEditCellChange, isCellEditable, - commitCellValueChanges, - setEditCellValue, + commitCellChange, + setEditCellProps, setEditRowsModel, }, 'EditRowApi', diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts index ce4db5def6ac..d12046c5a006 100644 --- a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -2,6 +2,11 @@ import { GridCellMode, GridCellValue } from '../gridCell'; import { GridEditRowsModel, GridEditRowUpdate } from '../gridEditRowModel'; import { GridRowId } from '../gridRows'; import { GridCellParams } from '../params/gridCellParams'; +import { + GridCellModeChangeParams, + GridEditCellParams, + GridEditRowModelParams, +} from '../params/gridEditCellParams'; export interface GridEditRowApi { /** @@ -15,7 +20,7 @@ export interface GridEditRowApi { * @param string * @param 'edit' | 'view' */ - setCellMode: (rowId: GridRowId, field: string, mode: GridCellMode) => void; + setCellMode: (id: GridRowId, field: string, mode: GridCellMode) => void; /** * Returns true if the cell is editable * @param params @@ -25,12 +30,12 @@ export interface GridEditRowApi { * Set the edit cell input props * @param update */ - setEditCellValue: (update: GridEditRowUpdate) => void; + setEditCellProps: (id: GridRowId, update: GridEditRowUpdate) => void; /** * commit the cell value changes to update the cell value. * @param update */ - commitCellValueChanges: (update: GridEditRowUpdate) => void; + commitCellChange: (id: GridRowId, update: GridEditRowUpdate) => void; /** * get the cell value of a row and field * @param id @@ -38,8 +43,8 @@ export interface GridEditRowApi { */ getCellValue: (id: GridRowId, field: string) => GridCellValue; - onEditRowModelChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; - onCellModeChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; - onEditCellValueChangeCommitted: (handler: (param: { update: GridEditRowUpdate }) => void) => void; - onEditCellValueChange: (handler: (param: { update: GridEditRowUpdate }) => void) => void; + onEditRowModelChange: (handler: (param: GridEditRowModelParams) => void) => void; + onCellModeChange: (handler: (param: GridCellModeChangeParams) => void) => void; + onEditCellChangeCommitted: (handler: (param: GridEditCellParams) => void) => void; + onEditCellChange: (handler: (param: GridEditCellParams) => void) => void; } diff --git a/packages/grid/_modules_/grid/models/gridEditRowModel.ts b/packages/grid/_modules_/grid/models/gridEditRowModel.ts index 64e809f86c01..be803873a7fa 100644 --- a/packages/grid/_modules_/grid/models/gridEditRowModel.ts +++ b/packages/grid/_modules_/grid/models/gridEditRowModel.ts @@ -1,14 +1,11 @@ import { GridCellValue } from './gridCell'; -import { GridRowId } from './gridRows'; export interface GridEditCellProps { value: GridCellValue; [prop: string]: any; } -export interface GridEditRowUpdate { - id: GridRowId; - [prop: string]: GridCellValue | GridEditCellProps; -} + +export type GridEditRowUpdate = { [field: string]: GridEditCellProps }; export type GridEditRowsModel = { [rowId: string]: GridEditRowUpdate }; diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 1717bd303509..95f2377b31d5 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -7,7 +7,6 @@ import { getGridDefaultColumnTypes } from './colDef/gridDefaultColumnTypes'; import { GridDensity, GridDensityTypes } from './gridDensity'; import { GridEditRowsModel } from './gridEditRowModel'; import { GridFeatureMode, GridFeatureModeConstant } from './gridFeatureMode'; -import { GridRowModelUpdate } from './gridRows'; import { GridCellParams } from './params/gridCellParams'; import { GridColParams } from './params/gridColParams'; import { GridFilterModelParams } from './params/gridFilterModelParams'; @@ -18,6 +17,11 @@ import { GridSelectionModelChangeParams } from './params/gridSelectionModelChang import { GridSortModelParams } from './params/gridSortModelParams'; import { GridSelectionModel } from './gridSelectionModel'; import { GridSortDirection, GridSortModel } from './gridSortModel'; +import { + GridCellModeChangeParams, + GridEditCellParams, + GridEditRowModelParams, +} from './params/gridEditCellParams'; // TODO add multiSortKey /** @@ -297,10 +301,10 @@ export interface GridOptions { * Callback fired when the cell is rendered. */ isCellEditable?: (params: GridCellParams) => boolean; - onCellModeChange?: ({ id: RowId, field: string, api: any, mode: CellMode }) => void; - onEditCellValueChange?: (params: { api: any; update: GridRowModelUpdate }) => void; - onEditCellValueChangeCommitted?: (params: { api: any; update: GridRowModelUpdate }) => void; - onEditRowModelChange?: (model: GridEditRowsModel) => void; + onEditRowModelChange?: (params: GridEditRowModelParams) => void; + onCellModeChange?: (params: GridCellModeChangeParams) => void; + onEditCellChange?: (params: GridEditCellParams) => void; + onEditCellChangeCommitted?: (params: GridEditCellParams) => void; /** * Extend native column types with your new column types. diff --git a/packages/grid/_modules_/grid/models/params/index.ts b/packages/grid/_modules_/grid/models/params/index.ts index 58d70977b070..3a35a6058fa1 100644 --- a/packages/grid/_modules_/grid/models/params/index.ts +++ b/packages/grid/_modules_/grid/models/params/index.ts @@ -1,4 +1,5 @@ export * from './gridCellParams'; +export * from './gridEditCellParams'; export * from './gridColParams'; export * from './gridBaseComponentProps'; export * from './gridFilterModelParams'; diff --git a/packages/grid/_modules_/grid/utils/mergeUtils.ts b/packages/grid/_modules_/grid/utils/mergeUtils.ts index 3f6a32b6464b..ba1d9bba3c86 100644 --- a/packages/grid/_modules_/grid/utils/mergeUtils.ts +++ b/packages/grid/_modules_/grid/utils/mergeUtils.ts @@ -10,9 +10,17 @@ export function mergeGridColTypes( Object.entries(mergedColTypes).forEach(([colType, colTypeDef]: [string, any]) => { if (colTypeDef.extendType) { - colTypeDef = { ...mergedColTypes[colTypeDef.extendType], ...colTypeDef, type: colType }; + colTypeDef = { + ...mergedColTypes[colTypeDef.extendType], + ...colTypeDef, + type: colTypeDef.type, + }; } else { - colTypeDef = { ...mergedColTypes[DEFAULT_GRID_COL_TYPE_KEY], ...colTypeDef, type: colType }; + colTypeDef = { + ...mergedColTypes[DEFAULT_GRID_COL_TYPE_KEY], + ...colTypeDef, + type: colTypeDef.type, + }; } hydratedOptionColTypes[colType] = colTypeDef; }); diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 5d4f7cb5549d..8aed2a93543d 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -14,6 +14,7 @@ import { GridRowData, useGridApiRef, XGrid, + GridEditCellParams, } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; import { randomInt } from '../data/random-generator'; @@ -464,7 +465,9 @@ export function EditRowsControl() { const apiRef = useGridApiRef(); const classes = useEditCellStyles(); - const [selectedCell, setSelectedCell] = React.useState<[string, string, GridCellValue] | null>(null); + const [selectedCell, setSelectedCell] = React.useState<[string, string, GridCellValue] | null>( + null, + ); const [isEditable, setIsEditable] = React.useState(false); const [editRowsModel, setEditRowsModel] = React.useState({}); @@ -472,13 +475,12 @@ export function EditRowsControl() { if (!selectedCell) { return; } + const [id, field, value] = selectedCell; setEditRowsModel((state) => { const editRowState: GridEditRowsModel = { ...state }; - editRowState[selectedCell[0]] = editRowState[selectedCell[0]] - ? editRowState[selectedCell[0]] - : {id: selectedCell[0]}; - editRowState[selectedCell[0]][selectedCell[1]] = selectedCell[2]; + editRowState[id] = editRowState[id] ? { ...editRowState[id] } : {}; + editRowState[id][field] = { value }; return { ...state, ...editRowState }; }); @@ -500,37 +502,21 @@ export function EditRowsControl() { const isCellEditable = React.useCallback((params: GridCellParams) => params.row.id !== 0, []); - const onEditCellValueChange = React.useCallback( - ({ update }) => { + const onEditCellChange = React.useCallback( + ({ id, update }: GridEditCellParams) => { if (update.email) { - const isValid = validateEmail(update.email); + const isValid = validateEmail(update.email.value); const newState = {}; - newState[update.id] = { - ...editRowsModel[update.id], - email: { value: update.email, error: !isValid }, - }; - setEditRowsModel((state) => ({ ...state, ...newState })); - return; - } - // todo handle native types like date internally? - if (update.DOB) { - const newState = {}; - newState[update.id] = { ...editRowsModel[update.id], DOB: { value: new Date(update.DOB) } }; - setEditRowsModel((state) => ({ ...state, ...newState })); - return; - } - if (update.meetup) { - const newState = {}; - newState[update.id] = { - ...editRowsModel[update.id], - meetup: { value: new Date(update.meetup) }, + newState[id] = { + ...editRowsModel[id], + email: { ...update.email, error: !isValid }, }; setEditRowsModel((state) => ({ ...state, ...newState })); return; } const newState = {}; - newState[update.id] = { - ...editRowsModel[update.id], + newState[id] = { + ...editRowsModel[id], ...update, }; setEditRowsModel((state) => ({ ...state, ...newState })); @@ -538,28 +524,31 @@ export function EditRowsControl() { [editRowsModel], ); - const onEditCellValueChangeCommitted = React.useCallback( - ({ update }) => { - const field = Object.keys(update).find((key) => key !== 'id')!; + const onEditCellChangeCommitted = React.useCallback( + ({ id, update }: GridEditCellParams) => { + const field = Object.keys(update)[0]!; + const rowUpdate = { id }; + rowUpdate[field] = update[field].value; + if (update.email) { const newState = {}; const componentProps = { - InputProps: { endAdornment: }, + endAdornment: , }; - newState[update.id] = {}; - newState[update.id][field] = { value: update.email, ...componentProps }; + newState[id] = {}; + newState[id][field] = { ...update.email, ...componentProps }; setEditRowsModel((state) => ({ ...state, ...newState })); setTimeout(() => { - apiRef.current.updateRows([update]); - apiRef.current.setCellMode(update.id, field, 'view'); + apiRef.current.updateRows([rowUpdate]); + apiRef.current.setCellMode(id, field, 'view'); }, 2000); - } else if (update.fullname) { - const [firstname, lastname] = update.fullname.split(' '); - apiRef.current.updateRows([{ id: update.id, firstname, lastname }]); - apiRef.current.setCellMode(update.id, field, 'view'); + } else if (update.fullname && update.fullname.value) { + const [firstname, lastname] = update.fullname.value.toString().split(' '); + apiRef.current.updateRows([{ id, firstname, lastname }]); + apiRef.current.setCellMode(id, field, 'view'); } else { - apiRef.current.updateRows([update]); - apiRef.current.setCellMode(update.id, field, 'view'); + apiRef.current.updateRows([rowUpdate]); + apiRef.current.setCellMode(id, field, 'view'); } }, [apiRef], @@ -581,8 +570,8 @@ export function EditRowsControl() { onCellClick={onCellClick} onCellDoubleClick={onCellDoubleClick} isCellEditable={isCellEditable} - onEditCellValueChange={onEditCellValueChange} - onEditCellValueChangeCommitted={onEditCellValueChangeCommitted} + onEditCellChange={onEditCellChange} + onEditCellChangeCommitted={onEditCellChangeCommitted} editRowsModel={editRowsModel} editMode="server" /> From 67451b956165b5fde93f34ae9be4b36a2456a9b1 Mon Sep 17 00:00:00 2001 From: damien Date: Thu, 25 Feb 2021 16:34:58 +0100 Subject: [PATCH 12/32] add gridEditCellParams --- .../grid/models/params/gridEditCellParams.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/grid/_modules_/grid/models/params/gridEditCellParams.ts diff --git a/packages/grid/_modules_/grid/models/params/gridEditCellParams.ts b/packages/grid/_modules_/grid/models/params/gridEditCellParams.ts new file mode 100644 index 000000000000..760d9590b6b7 --- /dev/null +++ b/packages/grid/_modules_/grid/models/params/gridEditCellParams.ts @@ -0,0 +1,19 @@ +import { GridCellMode } from '../gridCell'; +import { GridEditRowsModel, GridEditRowUpdate } from '../gridEditRowModel'; +import { GridRowId } from '../gridRows'; + +export interface GridEditCellParams { + api: any; + id: GridRowId; + update: GridEditRowUpdate; +} +export interface GridCellModeChangeParams { + id: GridRowId; + field: string; + api: any; + mode: GridCellMode; +} +export interface GridEditRowModelParams { + model: GridEditRowsModel; + api: any; +} From 357f385bdee45cce578551702666e1b0a14722ea Mon Sep 17 00:00:00 2001 From: damien Date: Thu, 25 Feb 2021 17:46:10 +0100 Subject: [PATCH 13/32] fix event publish and add api desc --- .../hooks/features/rows/useGridEditRows.ts | 33 ++++++++++++------- .../grid/models/api/gridEditRowApi.ts | 17 +++++++++- .../_modules_/grid/models/gridOptions.tsx | 7 ++-- packages/storybook/.storybook/preview.tsx | 2 +- .../src/stories/grid-rows.stories.tsx | 10 ++++-- 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index 9ed3fbc34ec7..e030fa73960b 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -61,18 +61,23 @@ export function useGridEditRows(apiRef: GridApiRef) { currentCellEditState[id] = currentCellEditState[id] || {}; currentCellEditState[id][field] = { value: getCellValue(id, field) }; - const newEditRowsState = { ...state.editRows, ...currentCellEditState }; + const newEditRowsState: GridEditRowsModel = { ...state.editRows, ...currentCellEditState }; - apiRef.current.publishEvent(GRID_EDIT_ROW_MODEL_CHANGE, newEditRowsState); return { ...state, editRows: newEditRowsState }; }); + forceUpdate(); apiRef.current.publishEvent(GRID_CELL_MODE_CHANGE, { id, field, mode: 'edit', api: apiRef.current, }); - forceUpdate(); + + const editRowParams: GridEditRowModelParams = { + api: apiRef.current, + model: apiRef.current.getState().editRows, + }; + apiRef.current.publishEvent(GRID_EDIT_ROW_MODEL_CHANGE, editRowParams); }, [apiRef, forceUpdate, getCellValue, setGridState], ); @@ -92,10 +97,9 @@ export function useGridEditRows(apiRef: GridApiRef) { delete newEditRowsState[id]; } } - apiRef.current.publishEvent(GRID_EDIT_ROW_MODEL_CHANGE, newEditRowsState); - return { ...state, editRows: newEditRowsState }; }); + forceUpdate(); const params: GridCellModeChangeParams = { id, field, @@ -103,7 +107,11 @@ export function useGridEditRows(apiRef: GridApiRef) { api: apiRef.current, }; apiRef.current.publishEvent(GRID_CELL_MODE_CHANGE, params); - forceUpdate(); + const editRowParams: GridEditRowModelParams = { + api: apiRef.current, + model: apiRef.current.getState().editRows, + }; + apiRef.current.publishEvent(GRID_EDIT_ROW_MODEL_CHANGE, editRowParams); }, [apiRef, forceUpdate, setGridState], ); @@ -151,15 +159,19 @@ export function useGridEditRows(apiRef: GridApiRef) { return; } setGridState((state) => { - const newState = { ...state.editRows }; - newState[id] = { + const editRowsModel: GridEditRowsModel = { ...state.editRows }; + editRowsModel[id] = { ...state.editRows[id], ...update, }; - - return { ...state, editRows: newState }; + return { ...state, editRows: editRowsModel }; }); forceUpdate(); + const params: GridEditRowModelParams = { + api: apiRef.current, + model: apiRef.current.getState().editRows, + }; + apiRef.current.publishEvent(GRID_EDIT_ROW_MODEL_CHANGE, params); }, [apiRef, forceUpdate, options.editMode, setGridState], ); @@ -200,7 +212,6 @@ export function useGridEditRows(apiRef: GridApiRef) { [apiRef], ); - // TODO add those options.handlers on apiRef useGridApiEventHandler(apiRef, GRID_CELL_CHANGE, options.onEditCellChange); useGridApiEventHandler(apiRef, GRID_CELL_CHANGE_COMMITTED, options.onEditCellChangeCommitted); useGridApiEventHandler(apiRef, GRID_CELL_MODE_CHANGE, options.onCellModeChange); diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts index d12046c5a006..494f01b5a25e 100644 --- a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -42,9 +42,24 @@ export interface GridEditRowApi { * @param field */ getCellValue: (id: GridRowId, field: string) => GridCellValue; - + /** + * Callback fired when the EditRowModel changed. + * @param handler + */ onEditRowModelChange: (handler: (param: GridEditRowModelParams) => void) => void; + /** + * Callback fired when the cell mode changed. + * @param handler + */ onCellModeChange: (handler: (param: GridCellModeChangeParams) => void) => void; + /** + * Callback fired when the cell changes are commited. + * @param handler + */ onEditCellChangeCommitted: (handler: (param: GridEditCellParams) => void) => void; + /** + * Callback fired when the edit cell value changed. + * @param handler + */ onEditCellChange: (handler: (param: GridEditCellParams) => void) => void; } diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 95f2377b31d5..25ec7ae42d2a 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -295,12 +295,15 @@ export interface GridOptions { * Callback fired when the state of the grid is updated. */ onStateChange?: (params: any) => void; - + /** + * Set the edit rows model of the grid + */ editRowsModel?: GridEditRowsModel; /** - * Callback fired when the cell is rendered. + * Callback fired when the cell is rendered. Returns true if the cell is editable */ isCellEditable?: (params: GridCellParams) => boolean; + onEditRowModelChange?: (params: GridEditRowModelParams) => void; onCellModeChange?: (params: GridCellModeChangeParams) => void; onEditCellChange?: (params: GridEditCellParams) => void; diff --git a/packages/storybook/.storybook/preview.tsx b/packages/storybook/.storybook/preview.tsx index 0fa4e545bfbc..b9f540b2b564 100644 --- a/packages/storybook/.storybook/preview.tsx +++ b/packages/storybook/.storybook/preview.tsx @@ -9,7 +9,7 @@ LicenseInfo.setLicenseKey( ); configureActions({ - depth: 3, + depth: 6, limit: 10, }); diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 8aed2a93543d..dc3a5e0a966a 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -18,6 +18,7 @@ import { } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; import { randomInt } from '../data/random-generator'; +import { action } from '@storybook/addon-actions'; export default { title: 'X-Grid Tests/Rows', @@ -593,9 +594,14 @@ export function EditRowsBasic() { return ( - Green cells are editable! Double click + Double click to edit.
- +
); From 72a9863f2e6831acaf14b9a79fa57e0f6d16ecf4 Mon Sep 17 00:00:00 2001 From: damien Date: Thu, 25 Feb 2021 18:00:55 +0100 Subject: [PATCH 14/32] fix import order --- packages/storybook/src/stories/grid-rows.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index dc3a5e0a966a..64a452453f51 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -17,8 +17,8 @@ import { GridEditCellParams, } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; -import { randomInt } from '../data/random-generator'; import { action } from '@storybook/addon-actions'; +import { randomInt } from '../data/random-generator'; export default { title: 'X-Grid Tests/Rows', From 7d7d36179961097b1d63182bbb670a0cb0e34743 Mon Sep 17 00:00:00 2001 From: damien Date: Fri, 26 Feb 2021 16:32:07 +0100 Subject: [PATCH 15/32] add basic test to switch cell mode --- .../x-grid/src/tests/apiRef.XGrid.test.tsx | 49 +++++++++++++++++-- .../grid/x-grid/src/tests/rows.XGrid.test.tsx | 33 +++++++++++-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/packages/grid/x-grid/src/tests/apiRef.XGrid.test.tsx b/packages/grid/x-grid/src/tests/apiRef.XGrid.test.tsx index f84f5aa55852..dcad48a0e19b 100644 --- a/packages/grid/x-grid/src/tests/apiRef.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/apiRef.XGrid.test.tsx @@ -1,8 +1,14 @@ -import { GridApiRef, GridRowData, useGridApiRef, XGrid } from '@material-ui/x-grid'; +import { + GridApiRef, + GridComponentProps, + GridRowData, + useGridApiRef, + XGrid, +} from '@material-ui/x-grid'; import { expect } from 'chai'; import * as React from 'react'; import { useFakeTimers } from 'sinon'; -import { getColumnValues } from 'test/utils/helperFn'; +import { getCell, getColumnValues } from 'test/utils/helperFn'; import { createClientRenderStrictMode } from 'test/utils'; describe(' - apiRef', () => { @@ -46,11 +52,16 @@ describe(' - apiRef', () => { let apiRef: GridApiRef; - const TestCase = () => { + const TestCase = (props: Partial) => { apiRef = useGridApiRef(); return (
- +
); }; @@ -157,4 +168,34 @@ describe(' - apiRef', () => { expect(apiRef.current.getDataAsCsv()).to.equal('Brand\r\nNike\r\nAdidas\r\nPuma'); }); + + it('should allow to switch between cell mode', () => { + baselineProps.columns = baselineProps.columns.map((col) => ({ ...col, editable: true })); + + render(); + apiRef!.current.setCellMode(1, 'brand', 'edit'); + const cell = getCell(1, 0); + + expect(cell.classList.contains('MuiDataGrid-cellEditable')).to.equal(true); + expect(cell.classList.contains('MuiDataGrid-cellEditing')).to.equal(true); + expect(cell.querySelector('input')!.value).to.equal('Adidas'); + + apiRef!.current.setCellMode(1, 'brand', 'view'); + expect(cell.classList.contains('MuiDataGrid-cellEditable')).to.equal(true); + expect(cell.classList.contains('MuiDataGrid-cellEditing')).to.equal(false); + expect(cell.querySelector('input')).to.equal(null); + }); + + it('isCellEditable should add the class MuiDataGrid-cellEditable to editable cells but not prevent a cell from switching mode', () => { + baselineProps.columns = baselineProps.columns.map((col) => ({ ...col, editable: true })); + + render( params.value === 'Adidas'} />); + const cellNike = getCell(0, 0); + expect(cellNike!.classList.contains('MuiDataGrid-cellEditable')).to.equal(false); + const cellAdidas = getCell(1, 0); + expect(cellAdidas!.classList.contains('MuiDataGrid-cellEditable')).to.equal(true); + + apiRef!.current.setCellMode(0, 'brand', 'edit'); + expect(cellNike.classList.contains('MuiDataGrid-cellEditing')).to.equal(true); + }); }); diff --git a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx index 042200a72780..d7ec3adbe291 100644 --- a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import { createClientRenderStrictMode } from 'test/utils'; import { useFakeTimers } from 'sinon'; import { expect } from 'chai'; -import { getColumnValues } from 'test/utils/helperFn'; -import { GridApiRef, useGridApiRef, XGrid } from '@material-ui/x-grid'; +import { getCell, getColumnValues } from 'test/utils/helperFn'; +import { GridApiRef, GridColDef, GridRowData, useGridApiRef, XGrid } from '@material-ui/x-grid'; describe(' - Rows ', () => { let clock; @@ -18,7 +18,7 @@ describe(' - Rows ', () => { // TODO v5: replace with createClientRender const render = createClientRenderStrictMode(); - const baselineProps = { + const baselineProps: { columns: GridColDef[]; rows: GridRowData[] } = { rows: [ { clientId: 'c1', @@ -70,5 +70,32 @@ describe(' - Rows ', () => { expect(getColumnValues(2)).to.deep.equal(['11', '30', '31']); }); }); + it('should allow to switch between cell mode', () => { + let apiRef: GridApiRef; + const editableProps = { ...baselineProps }; + editableProps.columns = editableProps.columns.map((col) => ({ ...col, editable: true })); + const getRowId = (row) => `${row.clientId}`; + + const Test = () => { + apiRef = useGridApiRef(); + return ( +
+ +
+ ); + }; + render(); + apiRef!.current.setCellMode('c2', 'first', 'edit'); + const cell = getCell(1, 1); + + expect(cell.classList.contains('MuiDataGrid-cellEditable')).to.equal(true); + expect(cell.classList.contains('MuiDataGrid-cellEditing')).to.equal(true); + expect(cell.querySelector('input')!.value).to.equal('Jack'); + apiRef!.current.setCellMode('c2', 'first', 'view'); + + expect(cell.classList.contains('MuiDataGrid-cellEditable')).to.equal(true); + expect(cell.classList.contains('MuiDataGrid-cellEditing')).to.equal(false); + expect(cell.querySelector('input')).to.equal(null); + }); }); }); From 77a17b37b6ab2b2da431cc2ad4c963b20310dcce Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sat, 27 Feb 2021 22:41:32 +0100 Subject: [PATCH 16/32] sort asc --- packages/grid/_modules_/grid/components/GridCell.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grid/_modules_/grid/components/GridCell.tsx b/packages/grid/_modules_/grid/components/GridCell.tsx index aec744ff534f..5f865146e31c 100644 --- a/packages/grid/_modules_/grid/components/GridCell.tsx +++ b/packages/grid/_modules_/grid/components/GridCell.tsx @@ -26,16 +26,16 @@ export const GridCell: React.FC = React.memo((props) => { children, colIndex, cssClass, - hasFocus, - isEditable, field, formattedValue, + hasFocus, + height, + isEditable, rowIndex, showRightBorder, tabIndex, value, width, - height, } = props; const valueToRender = formattedValue || value; From d84a10abea77fea8747b0ee2b9d85dce3ada9d7a Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sat, 27 Feb 2021 22:43:14 +0100 Subject: [PATCH 17/32] sort asc --- .../grid/_modules_/grid/components/GridCell.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/grid/_modules_/grid/components/GridCell.tsx b/packages/grid/_modules_/grid/components/GridCell.tsx index 5f865146e31c..57ff12801d0b 100644 --- a/packages/grid/_modules_/grid/components/GridCell.tsx +++ b/packages/grid/_modules_/grid/components/GridCell.tsx @@ -5,19 +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; - showRightBorder?: boolean; isEditable?: boolean; - hasFocus?: boolean; - align: GridAlignment; - cssClass?: string; - tabIndex?: number; - colIndex?: number; rowIndex?: number; + showRightBorder?: boolean; + tabIndex?: number; + value?: GridCellValue; + width: number; } export const GridCell: React.FC = React.memo((props) => { From 5e7dd296fd03f0205275342aee0721f16733af1d Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sat, 27 Feb 2021 22:45:13 +0100 Subject: [PATCH 18/32] blank line convention --- packages/grid/_modules_/grid/utils/domUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grid/_modules_/grid/utils/domUtils.ts b/packages/grid/_modules_/grid/utils/domUtils.ts index 5b12f3d84b8a..1a45e1b3872d 100644 --- a/packages/grid/_modules_/grid/utils/domUtils.ts +++ b/packages/grid/_modules_/grid/utils/domUtils.ts @@ -39,6 +39,7 @@ export function isGridHeaderTitleContainer(elem: Element): boolean { export function getIdFromRowElem(rowEl: Element): string { return rowEl.getAttribute('data-id')!; } + export function getFieldFromCellElem(cellEl: Element): string { return cellEl.getAttribute('data-field')!; } From 142d693bf410ff2e8121443c357f6ec944b38172 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sat, 27 Feb 2021 22:46:48 +0100 Subject: [PATCH 19/32] blank line convention --- packages/grid/x-grid/src/tests/rows.XGrid.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx index d7ec3adbe291..bb0af57e452f 100644 --- a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx @@ -70,6 +70,7 @@ describe(' - Rows ', () => { expect(getColumnValues(2)).to.deep.equal(['11', '30', '31']); }); }); + it('should allow to switch between cell mode', () => { let apiRef: GridApiRef; const editableProps = { ...baselineProps }; From 2b1acc34dfcf8647f05cc4959f42f614f84b2e5b Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sat, 27 Feb 2021 22:57:41 +0100 Subject: [PATCH 20/32] prefer assertion with a potentially better DX --- packages/grid/x-grid/src/tests/rows.XGrid.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx index bb0af57e452f..daca0e523204 100644 --- a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx @@ -89,8 +89,8 @@ describe(' - Rows ', () => { apiRef!.current.setCellMode('c2', 'first', 'edit'); const cell = getCell(1, 1); - expect(cell.classList.contains('MuiDataGrid-cellEditable')).to.equal(true); - expect(cell.classList.contains('MuiDataGrid-cellEditing')).to.equal(true); + expect(cell).to.have.class('MuiDataGrid-cellEditable'); + expect(cell).to.have.class('MuiDataGrid-cellEditing'); expect(cell.querySelector('input')!.value).to.equal('Jack'); apiRef!.current.setCellMode('c2', 'first', 'view'); From 0d039414527c905f66d7c228ee828f172af4ebac Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sat, 27 Feb 2021 23:00:30 +0100 Subject: [PATCH 21/32] blank line convention --- packages/grid/_modules_/grid/utils/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grid/_modules_/grid/utils/utils.ts b/packages/grid/_modules_/grid/utils/utils.ts index 4f5e714b71eb..63ba488962cf 100644 --- a/packages/grid/_modules_/grid/utils/utils.ts +++ b/packages/grid/_modules_/grid/utils/utils.ts @@ -12,6 +12,7 @@ export interface DebouncedFunction extends Function { export function isDate(value: any): value is Date { return value instanceof Date; } + export function formatDateToLocalInputDate({ value, withTime, @@ -26,6 +27,7 @@ export function formatDateToLocalInputDate({ } return value; } + export function isArray(value: any): value is Array { return Array.isArray(value); } From d8e117bdb3137b7c1822b35bce74208a03949cc2 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sat, 27 Feb 2021 23:04:35 +0100 Subject: [PATCH 22/32] blank line convention --- .../grid/_modules_/grid/models/params/gridEditCellParams.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grid/_modules_/grid/models/params/gridEditCellParams.ts b/packages/grid/_modules_/grid/models/params/gridEditCellParams.ts index 760d9590b6b7..437aabc9604a 100644 --- a/packages/grid/_modules_/grid/models/params/gridEditCellParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridEditCellParams.ts @@ -7,12 +7,14 @@ export interface GridEditCellParams { id: GridRowId; update: GridEditRowUpdate; } + export interface GridCellModeChangeParams { id: GridRowId; field: string; api: any; mode: GridCellMode; } + export interface GridEditRowModelParams { model: GridEditRowsModel; api: any; From d9b461f08b3726e84776ffc08a30796e42396ded Mon Sep 17 00:00:00 2001 From: damien Date: Mon, 1 Mar 2021 12:57:30 +0100 Subject: [PATCH 23/32] Api description improvements Co-authored-by: Olivier Tassinari --- .../grid/models/api/gridEditRowApi.ts | 12 ++++++------ .../_modules_/grid/models/colDef/gridColDef.ts | 2 +- .../grid/_modules_/grid/models/gridCell.ts | 2 +- .../grid/_modules_/grid/models/gridOptions.tsx | 18 ++++++++++++++---- .../grid/models/params/gridCellParams.ts | 2 +- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts index 494f01b5a25e..c43cd6ee9b7e 100644 --- a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -10,34 +10,34 @@ import { export interface GridEditRowApi { /** - * Set the edit rows model of the grid + * Set the edit rows model of the grid. * @param GridEditRowsModel */ setEditRowsModel: (model: GridEditRowsModel) => void; /** - * Set the cell mode of a cell + * Set the cellMode of a cell. * @param GridRowId * @param string * @param 'edit' | 'view' */ setCellMode: (id: GridRowId, field: string, mode: GridCellMode) => void; /** - * Returns true if the cell is editable + * Returns true if the cell is editable. * @param params */ isCellEditable: (params: GridCellParams) => boolean; /** - * Set the edit cell input props + * Set the edit cell input props. * @param update */ setEditCellProps: (id: GridRowId, update: GridEditRowUpdate) => void; /** - * commit the cell value changes to update the cell value. + * Commit the cell value changes to update the cell value. * @param update */ commitCellChange: (id: GridRowId, update: GridEditRowUpdate) => void; /** - * get the cell value of a row and field + * Get the cell value of a row and field. * @param id * @param field */ diff --git a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts index 421fb3c9ded6..3bd0888cb258 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts @@ -53,7 +53,7 @@ export interface GridColDef { */ resizable?: boolean; /** - * If `true`, the column cell would be editable. + * If `true`, the cells of the column are editable. * @default true */ editable?: boolean; diff --git a/packages/grid/_modules_/grid/models/gridCell.ts b/packages/grid/_modules_/grid/models/gridCell.ts index 6e19aa4c53e7..9190d9f2bc81 100644 --- a/packages/grid/_modules_/grid/models/gridCell.ts +++ b/packages/grid/_modules_/grid/models/gridCell.ts @@ -1,5 +1,5 @@ /** - * The Cell mode values. + * The mode of the cell. */ export type GridCellMode = 'edit' | 'view'; diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 25ec7ae42d2a..685c709e3fb3 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -296,19 +296,29 @@ export interface GridOptions { */ onStateChange?: (params: any) => void; /** - * Set the edit rows model of the grid + * Set the edit rows model of the grid. */ editRowsModel?: GridEditRowsModel; /** - * Callback fired when the cell is rendered. Returns true if the cell is editable + * Callback fired when a cell is rendered, returns true if the cell is editable. */ isCellEditable?: (params: GridCellParams) => boolean; - + /** + * Callback fired when […]. + */ onEditRowModelChange?: (params: GridEditRowModelParams) => void; + /** + * Callback fired when […]. + */ onCellModeChange?: (params: GridCellModeChangeParams) => void; + /** + * Callback fired when […]. + */ onEditCellChange?: (params: GridEditCellParams) => void; + /** + * Callback fired when […]. + */ onEditCellChangeCommitted?: (params: GridEditCellParams) => void; - /** * Extend native column types with your new column types. */ diff --git a/packages/grid/_modules_/grid/models/params/gridCellParams.ts b/packages/grid/_modules_/grid/models/params/gridCellParams.ts index d7e55516c610..eec3fe850748 100644 --- a/packages/grid/_modules_/grid/models/params/gridCellParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridCellParams.ts @@ -44,7 +44,7 @@ export interface GridCellParams { */ api: any; /** - * true: if the cell is editable + * If true, the cell is editable. */ isEditable?: boolean; } From df4ee439f7795bfca01f7f612796fef57daa0e95 Mon Sep 17 00:00:00 2001 From: damien Date: Mon, 1 Mar 2021 15:58:01 +0100 Subject: [PATCH 24/32] small refactoring --- .../grid/components/editCell/EditInputCell.tsx | 2 +- .../grid/hooks/features/rows/useGridEditRows.ts | 2 +- .../grid/models/colDef/gridDateColDef.ts | 3 --- .../grid/models/colDef/gridNumericColDef.ts | 2 -- .../_modules_/grid/models/gridEditRowModel.ts | 1 - .../grid/models/params/gridCellParams.ts | 1 - .../grid/_modules_/grid/utils/mergeUtils.ts | 17 ++++------------- .../grid/x-grid/src/tests/rows.XGrid.test.tsx | 4 ++-- 8 files changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx index 0c26866162f1..30ad627405d5 100644 --- a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx +++ b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx @@ -20,7 +20,7 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { } = props; const editRowApi = api as GridEditRowApi; - const caretRafRef = React.useRef(0); + const caretRafRef = React.useRef(0); React.useEffect(() => { return () => { cancelAnimationFrame(caretRafRef.current); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index e030fa73960b..dadcdd550f5a 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -236,5 +236,5 @@ export function useGridEditRows(apiRef: GridApiRef) { React.useEffect(() => { apiRef.current.setEditRowsModel(options.editRowsModel || {}); - }, [apiRef, forceUpdate, options.editRowsModel]); + }, [apiRef, options.editRowsModel]); } diff --git a/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts index 7cbf6416ba55..b0bc947a885d 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridDateColDef.ts @@ -1,4 +1,3 @@ -import { renderEditInputCell } from '../../components/editCell/EditInputCell'; import { isDate } from '../../utils/utils'; import { gridDateComparer } from '../../utils/sortingUtils'; import { GridCellValue } from '../gridCell'; @@ -26,7 +25,6 @@ export const GRID_DATE_COL_DEF: GridColTypeDef = { sortComparator: gridDateComparer, valueFormatter: gridDateFormatter, filterOperators: getGridDateOperators(), - renderEditCell: renderEditInputCell, }; export const GRID_DATETIME_COL_DEF: GridColTypeDef = { @@ -35,5 +33,4 @@ export const GRID_DATETIME_COL_DEF: GridColTypeDef = { sortComparator: gridDateComparer, valueFormatter: gridDateTimeFormatter, filterOperators: getGridDateOperators(true), - renderEditCell: renderEditInputCell, }; diff --git a/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts index 83004b1d8a7e..87c2c00debbf 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridNumericColDef.ts @@ -1,4 +1,3 @@ -import { renderEditInputCell } from '../../components/editCell/EditInputCell'; import { gridNumberComparer } from '../../utils/sortingUtils'; import { isNumber } from '../../utils/utils'; import { getGridNumericColumnOperators } from './gridNumericOperators'; @@ -13,5 +12,4 @@ export const GRID_NUMERIC_COL_DEF: GridColTypeDef = { sortComparator: gridNumberComparer, valueFormatter: ({ value }) => (value && isNumber(value) && value.toLocaleString()) || value, filterOperators: getGridNumericColumnOperators(), - renderEditCell: renderEditInputCell, }; diff --git a/packages/grid/_modules_/grid/models/gridEditRowModel.ts b/packages/grid/_modules_/grid/models/gridEditRowModel.ts index be803873a7fa..2757b7ead47e 100644 --- a/packages/grid/_modules_/grid/models/gridEditRowModel.ts +++ b/packages/grid/_modules_/grid/models/gridEditRowModel.ts @@ -2,7 +2,6 @@ import { GridCellValue } from './gridCell'; export interface GridEditCellProps { value: GridCellValue; - [prop: string]: any; } diff --git a/packages/grid/_modules_/grid/models/params/gridCellParams.ts b/packages/grid/_modules_/grid/models/params/gridCellParams.ts index eec3fe850748..de00904d825f 100644 --- a/packages/grid/_modules_/grid/models/params/gridCellParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridCellParams.ts @@ -35,7 +35,6 @@ export interface GridCellParams { */ rowIndex?: number; /** - * GridApiRef that let you manipulate the grid. * The column index that the current cell belongs to. */ colIndex?: number; diff --git a/packages/grid/_modules_/grid/utils/mergeUtils.ts b/packages/grid/_modules_/grid/utils/mergeUtils.ts index ba1d9bba3c86..04a69ce097c2 100644 --- a/packages/grid/_modules_/grid/utils/mergeUtils.ts +++ b/packages/grid/_modules_/grid/utils/mergeUtils.ts @@ -9,19 +9,10 @@ export function mergeGridColTypes( const hydratedOptionColTypes: GridColumnTypesRecord = {}; Object.entries(mergedColTypes).forEach(([colType, colTypeDef]: [string, any]) => { - if (colTypeDef.extendType) { - colTypeDef = { - ...mergedColTypes[colTypeDef.extendType], - ...colTypeDef, - type: colTypeDef.type, - }; - } else { - colTypeDef = { - ...mergedColTypes[DEFAULT_GRID_COL_TYPE_KEY], - ...colTypeDef, - type: colTypeDef.type, - }; - } + colTypeDef = { + ...mergedColTypes[colTypeDef.extendType || DEFAULT_GRID_COL_TYPE_KEY], + ...colTypeDef, + }; hydratedOptionColTypes[colType] = colTypeDef; }); diff --git a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx index daca0e523204..803d6f36ffd3 100644 --- a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx @@ -94,8 +94,8 @@ describe(' - Rows ', () => { expect(cell.querySelector('input')!.value).to.equal('Jack'); apiRef!.current.setCellMode('c2', 'first', 'view'); - expect(cell.classList.contains('MuiDataGrid-cellEditable')).to.equal(true); - expect(cell.classList.contains('MuiDataGrid-cellEditing')).to.equal(false); + expect(cell).to.have.class('MuiDataGrid-cellEditable'); + expect(cell).not.to.have.class('MuiDataGrid-cellEditing'); expect(cell.querySelector('input')).to.equal(null); }); }); From 629e2a5f64f5361059673d284c01b968d668f4a6 Mon Sep 17 00:00:00 2001 From: damien Date: Mon, 1 Mar 2021 16:12:33 +0100 Subject: [PATCH 25/32] more small refactoring --- .../grid/_modules_/grid/constants/eventsConstants.ts | 2 +- .../grid/hooks/features/rows/useGridEditRows.ts | 3 +-- packages/grid/_modules_/grid/models/gridOptions.tsx | 12 ++++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index 8a4477e3efa3..4dab2c660ba8 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -9,7 +9,7 @@ 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'; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index dadcdd550f5a..a5f13a0dfdc4 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -20,7 +20,6 @@ import { import { buildGridCellParams } from '../../../utils/paramsUtils'; import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; import { useGridApiMethod } from '../../root/useGridApiMethod'; - import { optionsSelector } from '../../utils/optionsSelector'; import { useGridSelector } from '../core/useGridSelector'; import { useGridState } from '../core/useGridState'; @@ -58,7 +57,7 @@ export function useGridEditRows(apiRef: GridApiRef) { } const currentCellEditState: GridEditRowsModel = { ...state.editRows }; - currentCellEditState[id] = currentCellEditState[id] || {}; + currentCellEditState[id] = {...currentCellEditState[id]} || {}; currentCellEditState[id][field] = { value: getCellValue(id, field) }; const newEditRowsState: GridEditRowsModel = { ...state.editRows, ...currentCellEditState }; diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 685c709e3fb3..a0dc043bb01a 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -304,19 +304,23 @@ export interface GridOptions { */ isCellEditable?: (params: GridCellParams) => boolean; /** - * Callback fired when […]. + * Callback fired when the EditRowModel changed. + * @param handler */ onEditRowModelChange?: (params: GridEditRowModelParams) => void; /** - * Callback fired when […]. + * Callback fired when the cell mode changed. + * @param handler */ onCellModeChange?: (params: GridCellModeChangeParams) => void; /** - * Callback fired when […]. + * Callback fired when the edit cell value changed. + * @param handler */ onEditCellChange?: (params: GridEditCellParams) => void; /** - * Callback fired when […]. + * Callback fired when the cell changes are commited. + * @param handler */ onEditCellChangeCommitted?: (params: GridEditCellParams) => void; /** From c067a32c7e0f1e08a5ef1dbb1430ae8810ce90f3 Mon Sep 17 00:00:00 2001 From: damien Date: Mon, 1 Mar 2021 16:44:36 +0100 Subject: [PATCH 26/32] cleanup and prettier --- .../hooks/features/rows/useGridEditRows.ts | 2 +- .../src/stories/grid-rows.stories.tsx | 32 +------------------ 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index a5f13a0dfdc4..ebbc87ad2633 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -57,7 +57,7 @@ export function useGridEditRows(apiRef: GridApiRef) { } const currentCellEditState: GridEditRowsModel = { ...state.editRows }; - currentCellEditState[id] = {...currentCellEditState[id]} || {}; + currentCellEditState[id] = { ...currentCellEditState[id] } || {}; currentCellEditState[id][field] = { value: getCellValue(id, field) }; const newEditRowsState: GridEditRowsModel = { ...state.editRows, ...currentCellEditState }; diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 64a452453f51..5b992997b42e 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -354,39 +354,9 @@ export function ExpendRowCell() { } // Requirements -/* -- Turn edit mode, using a button or events such as double click... -- Expose double click cell -- Be able to edit rows as well as individual cell -- Validate the value of a cell -- render different input component according to the type of value to edit -- fix issue with number as IDs -- Provide a basic Edit UX out of the box -- Customise the edit for a particular cell -- Some columns should not be editable -- Some rows should not be editable - -colDef.renderEditCell - - boolean} - onCellModeChange - onRowModeChange - onCellValueChange=??? => while typing, allows to validate? Or feedback user... - onCellValueChangeCommitted => pressing enter? What happens when you press ESC? - /> - - */ // TODO demo with Cell edit with value getter // Todo demo with cell not editable according to value -// demo with cell edit validation -// demo with cell edit validation serverside ie username -// demo with cell edit client and serverside ie username - -// TODO create inputs for each col types +// demo with cell edit validation, email, username(serverside) const baselineEditProps = { rows: [ From 19ab929b2e81da858784597008c2f05f5dfb108d Mon Sep 17 00:00:00 2001 From: damien Date: Mon, 1 Mar 2021 18:35:49 +0100 Subject: [PATCH 27/32] fix edit input ux, rm caret --- .../components/containers/GridRootStyles.ts | 11 ++++++ .../components/editCell/EditInputCell.tsx | 37 +++++++------------ packages/storybook/package.json | 8 +++- .../src/stories/grid-rows.stories.tsx | 31 ++++++++++++++++ 4 files changed, 62 insertions(+), 25 deletions(-) diff --git a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts index 3b7db046ea20..bdebf5b403da 100644 --- a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts @@ -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%)' + } + }, // The very last cell '& .MuiDataGrid-colCellWrapper .MuiDataGrid-cell': { borderBottom: 'none', diff --git a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx index 30ad627405d5..1b955857d592 100644 --- a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx +++ b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx @@ -20,36 +20,19 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { } = props; const editRowApi = api as GridEditRowApi; - const caretRafRef = React.useRef(0); - React.useEffect(() => { - return () => { - cancelAnimationFrame(caretRafRef.current); - }; - }, []); - - const keepCaretPosition = React.useCallback((event) => { - // Fix caret from jumping to the end of the input - const caret = event.target.selectionStart; - const element = event.target; - caretRafRef.current = window.requestAnimationFrame(() => { - element.selectionStart = caret; - element.selectionEnd = caret; - }); - }, []); + const [valueState, setValueState] = React.useState(value); const onValueChange = React.useCallback( (event) => { - if (colDef.type === 'string') { - keepCaretPosition(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, keepCaretPosition, row.id], + [editRowApi, colDef.type, field, row.id], ); const onKeyDown = React.useCallback( @@ -69,18 +52,24 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { const inputType = mapColDefTypeToInputType(colDef.type); const formattedValue = - value && isDate(value) - ? formatDateToLocalInputDate({ value, withTime: colDef.type === 'dateTime' }) - : value; + 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)); + }, [value]); return ( ); diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 6b9a3fab435f..a8d896982703 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -52,5 +52,11 @@ "string-replace-loader": "^3.0.1", "ts-loader": "^8.0.2", "typescript": "^4.1.2" - } + }, + "browserslist": [ + ">0.3%", + "not ie 11", + "not dead", + "not op_mini all" + ] } diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 5b992997b42e..a689c36896ce 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -482,6 +482,7 @@ export function EditRowsControl() { ...editRowsModel[id], email: { ...update.email, error: !isValid }, }; + newState[id].email.value += 'EXTERRRR'; setEditRowsModel((state) => ({ ...state, ...newState })); return; } @@ -576,3 +577,33 @@ export function EditRowsBasic() { ); } +const singleData = { rows: [...baselineEditProps.rows], columns: [...baselineEditProps.columns] }; +singleData.rows.length = 1; +singleData.columns.length = 1; +singleData.columns[0].width = 200; + +export function SingleCellBasic() { + const apiRef = useGridApiRef(); + const onCellDoubleClick = React.useCallback( + (params: GridCellParams) => { + if (params.isEditable) { + apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); + } + }, + [apiRef], + ); + + return ( + + Double click to edit. +
+ +
+
+ ); +} From ee320e824b16b824be7fd585ba6dec5eccc4fd8b Mon Sep 17 00:00:00 2001 From: damien Date: Mon, 1 Mar 2021 18:42:11 +0100 Subject: [PATCH 28/32] prettier lint --- .../_modules_/grid/components/containers/GridRootStyles.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts index bdebf5b403da..9f084f45f8b9 100644 --- a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts @@ -235,11 +235,11 @@ export const useStyles = makeStyles( border: `1px solid ${borderColor}`, borderRadius: theme.shape.borderRadius, '& :focus': { - borderColor: theme.palette.text.primary, + 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%)' - } + boxShadow: 'inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%)', + }, }, // The very last cell '& .MuiDataGrid-colCellWrapper .MuiDataGrid-cell': { From c27e5d11d0a923736462235a3fe3a41dd18da0d9 Mon Sep 17 00:00:00 2001 From: damien Date: Tue, 2 Mar 2021 11:29:40 +0100 Subject: [PATCH 29/32] fix package.json syntax --- packages/storybook/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/storybook/package.json b/packages/storybook/package.json index b61cfeff2585..46eea8009ffc 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -40,7 +40,7 @@ "react-docgen-typescript-loader": "^3.7.2", "source-map-loader": "^1.0.2", "string-replace-loader": "^3.0.1", - "ts-loader": "^8.0.2", + "ts-loader": "^8.0.2" }, "browserslist": [ ">0.3%", From df0e557ef27a337aeeabe57dfb44562842d7dbe8 Mon Sep 17 00:00:00 2001 From: damien Date: Tue, 2 Mar 2021 16:25:28 +0100 Subject: [PATCH 30/32] small fixes --- .../grid/_modules_/grid/components/editCell/EditInputCell.tsx | 3 +-- .../grid/x-grid-data-generator/src/renderer/renderCountry.tsx | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx index 1b955857d592..7f6cbecf4bd1 100644 --- a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx +++ b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx @@ -57,8 +57,7 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { : 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)); + setValueState(value); }, [value]); return ( diff --git a/packages/grid/x-grid-data-generator/src/renderer/renderCountry.tsx b/packages/grid/x-grid-data-generator/src/renderer/renderCountry.tsx index e150584630de..efa2500c58bf 100644 --- a/packages/grid/x-grid-data-generator/src/renderer/renderCountry.tsx +++ b/packages/grid/x-grid-data-generator/src/renderer/renderCountry.tsx @@ -5,7 +5,7 @@ import { GridCellParams } from '@material-ui/x-grid'; // ISO 3166-1 alpha-2 // ⚠️ No support for IE 11 function countryToFlag(isoCode: string) { - return typeof String.fromCodePoint !== 'undefined' + return typeof String.fromCodePoint !== 'undefined' && isoCode ? isoCode .toUpperCase() .replace(/./g, (char) => String.fromCodePoint(char.charCodeAt(0) + 127397)) @@ -42,7 +42,7 @@ const Country = React.memo(function Country(props: CountryProps) { return (
- {countryToFlag(value.code)} + {value.code && countryToFlag(value.code)} {value.label}
); From cf6526648502f9e35b509f5e802137f1a6d36dea Mon Sep 17 00:00:00 2001 From: damien Date: Tue, 2 Mar 2021 17:11:51 +0100 Subject: [PATCH 31/32] add methods in api pages --- docs/pages/api-docs/data-grid.md | 6 ++++++ docs/pages/api-docs/x-grid.md | 7 ++++++- packages/grid/_modules_/grid/models/api/gridEditRowApi.ts | 2 +- packages/grid/_modules_/grid/models/gridOptions.tsx | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/pages/api-docs/data-grid.md b/docs/pages/api-docs/data-grid.md index ef1221be3dbd..7be86e278c35 100644 --- a/docs/pages/api-docs/data-grid.md +++ b/docs/pages/api-docs/data-grid.md @@ -28,6 +28,7 @@ import { DataGrid } from '@material-ui/data-grid'; | disableExtendRowFullWidth | boolean | false | If `true`, rows will not be extended to fill the full width of the grid container. | | disableSelectionOnClick | boolean | false | If `true`, the selection on click on a row or cell is disabled. | | error | any | | An error that will turn the grid into its error state and display the error component. | +| editRowsModel | GridEditRowsModel | undefined | Set the edit rows model of the grid. | | getRowId | GridRowIdGetter | (row)=> row.id | A function that allows the grid to retrieve the row id. | | headerHeight | number | 56 | Set the height in pixel of the column headers in the grid. | | hideFooter | boolean | false | If `true`, the footer component is hidden. | @@ -35,6 +36,7 @@ import { DataGrid } from '@material-ui/data-grid'; | hideFooterRowCount | boolean | false | If `true`, the row count in the footer is hidden. | | hideFooterSelectedRowCount | boolean | false | If `true`, the selected row count in the footer is hidden. | | icons | IconsOptions | | Set of icons used in the grid. | +| isCellEditable | (params: GridCellParams) => boolean; | | Callback fired when a cell is rendered, returns true if the cell is editable. | | loading | boolean | false | If `true`, a loading overlay is displayed. | | localeText | GridLocaleText | | Set of text labels used in the grid. You can find all the translation keys supported in [the source](https://github.com/mui-org/material-ui-x/blob/HEAD/packages/grid/_modules_/grid/constants/localeTextConstants.ts) in the GitHub repository. | | logger | Logger | null | Pass a custom logger in the components that implements the 'Logger' interface. | @@ -42,8 +44,12 @@ import { DataGrid } from '@material-ui/data-grid'; | nonce | string | | Nonce of the inline styles for [Content Security Policy](https://www.w3.org/TR/2016/REC-CSP2-20161215/#script-src-the-nonce-attribute). | | onCellClick | (param: GridCellParams) => void | | Callback fired when a click event comes from a cell element. | | onCellHover | (param: GridCellParams) => void | | Callback fired when a hover event comes from a cell element. | +| onCellModeChange | (params: GridCellModeChangeParams) => void | | Callback fired when the cell mode changed. | | onColumnHeaderClick | (param: GridColParams) => void | | Callback fired when a click event comes from a column header element. | | onError | (args: any) => void | | Callback fired when an exception is thrown in the grid, or when the `showError` API method is called. | +| onEditCellChange | (params: GridEditCellParams) => void | | Callback fired when the edit cell value changed. | +| onEditCellChangeCommitted | (params: GridEditCellParams) => void | | Callback fired when the cell changes are committed. | +| onEditRowModelChange | (params: GridEditRowModelParams) => void | | Callback fired when the EditRowModel changed. | | onFilterModelChange | (params: GridFilterModelParams) => void | | Callback fired when the Filter model changes before the filters are applied. | | onPageChange | (param: GridPageChangeParams) => void | | Callback fired when the current page has changed. | | onPageSizeChange | (param: GridPageChangeParams) => void | | Callback fired when the page size has changed. | diff --git a/docs/pages/api-docs/x-grid.md b/docs/pages/api-docs/x-grid.md index 6b877ce6d723..a105537ee169 100644 --- a/docs/pages/api-docs/x-grid.md +++ b/docs/pages/api-docs/x-grid.md @@ -32,13 +32,14 @@ import { XGrid } from '@material-ui/x-grid'; | disableMultipleSelection | boolean | false | If `true`, multiple selection using the CTRL or CMD key is disabled. | | disableSelectionOnClick | boolean | false | If `true`, the selection on click on a row or cell is disabled. | | error | any | | An error that will turn the grid into its error state and display the error component. | -| getRowId | GridRowIdGetter | (row)=> row.id | A function that allows the grid to retrieve the row id. | +| editRowsModel | GridEditRowsModel | undefined | Set the edit rows model of the grid. || getRowId | GridRowIdGetter | (row)=> row.id | A function that allows the grid to retrieve the row id. | | headerHeight | number | 56 | Set the height in pixel of the column headers in the grid. | | hideFooter | boolean | false | If `true`, the footer component is hidden. | | hideFooterPagination | boolean | false | If `true`, the pagination component in the footer is hidden. | | hideFooterRowCount | boolean | false | If `true`, the row count in the footer is hidden. | | hideFooterSelectedRowCount | boolean | false | If `true`, the selected row count in the footer is hidden. | | icons | IconsOptions | | Set of icons used in the grid. | +| isCellEditable | (params: GridCellParams) => boolean; | | Callback fired when a cell is rendered, returns true if the cell is editable. | | loading | boolean | false | If `true`, a loading overlay is displayed.. | | localeText | GridLocaleText | | Set of text labels used in the grid. You can find all the translation keys supported in [the source](https://github.com/mui-org/material-ui-x/blob/HEAD/packages/grid/_modules_/grid/constants/localeTextConstants.ts) in the GitHub repository. | | logger | Logger | null | Pass a custom logger in the components that implements the 'Logger' interface. | @@ -46,8 +47,12 @@ import { XGrid } from '@material-ui/x-grid'; | nonce | string | | Nonce of the inline styles for [Content Security Policy](https://www.w3.org/TR/2016/REC-CSP2-20161215/#script-src-the-nonce-attribute). | | onCellClick | (param: GridCellParams) => void | | Callback fired when a click event comes from a cell element. | | onCellHover | (param: GridCellParams) => void | | Callback fired when a hover event comes from a cell element. | +| onCellModeChange | (params: GridCellModeChangeParams) => void | | Callback fired when the cell mode changed. | | onColumnHeaderClick | (param: GridColParams) => void | | Callback fired when a click event comes from a column header element. | | onError | (args: any) => void | | Callback fired when an exception is thrown in the grid, or when the `showError` API method is called. | +| onEditCellChange | (params: GridEditCellParams) => void | | Callback fired when the edit cell value changed. | +| onEditCellChangeCommitted | (params: GridEditCellParams) => void | | Callback fired when the cell changes are committed. | +| onEditRowModelChange | (params: GridEditRowModelParams) => void | | Callback fired when the EditRowModel changed. | | onFilterModelChange | (params: GridFilterModelParams) => void | | Callback fired when the Filter model changes before the filters are applied. | | onPageChange | (param: GridPageChangeParams) => void | | Callback fired when the current page has changed. | | onPageSizeChange | (param: GridPageChangeParams) => void | | Callback fired when the page size has changed. | diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts index c43cd6ee9b7e..0ee6b9e70960 100644 --- a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -53,7 +53,7 @@ export interface GridEditRowApi { */ onCellModeChange: (handler: (param: GridCellModeChangeParams) => void) => void; /** - * Callback fired when the cell changes are commited. + * Callback fired when the cell changes are committed. * @param handler */ onEditCellChangeCommitted: (handler: (param: GridEditCellParams) => void) => void; diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index a0dc043bb01a..cf13904586a6 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -319,7 +319,7 @@ export interface GridOptions { */ onEditCellChange?: (params: GridEditCellParams) => void; /** - * Callback fired when the cell changes are commited. + * Callback fired when the cell changes are committed. * @param handler */ onEditCellChangeCommitted?: (params: GridEditCellParams) => void; From 6dca60e36bc8ce45507d963f4e331ad910003d64 Mon Sep 17 00:00:00 2001 From: damien Date: Tue, 2 Mar 2021 19:23:42 +0100 Subject: [PATCH 32/32] fix update rows --- .../grid/hooks/features/rows/useGridRows.ts | 9 +++++--- .../src/stories/grid-rows.stories.tsx | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index 376c3ec93b23..0586d9513e28 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -158,13 +158,16 @@ export const useGridRows = ( addedRows.push(partialRow); return; } - Object.assign(internalRowsState.current.idRowsLookup[id], { + const lookup = { ...internalRowsState.current.idRowsLookup }; + + lookup[id] = { ...oldRow, ...partialRow, - }); + }; + internalRowsState.current.idRowsLookup = lookup; }); - setGridState((state) => ({ ...state, rows: internalRowsState.current })); + setGridState((state) => ({ ...state, rows: { ...internalRowsState.current } })); if (deletedRows.length > 0 || addedRows.length > 0) { deletedRows.forEach((row) => { diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index a689c36896ce..9058a2855e6d 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -607,3 +607,25 @@ export function SingleCellBasic() { ); } +export function CommodityEdit() { + const apiRef = useGridApiRef(); + const onCellDoubleClick = React.useCallback( + (params: GridCellParams) => { + apiRef.current.setCellMode(params.row.id!.toString(), params.field, 'edit'); + }, + [apiRef], + ); + + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 100000, + }); + + return ( + +
+ +
+
+ ); +}