Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
placeCaretAtHorizontalEdge,
placeCaretAtVerticalEdge,
isRTL,
isFormElement,
} from '@wordpress/dom';
import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes';
import { useDispatch, useSelect } from '@wordpress/data';
Expand All @@ -17,7 +18,7 @@ import { useRefEffect } from '@wordpress/compose';
/**
* Internal dependencies
*/
import { getBlockClientId, isInSameBlock } from '../../utils/dom';
import { getBlockClientId } from '../../utils/dom';
import { store as blockEditorStore } from '../../store';

/**
Expand Down Expand Up @@ -114,14 +115,18 @@ export function getClosestTabbable(
}

function isTabCandidate( node ) {
// Skip if there's only one child that is content editable (and thus a
// better candidate).
// If it's a block and there are nested focusable nodes, skip because
// there are better candidates.
if (
node.children.length === 1 &&
isInSameBlock( node, node.firstElementChild ) &&
node.firstElementChild.getAttribute( 'contenteditable' ) === 'true'
getBlockClientId( node ) &&
focus.focusable
.find( node )
// Exclude form elements for now because primary+a cannot be
// used to select the parent element.
.filter( ( element ) => ! isFormElement( element ) ).length !==
0
) {
return;
return false;
}

// Not a candidate if the node is not tabbable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useRefEffect } from '@wordpress/compose';
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { isInsideRootBlock, getBlockClientId } from '../../utils/dom';

export default function useSelectAll() {
const { getBlockOrder, getSelectedBlockClientIds, getBlockRootClientId } =
Expand All @@ -34,7 +35,27 @@ export default function useSelectAll() {

event.preventDefault();

const { ownerDocument } = event.target;
const [ firstSelectedClientId ] = selectedClientIds;
const activeClientId = getBlockClientId(
ownerDocument.activeElement
);

// Handle the case when an appender is selected.
if (
activeClientId &&
activeClientId !== firstSelectedClientId &&
! isInsideRootBlock(
ownerDocument.getElementById(
'block-' + firstSelectedClientId
),
ownerDocument.activeElement
)
) {
selectBlock( activeClientId );
return;
}

const rootClientId = getBlockRootClientId( firstSelectedClientId );
const blockClientIds = getBlockOrder( rootClientId );

Expand Down
8 changes: 6 additions & 2 deletions test/e2e/specs/editor/blocks/columns.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,11 @@ test.describe( 'Columns', () => {
} );
} );

test( 'should arrow up into empty columns', async ( { editor, page } ) => {
test( 'should arrow up into empty columns', async ( {
editor,
page,
pageUtils,
} ) => {
await editor.insertBlock( {
name: 'core/columns',
innerBlocks: [ { name: 'core/column' }, { name: 'core/column' } ],
Expand All @@ -389,7 +393,7 @@ test.describe( 'Columns', () => {
} );

await page.keyboard.press( 'ArrowUp' );
await page.keyboard.press( 'ArrowUp' );
await pageUtils.pressKeys( 'primary+a' );
await page.keyboard.press( 'Delete' );

await expect.poll( editor.getBlocks ).toMatchObject( [
Expand Down
32 changes: 9 additions & 23 deletions test/e2e/specs/editor/blocks/list.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,8 @@ test.describe( 'List (@firefox)', () => {
editor,
page,
} ) => {
await editor.canvas
.locator( 'role=button[name="Add default block"i]' )
.click();
await editor.insertBlock( { name: 'core/paragraph' } );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( '* ' );
await expect(
editor.canvas.locator( '[data-type="core/list"]' )
Expand Down Expand Up @@ -408,7 +407,9 @@ test.describe( 'List (@firefox)', () => {
await page.keyboard.press( 'Enter' );
await editor.clickBlockToolbarButton( 'Indent' );
await page.keyboard.type( 'two' );
await pageUtils.pressKeys( 'ArrowUp', { times: 4 } );
// Select all escalates: text → block → siblings → parent, repeat
// until the top-level list block is selected.
await pageUtils.pressKeys( 'primary+a', { times: 5 } );
await editor.transformBlockTo( 'core/paragraph' );

await expect.poll( editor.getEditedPostContent ).toBe(
Expand Down Expand Up @@ -593,7 +594,6 @@ test.describe( 'List (@firefox)', () => {

// Move the caret in front of "1" in the first list item.
await page.keyboard.press( 'ArrowRight' );
await page.keyboard.press( 'ArrowRight' );
await page.keyboard.press( 'Backspace' );

await expect.poll( editor.getBlocks ).toMatchObject( [
Expand Down Expand Up @@ -866,11 +866,7 @@ test.describe( 'List (@firefox)', () => {
);
} );

test( 'should outdent with children', async ( {
editor,
page,
pageUtils,
} ) => {
test( 'should outdent with children', async ( { editor, page } ) => {
await editor.insertBlock( { name: 'core/list' } );
await page.keyboard.type( 'a' );
await page.keyboard.press( 'Enter' );
Expand All @@ -896,7 +892,7 @@ test.describe( 'List (@firefox)', () => {
<!-- /wp:list -->`
);

await pageUtils.pressKeys( 'ArrowUp', { times: 3 } );
await page.keyboard.press( 'ArrowUp' );
await editor.clickBlockToolbarButton( 'Outdent' );

await expect.poll( editor.getEditedPostContent ).toBe(
Expand Down Expand Up @@ -1083,15 +1079,14 @@ test.describe( 'List (@firefox)', () => {
test( 'should place the caret in the right place with nested list', async ( {
editor,
page,
pageUtils,
} ) => {
await editor.canvas
.locator( 'role=button[name="Add default block"i]' )
.click();
await page.keyboard.type( '* 1' );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( ' a' );
await pageUtils.pressKeys( 'ArrowUp', { times: 2 } );
await page.keyboard.press( 'ArrowUp' );
await page.keyboard.press( 'Enter' );
// The caret should land in the second item.
await page.keyboard.type( '2' );
Expand Down Expand Up @@ -1517,10 +1512,7 @@ test.describe( 'List (@firefox)', () => {
test( 'Backspace', async ( { editor, page } ) => {
await editor.insertBlock( start );

// Navigate to the start of the third item.
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
// Navigate to the start of item "2".
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
Expand All @@ -1536,9 +1528,6 @@ test.describe( 'List (@firefox)', () => {
test( 'Delete (forward)', async ( { editor, page } ) => {
await editor.insertBlock( start );

// Navigate to the end of the second item.
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowRight' );
Expand Down Expand Up @@ -1582,7 +1571,6 @@ test.describe( 'List (@firefox)', () => {
],
} );

await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'Backspace' );
Expand Down Expand Up @@ -1641,7 +1629,6 @@ test.describe( 'List (@firefox)', () => {
],
} );

await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'Backspace' );

Expand Down Expand Up @@ -1685,7 +1672,6 @@ test.describe( 'List (@firefox)', () => {
],
} );

await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.press( 'Enter' );
await page.keyboard.press( 'Backspace' );
Expand Down
14 changes: 8 additions & 6 deletions test/e2e/specs/editor/blocks/navigation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,9 @@ test.describe( 'Navigation block', () => {
await navigation.useLinkShortcut();

await navigation.addSubmenuPage( 'Dog' );
await pageUtils.pressKeys( 'ArrowUp' );
await pageUtils.pressKeys( 'ArrowRight' );
// Navigate to the Dog link content using ArrowLeft (ArrowUp skips
// blocks with nested focusables).
await pageUtils.pressKeys( 'ArrowLeft', { times: 2 } );
} );

await test.step( 'can use the shortcut to open the preview with the keyboard and escape keypress sends focus back to the navigation link block in the submenu', async () => {
Expand Down Expand Up @@ -512,8 +513,9 @@ test.describe( 'Navigation block', () => {
*/
// Exit the toolbar
await page.keyboard.press( 'Escape' );
// Move to the submenu item
await pageUtils.pressKeys( 'ArrowUp', { times: 2 } );
// Move to the submenu item (only one ArrowUp needed - skips the
// submenu wrapper directly to Cat's content)
await page.keyboard.press( 'ArrowUp' );
await page.keyboard.press( 'Home' );

