From 48df97fb78346d7b4b372f7f1d500f775b08232a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Thu, 22 Jul 2021 19:14:23 +0300 Subject: [PATCH] Block editor: use React events for shortcuts (portal bubbles & contextual) (#32323) --- .../src/components/block-tools/index.js | 117 ++++++++++++-- .../components/keyboard-shortcuts/index.js | 149 +----------------- .../components/sidebar-block-editor/index.js | 1 - .../specs/widgets/editing-widgets.test.js | 1 + .../src/components/visual-editor/index.js | 93 ++++++----- .../src/components/block-editor/index.js | 2 - .../index.js | 2 - .../visual-editor-shortcuts.js | 8 +- packages/keycodes/src/index.js | 18 ++- packages/keycodes/src/test/index.js | 9 +- storybook/stories/playground/index.js | 1 - 11 files changed, 173 insertions(+), 228 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index 04302c68e173f6..4b81040dfa1a60 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -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 @@ -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 ( - - { ( hasFixedToolbar || ! isLargeViewport ) && ( - - ) } - { /* Even if the toolbar is fixed, the block popover is still + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
+ + { ( hasFixedToolbar || ! isLargeViewport ) && ( + + ) } + { /* Even if the toolbar is fixed, the block popover is still needed for navigation mode. */ } - - { /* Used for the inline rich text toolbar. */ } - - { children } - { /* Forward compatibility: a place to render block tools behind the + + { /* Used for the inline rich text toolbar. */ } + + { children } + { /* Forward compatibility: a place to render block tools behind the content so it can be tabbed to properly. */ } - + +
); } diff --git a/packages/block-editor/src/components/keyboard-shortcuts/index.js b/packages/block-editor/src/components/keyboard-shortcuts/index.js index 372c99e418fd4a..ee13d6018d6c2b 100644 --- a/packages/block-editor/src/components/keyboard-shortcuts/index.js +++ b/packages/block-editor/src/components/keyboard-shortcuts/index.js @@ -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; } diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/index.js b/packages/customize-widgets/src/components/sidebar-block-editor/index.js index 34e38c01e86c44..911b24c119e767 100644 --- a/packages/customize-widgets/src/components/sidebar-block-editor/index.js +++ b/packages/customize-widgets/src/components/sidebar-block-editor/index.js @@ -100,7 +100,6 @@ export default function SidebarBlockEditor( { - { 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' ); diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index 272e8ed5348a99..11280543f9d640 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -192,66 +192,65 @@ export default function VisualEditor( { styles } ) { }, [ isTemplateMode, themeSupportsLayout, contentSize, wideSize ] ); return ( -
- + + { isTemplateMode && ( + + ) } - { isTemplateMode && ( - - ) } - - - { themeSupportsLayout && ! isTemplateMode && ( - - ) } - { ! isTemplateMode && ( -
- -
- ) } - - - -
-
+ { themeSupportsLayout && ! isTemplateMode && ( + + ) } + { ! isTemplateMode && ( +
+ +
+ ) } + + + +
-
+ <__unstableBlockSettingsMenuFirstItem> { ( { onClose } ) => ( ) } -
+ ); } diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index 104e20a80982ca..7313ba2bd15309 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -6,7 +6,6 @@ import { useCallback, useRef } from '@wordpress/element'; import { useEntityBlockEditor } from '@wordpress/core-data'; import { BlockEditorProvider, - BlockEditorKeyboardShortcuts, __experimentalLinkControl, BlockInspector, BlockList, @@ -71,7 +70,6 @@ export default function BlockEditor( { setIsInserterOpen } ) { onChange={ onChange } useSubRegistry={ false } > - <__experimentalLinkControl.ViewerFill> { useCallback( diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js index a9c74f195dee04..897958c9e2fbcf 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js @@ -4,7 +4,6 @@ import { BlockList, BlockTools, - BlockEditorKeyboardShortcuts, BlockSelectionClearer, WritingFlow, ObserveTyping, @@ -38,7 +37,6 @@ export default function WidgetAreasBlockEditorContent( { - diff --git a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js index 87f799ba10e10f..1d406b54f47022 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js @@ -3,7 +3,6 @@ */ import { useShortcut } from '@wordpress/keyboard-shortcuts'; import { useDispatch } from '@wordpress/data'; -import { BlockEditorKeyboardShortcuts } from '@wordpress/block-editor'; /** * Internal dependencies @@ -32,12 +31,7 @@ function VisualEditorGlobalKeyboardShortcuts() { { bindGlobal: true } ); - return ( - <> - - - - ); + return ; } export default VisualEditorGlobalKeyboardShortcuts; diff --git a/packages/keycodes/src/index.js b/packages/keycodes/src/index.js index 0afadecb07705a..6390a01f5e4dbc 100644 --- a/packages/keycodes/src/index.js +++ b/packages/keycodes/src/index.js @@ -26,7 +26,7 @@ import { isAppleOS } from './platform'; /** @typedef {typeof ALT | CTRL | COMMAND | SHIFT } WPModifierPart */ -/** @typedef {'primary' | 'primaryShift' | 'primaryAlt' | 'secondary' | 'access' | 'ctrl' | 'alt' | 'ctrlShift' | 'shift' | 'shiftAlt'} WPKeycodeModifier */ +/** @typedef {'primary' | 'primaryShift' | 'primaryAlt' | 'secondary' | 'access' | 'ctrl' | 'alt' | 'ctrlShift' | 'shift' | 'shiftAlt' | 'undefined'} WPKeycodeModifier */ /** * An object of handler functions for each of the possible modifier @@ -144,6 +144,7 @@ export const modifiers = { ctrlShift: () => [ CTRL, SHIFT ], shift: () => [ SHIFT ], shiftAlt: () => [ SHIFT, ALT ], + undefined: () => [], }; /** @@ -325,10 +326,21 @@ export const isKeyboardEvent = mapValues( modifiers, ( getModifiers ) => { return false; } + let key = event.key.toLowerCase(); + if ( ! character ) { - return includes( mods, event.key.toLowerCase() ); + return includes( mods, key ); + } + + if ( event.altKey ) { + key = String.fromCharCode( event.keyCode ).toLowerCase(); + } + + // For backwards compatibility. + if ( character === 'del' ) { + character = 'delete'; } - return event.key === character; + return key === character; }; } ); diff --git a/packages/keycodes/src/test/index.js b/packages/keycodes/src/test/index.js index 2aaa15be2f45b5..924d48070ea076 100644 --- a/packages/keycodes/src/test/index.js +++ b/packages/keycodes/src/test/index.js @@ -277,10 +277,13 @@ describe( 'isKeyboardEvent', () => { } } ); - function keyPress( target, modifiers = {} ) { + function keyPress( target, modifiers ) { [ 'keydown', 'keypress', 'keyup' ].forEach( ( eventName ) => { - const event = new window.Event( eventName, { bubbles: true } ); - Object.assign( event, modifiers ); + const event = new window.KeyboardEvent( eventName, { + ...modifiers, + bubbles: true, + keyCode: modifiers.key.charCodeAt( 0 ), + } ); target.dispatchEvent( event ); } ); } diff --git a/storybook/stories/playground/index.js b/storybook/stories/playground/index.js index 171034d1161e4b..6a9d3b528a3652 100644 --- a/storybook/stories/playground/index.js +++ b/storybook/stories/playground/index.js @@ -42,7 +42,6 @@ function App() {
-