@mui/x-data-grid
Version:
The community edition of the data grid component (MUI X).
334 lines (290 loc) • 12 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useEventCallback } from '@mui/material/utils';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { GridRowModes, GridEditModes, GridCellModes } from '../../../models/gridEditRowModel';
import { useGridSelector } from '../../utils/useGridSelector';
import { gridColumnDefinitionsSelector } from '../columns/gridColumnsSelector';
import { gridEditRowsStateSelector } from './gridEditRowsSelector';
import { gridFocusCellSelector } from '../focus/gridFocusStateSelector';
import { useGridApiOptionHandler, useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
export const useGridRowEditing = (apiRef, props) => {
var _props$experimentalFe2, _props$experimentalFe4;
const focusTimeout = React.useRef(null);
const nextFocusedCell = React.useRef(null);
const columns = useGridSelector(apiRef, gridColumnDefinitionsSelector);
const buildCallback = callback => (...args) => {
if (props.editMode === GridEditModes.Row) {
callback(...args);
}
};
const setRowMode = React.useCallback((id, mode) => {
if (mode === apiRef.current.getRowMode(id)) {
return;
}
apiRef.current.setState(state => {
const newEditRowsState = _extends({}, state.editRows);
if (mode === GridRowModes.Edit) {
newEditRowsState[id] = {};
columns.forEach(column => {
const cellParams = apiRef.current.getCellParams(id, column.field);
if (cellParams.isEditable) {
newEditRowsState[id][column.field] = {
value: cellParams.value
};
}
});
} else {
delete newEditRowsState[id];
}
return _extends({}, state, {
editRows: newEditRowsState
});
});
apiRef.current.forceUpdate();
}, [apiRef, columns]);
const getRowMode = React.useCallback(id => {
if (props.editMode === GridEditModes.Cell) {
return GridRowModes.View;
}
const editRowsState = gridEditRowsStateSelector(apiRef.current.state);
return editRowsState[id] ? GridRowModes.Edit : GridRowModes.View;
}, [apiRef, props.editMode]);
const commitRowChange = React.useCallback((id, event = {}) => {
var _props$experimentalFe;
if (props.editMode === GridEditModes.Cell) {
throw new Error(`MUI: You can't commit changes when the edit mode is 'cell'.`);
}
apiRef.current.unstable_runPendingEditCellValueMutation(id);
const model = apiRef.current.getEditRowsModel();
const editRowProps = model[id];
if (!editRowProps) {
throw new Error(`MUI: Row at id: ${id} is not being edited.`);
}
if ((_props$experimentalFe = props.experimentalFeatures) != null && _props$experimentalFe.preventCommitWhileValidating) {
const isValid = Object.keys(editRowProps).reduce((acc, field) => {
return acc && !editRowProps[field].isValidating && !editRowProps[field].error;
}, true);
if (!isValid) {
return false;
}
}
const hasFieldWithError = Object.values(editRowProps).some(value => !!value.error);
if (hasFieldWithError) {
return false;
}
const fieldsWithValidator = Object.keys(editRowProps).filter(field => {
const column = apiRef.current.getColumn(field);
return typeof column.preProcessEditCellProps === 'function';
});
if (fieldsWithValidator.length > 0) {
const row = apiRef.current.getRow(id);
const validatorErrors = fieldsWithValidator.map(async field => {
const column = apiRef.current.getColumn(field);
const newEditCellProps = await Promise.resolve(column.preProcessEditCellProps({
id,
row,
props: editRowProps[field]
}));
apiRef.current.unstable_setEditCellProps({
id,
field,
props: newEditCellProps
});
return newEditCellProps.error;
});
return Promise.all(validatorErrors).then(errors => {
if (errors.some(error => !!error)) {
return false;
}
apiRef.current.publishEvent('rowEditCommit', id, event);
return true;
});
}
apiRef.current.publishEvent('rowEditCommit', id, event);
return true;
}, [apiRef, props.editMode, (_props$experimentalFe2 = props.experimentalFeatures) == null ? void 0 : _props$experimentalFe2.preventCommitWhileValidating]);
const setRowEditingEditCellValue = React.useCallback(params => {
const model = apiRef.current.getEditRowsModel();
const editRow = model[params.id];
const row = apiRef.current.getRow(params.id);
let isValid = true;
return new Promise(resolve => {
Object.keys(editRow).forEach(async field => {
const column = apiRef.current.getColumn(field);
let editCellProps = field === params.field ? {
value: params.value
} : editRow[field]; // setEditCellProps runs the value parser and returns the updated props
editCellProps = apiRef.current.unstable_setEditCellProps({
id: params.id,
field,
props: _extends({}, editCellProps, {
isValidating: true
})
});
if (column.preProcessEditCellProps) {
editCellProps = await Promise.resolve(column.preProcessEditCellProps({
id: params.id,
row,
props: _extends({}, editCellProps, {
value: field === params.field ? apiRef.current.unstable_parseValue(params.id, field, params.value) : editCellProps.value
})
}));
}
if (editCellProps.error) {
isValid = false;
}
apiRef.current.unstable_setEditCellProps({
id: params.id,
field,
props: _extends({}, editCellProps, {
isValidating: false
})
});
});
resolve(isValid);
});
}, [apiRef]);
const rowEditingApi = {
setRowMode,
getRowMode,
commitRowChange,
unstable_setRowEditingEditCellValue: setRowEditingEditCellValue
};
useGridApiMethod(apiRef, rowEditingApi, 'EditRowApi');
const handleCellKeyDown = React.useCallback(async (params, event) => {
// Wait until IME is settled for Asian languages like Japanese and Chinese
// TODO: `event.which` is depricated but this is a temporary workaround
if (event.which === 229) {
return;
}
const {
cellMode,
isEditable
} = params;
if (!isEditable) {
return;
}
const isEditMode = cellMode === GridCellModes.Edit;
const rowParams = apiRef.current.getRowParams(params.id);
if (isEditMode) {
if (event.key === 'Enter') {
var _props$experimentalFe3;
// TODO: check the return before firing 'rowEditStop'
// On cell editing, it won't exits the edit mode with error
const isValid = await apiRef.current.commitRowChange(params.id);
if (!isValid && (_props$experimentalFe3 = props.experimentalFeatures) != null && _props$experimentalFe3.preventCommitWhileValidating) {
return;
}
apiRef.current.publishEvent('rowEditStop', rowParams, event);
} else if (event.key === 'Escape') {
apiRef.current.publishEvent('rowEditStop', rowParams, event);
}
} else if (event.key === 'Enter') {
apiRef.current.publishEvent('rowEditStart', rowParams, event);
}
}, [apiRef, (_props$experimentalFe4 = props.experimentalFeatures) == null ? void 0 : _props$experimentalFe4.preventCommitWhileValidating]);
const handleCellDoubleClick = React.useCallback((params, event) => {
if (!params.isEditable) {
return;
}
const rowParams = apiRef.current.getRowParams(params.id);
apiRef.current.publishEvent('rowEditStart', rowParams, event);
}, [apiRef]);
const handleEditCellPropsChange = React.useCallback(params => {
const row = apiRef.current.getRow(params.id);
const model = apiRef.current.getEditRowsModel();
const editRow = model[params.id];
Object.keys(editRow).forEach(async field => {
const column = apiRef.current.getColumn(field);
if (column.preProcessEditCellProps) {
const editCellProps = field === params.field ? params.props : editRow[field];
const newEditCellProps = await Promise.resolve(column.preProcessEditCellProps({
id: params.id,
row,
props: editCellProps
}));
apiRef.current.unstable_setEditCellProps({
id: params.id,
field,
props: newEditCellProps
});
} else if (field === params.field) {
apiRef.current.unstable_setEditCellProps(params);
}
});
}, [apiRef]);
const handleRowEditStart = React.useCallback(params => {
apiRef.current.setRowMode(params.id, GridRowModes.Edit);
}, [apiRef]);
const handleRowEditStop = React.useCallback((params, event) => {
apiRef.current.setRowMode(params.id, GridRowModes.View);
if (event.key === 'Enter') {
apiRef.current.publishEvent('cellNavigationKeyDown', params, event);
}
}, [apiRef]);
const handleRowEditCommit = React.useCallback(id => {
const model = apiRef.current.getEditRowsModel();
const editRow = model[id];
if (!editRow) {
throw new Error(`MUI: Row at id: ${id} is not being edited.`);
}
const row = apiRef.current.getRow(id);
if (row) {
let rowUpdate = _extends({}, row);
Object.keys(editRow).forEach(field => {
const column = apiRef.current.getColumn(field);
const value = editRow[field].value;
if (column.valueSetter) {
rowUpdate = column.valueSetter({
row: rowUpdate,
value
});
} else {
rowUpdate[field] = value;
}
});
apiRef.current.updateRows([rowUpdate]);
}
}, [apiRef]);
const handleCellFocusIn = React.useCallback(params => {
nextFocusedCell.current = params;
}, []);
const commitPropsAndExit = async (params, event) => {
if (params.cellMode === GridCellModes.View) {
return;
}
nextFocusedCell.current = null;
focusTimeout.current = setTimeout(async () => {
var _nextFocusedCell$curr;
if (((_nextFocusedCell$curr = nextFocusedCell.current) == null ? void 0 : _nextFocusedCell$curr.id) !== params.id) {
await apiRef.current.commitRowChange(params.id, event);
const rowParams = apiRef.current.getRowParams(params.id);
apiRef.current.publishEvent('rowEditStop', rowParams, event);
}
});
};
const handleCellFocusOut = useEventCallback((params, event) => {
commitPropsAndExit(params, event);
});
const handleColumnHeaderDragStart = useEventCallback(() => {
const cell = gridFocusCellSelector(apiRef);
if (!cell) {
return;
}
const params = apiRef.current.getCellParams(cell.id, cell.field);
commitPropsAndExit(params, {});
});
useGridApiEventHandler(apiRef, 'cellKeyDown', buildCallback(handleCellKeyDown));
useGridApiEventHandler(apiRef, 'cellDoubleClick', buildCallback(handleCellDoubleClick));
useGridApiEventHandler(apiRef, 'editCellPropsChange', buildCallback(handleEditCellPropsChange));
useGridApiEventHandler(apiRef, 'rowEditStart', buildCallback(handleRowEditStart));
useGridApiEventHandler(apiRef, 'rowEditStop', buildCallback(handleRowEditStop));
useGridApiEventHandler(apiRef, 'rowEditCommit', buildCallback(handleRowEditCommit));
useGridApiEventHandler(apiRef, 'cellFocusIn', buildCallback(handleCellFocusIn));
useGridApiEventHandler(apiRef, 'cellFocusOut', buildCallback(handleCellFocusOut));
useGridApiEventHandler(apiRef, 'columnHeaderDragStart', buildCallback(handleColumnHeaderDragStart));
useGridApiOptionHandler(apiRef, 'rowEditCommit', props.onRowEditCommit);
useGridApiOptionHandler(apiRef, 'rowEditStart', props.onRowEditStart);
useGridApiOptionHandler(apiRef, 'rowEditStop', props.onRowEditStop);
};