Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List View: Add keyboard shortcut to select all blocks #54899

Merged
merged 6 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { SPACE, ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes';
import { useSelect, useDispatch } from '@wordpress/data';
import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts';
import { __, sprintf } from '@wordpress/i18n';
import isShallowEqual from '@wordpress/is-shallow-equal';

/**
* Internal dependencies
Expand All @@ -30,6 +31,7 @@ import ListViewExpander from './expander';
import { useBlockLock } from '../block-lock';
import { store as blockEditorStore } from '../../store';
import useListViewImages from './use-list-view-images';
import { useListViewContext } from './context';

function ListViewBlockSelectButton(
{
Expand Down Expand Up @@ -64,10 +66,12 @@ function ListViewBlockSelectButton(
getBlocksByClientId,
canRemoveBlocks,
} = useSelect( blockEditorStore );
const { duplicateBlocks, removeBlocks } = useDispatch( blockEditorStore );
const { duplicateBlocks, multiSelect, removeBlocks } =
useDispatch( blockEditorStore );
const isMatch = useShortcutEventMatch();
const isSticky = blockInformation?.positionType === 'sticky';
const images = useListViewImages( { clientId, isExpanded } );
const { rootClientId } = useListViewContext();

const positionLabel = blockInformation?.positionLabel
? sprintf(
Expand Down Expand Up @@ -183,6 +187,45 @@ function ListViewBlockSelectButton(
updateFocusAndSelection( updatedBlocks[ 0 ], false );
}
}
} else if ( isMatch( 'core/block-editor/select-all', event ) ) {
if ( event.defaultPrevented ) {
return;
}
event.preventDefault();

const { firstBlockRootClientId, selectedBlockClientIds } =
getBlocksToUpdate();
const blockClientIds = getBlockOrder( firstBlockRootClientId );
if ( ! blockClientIds.length ) {
return;
}

// If we have selected all sibling nested blocks, try selecting up a level.
// This is a similar implementation to that used by `useSelectAll`.
// `isShallowEqual` is used for the list view instead of a length check,
// as the array of siblings of the currently focused block may be a different
// set of blocks from the current block selection if the user is focused
// on a different part of the list view from the block selection.
if ( isShallowEqual( selectedBlockClientIds, blockClientIds ) ) {
// Only select up a level if the first block is not the root block.
// This ensures that the block selection can't break out of the root block
// used by the list view, if the list view is only showing a partial hierarchy.
if (
firstBlockRootClientId &&
firstBlockRootClientId !== rootClientId
) {
updateFocusAndSelection( firstBlockRootClientId, true );
return;
}
}

// Select all while passing `null` to skip focusing to the editor canvas,
// and retain focus within the list view.
multiSelect(
blockClientIds[ 0 ],
blockClientIds[ blockClientIds.length - 1 ],
null
);
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/components/list-view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ function ListViewComponent(
insertedBlock,
setInsertedBlock,
treeGridElementRef: elementRef,
rootClientId,
} ),
[
draggedClientIds,
Expand All @@ -233,6 +234,7 @@ function ListViewComponent(
AdditionalBlockContent,
insertedBlock,
setInsertedBlock,
rootClientId,
]
);

Expand Down
112 changes: 111 additions & 1 deletion test/e2e/specs/editor/various/list-view.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ test.describe( 'List View', () => {
).toBeFocused();
} );

test( 'should duplicate, delete, and deselect blocks using keyboard', async ( {
test( 'should select, duplicate, delete, and deselect blocks using keyboard', async ( {
editor,
page,
pageUtils,
Expand Down Expand Up @@ -464,6 +464,116 @@ test.describe( 'List View', () => {
{ name: 'core/file', selected: true, focused: true },
] );

// Move up to columns block, expand, and then move to the first column block.
await page.keyboard.press( 'ArrowUp' );
await page.keyboard.press( 'ArrowRight' );
await page.keyboard.press( 'ArrowDown' );

await expect
.poll(
listViewUtils.getBlocksWithA11yAttributes,
'The last inserted block should be selected, while the first column block should be focused.'
)
.toMatchObject( [
{ name: 'core/group' },
{
name: 'core/columns',
innerBlocks: [
{ name: 'core/column', selected: false, focused: true },
{ name: 'core/column' },
],
},
{ name: 'core/file', selected: true, focused: false },
] );

// Select all sibling column blocks at current level.
await pageUtils.pressKeys( 'primary+a' );
await expect
.poll(
listViewUtils.getBlocksWithA11yAttributes,
'All column blocks should be selected, with the first one focused.'
)
.toMatchObject( [
{ name: 'core/group', selected: false, focused: false },
{
name: 'core/columns',
innerBlocks: [
{ name: 'core/column', selected: true, focused: true },
{ name: 'core/column', selected: true, focused: false },
],
selected: false,
},
{ name: 'core/file', selected: false, focused: false },
] );

// Select next parent (the columns block).
await pageUtils.pressKeys( 'primary+a' );
await expect
.poll(
listViewUtils.getBlocksWithA11yAttributes,
'The columns block should be selected and focused.'
)
.toMatchObject( [
{ name: 'core/group', selected: false, focused: false },
{
name: 'core/columns',
innerBlocks: [
{ name: 'core/column' },
{ name: 'core/column' },
],
selected: true,
focused: true,
},
{ name: 'core/file', selected: false, focused: false },
] );

// Select all siblings at root level.
await pageUtils.pressKeys( 'primary+a' );
await expect
.poll(
listViewUtils.getBlocksWithA11yAttributes,
'All blocks should be selected.'
)
.toMatchObject( [
{ name: 'core/group', selected: true, focused: false },
{
name: 'core/columns',
innerBlocks: [
{ name: 'core/column' },
{ name: 'core/column' },
],
selected: true,
focused: true,
},
{ name: 'core/file', selected: true, focused: false },
] );

// Deselect blocks via Escape key.
await page.keyboard.press( 'Escape' );
// Collapse the columns block.
await page.keyboard.press( 'ArrowLeft' );

await expect
.poll(
listViewUtils.getBlocksWithA11yAttributes,
'All blocks should be deselected, with focus on the Columns block.'
)
.toMatchObject( [
{ name: 'core/group', selected: false, focused: false },
{
name: 'core/columns',
selected: false,
focused: true,
},
{ name: 'core/file', selected: false, focused: false },
] );

// Move focus and selection to the file block to set up for testing duplication.
await listView
.getByRole( 'gridcell', { name: 'File', exact: true } )
.dblclick();

// Test duplication behaviour.
await pageUtils.pressKeys( 'primaryShift+d' );

await expect
Expand Down
Loading