diff --git a/docs/pages/api-docs/data-grid/data-grid-pro.json b/docs/pages/api-docs/data-grid/data-grid-pro.json index ebdbd56250275..12bd2de04e4ba 100644 --- a/docs/pages/api-docs/data-grid/data-grid-pro.json +++ b/docs/pages/api-docs/data-grid/data-grid-pro.json @@ -312,6 +312,7 @@ "columnHeaderDropZone", "columnHeaderTitle", "columnHeaderTitleContainer", + "columnHeaderTitleContainerContent", "columnHeaders", "columnHeadersInner", "columnHeadersInner--scrollable", diff --git a/docs/pages/api-docs/data-grid/data-grid.json b/docs/pages/api-docs/data-grid/data-grid.json index 26992b12e18f6..91db2cefd626a 100644 --- a/docs/pages/api-docs/data-grid/data-grid.json +++ b/docs/pages/api-docs/data-grid/data-grid.json @@ -259,6 +259,7 @@ "columnHeaderDropZone", "columnHeaderTitle", "columnHeaderTitleContainer", + "columnHeaderTitleContainerContent", "columnHeaders", "columnHeadersInner", "columnHeadersInner--scrollable", diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index bf98f6da8b08e..175fe126a6563 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -312,6 +312,7 @@ "columnHeaderDropZone", "columnHeaderTitle", "columnHeaderTitleContainer", + "columnHeaderTitleContainerContent", "columnHeaders", "columnHeadersInner", "columnHeadersInner--scrollable", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 6fd1b72c7863f..46f49557431fd 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -259,6 +259,7 @@ "columnHeaderDropZone", "columnHeaderTitle", "columnHeaderTitleContainer", + "columnHeaderTitleContainerContent", "columnHeaders", "columnHeadersInner", "columnHeadersInner--scrollable", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro-pt.json b/docs/translations/api-docs/data-grid/data-grid-pro-pt.json index 2093ad9cb269b..3922fb93572c7 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro-pt.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro-pt.json @@ -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" diff --git a/docs/translations/api-docs/data-grid/data-grid-pro-zh.json b/docs/translations/api-docs/data-grid/data-grid-pro-zh.json index 2093ad9cb269b..3922fb93572c7 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro-zh.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro-zh.json @@ -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" diff --git a/docs/translations/api-docs/data-grid/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro.json index 2093ad9cb269b..3922fb93572c7 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro.json @@ -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" diff --git a/docs/translations/api-docs/data-grid/data-grid-pt.json b/docs/translations/api-docs/data-grid/data-grid-pt.json index 845ea76bf8dc2..98edaa2f1c678 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pt.json +++ b/docs/translations/api-docs/data-grid/data-grid-pt.json @@ -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" diff --git a/docs/translations/api-docs/data-grid/data-grid-zh.json b/docs/translations/api-docs/data-grid/data-grid-zh.json index 845ea76bf8dc2..98edaa2f1c678 100644 --- a/docs/translations/api-docs/data-grid/data-grid-zh.json +++ b/docs/translations/api-docs/data-grid/data-grid-zh.json @@ -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" diff --git a/docs/translations/api-docs/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid.json index 845ea76bf8dc2..98edaa2f1c678 100644 --- a/docs/translations/api-docs/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid.json @@ -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" diff --git a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx b/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx index 40378758b9cb4..12dfee4184fec 100644 --- a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx +++ b/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx @@ -64,6 +64,7 @@ const useUtilityClasses = (ownerState: OwnerState) => { ], draggableContainer: ['columnHeaderDraggableContainer'], titleContainer: ['columnHeaderTitleContainer'], + titleContainerContent: ['columnHeaderTitleContainerContent'], }; return composeClasses(slots, getDataGridUtilityClass, classes); @@ -232,13 +233,15 @@ function GridColumnHeaderItem(props: GridColumnHeaderItemProps) { {...draggableEventHandlers} >
- {headerComponent || ( - - )} +
+ {headerComponent || ( + + )} +
{columnTitleIconButtons}
diff --git a/packages/grid/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/grid/x-data-grid/src/components/containers/GridRootStyles.ts index 0f4e196f09c4f..a1f15db348342 100644 --- a/packages/grid/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/grid/x-data-grid/src/components/containers/GridRootStyles.ts @@ -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', }, diff --git a/packages/grid/x-data-grid/src/constants/gridClasses.ts b/packages/grid/x-data-grid/src/constants/gridClasses.ts index 659dcafc5b4a4..a1f370d9cf985 100644 --- a/packages/grid/x-data-grid/src/constants/gridClasses.ts +++ b/packages/grid/x-data-grid/src/constants/gridClasses.ts @@ -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. */ @@ -394,6 +398,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'columnHeaderDropZone', 'columnHeaderTitle', 'columnHeaderTitleContainer', + 'columnHeaderTitleContainerContent', 'columnHeaders', 'columnHeadersInner', 'columnHeadersInner--scrollable', diff --git a/packages/grid/x-data-grid/src/hooks/features/keyboard/useGridKeyboardNavigation.ts b/packages/grid/x-data-grid/src/hooks/features/keyboard/useGridKeyboardNavigation.ts index b35b342d62d89..f619849ebaf8c 100644 --- a/packages/grid/x-data-grid/src/hooks/features/keyboard/useGridKeyboardNavigation.ts +++ b/packages/grid/x-data-grid/src/hooks/features/keyboard/useGridKeyboardNavigation.ts @@ -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) @@ -166,12 +168,21 @@ export const useGridKeyboardNavigation = ( ); const handleColumnHeaderKeyDown = React.useCallback< - GridEventListener + GridEventListener >( (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; diff --git a/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 9e723697b7f74..844bb87d079b4 100644 --- a/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -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'; @@ -365,42 +365,97 @@ describe(' - 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: () => ( - - ), - }, - ]; + render(); - 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( -
- -
, - ); - 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: () => , + }, + ]; + + const rows = [ + { + id: 1, + name: 'John', + }, + ]; + + render( +
+ +
, + ); + 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: () => , + }, + ]; + + const rows = [ + { + id: 1, + name: 'John', + }, + ]; + + render( +
+ +
, + ); + 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', () => {