Skip to content

Commit

Permalink
[DataGrid] Fix input element in custom header (#3624)
Browse files Browse the repository at this point in the history
Co-authored-by: Matheus Wichman <matheushw@outlook.com>
  • Loading branch information
alexfauquette and m4theushw authored Mar 2, 2022
1 parent cc985bb commit 91cd50d
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 41 deletions.
1 change: 1 addition & 0 deletions docs/pages/api-docs/data-grid/data-grid-pro.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
"columnHeaderDropZone",
"columnHeaderTitle",
"columnHeaderTitleContainer",
"columnHeaderTitleContainerContent",
"columnHeaders",
"columnHeadersInner",
"columnHeadersInner--scrollable",
Expand Down
1 change: 1 addition & 0 deletions docs/pages/api-docs/data-grid/data-grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@
"columnHeaderDropZone",
"columnHeaderTitle",
"columnHeaderTitleContainer",
"columnHeaderTitleContainerContent",
"columnHeaders",
"columnHeadersInner",
"columnHeadersInner--scrollable",
Expand Down
1 change: 1 addition & 0 deletions docs/pages/x/api/data-grid/data-grid-pro.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
"columnHeaderDropZone",
"columnHeaderTitle",
"columnHeaderTitleContainer",
"columnHeaderTitleContainerContent",
"columnHeaders",
"columnHeadersInner",
"columnHeadersInner--scrollable",
Expand Down
1 change: 1 addition & 0 deletions docs/pages/x/api/data-grid/data-grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@
"columnHeaderDropZone",
"columnHeaderTitle",
"columnHeaderTitleContainer",
"columnHeaderTitleContainerContent",
"columnHeaders",
"columnHeadersInner",
"columnHeadersInner--scrollable",
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/data-grid/data-grid-pro-pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title container element"
},
"columnHeaderTitleContainerContent": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title excepted buttons"
},
"columnHeaders": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column headers"
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/data-grid/data-grid-pro-zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title container element"
},
"columnHeaderTitleContainerContent": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title excepted buttons"
},
"columnHeaders": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column headers"
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/data-grid/data-grid-pro.json
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title container element"
},
"columnHeaderTitleContainerContent": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title excepted buttons"
},
"columnHeaders": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column headers"
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/data-grid/data-grid-pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title container element"
},
"columnHeaderTitleContainerContent": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title excepted buttons"
},
"columnHeaders": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column headers"
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/data-grid/data-grid-zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title container element"
},
"columnHeaderTitleContainerContent": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title excepted buttons"
},
"columnHeaders": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column headers"
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/data-grid/data-grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title container element"
},
"columnHeaderTitleContainerContent": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column header's title excepted buttons"
},
"columnHeaders": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the column headers"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const useUtilityClasses = (ownerState: OwnerState) => {
],
draggableContainer: ['columnHeaderDraggableContainer'],
titleContainer: ['columnHeaderTitleContainer'],
titleContainerContent: ['columnHeaderTitleContainerContent'],
};

