-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Escape on Block Toolbar returns focus to Editor Canvas #55712
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
Changes from all commits
8123cc4
3f5ceb3
e50fd49
5718fdf
dfc5f6d
7eece08
ad87967
8558e69
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 |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ import classnames from 'classnames'; | |
| */ | ||
| import { __ } from '@wordpress/i18n'; | ||
| import { | ||
| forwardRef, | ||
| useLayoutEffect, | ||
| useEffect, | ||
| useRef, | ||
|
|
@@ -31,7 +32,10 @@ import BlockToolbar from '../block-toolbar'; | |
| import { store as blockEditorStore } from '../../store'; | ||
| import { useHasAnyBlockControls } from '../block-controls/use-has-block-controls'; | ||
|
|
||
| function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { | ||
| function UnforwardBlockContextualToolbar( | ||
|
Contributor
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. This name confuses me |
||
| { focusOnMount, isFixed, ...props }, | ||
| ref | ||
| ) { | ||
| // When the toolbar is fixed it can be collapsed | ||
| const [ isCollapsed, setIsCollapsed ] = useState( false ); | ||
| const toolbarButtonRef = useRef(); | ||
|
|
@@ -184,7 +188,9 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { | |
|
|
||
| return ( | ||
| <NavigableToolbar | ||
| ref={ ref } | ||
| focusOnMount={ focusOnMount } | ||
| focusEditorOnEscape | ||
| className={ classes } | ||
| /* translators: accessibility text for the block toolbar */ | ||
| aria-label={ __( 'Block tools' ) } | ||
|
|
@@ -220,4 +226,4 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { | |
| ); | ||
| } | ||
|
|
||
| export default BlockContextualToolbar; | ||
| export default forwardRef( UnforwardBlockContextualToolbar ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,274 @@ | ||
| /** | ||
| * External dependencies | ||
| */ | ||
| import classnames from 'classnames'; | ||
|
|
||
| /** | ||
| * WordPress dependencies | ||
| */ | ||
| import { forwardRef, useRef, useEffect } from '@wordpress/element'; | ||
| import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; | ||
| import { useDispatch, useSelect } from '@wordpress/data'; | ||
| import { useShortcut } from '@wordpress/keyboard-shortcuts'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import BlockSelectionButton from './block-selection-button'; | ||
| import BlockContextualToolbar from './block-contextual-toolbar'; | ||
| import { store as blockEditorStore } from '../../store'; | ||
| import BlockPopover from '../block-popover'; | ||
| import useBlockToolbarPopoverProps from './use-block-toolbar-popover-props'; | ||
| import Inserter from '../inserter'; | ||
| import { useShouldContextualToolbarShow } from '../../utils/use-should-contextual-toolbar-show'; | ||
|
|
||
| function selector( select ) { | ||
| const { | ||
| __unstableGetEditorMode, | ||
| hasMultiSelection, | ||
| isTyping, | ||
| getLastMultiSelectedBlockClientId, | ||
| } = select( blockEditorStore ); | ||
|
|
||
| return { | ||
| editorMode: __unstableGetEditorMode(), | ||
| hasMultiSelection: hasMultiSelection(), | ||
| isTyping: isTyping(), | ||
| lastClientId: hasMultiSelection() | ||
| ? getLastMultiSelectedBlockClientId() | ||
| : null, | ||
| }; | ||
| } | ||
|
|
||
| function UnforwardSelectedBlockPopover( | ||
| { | ||
| clientId, | ||
| rootClientId, | ||
| isEmptyDefaultBlock, | ||
| capturingClientId, | ||
| __unstablePopoverSlot, | ||
| __unstableContentRef, | ||
| }, | ||
| ref | ||
| ) { | ||
| const { editorMode, hasMultiSelection, isTyping, lastClientId } = useSelect( | ||
| selector, | ||
| [] | ||
| ); | ||
|
|
||
| const isInsertionPointVisible = useSelect( | ||
| ( select ) => { | ||
| const { | ||
| isBlockInsertionPointVisible, | ||
| getBlockInsertionPoint, | ||
| getBlockOrder, | ||
| } = select( blockEditorStore ); | ||
|
|
||
| if ( ! isBlockInsertionPointVisible() ) { | ||
| return false; | ||
| } | ||
|
|
||
| const insertionPoint = getBlockInsertionPoint(); | ||
| const order = getBlockOrder( insertionPoint.rootClientId ); | ||
| return order[ insertionPoint.index ] === clientId; | ||
| }, | ||
| [ clientId ] | ||
| ); | ||
| const isToolbarForced = useRef( false ); | ||
| const { shouldShowContextualToolbar, canFocusHiddenToolbar } = | ||
| useShouldContextualToolbarShow(); | ||
|
|
||
| const { stopTyping } = useDispatch( blockEditorStore ); | ||
|
|
||
| const showEmptyBlockSideInserter = | ||
| ! isTyping && editorMode === 'edit' && isEmptyDefaultBlock; | ||
| const shouldShowBreadcrumb = | ||
| ! hasMultiSelection && | ||
| ( editorMode === 'navigation' || editorMode === 'zoom-out' ); | ||
|
|
||
| useShortcut( | ||
| 'core/block-editor/focus-toolbar', | ||
| () => { | ||
| isToolbarForced.current = true; | ||
| stopTyping( true ); | ||
| }, | ||
| { | ||
| isDisabled: ! canFocusHiddenToolbar, | ||
| } | ||
| ); | ||
|
|
||
| useEffect( () => { | ||
| isToolbarForced.current = false; | ||
| } ); | ||
|
|
||
| // Stores the active toolbar item index so the block toolbar can return focus | ||
| // to it when re-mounting. | ||
| const initialToolbarItemIndexRef = useRef(); | ||
|
|
||
| useEffect( () => { | ||
| // Resets the index whenever the active block changes so this is not | ||
| // persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169 | ||
| initialToolbarItemIndexRef.current = undefined; | ||
| }, [ clientId ] ); | ||
|
|
||
| const popoverProps = useBlockToolbarPopoverProps( { | ||
| contentElement: __unstableContentRef?.current, | ||
| clientId, | ||
| } ); | ||
|
|
||
| if ( showEmptyBlockSideInserter ) { | ||
| return ( | ||
| <BlockPopover | ||
| clientId={ capturingClientId || clientId } | ||
| __unstableCoverTarget | ||
| bottomClientId={ lastClientId } | ||
| className={ classnames( | ||
| 'block-editor-block-list__block-side-inserter-popover', | ||
| { | ||
| 'is-insertion-point-visible': isInsertionPointVisible, | ||
| } | ||
| ) } | ||
| __unstablePopoverSlot={ __unstablePopoverSlot } | ||
| __unstableContentRef={ __unstableContentRef } | ||
| resize={ false } | ||
| shift={ false } | ||
| { ...popoverProps } | ||
| > | ||
| <div className="block-editor-block-list__empty-block-inserter"> | ||
| <Inserter | ||
| position="bottom right" | ||
| rootClientId={ rootClientId } | ||
| clientId={ clientId } | ||
| __experimentalIsQuick | ||
| /> | ||
| </div> | ||
| </BlockPopover> | ||
| ); | ||
| } | ||
|
|
||
| if ( shouldShowBreadcrumb || shouldShowContextualToolbar ) { | ||
| return ( | ||
| <BlockPopover | ||
| clientId={ capturingClientId || clientId } | ||
| bottomClientId={ lastClientId } | ||
| className={ classnames( | ||
| 'block-editor-block-list__block-popover', | ||
| { | ||
| 'is-insertion-point-visible': isInsertionPointVisible, | ||
| } | ||
| ) } | ||
| __unstablePopoverSlot={ __unstablePopoverSlot } | ||
| __unstableContentRef={ __unstableContentRef } | ||
| resize={ false } | ||
| { ...popoverProps } | ||
| > | ||
| { shouldShowContextualToolbar && ( | ||
| <BlockContextualToolbar | ||
| ref={ ref } | ||
| // If the toolbar is being shown because of being forced | ||
| // it should focus the toolbar right after the mount. | ||
| focusOnMount={ isToolbarForced.current } | ||
| __experimentalInitialIndex={ | ||
| initialToolbarItemIndexRef.current | ||
| } | ||
| __experimentalOnIndexChange={ ( index ) => { | ||
| initialToolbarItemIndexRef.current = index; | ||
| } } | ||
| // Resets the index whenever the active block changes so | ||
| // this is not persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169 | ||
| key={ clientId } | ||
| /> | ||
| ) } | ||
| { shouldShowBreadcrumb && ( | ||
| <BlockSelectionButton | ||
| clientId={ clientId } | ||
| rootClientId={ rootClientId } | ||
| /> | ||
| ) } | ||
| </BlockPopover> | ||
| ); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| const SelectedBlockPopover = forwardRef( UnforwardSelectedBlockPopover ); | ||
|
Contributor
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. Same comment, I think adding an -ed suffix makes it better :) |
||
|
|
||
| function wrapperSelector( select ) { | ||
| const { | ||
| getSelectedBlockClientId, | ||
| getFirstMultiSelectedBlockClientId, | ||
| getBlockRootClientId, | ||
| getBlock, | ||
| getBlockParents, | ||
| __experimentalGetBlockListSettingsForBlocks, | ||
| } = select( blockEditorStore ); | ||
|
|
||
| const clientId = | ||
| getSelectedBlockClientId() || getFirstMultiSelectedBlockClientId(); | ||
|
|
||
| if ( ! clientId ) { | ||
| return; | ||
| } | ||
|
|
||
| const { name, attributes = {} } = getBlock( clientId ) || {}; | ||
| const blockParentsClientIds = getBlockParents( clientId ); | ||
|
|
||
| // Get Block List Settings for all ancestors of the current Block clientId. | ||
| const parentBlockListSettings = __experimentalGetBlockListSettingsForBlocks( | ||
| blockParentsClientIds | ||
| ); | ||
|
|
||
| // Get the clientId of the topmost parent with the capture toolbars setting. | ||
| const capturingClientId = blockParentsClientIds.find( | ||
| ( parentClientId ) => | ||
| parentBlockListSettings[ parentClientId ] | ||
| ?.__experimentalCaptureToolbars | ||
| ); | ||
|
|
||
| return { | ||
| clientId, | ||
| rootClientId: getBlockRootClientId( clientId ), | ||
| name, | ||
| isEmptyDefaultBlock: | ||
| name && isUnmodifiedDefaultBlock( { name, attributes } ), | ||
| capturingClientId, | ||
| }; | ||
| } | ||
|
|
||
| function UnforwardWrappedBlockPopover( | ||
| { __unstablePopoverSlot, __unstableContentRef }, | ||
| ref | ||
| ) { | ||
| const selected = useSelect( wrapperSelector, [] ); | ||
|
|
||
| if ( ! selected ) { | ||
| return null; | ||
| } | ||
|
|
||
| const { | ||
| clientId, | ||
| rootClientId, | ||
| name, | ||
| isEmptyDefaultBlock, | ||
| capturingClientId, | ||
| } = selected; | ||
|
|
||
| if ( ! name ) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <SelectedBlockPopover | ||
| ref={ ref } | ||
| clientId={ clientId } | ||
| rootClientId={ rootClientId } | ||
| isEmptyDefaultBlock={ isEmptyDefaultBlock } | ||
| capturingClientId={ capturingClientId } | ||
| __unstablePopoverSlot={ __unstablePopoverSlot } | ||
| __unstableContentRef={ __unstableContentRef } | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| export default forwardRef( UnforwardWrappedBlockPopover ); | ||
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.
Can we avoid this being a public API please. (and the action as well)
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.
✅ #57557