-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[ListView] Allow deleting blocks using keyboard #50422
Changes from all commits
e0b8130
d793d18
9431f32
9354a81
4274a5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,10 @@ import { | |
useRef, | ||
} from '@wordpress/element'; | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; | ||
import { | ||
store as keyboardShortcutsStore, | ||
__unstableUseShortcutEventMatch, | ||
} from '@wordpress/keyboard-shortcuts'; | ||
import { pipe, useCopyToClipboard } from '@wordpress/compose'; | ||
|
||
/** | ||
|
@@ -30,7 +33,6 @@ import BlockSettingsMenuControls from '../block-settings-menu-controls'; | |
import { store as blockEditorStore } from '../../store'; | ||
import { useShowMoversGestures } from '../block-toolbar/utils'; | ||
|
||
const noop = () => {}; | ||
const POPOVER_PROPS = { | ||
className: 'block-editor-block-settings-menu__popover', | ||
position: 'bottom right', | ||
|
@@ -63,7 +65,6 @@ export function BlockSettingsDropdown( { | |
onlyBlock, | ||
parentBlockType, | ||
previousBlockClientId, | ||
nextBlockClientId, | ||
selectedBlockClientIds, | ||
} = useSelect( | ||
( select ) => { | ||
|
@@ -72,7 +73,6 @@ export function BlockSettingsDropdown( { | |
getBlockName, | ||
getBlockRootClientId, | ||
getPreviousBlockClientId, | ||
getNextBlockClientId, | ||
getSelectedBlockClientIds, | ||
getSettings, | ||
getBlockAttributes, | ||
|
@@ -98,12 +98,13 @@ export function BlockSettingsDropdown( { | |
getBlockType( parentBlockName ) ), | ||
previousBlockClientId: | ||
getPreviousBlockClientId( firstBlockClientId ), | ||
nextBlockClientId: getNextBlockClientId( firstBlockClientId ), | ||
selectedBlockClientIds: getSelectedBlockClientIds(), | ||
}; | ||
}, | ||
[ firstBlockClientId ] | ||
); | ||
const { getBlockOrder, getSelectedBlockClientIds } = | ||
useSelect( blockEditorStore ); | ||
|
||
const shortcuts = useSelect( ( select ) => { | ||
const { getShortcutRepresentation } = select( keyboardShortcutsStore ); | ||
|
@@ -120,51 +121,47 @@ export function BlockSettingsDropdown( { | |
), | ||
}; | ||
}, [] ); | ||
const isMatch = __unstableUseShortcutEventMatch(); | ||
|
||
const { selectBlock, toggleBlockHighlight } = | ||
useDispatch( blockEditorStore ); | ||
const hasSelectedBlocks = selectedBlockClientIds.length > 0; | ||
|
||
const updateSelectionAfterDuplicate = useCallback( | ||
__experimentalSelectBlock | ||
? async ( clientIdsPromise ) => { | ||
const ids = await clientIdsPromise; | ||
if ( ids && ids[ 0 ] ) { | ||
__experimentalSelectBlock( ids[ 0 ] ); | ||
} | ||
} | ||
: noop, | ||
async ( clientIdsPromise ) => { | ||
if ( __experimentalSelectBlock ) { | ||
const ids = await clientIdsPromise; | ||
if ( ids && ids[ 0 ] ) { | ||
__experimentalSelectBlock( ids[ 0 ], false ); | ||
} | ||
} | ||
}, | ||
[ __experimentalSelectBlock ] | ||
); | ||
|
||
const updateSelectionAfterRemove = useCallback( | ||
__experimentalSelectBlock | ||
? () => { | ||
const blockToSelect = | ||
previousBlockClientId || | ||
nextBlockClientId || | ||
firstParentClientId; | ||
const updateSelectionAfterRemove = useCallback( () => { | ||
if ( __experimentalSelectBlock ) { | ||
let blockToFocus = previousBlockClientId || firstParentClientId; | ||
|
||
if ( | ||
blockToSelect && | ||
// From the block options dropdown, it's possible to remove a block that is not selected, | ||
// in this case, it's not necessary to update the selection since the selected block wasn't removed. | ||
selectedBlockClientIds.includes( firstBlockClientId ) && | ||
// Don't update selection when next/prev block also is in the selection ( and gets removed ), | ||
// In case someone selects all blocks and removes them at once. | ||
! selectedBlockClientIds.includes( blockToSelect ) | ||
) { | ||
__experimentalSelectBlock( blockToSelect ); | ||
} | ||
} | ||
: noop, | ||
[ | ||
__experimentalSelectBlock, | ||
previousBlockClientId, | ||
nextBlockClientId, | ||
firstParentClientId, | ||
selectedBlockClientIds, | ||
] | ||
); | ||
// Focus the first block if there's no previous block nor parent block. | ||
if ( ! blockToFocus ) { | ||
blockToFocus = getBlockOrder()[ 0 ]; | ||
} | ||
|
||
// Only update the selection if the original selection is removed. | ||
const shouldUpdateSelection = | ||
hasSelectedBlocks && getSelectedBlockClientIds().length === 0; | ||
|
||
__experimentalSelectBlock( blockToFocus, shouldUpdateSelection ); | ||
} | ||
}, [ | ||
__experimentalSelectBlock, | ||
previousBlockClientId, | ||
firstParentClientId, | ||
getBlockOrder, | ||
hasSelectedBlocks, | ||
getSelectedBlockClientIds, | ||
] ); | ||
|
||
const removeBlockLabel = | ||
count === 1 ? __( 'Delete' ) : __( 'Delete blocks' ); | ||
|
@@ -212,6 +209,49 @@ export function BlockSettingsDropdown( { | |
className="block-editor-block-settings-menu" | ||
popoverProps={ POPOVER_PROPS } | ||
noIcons | ||
menuProps={ { | ||
/** | ||
* @param {KeyboardEvent} event | ||
*/ | ||
onKeyDown( event ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the only reason for this callback to keep the focus in the list view after deleting/duplicating/inserting a block? Or is there another reason? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's to allow pressing keyboard shortcuts when the focus is in the menu dropdown. I couldn't find any other way to do this without having to duplicate a lot of the code 😢 . |
||
if ( event.defaultPrevented ) return; | ||
|
||
if ( | ||
isMatch( 'core/block-editor/remove', event ) && | ||
canRemove | ||
) { | ||
event.preventDefault(); | ||
updateSelectionAfterRemove( onRemove() ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops. I think I was trying to follow the same style of |
||
} else if ( | ||
isMatch( | ||
'core/block-editor/duplicate', | ||
event | ||
) && | ||
canDuplicate | ||
) { | ||
event.preventDefault(); | ||
updateSelectionAfterDuplicate( onDuplicate() ); | ||
} else if ( | ||
isMatch( | ||
'core/block-editor/insert-after', | ||
event | ||
) && | ||
canInsertDefaultBlock | ||
) { | ||
event.preventDefault(); | ||
onInsertAfter(); | ||
} else if ( | ||
isMatch( | ||
'core/block-editor/insert-before', | ||
event | ||
) && | ||
canInsertDefaultBlock | ||
) { | ||
event.preventDefault(); | ||
onInsertBefore(); | ||
} | ||
}, | ||
} } | ||
{ ...props } | ||
> | ||
{ ( { onClose } ) => ( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still undecided if we should also rename this to
__experimentalFocusAndSelectBlock
. Even though it's an experimental API, for some reason it's still documented in the README 😅 .There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting! I didn't realise it was documented, either. Perhaps we could keep the name the same, but update the docs to mention the additional param?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking maybe just delete the docs for this prop? It's an "experimental" prop anyway? 😅 Not sure about the impact though 🤔 .