Skip to content

Commit

Permalink
[DataGrid] Cell clipboard paste, leading "v" (#9205)
Browse files Browse the repository at this point in the history
Co-authored-by: Olivier Tassinari <[email protected]>
Co-authored-by: Andrew Cherniavskyi <[email protected]>
  • Loading branch information
3 people authored Nov 23, 2023
1 parent 316fa94 commit f908aec
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ describe('<DataGridPro /> - Cell editing', () => {
error: false,
isProcessingProps: true,
changeReason: 'setEditCellValue',
unstable_updateValueOnRender: false,
});
});

Expand Down Expand Up @@ -917,7 +916,7 @@ describe('<DataGridPro /> - Cell editing', () => {
expect(spiedStartCellEditMode.lastCall.args[0]).to.deep.equal({
id: 0,
field: 'currencyPair',
initialValue: 'a',
deleteValue: true,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,20 +248,6 @@ describe('<DataGridPro /> - Edit components', () => {
);
});

it('should call setEditCellValue when entering the edit mode by pressing a digit', () => {
render(<TestCase />);
const spiedSetEditCellValue = spyApi(apiRef.current, 'setEditCellValue');

const cell = getCell(0, 0);
userEvent.mousePress(cell);
fireEvent.keyDown(cell, { key: '5' });

expect(spiedSetEditCellValue.lastCall.args[0].id).to.equal(0);
expect(spiedSetEditCellValue.lastCall.args[0].field).to.equal('createdAt');
expect(spiedSetEditCellValue.lastCall.args[0].debounceMs).to.equal(undefined);
expect(spiedSetEditCellValue.lastCall.args[0].value).to.be.instanceOf(Date);
});

it('should call setEditCellValue with null when entered an empty value', () => {
render(<TestCase />);
const spiedSetEditCellValue = spyApi(apiRef.current, 'setEditCellValue');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ describe('<DataGridPro /> - Row editing', () => {
error: false,
isProcessingProps: true,
changeReason: 'setEditCellValue',
unstable_updateValueOnRender: false,
});

const args2 = column2Props.preProcessEditCellProps.lastCall.args[0];
Expand All @@ -244,7 +243,6 @@ describe('<DataGridPro /> - Row editing', () => {
value: 1,
error: false,
isProcessingProps: true,
unstable_updateValueOnRender: false,
});
});

Expand Down Expand Up @@ -911,7 +909,7 @@ describe('<DataGridPro /> - Row editing', () => {
expect(spiedStartRowEditMode.lastCall.args[0]).to.deep.equal({
id: 0,
fieldToFocus: 'currencyPair',
initialValue: 'a',
deleteValue: true,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ function GridEditDateCell(props: GridEditDateCellProps) {
const ownerState = { classes: rootProps.classes };
const classes = useUtilityClasses(ownerState);

const hasUpdatedEditValueOnMount = React.useRef(false);

const parseValueToDate = React.useCallback((value: string) => {
if (value === '') {
return null;
Expand Down Expand Up @@ -152,24 +150,9 @@ function GridEditDateCell(props: GridEditDateCellProps) {
inputRef.current!.focus();
}
}, [hasFocus]);

const meta = apiRef.current.unstable_getEditCellMeta(id, field);

const handleInputRef = (el: HTMLInputElement) => {
inputRef.current = el;

if (meta?.unstable_updateValueOnRender && !hasUpdatedEditValueOnMount.current) {
const inputValue = inputRef.current.value;
const parsedDate = parseValueToDate(inputValue);
setValueState({ parsed: parsedDate, formatted: inputValue });
apiRef.current.setEditCellValue({ id, field, value: parsedDate });
hasUpdatedEditValueOnMount.current = true;
}
};

return (
<StyledInputBase
inputRef={handleInputRef}
inputRef={inputRef}
fullWidth
className={classes.root}
type={isDateTime ? 'datetime-local' : 'date'}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as React from 'react';
import { unstable_useEventCallback as useEventCallback } from '@mui/utils';
import {
unstable_useEventCallback as useEventCallback,
unstable_useEnhancedEffect as useEnhancedEffect,
} from '@mui/utils';
import {
useGridApiEventHandler,
useGridApiOptionHandler,
Expand Down Expand Up @@ -166,11 +169,10 @@ export const useGridCellEditing = (
if (!canStartEditing) {
return;
}

if (isPrintableKey(event)) {
reason = GridCellEditStartReasons.printableKeyDown;
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
reason = GridCellEditStartReasons.printableKeyDown;
reason = GridCellEditStartReasons.pasteKeyDown;
} else if (event.key === 'Enter') {
reason = GridCellEditStartReasons.enterKeyDown;
} else if (event.key === 'Delete' || event.key === 'Backspace') {
Expand All @@ -189,20 +191,15 @@ export const useGridCellEditing = (

const handleCellEditStart = React.useCallback<GridEventListener<'cellEditStart'>>(
(params) => {
const { id, field, reason, key, colDef } = params;
const { id, field, reason } = params;

const startCellEditModeParams: GridStartCellEditModeParams = { id, field };

if (reason === GridCellEditStartReasons.printableKeyDown) {
if (React.version.startsWith('17')) {
// In React 17, cleaning the input is enough.
// The sequence of events makes the key pressed by the end-users update the textbox directly.
startCellEditModeParams.deleteValue = true;
} else {
const initialValue = colDef.valueParser ? colDef.valueParser(key) : key;
startCellEditModeParams.initialValue = initialValue;
}
} else if (reason === GridCellEditStartReasons.deleteKeyDown) {
if (
reason === GridCellEditStartReasons.printableKeyDown ||
reason === GridCellEditStartReasons.deleteKeyDown ||
reason === GridCellEditStartReasons.pasteKeyDown
) {
startCellEditModeParams.deleteValue = true;
}

Expand Down Expand Up @@ -332,18 +329,14 @@ export const useGridCellEditing = (
const { id, field, deleteValue, initialValue } = params;

let newValue = apiRef.current.getCellValue(id, field);
// eslint-disable-next-line @typescript-eslint/naming-convention
let unstable_updateValueOnRender = false;
if (deleteValue || initialValue) {
newValue = deleteValue ? '' : initialValue;
unstable_updateValueOnRender = true;
}

const newProps = {
value: newValue,
error: false,
isProcessingProps: false,
unstable_updateValueOnRender,
};

updateOrDeleteFieldState(id, field, newProps);
Expand Down Expand Up @@ -522,7 +515,8 @@ export const useGridCellEditing = (
}
}, [cellModesModelProp, updateCellModesModel]);

React.useEffect(() => {
// Run this effect synchronously so that the keyboard event can impact the yet-to-be-rendered input.
useEnhancedEffect(() => {
const idToIdLookup = gridRowsDataRowIdToIdLookupSelector(apiRef);

// Update the ref here because updateStateToStopCellEditMode may change it later
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as React from 'react';
import { unstable_useEventCallback as useEventCallback } from '@mui/utils';
import {
unstable_useEventCallback as useEventCallback,
unstable_useEnhancedEffect as useEnhancedEffect,
} from '@mui/utils';
import {
useGridApiEventHandler,
useGridApiOptionHandler,
Expand Down Expand Up @@ -257,7 +260,6 @@ export const useGridRowEditing = (
const newParams: GridRowEditStartParams = {
...rowParams,
field: params.field,
key: event.key,
reason,
};
apiRef.current.publishEvent('rowEditStart', newParams, event);
Expand All @@ -269,20 +271,14 @@ export const useGridRowEditing = (

const handleRowEditStart = React.useCallback<GridEventListener<'rowEditStart'>>(
(params) => {
const { id, field, reason, key, columns } = params;
const { id, field, reason } = params;

const startRowEditModeParams: GridStartRowEditModeParams = { id, fieldToFocus: field };

if (reason === GridRowEditStartReasons.printableKeyDown) {
if (React.version.startsWith('17')) {
// In React 17, cleaning the input is enough.
// The sequence of events makes the key pressed by the end-users update the textbox directly.
startRowEditModeParams.deleteValue = !!field;
} else {
const colDef = columns.find((col) => col.field === field)!;
startRowEditModeParams.initialValue = colDef.valueParser ? colDef.valueParser(key) : key;
}
} else if (reason === GridRowEditStartReasons.deleteKeyDown) {
if (
reason === GridRowEditStartReasons.printableKeyDown ||
reason === GridRowEditStartReasons.deleteKeyDown
) {
startRowEditModeParams.deleteValue = !!field;
}

Expand Down Expand Up @@ -430,18 +426,14 @@ export const useGridRowEditing = (
}

let newValue = apiRef.current.getCellValue(id, field);
// eslint-disable-next-line @typescript-eslint/naming-convention
let unstable_updateValueOnRender = false;
if (fieldToFocus === field && (deleteValue || initialValue)) {
newValue = deleteValue ? '' : initialValue;
unstable_updateValueOnRender = true;
}

acc[field] = {
value: newValue,
error: false,
isProcessingProps: false,
unstable_updateValueOnRender,
};

return acc;
Expand Down Expand Up @@ -710,7 +702,8 @@ export const useGridRowEditing = (
}
}, [rowModesModelProp, updateRowModesModel]);

React.useEffect(() => {
// Run this effect synchronously so that the keyboard event can impact the yet-to-be-rendered input.
useEnhancedEffect(() => {
const idToIdLookup = gridRowsDataRowIdToIdLookupSelector(apiRef);

// Update the ref here because updateStateToStopRowEditMode may change it later
Expand Down
6 changes: 2 additions & 4 deletions packages/grid/x-data-grid/src/models/api/gridEditingApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ export type GridRowModesModel = Record<GridRowId, GridRowModesModelProps>;

export interface GridEditCellMeta {
changeReason?: 'debouncedSetEditCellValue' | 'setEditCellValue';
/**
* Determines if `setEditCellValue` should be called on the first render to sync the value.
*/
unstable_updateValueOnRender?: boolean;
}

export interface GridEditingSharedApi {
Expand Down Expand Up @@ -88,6 +84,7 @@ export interface GridStartCellEditModeParams {
/**
* The initial value for the field.
* If `deleteValue` is also true, this value is not used.
* @deprecated No longer needed.
*/
initialValue?: any;
}
Expand Down Expand Up @@ -135,6 +132,7 @@ export interface GridStartRowEditModeParams {
/**
* The initial value for the given `fieldToFocus`.
* If `deleteValue` is also true, this value is not used.
* @deprecated No longer needed.
*/
initialValue?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ enum GridCellEditStartReasons {
cellDoubleClick = 'cellDoubleClick',
printableKeyDown = 'printableKeyDown',
deleteKeyDown = 'deleteKeyDown',
pasteKeyDown = 'pasteKeyDown',
}

/**
Expand All @@ -45,6 +46,7 @@ export interface GridCellEditStartParams<R extends GridValidRowModel = any, V =
reason?: GridCellEditStartReasons;
/**
* If the reason is related to a keyboard event, it contains which key was pressed.
* @deprecated No longer needed.
*/
key?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface GridRowEditStartParams<R extends GridValidRowModel = any>
reason?: GridRowEditStartReasons;
/**
* If the reason is related to a keyboard event, it contains which key was pressed.
* @deprecated No longer needed.
*/
key?: string;
}
Expand Down
33 changes: 33 additions & 0 deletions test/e2e/fixtures/DataGrid/KeyboardEditNumber.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { DataGrid } from '@mui/x-data-grid';

const baselineProps = {
rows: [{ id: 0, age: 40 }],
columns: [{ field: 'age', type: 'number', editable: true }],
};

export default function KeyboardEditNumber() {
const [updates, setUpdates] = React.useState<number[]>([]);
return (
<div>
<div style={{ width: 300, height: 300 }}>
<DataGrid
{...baselineProps}
processRowUpdate={(newRow) => {
setUpdates((prev) => [...prev, newRow.age]);
return newRow;
}}
/>
</div>
<div>
{updates.map((update, key) => {
return (
<div key={key} data-testid="new-value">
{typeof update} {update}
</div>
);
})}
</div>
</div>
);
}
Loading

0 comments on commit f908aec

Please sign in to comment.