diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index ec9109fc062fe1..95c48f6dde785e 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -46,6 +46,7 @@ - `useInsertionPoint`: Leave the insertion cue alone when it belongs to the in-between inserter. The cue is shared state and the in-between inserter mounts inside its popover, so the sidebar inserter hiding it tore down the inline inserter that had just been opened ([#76241](https://github.com/WordPress/gutenberg/pull/76241)). - `BorderPanel`: Match a chosen drop shadow against the presets from every origin rather than the first origin that defines any, so theme and default presets keep being stored as `var:preset|shadow|` once a custom preset exists ([#81346](https://github.com/WordPress/gutenberg/pull/81346)). - `InserterMenu`: Cancel the animation frame that focuses the active tab when the menu unmounts before it runs, so an unmount right after mount no longer throws [#81918](https://github.com/WordPress/gutenberg/pull/81918). +- `ListView`: On keyboard activation, focus the start of the first field instead of the end of the last. A Table no longer focuses its last cell. Clicking a List View item still keeps focus in the list ([#81964](https://github.com/WordPress/gutenberg/pull/81964)). ### Internal diff --git a/packages/block-editor/src/components/list-view/block.jsx b/packages/block-editor/src/components/list-view/block.jsx index 95b4c941c7a29a..53d0b0400821c6 100644 --- a/packages/block-editor/src/components/list-view/block.jsx +++ b/packages/block-editor/src/components/list-view/block.jsx @@ -431,11 +431,12 @@ function ListViewBlock( { const selectEditorBlock = useCallback( ( event ) => { // For keyboard activation (Enter/Space on a link), transfer focus - // to the canvas with the caret at the end of the block. - // For mouse clicks, keep focus in the list view so that subsequent - // keyboard operations (arrow navigation, copy/paste) still work. + // to the canvas at the default initial position (start of the first + // field). For mouse clicks, keep focus in the list view so that + // subsequent keyboard operations (arrow navigation, copy/paste) + // still work. const isKeyboardActivation = event?.detail === 0; - selectBlock( event, clientId, isKeyboardActivation ? -1 : null ); + selectBlock( event, clientId, isKeyboardActivation ? 0 : null ); event.preventDefault(); }, [ clientId, selectBlock ] diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js index e2f6228e804b2c..02bc0c62f9af30 100644 --- a/test/e2e/specs/editor/various/list-view.spec.js +++ b/test/e2e/specs/editor/various/list-view.spec.js @@ -1445,7 +1445,7 @@ test.describe( 'List View', () => { ).toBeVisible(); } ); - test( 'should place the caret at the end of the block when activating from List View', async ( { + test( 'should place the caret at the start of the block when activating from List View', async ( { editor, page, listViewUtils, @@ -1465,8 +1465,8 @@ test.describe( 'List View', () => { // Click the first paragraph in List View to select it, // then press Enter to activate it (transfer focus to canvas). - // Keyboard activation (Enter/Space) places the caret at the end - // of the block, while mouse click keeps focus in the list view. + // Keyboard activation places the caret at the start of the first + // field. Mouse click keeps focus in the list view. await listView .getByRole( 'gridcell', { name: 'Paragraph' } ) .first() @@ -1474,24 +1474,75 @@ test.describe( 'List View', () => { await page.keyboard.press( 'Enter' ); // Press Enter to split the block at the caret position. - // If the caret is at the end, this creates a new block after the first paragraph. - // If the caret is at the start, this creates a new block before the first paragraph. + // With the caret at the start, this creates a new block before + // the first paragraph. await page.keyboard.press( 'Enter' ); - // Verify the block order: if the caret was at the end, the new empty - // block should be after the first paragraph, not before it. await expect.poll( editor.getBlocks ).toMatchObject( [ + { name: 'core/paragraph', attributes: { content: '' } }, { name: 'core/paragraph', attributes: { content: 'First paragraph' }, }, - { name: 'core/paragraph', attributes: { content: '' } }, { name: 'core/paragraph', attributes: { content: 'Second paragraph' }, }, ] ); } ); + + test( 'should focus the first cell of a Table when activating from List View', async ( { + editor, + page, + listViewUtils, + } ) => { + await editor.insertBlock( { + name: 'core/table', + attributes: { + body: [ + { + cells: [ + { content: 'R1C1', tag: 'td' }, + { content: 'R1C2', tag: 'td' }, + ], + }, + { + cells: [ + { content: 'R2C1', tag: 'td' }, + { content: 'R2C2', tag: 'td' }, + ], + }, + ], + }, + } ); + + const listView = await listViewUtils.openListView(); + await listView.getByRole( 'gridcell', { name: 'Table' } ).click(); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'X' ); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/table', + attributes: { + body: [ + { + cells: [ + { content: 'XR1C1', tag: 'td' }, + { content: 'R1C2', tag: 'td' }, + ], + }, + { + cells: [ + { content: 'R2C1', tag: 'td' }, + { content: 'R2C2', tag: 'td' }, + ], + }, + ], + }, + }, + ] ); + } ); } ); /** @typedef {import('@playwright/test').Locator} Locator */