diff --git a/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts b/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts index 6ab4b4729f83d..1d8e8dfd88954 100644 --- a/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts +++ b/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts @@ -24,6 +24,7 @@ import { getPublicApiRef, isPasteShortcut, useGridLogger, + isEventTargetInPortal, } from '@mui/x-data-grid/internals'; import { warnOnce } from '@mui/x-internals/warning'; import { GRID_DETAIL_PANEL_TOGGLE_FIELD, GRID_REORDER_COL_DEF } from '@mui/x-data-grid-pro'; @@ -371,6 +372,11 @@ export const useGridClipboardImport = ( const handlePaste = React.useCallback>( async (params, event) => { + // Ignore portal + // Do not apply shortcuts if the focus is not on the cell root component + if (isEventTargetInPortal(event)) { + return; + } if (!enableClipboardPaste) { return; } diff --git a/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx index 82acf5d54afef..2069fe39968ba 100644 --- a/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx @@ -10,6 +10,7 @@ import { import { act, createRenderer, fireEvent, waitFor } from '@mui/internal-test-utils'; import { type SinonSpy, spy, stub, type SinonStub } from 'sinon'; import { getCell, getColumnValues, includeRowSelection, sleep } from 'test/utils/helperFn'; +import Portal from '@mui/material/Portal'; import { getBasicGridData } from '@mui/x-data-grid-generator'; import { isJSDOM } from 'test/utils/skipIf'; @@ -1259,5 +1260,59 @@ describe(' - Clipboard', () => { // Should not be empty expect(getCell(2, 1)).to.have.text('GBPEUR'); }); + + // https://github.com/mui/mui-x/issues/21891 + it.skipIf(isJSDOM)( + 'should not intercept paste shortcuts from portaled elements inside a cell', + async () => { + function PortalCell() { + return ( + + + + ); + } + + const columns: GridColDef[] = [ + { field: 'id', renderCell: () => , editable: true }, + { field: 'name', editable: true }, + ]; + const rows = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ]; + + const { user } = render( +
+ +
, + ); + + const portalInput = document.querySelector( + 'input[name="portal-input"]', + ) as HTMLInputElement; + + // First click on a cell to establish grid focus, then focus the portal input + const cell = getCell(0, 0); + await user.click(cell); + await act(() => portalInput.focus()); + expect(portalInput).toHaveFocus(); + + // Simulate Ctrl+V on the portal input — the grid should ignore this. + // fireEvent is used because isPasteShortcut() requires keyCode (deprecated), + // which user.keyboard does not set. + fireEvent.keyDown(portalInput, { key: 'v', keyCode: 86, ctrlKey: true }); + + // Without the fix, getTextFromClipboard() creates a hidden inside the grid + // root element and steals focus from the portal input. + // With the fix, the portal input retains focus. + expect(portalInput).toHaveFocus(); + }, + ); }); });