Skip to content

Commit

Permalink
Block editor: use React events for shortcuts (portal bubbles & contex…
Browse files Browse the repository at this point in the history
…tual) (#32323)
  • Loading branch information
ellatrix committed Jul 22, 2021
1 parent da2456e commit 48df97f
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 228 deletions.
117 changes: 101 additions & 16 deletions packages/block-editor/src/components/block-tools/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
/**
* External dependencies
*/
import { first, last } from 'lodash';

/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import { useViewportMatch } from '@wordpress/compose';
import { Popover } from '@wordpress/components';
import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts';

/**
* Internal dependencies
Expand All @@ -23,29 +29,108 @@ import { usePopoverScroll } from './use-popover-scroll';
* @param {Object} $0.children The block content and style container.
* @param {Object} $0.__unstableContentRef Ref holding the content scroll container.
*/
export default function BlockTools( { children, __unstableContentRef } ) {
export default function BlockTools( {
children,
__unstableContentRef,
...props
} ) {
const isLargeViewport = useViewportMatch( 'medium' );
const hasFixedToolbar = useSelect(
( select ) => select( blockEditorStore ).getSettings().hasFixedToolbar,
[]
);
const isMatch = useShortcutEventMatch();
const { getSelectedBlockClientIds, getBlockRootClientId } = useSelect(
blockEditorStore
);
const {
duplicateBlocks,
removeBlocks,
insertAfterBlock,
insertBeforeBlock,
clearSelectedBlock,
moveBlocksUp,
moveBlocksDown,
} = useDispatch( blockEditorStore );

function onKeyDown( event ) {
if ( isMatch( 'core/block-editor/move-up', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
const rootClientId = getBlockRootClientId( first( clientIds ) );
moveBlocksUp( clientIds, rootClientId );
}
} else if ( isMatch( 'core/block-editor/move-down', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
const rootClientId = getBlockRootClientId( first( clientIds ) );
moveBlocksDown( clientIds, rootClientId );
}
} else if ( isMatch( 'core/block-editor/duplicate', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
duplicateBlocks( clientIds );
}
} else if ( isMatch( 'core/block-editor/remove', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
removeBlocks( clientIds );
}
} else if ( isMatch( 'core/block-editor/insert-after', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
insertAfterBlock( last( clientIds ) );
}
} else if ( isMatch( 'core/block-editor/insert-before', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
insertBeforeBlock( first( clientIds ) );
}
} else if (
isMatch( 'core/block-editor/delete-multi-selection', event )
) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length > 1 ) {
event.preventDefault();
removeBlocks( clientIds );
}
} else if ( isMatch( 'core/block-editor/unselect', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length > 1 ) {
event.preventDefault();
clearSelectedBlock();
event.target.ownerDocument.defaultView
.getSelection()
.removeAllRanges();
}
}
}

return (
<InsertionPoint __unstableContentRef={ __unstableContentRef }>
{ ( hasFixedToolbar || ! isLargeViewport ) && (
<BlockContextualToolbar isFixed />
) }
{ /* Even if the toolbar is fixed, the block popover is still
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div { ...props } onKeyDown={ onKeyDown }>
<InsertionPoint __unstableContentRef={ __unstableContentRef }>
{ ( hasFixedToolbar || ! isLargeViewport ) && (
<BlockContextualToolbar isFixed />
) }
{ /* Even if the toolbar is fixed, the block popover is still
needed for navigation mode. */ }
<BlockPopover __unstableContentRef={ __unstableContentRef } />
{ /* Used for the inline rich text toolbar. */ }
<Popover.Slot
name="block-toolbar"
ref={ usePopoverScroll( __unstableContentRef ) }
/>
{ children }
{ /* Forward compatibility: a place to render block tools behind the
<BlockPopover __unstableContentRef={ __unstableContentRef } />
{ /* Used for the inline rich text toolbar. */ }
<Popover.Slot
name="block-toolbar"
ref={ usePopoverScroll( __unstableContentRef ) }
/>
{ children }
{ /* Forward compatibility: a place to render block tools behind the
content so it can be tabbed to properly. */ }
</InsertionPoint>
</InsertionPoint>
</div>
);
}
149 changes: 3 additions & 146 deletions packages/block-editor/src/components/keyboard-shortcuts/index.js
Original file line number Diff line number Diff line change
@@ -1,155 +1,12 @@
/**
* External dependencies
*/
import { first, last } from 'lodash';
/**
* WordPress dependencies
*/
import { useEffect, useCallback } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import {
useShortcut,
store as keyboardShortcutsStore,
} from '@wordpress/keyboard-shortcuts';
import { useEffect } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';

