Skip to content

Commit

Permalink
List View: Add keyboard shortcut for duplicating blocks (#53559)
Browse files Browse the repository at this point in the history
* List View: Add keyboard shortcut for duplicating blocks

* Fix focus issue after duplicating blocks

* Update existing test to cover duplication shortcut

* Use pressKeys instead

* Focus first duplicated block on success

* Fix tests
  • Loading branch information
andrewserong authored Aug 20, 2023
1 parent 540192a commit 644805a
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { hasBlockSupport } from '@wordpress/blocks';
import {
Button,
__experimentalHStack as HStack,
Expand Down Expand Up @@ -55,13 +56,15 @@ function ListViewBlockSelectButton(
} );
const { isLocked } = useBlockLock( clientId );
const {
canInsertBlockType,
getSelectedBlockClientIds,
getPreviousBlockClientId,
getBlockRootClientId,
getBlockOrder,
getBlocksByClientId,
canRemoveBlocks,
} = useSelect( blockEditorStore );
const { removeBlocks } = useDispatch( blockEditorStore );
const { duplicateBlocks, removeBlocks } = useDispatch( blockEditorStore );
const isMatch = useShortcutEventMatch();
const isSticky = blockInformation?.positionType === 'sticky';
const images = useListViewImages( { clientId, isExpanded } );
Expand All @@ -83,29 +86,48 @@ function ListViewBlockSelectButton(
onDragStart?.( event );
};

// Determine which blocks to update:
// If the current (focused) block is part of the block selection, use the whole selection.
// If the focused block is not part of the block selection, only update the focused block.
function getBlocksToUpdate() {
const selectedBlockClientIds = getSelectedBlockClientIds();
const isUpdatingSelectedBlocks =
selectedBlockClientIds.includes( clientId );
const firstBlockClientId = isUpdatingSelectedBlocks
? selectedBlockClientIds[ 0 ]
: clientId;
const firstBlockRootClientId =
getBlockRootClientId( firstBlockClientId );

const blocksToUpdate = isUpdatingSelectedBlocks
? selectedBlockClientIds
: [ clientId ];

return {
blocksToUpdate,
firstBlockClientId,
firstBlockRootClientId,
selectedBlockClientIds,
};
}

/**
* @param {KeyboardEvent} event
*/
function onKeyDownHandler( event ) {
async function onKeyDownHandler( event ) {
if ( event.keyCode === ENTER || event.keyCode === SPACE ) {
onClick( event );
} else if (
event.keyCode === BACKSPACE ||
event.keyCode === DELETE ||
isMatch( 'core/block-editor/remove', event )
) {
const selectedBlockClientIds = getSelectedBlockClientIds();
const isDeletingSelectedBlocks =
selectedBlockClientIds.includes( clientId );
const firstBlockClientId = isDeletingSelectedBlocks
? selectedBlockClientIds[ 0 ]
: clientId;
const firstBlockRootClientId =
getBlockRootClientId( firstBlockClientId );

const blocksToDelete = isDeletingSelectedBlocks
? selectedBlockClientIds
: [ clientId ];
const {
blocksToUpdate: blocksToDelete,
firstBlockClientId,
firstBlockRootClientId,
selectedBlockClientIds,
} = getBlocksToUpdate();

// Don't update the selection if the blocks cannot be deleted.
if ( ! canRemoveBlocks( blocksToDelete, firstBlockRootClientId ) ) {
Expand All @@ -131,6 +153,36 @@ function ListViewBlockSelectButton(
}

updateFocusAndSelection( blockToFocus, shouldUpdateSelection );
} else if ( isMatch( 'core/block-editor/duplicate', event ) ) {
if ( event.defaultPrevented ) {
return;
}
event.preventDefault();

const { blocksToUpdate, firstBlockRootClientId } =
getBlocksToUpdate();

const canDuplicate = getBlocksByClientId( blocksToUpdate ).every(
( block ) => {
return (
!! block &&
hasBlockSupport( block.name, 'multiple', true ) &&
canInsertBlockType( block.name, firstBlockRootClientId )
);
}
);

if ( canDuplicate ) {
const updatedBlocks = await duplicateBlocks(
blocksToUpdate,
false
);

if ( updatedBlocks?.length ) {
// If blocks have been duplicated, focus the first duplicated block.
updateFocusAndSelection( updatedBlocks[ 0 ], false );
}
}
}
}

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

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

await pageUtils.pressKeys( 'primaryShift+d' );

await expect
.poll(
listViewUtils.getBlocksWithA11yAttributes,
'Duplicating a block should retain selection on existing block, move focus to duplicated block.'
)
.toMatchObject( [
{ name: 'core/group' },
{ name: 'core/columns' },
{ name: 'core/file', selected: true },
{ name: 'core/file', focused: true },
] );

// Move focus to the first file block, and then delete it.
await page.keyboard.press( 'ArrowUp' );
await page.keyboard.press( 'Delete' );
await expect
.poll(
Expand All @@ -483,6 +499,7 @@ test.describe( 'List View', () => {
.toMatchObject( [
{ name: 'core/group' },
{ name: 'core/columns', selected: true, focused: true },
{ name: 'core/file' },
] );

// Expand the current column.
Expand All @@ -504,6 +521,7 @@ test.describe( 'List View', () => {
{ name: 'core/column', focused: true },
],
},
{ name: 'core/file' },
] );

await page.keyboard.press( 'Delete' );
Expand All @@ -525,6 +543,7 @@ test.describe( 'List View', () => {
},
],
},
{ name: 'core/file' },
] );

// Expand the current column.
Expand Down Expand Up @@ -555,6 +574,7 @@ test.describe( 'List View', () => {
},
],
},
{ name: 'core/file' },
] );

// Move focus and select the first block.
Expand All @@ -573,14 +593,17 @@ test.describe( 'List View', () => {
selected: true,
focused: true,
},
{ name: 'core/file' },
] );

// Delete remaining blocks.
// Keyboard shortcut should also work.
await pageUtils.pressKeys( 'access+z' );
await pageUtils.pressKeys( 'access+z' );
await expect
.poll(
listViewUtils.getBlocksWithA11yAttributes,
'Deleting the only block left will create a default block and focus/select it'
'Deleting the only blocks left will create a default block and focus/select it'
)
.toMatchObject( [
{
Expand Down

0 comments on commit 644805a

Please sign in to comment.