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 for duplicating blocks #53559

Merged
merged 6 commits into from Aug 20, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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