function KeyboardShortcuts() {
// Shortcuts Logic
const { clientIds, rootClientId } = useSelect( ( select ) => {
const { getSelectedBlockClientIds, getBlockRootClientId } = select(
blockEditorStore
);
const selectedClientIds = getSelectedBlockClientIds();
const [ firstClientId ] = selectedClientIds;
return {
clientIds: selectedClientIds,
rootClientId: getBlockRootClientId( firstClientId ),
};
}, [] );

const {
duplicateBlocks,
removeBlocks,
insertAfterBlock,
insertBeforeBlock,
clearSelectedBlock,
moveBlocksUp,
moveBlocksDown,
} = useDispatch( blockEditorStore );

// Moves selected block/blocks up
useShortcut(
'core/block-editor/move-up',
useCallback(
( event ) => {
event.preventDefault();
moveBlocksUp( clientIds, rootClientId );
},
[ clientIds, moveBlocksUp ]
),
{ bindGlobal: true, isDisabled: clientIds.length === 0 }
);

// Moves selected block/blocks up
useShortcut(
'core/block-editor/move-down',
useCallback(
( event ) => {
event.preventDefault();
moveBlocksDown( clientIds, rootClientId );
},
[ clientIds, moveBlocksDown ]
),
{ bindGlobal: true, isDisabled: clientIds.length === 0 }
);

// Prevents bookmark all Tabs shortcut in Chrome when devtools are closed.
// Prevents reposition Chrome devtools pane shortcut when devtools are open.
useShortcut(
'core/block-editor/duplicate',
useCallback(
( event ) => {
event.preventDefault();
duplicateBlocks( clientIds );
},
[ clientIds, duplicateBlocks ]
),
{ bindGlobal: true, isDisabled: clientIds.length === 0 }
);

// Does not clash with any known browser/native shortcuts, but preventDefault
// is used to prevent any obscure unknown shortcuts from triggering.
useShortcut(
'core/block-editor/remove',
useCallback(
( event ) => {
event.preventDefault();
removeBlocks( clientIds );
},
[ clientIds, removeBlocks ]
),
{ bindGlobal: true, isDisabled: clientIds.length === 0 }
);

// Does not clash with any known browser/native shortcuts, but preventDefault
// is used to prevent any obscure unknown shortcuts from triggering.
useShortcut(
'core/block-editor/insert-after',
useCallback(
( event ) => {
event.preventDefault();
insertAfterBlock( last( clientIds ) );
},
[ clientIds, insertAfterBlock ]
),
{ bindGlobal: true, isDisabled: clientIds.length === 0 }
);

// Prevent 'view recently closed tabs' in Opera using preventDefault.
useShortcut(
'core/block-editor/insert-before',
useCallback(
( event ) => {
event.preventDefault();
insertBeforeBlock( first( clientIds ) );
},
[ clientIds, insertBeforeBlock ]
),
{ bindGlobal: true, isDisabled: clientIds.length === 0 }
);

useShortcut(
'core/block-editor/delete-multi-selection',
useCallback(
( event ) => {
event.preventDefault();
removeBlocks( clientIds );
},
[ clientIds, removeBlocks ]
),
{ isDisabled: clientIds.length < 2 }
);

useShortcut(
'core/block-editor/unselect',
useCallback(
( event ) => {
event.preventDefault();
clearSelectedBlock();
event.target.ownerDocument.defaultView
.getSelection()
.removeAllRanges();
},
[ clientIds, clearSelectedBlock ]
),
{ isDisabled: clientIds.length < 2 }
);

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export default function SidebarBlockEditor( {
<KeyboardShortcuts.Register />

<SidebarEditorProvider sidebar={ sidebar } settings={ settings }>
<BlockEditorKeyboardShortcuts />
<KeyboardShortcuts
undo={ sidebar.undo }
redo={ sidebar.redo }
Expand Down
1 change: 1 addition & 0 deletions packages/e2e-tests/specs/widgets/editing-widgets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ describe( 'Widgets screen', () => {
await page.keyboard.type( 'Second Paragraph' );

await saveWidgets();
await page.focus( '.block-editor-writing-flow' );

// Delete the last block and save again.
await pressKeyWithModifier( 'access', 'z' );
Expand Down
Loading

0 comments on commit 48df97f

Please sign in to comment.