return composeClasses(slots, getDataGridUtilityClass, classes);
Expand Down Expand Up @@ -232,13 +233,15 @@ function GridColumnHeaderItem(props: GridColumnHeaderItemProps) {
{...draggableEventHandlers}
>
<div className={classes.titleContainer}>
{headerComponent || (
<GridColumnHeaderTitle
label={column.headerName ?? column.field}
description={column.description}
columnWidth={width}
/>
)}
<div className={classes.titleContainerContent}>
{headerComponent || (
<GridColumnHeaderTitle
label={column.headerName ?? column.field}
description={column.description}
columnWidth={width}
/>
)}
</div>

{columnTitleIconButtons}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ export const GridRootStyles = styled('div', {
whiteSpace: 'nowrap',
overflow: 'hidden',
},
[`& .${gridClasses.columnHeaderTitleContainerContent}`]: {
overflow: 'hidden',
display: 'flex',
alignItems: 'center',
},
[`& .${gridClasses.sortIcon}, & .${gridClasses.filterIcon}`]: {
fontSize: 'inherit',
},
Expand Down
5 changes: 5 additions & 0 deletions packages/grid/x-data-grid/src/constants/gridClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export interface GridClasses {
* Styles applied to the column header's title container element.
*/
columnHeaderTitleContainer: string;
/**
* Styles applied to the column header's title excepted buttons.
*/
columnHeaderTitleContainerContent: string;
/**
* Styles applied to the column headers.
*/
Expand Down Expand Up @@ -394,6 +398,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [
'columnHeaderDropZone',
'columnHeaderTitle',
'columnHeaderTitleContainer',
'columnHeaderTitleContainerContent',
'columnHeaders',
'columnHeadersInner',
'columnHeadersInner--scrollable',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { gridVisibleSortedRowEntriesSelector } from '../filter/gridFilterSelector';
import { useCurrentPageRows } from '../../utils/useCurrentPageRows';
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../colDef/gridCheckboxSelectionColDef';
import { gridClasses } from '../../../constants/gridClasses';

/**
* @requires useGridPage (state)
Expand Down Expand Up @@ -166,12 +168,21 @@ export const useGridKeyboardNavigation = (
);

const handleColumnHeaderKeyDown = React.useCallback<
GridEventListener<GridEvents.columnHeaderNavigationKeyDown>
GridEventListener<GridEvents.columnHeaderKeyDown>
>(
(params, event) => {
if (!params.field) {
const headerTitleNode = event.currentTarget.querySelector(
`.${gridClasses.columnHeaderTitleContainerContent}`,
);
const isFromInsideContent =
!!headerTitleNode && headerTitleNode.contains(event.target as Node | null);

if (isFromInsideContent && params.field !== GRID_CHECKBOX_SELECTION_COL_DEF.field) {
// When focus is on a nested input, keyboard events have no effect to avoid conflicts with native events.
// There is one exception for the checkBoxHeader
return;
}

const dimensions = apiRef.current.getRootDimensions();
if (!dimensions) {
return;
Expand Down
119 changes: 87 additions & 32 deletions packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { createRenderer, fireEvent, screen, createEvent } from '@mui/monorepo/test/utils';
import { createRenderer, fireEvent, screen } from '@mui/monorepo/test/utils';
import Portal from '@mui/material/Portal';
import { spy } from 'sinon';
import { expect } from 'chai';
Expand Down Expand Up @@ -365,42 +365,97 @@ describe('<DataGrid /> - Keyboard', () => {
fireEvent.keyDown(document.activeElement!, { key: 'PageDown' });
expect(getActiveCell()).to.equal(`5-1`);
});
});
/* eslint-enable material-ui/disallow-active-element-as-key-event-target */

it('should be able to type in an child input', () => {
const handleInputKeyDown = spy((event) => event.defaultPrevented);
it('should move focus when the focus is on a column header button', function test() {
if (isJSDOM) {
// This test is not relevant if we can't choose the actual height
this.skip();
}

const columns = [
{
field: 'name',
headerName: 'Name',
width: 200,
renderCell: () => (
<input type="text" data-testid="custom-input" onKeyDown={handleInputKeyDown} />
),
},
];
render(<NavigationTestCaseNoScrollX />);

const rows = [
{
id: 1,
name: 'John',
},
];
// get the sort button in column header 1
const columnMenuButton = getColumnHeaderCell(1).querySelector(
`button[title="Sort"]`,
) as HTMLElement;

render(
<div style={{ width: 300, height: 300 }}>
<DataGrid rows={rows} columns={columns} />
</div>,
);
const input = screen.getByTestId('custom-input');
fireClickEvent(input);
const keydownEvent = createEvent.keyDown(input, {
key: 'a',
// Simulate click on this button
fireEvent.mouseUp(columnMenuButton);
fireEvent.click(columnMenuButton);
columnMenuButton.focus();

fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' });
expect(getActiveCell()).to.equal(`0-1`);
});

/* eslint-enable material-ui/disallow-active-element-as-key-event-target */
it('should be able to type in an child input', () => {
const columns = [
{
field: 'name',
headerName: 'Name',
width: 200,
renderCell: () => <input type="text" data-testid="custom-input" />,
},
];

const rows = [
{
id: 1,
name: 'John',
},
];

render(
<div style={{ width: 300, height: 300 }}>
<DataGrid rows={rows} columns={columns} />
</div>,
);
const input = screen.getByTestId('custom-input');
fireEvent.mouseUp(input);
fireEvent.click(input);
input.focus();

// This does not work with navigation keys.
// For now, the workaround for developers is to stop the propagation
// But adding input is discouraged, action column or edit mode are better options
expect(fireEvent.keyDown(input, { key: 'a' })).to.equal(true);
});

it('should be able to use keyboard in a columnHeader child input', () => {
const columns = [
{
field: 'name',
headerName: 'Name',
width: 200,
renderHeader: () => <input type="text" data-testid="custom-input" />,
},
];

const rows = [
{
id: 1,
name: 'John',
},
];

render(
<div style={{ width: 300, height: 300 }}>
<DataGrid rows={rows} columns={columns} />
</div>,
);
const input = screen.getByTestId('custom-input');
fireEvent.mouseUp(input);
fireEvent.click(input);
input.focus();

// Verify that the event is not prevented during the bubbling.
// fireEvent.keyDown return false if it is the case
// For more info, see the related discussion: https://github.com/mui/mui-x/pull/3624#discussion_r787767632
expect(fireEvent.keyDown(input, { key: 'a' })).to.equal(true);
expect(fireEvent.keyDown(input, { key: ' ' })).to.equal(true);
expect(fireEvent.keyDown(input, { key: 'ArrowLeft' })).to.equal(true);
});
fireEvent(input, keydownEvent);
expect(handleInputKeyDown.returnValues).to.deep.equal([false]);
});

it('should ignore events coming from a portal inside the cell', () => {
Expand Down

0 comments on commit 91cd50d

Please sign in to comment.