// Check we're on our submenu link
Expand Down Expand Up @@ -575,9 +577,9 @@ test.describe( 'Navigation block', () => {
await navigation.addCustomURL( 'https://wordpress.org' );
await navigation.expectToHaveTextSelected( 'wordpress.org' );

await pageUtils.pressKeys( 'ArrowUp', { times: 2 } );
// One ArrowUp to get to Dog (skips wrapper)
await page.keyboard.press( 'ArrowUp' );
await navigation.checkLabelFocus( 'Dog' );
await pageUtils.pressKeys( 'ArrowUp', { times: 1 } );
await pageUtils.pressKeys( 'access+z' );
await pageUtils.pressKeys( 'ArrowDown' );
await navigation.checkLabelFocus( 'example.com' );
Expand Down
11 changes: 6 additions & 5 deletions test/e2e/specs/editor/blocks/quote.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,10 @@ test.describe( 'Quote', () => {
test( 'and renders only one paragraph for the cite, if the quote is void', async ( {
editor,
page,
pageUtils,
} ) => {
await editor.insertBlock( { name: 'core/quote' } );
await page.keyboard.press( 'ArrowUp' );
await pageUtils.pressKeys( 'primary+a' );
await editor.clickBlockToolbarButton( 'Add citation' );
await page.keyboard.type( 'cite' );
await editor.clickBlockOptionsMenuItem( 'Ungroup' );
Expand All @@ -215,11 +216,11 @@ test.describe( 'Quote', () => {

test( 'and renders a void paragraph if both the cite and quote are void', async ( {
editor,
page,
pageUtils,
} ) => {
await editor.insertBlock( { name: 'core/quote' } );
// Select the quote
await page.keyboard.press( 'ArrowUp' );
await pageUtils.pressKeys( 'primary+a' );
await editor.clickBlockOptionsMenuItem( 'Ungroup' );
expect( await editor.getEditedPostContent() ).toBe( '' );
} );
Expand Down Expand Up @@ -334,7 +335,7 @@ test.describe( 'Quote', () => {
} ) => {
await editor.insertBlock( { name: 'core/quote' } );
await page.keyboard.type( '1' );
await page.keyboard.press( 'ArrowUp' );
await pageUtils.pressKeys( 'primary+a', { times: 2 } );
await editor.clickBlockToolbarButton( 'Add citation' );
await page.keyboard.type( '2' );
expect( await editor.getEditedPostContent() ).toBe(
Expand Down Expand Up @@ -365,7 +366,7 @@ test.describe( 'Quote', () => {
} ) => {
await editor.insertBlock( { name: 'core/quote' } );
await page.keyboard.type( '1' );
await page.keyboard.press( 'ArrowUp' );
await pageUtils.pressKeys( 'primary+a', { times: 2 } );
await editor.clickBlockToolbarButton( 'Add citation' );
await page.keyboard.type( '2' );
await pageUtils.pressKeys( 'Shift+ArrowUp' );
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/specs/editor/plugins/block-variations.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ test.describe( 'Block variations', () => {
await page.getByRole( 'option', { name: 'Large Quote' } ).click();

// Select the quote block.
await page.keyboard.press( 'ArrowUp' );
await pageUtils.pressKeys( 'primary+a' );

await expect(
page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ test.describe( 'Allowed Blocks Setting on InnerBlocks', () => {
test( 'correctly applies dynamic allowed blocks restrictions', async ( {
editor,
page,
pageUtils,
} ) => {
await editor.canvas
.locator( 'role=button[name="Add default block"i]' )
Expand All @@ -139,8 +140,8 @@ test.describe( 'Allowed Blocks Setting on InnerBlocks', () => {

// Insert list block.
await blockListBox.getByRole( 'option', { name: 'List' } ).click();
// Select the list wrapper and then parent block.
await page.keyboard.press( 'ArrowUp' );
// Use primary+a to select up to list, then click parent selector.
await pageUtils.pressKeys( 'primary+a' );
await editor.clickBlockToolbarButton(
'Select parent block: Allowed Blocks Dynamic'
);
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/specs/editor/various/block-switcher.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test.describe( 'Block Switcher', () => {
.getByRole( 'button', { name: 'Add default block' } )
.click();
await page.keyboard.type( '- List content' );
await page.keyboard.press( 'ArrowUp' );
await pageUtils.pressKeys( 'primary+a', { times: 2 } );
await pageUtils.pressKeys( 'alt+F10' );

const blockSwitcher = page
Expand Down Expand Up @@ -64,7 +64,7 @@ test.describe( 'Block Switcher', () => {
.getByRole( 'button', { name: 'Add default block' } )
.click();
await page.keyboard.type( '- List content' );
await page.keyboard.press( 'ArrowUp' );
await pageUtils.pressKeys( 'primary+a', { times: 2 } );
await pageUtils.pressKeys( 'alt+F10' );

const blockSwitcher = page
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/specs/editor/various/splitting-merging.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => {
await page.keyboard.type( 'item 1' );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( 'item 2' );
await pageUtils.pressKeys( 'ArrowUp', { times: 3 } );
await pageUtils.pressKeys( 'ArrowUp', { times: 2 } );
await page.keyboard.press( 'Delete' );

expect( await editor.getBlocks() ).toMatchObject( snap1 );
Expand Down
19 changes: 16 additions & 3 deletions test/e2e/specs/editor/various/toolbar-roving-tabindex.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ test.describe( 'Toolbar roving tabindex', () => {
await ToolbarRovingTabindexUtils.wrapCurrentBlockWithGroup( 'List' );
await ToolbarRovingTabindexUtils.testGroupKeyboardNavigation(
'Block: List',
'List'
'List',
{ hasInnerBlocks: true }
);

// ensures table block toolbar uses roving tabindex
Expand All @@ -88,7 +89,9 @@ test.describe( 'Toolbar roving tabindex', () => {
);
await ToolbarRovingTabindexUtils.wrapCurrentBlockWithGroup( 'Table' );
await ToolbarRovingTabindexUtils.testGroupKeyboardNavigation(
'Block: Table',
// ArrowRight from Group enters the table cell directly,
// not the Table block wrapper.
'Body cell text',
'Table'
);

Expand Down Expand Up @@ -181,9 +184,19 @@ class ToolbarRovingTabindexUtils {
await this.page.click( `role=menuitem[name="Group"]` );
}

async testGroupKeyboardNavigation( currentBlockLabel, currentBlockTitle ) {
async testGroupKeyboardNavigation(
currentBlockLabel,
currentBlockTitle,
{ hasInnerBlocks = false } = {}
) {
await this.expectLabelToHaveFocus( 'Block: Group' );
await this.page.keyboard.press( 'ArrowRight' );
if ( hasInnerBlocks ) {
// ArrowRight enters a nested inner block (e.g. list-item
// inside list). Use primary+a to escalate selection back
// to the expected parent block.
await this.pageUtils.pressKeys( 'primary+a', { times: 2 } );
}
await this.expectLabelToHaveFocus( currentBlockLabel );
await this.pageUtils.pressKeys( 'shift+Tab' );
await this.expectLabelToHaveFocus( 'Select parent block: Group' );
Expand Down
Loading
Loading