diff --git a/packages/block-editor/src/components/writing-flow/use-clipboard-handler.js b/packages/block-editor/src/components/writing-flow/use-clipboard-handler.js index a6d5c61b8b5c8f..56583447d3f0e9 100644 --- a/packages/block-editor/src/components/writing-flow/use-clipboard-handler.js +++ b/packages/block-editor/src/components/writing-flow/use-clipboard-handler.js @@ -60,6 +60,25 @@ export default function useClipboardHandler() { return; } + // Let native copy/paste behaviour take over in input fields. + // But always handle multiple selected blocks. + if ( ! hasMultiSelection() ) { + const { target } = event; + const { ownerDocument } = target; + // If copying, only consider actual text selection as selection. + // Otherwise, any focus on an input field is considered. + const hasSelection = + event.type === 'copy' || event.type === 'cut' + ? documentHasUncollapsedSelection( ownerDocument ) + : documentHasSelection( ownerDocument ) && + ! ownerDocument.activeElement.isContentEditable; + + // Let native copy behaviour take over in input fields. + if ( hasSelection ) { + return; + } + } + const { activeElement } = event.target.ownerDocument; if ( ! node.contains( activeElement ) ) { @@ -72,22 +91,6 @@ export default function useClipboardHandler() { const expandSelectionIsNeeded = ! shouldHandleWholeBlocks && ! isSelectionMergeable; if ( event.type === 'copy' || event.type === 'cut' ) { - if ( ! hasMultiSelection() ) { - const { target } = event; - const { ownerDocument } = target; - // If copying, only consider actual text selection as selection. - // Otherwise, any focus on an input field is considered. - const hasSelection = - event.type === 'copy' || event.type === 'cut' - ? documentHasUncollapsedSelection( ownerDocument ) - : documentHasSelection( ownerDocument ); - - // Let native copy behaviour take over in input fields. - if ( hasSelection ) { - return; - } - } - event.preventDefault(); if ( selectedBlockClientIds.length === 1 ) { diff --git a/packages/e2e-test-utils-playwright/src/page-utils/press-keys.ts b/packages/e2e-test-utils-playwright/src/page-utils/press-keys.ts index 11bc11c43f603c..886b35327fd4ec 100644 --- a/packages/e2e-test-utils-playwright/src/page-utils/press-keys.ts +++ b/packages/e2e-test-utils-playwright/src/page-utils/press-keys.ts @@ -50,7 +50,7 @@ export function setClipboardData( } async function emulateClipboard( page: Page, type: 'copy' | 'cut' | 'paste' ) { - clipboardDataHolder = await page.evaluate( + const output = await page.evaluate( ( [ _type, _clipboardData ] ) => { const canvasDoc = // @ts-ignore @@ -99,6 +99,10 @@ async function emulateClipboard( page: Page, type: 'copy' | 'cut' | 'paste' ) { canvasDoc.activeElement.dispatchEvent( event ); + if ( _type === 'paste' ) { + return event.defaultPrevented; + } + return { 'text/plain': event.clipboardData.getData( 'text/plain' ), 'text/html': event.clipboardData.getData( 'text/html' ), @@ -107,6 +111,17 @@ async function emulateClipboard( page: Page, type: 'copy' | 'cut' | 'paste' ) { }, [ type, clipboardDataHolder ] as const ); + + if ( typeof output === 'object' ) { + clipboardDataHolder = output; + } + + if ( output === false ) { + // Emulate paste by typing the clipboard content, which works across all + // elements and documents (keyboard.type does uses the nested active + // element automatically). + await page.keyboard.type( clipboardDataHolder[ 'text/plain' ] ); + } } const isAppleOS = () => process.platform === 'darwin'; diff --git a/test/e2e/specs/editor/blocks/rss.spec.js b/test/e2e/specs/editor/blocks/rss.spec.js new file mode 100644 index 00000000000000..1f6b640926b7c0 --- /dev/null +++ b/test/e2e/specs/editor/blocks/rss.spec.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'RSS', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + // See: https://github.com/WordPress/gutenberg/pull/61389. + test( 'should retain native copy/paste behavior for input fields', async ( { + editor, + pageUtils, + } ) => { + await editor.insertBlock( { name: 'core/rss' } ); + pageUtils.setClipboardData( { + plainText: 'https://developer.wordpress.org/news/feed/', + } ); + await pageUtils.pressKeys( 'primary+v' ); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/rss', + attributes: { + feedURL: 'https://developer.wordpress.org/news/feed/', + }, + }, + ] ); + } ); +} );