Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[DataGrid] Fix start edit mode with printable character in React 18 #6478

Merged
merged 2 commits into from Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -908,7 +908,7 @@ describe('<DataGridPro /> - Cell Editing', () => {
expect(spiedStartCellEditMode.lastCall.args[0]).to.deep.equal({
id: 0,
field: 'currencyPair',
deleteValue: true,
initialValue: 'a',
});
});

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

Expand Down
Expand Up @@ -174,7 +174,7 @@ export const useGridCellEditing = (
}

if (reason) {
const newParams: GridCellEditStartParams = { ...params, reason };
const newParams: GridCellEditStartParams = { ...params, reason, key: event.key };
apiRef.current.publishEvent('cellEditStart', newParams, event);
}
}
Expand All @@ -184,14 +184,17 @@ export const useGridCellEditing = (

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

const startCellEditModeParams: GridStartCellEditModeParams = { id, field };

if (
reason === GridCellEditStartReasons.deleteKeyDown ||
reason === GridCellEditStartReasons.printableKeyDown
) {
if (reason === GridCellEditStartReasons.printableKeyDown) {
if (React.version.startsWith('18')) {
startCellEditModeParams.initialValue = key; // In React 17, cleaning the input is enough
} else {
startCellEditModeParams.deleteValue = true;
}
} else if (reason === GridCellEditStartReasons.deleteKeyDown) {
startCellEditModeParams.deleteValue = true;
}

Expand Down Expand Up @@ -328,10 +331,15 @@ export const useGridCellEditing = (

const updateStateToStartCellEditMode = useEventCallback<[GridStartCellEditModeParams], void>(
(params) => {
const { id, field, deleteValue } = params;
const { id, field, deleteValue, initialValue } = params;

let newValue = apiRef.current.getCellValue(id, field);
if (deleteValue || initialValue) {
newValue = deleteValue ? '' : initialValue;
}

const newProps = {
value: deleteValue ? '' : apiRef.current.getCellValue(id, field),
value: newValue,
error: false,
isProcessingProps: false,
};
Expand Down
Expand Up @@ -237,7 +237,12 @@ export const useGridRowEditing = (

if (reason) {
const rowParams = apiRef.current.getRowParams(params.id);
const newParams: GridRowEditStartParams = { ...rowParams, field: params.field, reason };
const newParams: GridRowEditStartParams = {
...rowParams,
field: params.field,
key: event.key,
reason,
};
apiRef.current.publishEvent('rowEditStart', newParams, event);
}
}
Expand All @@ -247,14 +252,17 @@ export const useGridRowEditing = (

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

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

if (
reason === GridRowEditStartReasons.deleteKeyDown ||
reason === GridRowEditStartReasons.printableKeyDown
) {
if (reason === GridRowEditStartReasons.printableKeyDown) {
if (React.version.startsWith('18')) {
startRowEditModeParams.initialValue = key; // In React 17, cleaning the input is enough
} else {
startRowEditModeParams.deleteValue = !!field;
}
} else if (reason === GridRowEditStartReasons.deleteKeyDown) {
startRowEditModeParams.deleteValue = !!field;
}

Expand Down Expand Up @@ -400,7 +408,7 @@ export const useGridRowEditing = (

const updateStateToStartRowEditMode = useEventCallback<[GridStartRowEditModeParams], void>(
(params) => {
const { id, fieldToFocus, deleteValue } = params;
const { id, fieldToFocus, deleteValue, initialValue } = params;

const columnFields = gridColumnFieldsSelector(apiRef);

Expand All @@ -410,10 +418,13 @@ export const useGridRowEditing = (
return acc;
}

const shouldDeleteValue = deleteValue && fieldToFocus === field;
let newValue = apiRef.current.getCellValue(id, field);
if (fieldToFocus === field && (deleteValue || initialValue)) {
newValue = deleteValue ? '' : initialValue;
}

acc[field] = {
value: shouldDeleteValue ? '' : apiRef.current.getCellValue(id, field),
value: newValue,
error: false,
isProcessingProps: false,
};
Expand Down
10 changes: 10 additions & 0 deletions packages/grid/x-data-grid/src/models/api/gridEditingApi.ts
Expand Up @@ -207,6 +207,11 @@ export interface GridStartCellEditModeParams {
* If `true`, the value will be deleted before entering the edit mode.
*/
deleteValue?: boolean;
/**
* The initial value for the field.
* If `deleteValue` is also true, this value is not used.
*/
initialValue?: any;
}

/**
Expand Down Expand Up @@ -249,6 +254,11 @@ export interface GridStartRowEditModeParams {
* If `true`, the value in `fieldToFocus` will be deleted before entering the edit mode.
*/
deleteValue?: boolean;
/**
* The initial value for the given `fieldToFocus`.
* If `deleteValue` is also true, this value is not used.
*/
initialValue?: string;
}

/**
Expand Down
Expand Up @@ -68,6 +68,10 @@ export interface GridCellEditStartParams<
* Only applied if `props.experimentalFeatures.newEditingApi: true`.
*/
reason?: GridCellEditStartReasons;
/**
* If the reason is related to a keyboard event, it contains which key was pressed.
*/
key?: string;
}

enum GridCellEditStopReasons {
Expand Down
4 changes: 4 additions & 0 deletions packages/grid/x-data-grid/src/models/params/gridRowParams.ts
Expand Up @@ -87,6 +87,10 @@ export interface GridRowEditStartParams<R extends GridValidRowModel = any>
* Only applied if `props.experimentalFeatures.newEditingApi: true`.
*/
reason?: GridRowEditStartReasons;
/**
* If the reason is related to a keyboard event, it contains which key was pressed.
*/
key?: string;
}

enum GridRowEditStopReasons {
Expand Down
15 changes: 15 additions & 0 deletions test/e2e/fixtures/DataGrid/KeyboardEditInput.tsx
@@ -0,0 +1,15 @@
import * as React from 'react';
import { DataGrid } from '@mui/x-data-grid';

const baselineProps = {
rows: [{ id: 0, brand: 'Nike' }],
columns: [{ field: 'brand', type: 'string', editable: true, width: 100 }],
};

export default function KeyboardEditInput() {
return (
<div style={{ width: 300, height: 300 }}>
<DataGrid {...baselineProps} />
</div>
);
}
4 changes: 2 additions & 2 deletions test/e2e/index.js
@@ -1,5 +1,5 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as ReactDOM from 'react-dom/client';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import TestViewer from './TestViewer';

Expand Down Expand Up @@ -100,4 +100,4 @@ function App() {
);
}

ReactDOM.render(<App />, document.getElementById('react-root'));
ReactDOM.createRoot(document.getElementById('react-root')).render(<App />);
73 changes: 37 additions & 36 deletions test/e2e/index.test.ts
Expand Up @@ -94,9 +94,8 @@ describe('e2e', () => {
it('should select the first column header when pressing tab key', async () => {
await renderFixture('DataGrid/KeyboardNavigationFocus');

expect(
await page.evaluate(() => document.activeElement?.getAttribute('data-testid')),
).to.equal('initial-focus');
const button = page.locator('text=initial focus');
expect(await button.evaluate((node) => document.activeElement === node));

await page.keyboard.press('Tab');
expect(await page.evaluate(() => document.activeElement?.textContent)).to.equal('brand');
Expand All @@ -105,9 +104,9 @@ describe('e2e', () => {
it('should implement the roving tabindex pattern', async () => {
await renderFixture('DataGrid/KeyboardNavigationFocus');

expect(
await page.evaluate(() => document.activeElement?.getAttribute('data-testid')),
).to.equal('initial-focus');
const button = page.locator('text=initial focus');
expect(await button.evaluate((node) => document.activeElement === node));

await page.keyboard.press('Tab');
await waitFor(async () => {
expect(await page.evaluate(() => document.activeElement?.textContent)).to.equal('brand');
Expand Down Expand Up @@ -153,11 +152,9 @@ describe('e2e', () => {

it('should display the rows', async () => {
await renderFixture('DataGrid/ConcurrentReactUpdate');
expect(
await page.evaluate(() =>
Array.from(document.querySelectorAll('[role="cell"]')).map((node) => node.textContent),
),
).to.deep.equal(['1', '2']);
const cells = page.locator('[role="cell"]');
await cells.first().waitFor();
expect(await cells.allTextContents()).to.deep.equal(['1', '2']);
});

it('should work with a select as the edit cell', async () => {
Expand All @@ -174,9 +171,7 @@ describe('e2e', () => {

it('should reorder columns by dropping into the header', async () => {
await renderFixture('DataGrid/ColumnReorder');
expect(
await page.evaluate(() => document.querySelector('[role="row"]')!.textContent!),
).to.equal('brandyear');
expect(await page.locator('[role="row"]').first().textContent()).to.equal('brandyear');
const brand = await page.$('[role="columnheader"][aria-colindex="1"] > [draggable]');
const brandBoundingBox = await brand?.boundingBox();
const year = await page.$('[role="columnheader"][aria-colindex="2"] > [draggable]');
Expand All @@ -203,9 +198,7 @@ describe('e2e', () => {

it('should reorder columns by dropping into the body', async () => {
await renderFixture('DataGrid/ColumnReorder');
expect(
await page.evaluate(() => document.querySelector('[role="row"]')!.textContent!),
).to.equal('brandyear');
expect(await page.locator('[role="row"]').first().textContent()).to.equal('brandyear');
const brand = await page.$('[role="columnheader"][aria-colindex="1"] > [draggable]');
const brandBoundingBox = await brand?.boundingBox();
const cell = await page.$('[role="row"][data-rowindex="0"] [role="cell"][data-colindex="1"]');
Expand All @@ -225,9 +218,7 @@ describe('e2e', () => {
);
await page.mouse.up();
}
expect(
await page.evaluate(() => document.querySelector('[role="row"]')!.textContent!),
).to.equal('yearbrand');
expect(await page.locator('[role="row"]').first().textContent()).to.equal('yearbrand');
});

it('should select one row', async () => {
Expand Down Expand Up @@ -262,29 +253,23 @@ describe('e2e', () => {
await renderFixture('DataGrid/KeyboardEditDate');

// Edit date column
expect(
await page.evaluate(
() => document.querySelector('[role="cell"][data-field="birthday"]')!.textContent!,
),
).to.equal('2/29/1984');
expect(await page.locator('[role="cell"][data-field="birthday"]').textContent()).to.equal(
'2/29/1984',
);

// set 06/25/1986
await page.dblclick('[role="cell"][data-field="birthday"]');
await page.type('[role="cell"][data-field="birthday"] input', '06251986');

await page.keyboard.press('Enter');

expect(
await page.evaluate(
() => document.querySelector('[role="cell"][data-field="birthday"]')!.textContent!,
),
).to.equal('6/25/1986');
expect(await page.locator('[role="cell"][data-field="birthday"]').textContent()).to.equal(
'6/25/1986',
);

// Edit dateTime column
expect(
await page.evaluate(
() => document.querySelector('[role="cell"][data-field="lastConnection"]')!.textContent!,
),
await page.locator('[role="cell"][data-field="lastConnection"]').textContent(),
).to.equal('2/20/2022, 6:50:00 AM');

// start editing lastConnection
Expand All @@ -296,9 +281,7 @@ describe('e2e', () => {
await page.keyboard.press('Enter');

expect(
await page.evaluate(
() => document.querySelector('[role="cell"][data-field="lastConnection"]')!.textContent!,
),
await page.locator('[role="cell"][data-field="lastConnection"]').textContent(),
).to.equal('1/31/2025, 4:05:00 PM');
});

Expand All @@ -312,6 +295,9 @@ describe('e2e', () => {
await sleep(100);
}

const button = page.locator('text=initial focus');
expect(await button.evaluate((node) => document.activeElement === node));

await page.keyboard.down('Tab');

await keyDown(); // 0
Expand Down Expand Up @@ -350,5 +336,20 @@ describe('e2e', () => {
await page.evaluate(() => (document.activeElement as HTMLInputElement).value),
).to.equal('0-1');
});

it('should start editing a cell when a printable char is pressed', async () => {
await renderFixture('DataGrid/KeyboardEditInput');

expect(await page.locator('[role="cell"][data-field="brand"]').textContent()).to.equal(
'Nike',
);

await page.click('[role="cell"][data-field="brand"]');
await page.type('[role="cell"][data-field="brand"]', 'p');

expect(await page.locator('[role="cell"][data-field="brand"] input').inputValue()).to.equal(
'p',
);
});
});
});