Skip to content

Commit

Permalink
[Navigation screen] Separate block navigator focus from the editor fo…
Browse files Browse the repository at this point in the history
…cus (#22996)

* Separate the navigator selection from the editor area selection

* add __experimental prefix and use list of selected blocks instead of a single id

* Don't attempt to call __experimentalSelectBlock when it's not provided

* Drop onClick and only pass selectBlock to BlockNavigationBlock

* Remove selection CSS

* Stop rendering movers for selected block

* Add "Go to block" navigator menu action

* Fix unit test

* Restore the previous way of focusing items in the navigator

* Lint

* Ensure clicking go to block always works

* Add a README file
  • Loading branch information
adamziel authored Jun 19, 2020
1 parent d10ce4c commit 4b9fc09
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,7 @@ Generator that triggers an action used to duplicate a list of blocks.
_Parameters_

- _clientIds_ `Array<string>`:
- _updateSelection_ `boolean`:

<a name="enterFormattedText" href="#enterFormattedText">#</a> **enterFormattedText**

Expand Down
10 changes: 7 additions & 3 deletions packages/block-editor/src/components/block-actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { hasBlockSupport, switchToBlockType } from '@wordpress/blocks';
*/
import { useNotifyCopy } from '../copy-handler';

export default function BlockActions( { clientIds, children } ) {
export default function BlockActions( {
clientIds,
children,
__experimentalUpdateSelection: updateSelection,
} ) {
const {
canInsertBlockType,
getBlockRootClientId,
Expand Down Expand Up @@ -59,10 +63,10 @@ export default function BlockActions( { clientIds, children } ) {
rootClientId,
blocks,
onDuplicate() {
return duplicateBlocks( clientIds );
return duplicateBlocks( clientIds, updateSelection );
},
onRemove() {
removeBlocks( clientIds );
return removeBlocks( clientIds, updateSelection );
},
onInsertBefore() {
insertBeforeBlock( first( castArray( clientIds ) ) );
Expand Down
46 changes: 38 additions & 8 deletions packages/block-editor/src/components/block-navigation/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import classnames from 'classnames';
import {
__experimentalTreeGridCell as TreeGridCell,
__experimentalTreeGridItem as TreeGridItem,
MenuGroup,
MenuItem,
} from '@wordpress/components';

import { __ } from '@wordpress/i18n';
import { moreVertical } from '@wordpress/icons';
import { useState } from '@wordpress/element';
import { useState, useRef, useEffect } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';

/**
* Internal dependencies
Expand All @@ -29,35 +32,44 @@ import { useBlockNavigationContext } from './context';

export default function BlockNavigationBlock( {
block,
onClick,
isSelected,
onClick,
position,
level,
rowCount,
showBlockMovers,
terminatedLevels,
path,
} ) {
const cellRef = useRef( null );
const [ isHovered, setIsHovered ] = useState( false );
const [ isFocused, setIsFocused ] = useState( false );
const { selectBlock: selectEditorBlock } = useDispatch(
'core/block-editor'
);
const { clientId } = block;

// Subtract 1 from rowCount, as it includes the block appender.
const siblingCount = rowCount - 1;
const hasSiblings = siblingCount > 1;
const hasRenderedMovers = showBlockMovers && hasSiblings;
const hasVisibleMovers = isHovered || isSelected || isFocused;
const hasVisibleMovers = isHovered || isFocused;
const moverCellClassName = classnames(
'block-editor-block-navigation-block__mover-cell',
{ 'is-visible': hasVisibleMovers }
);
const {
__experimentalFeatures: withBlockNavigationBlockSettings,
__experimentalFeatures: withExperimentalFeatures,
} = useBlockNavigationContext();
const blockNavigationBlockSettingsClassName = classnames(
'block-editor-block-navigation-block__menu-cell',
{ 'is-visible': hasVisibleMovers }
);
useEffect( () => {
if ( withExperimentalFeatures && isSelected ) {
cellRef.current.focus();
}
}, [ withExperimentalFeatures, isSelected ] );

return (
<BlockNavigationLeaf
Expand All @@ -76,6 +88,7 @@ export default function BlockNavigationBlock( {
<TreeGridCell
className="block-editor-block-navigation-block__contents-cell"
colSpan={ hasRenderedMovers ? undefined : 2 }
ref={ cellRef }
>
{ ( { ref, tabIndex, onFocus } ) => (
<div className="block-editor-block-navigation-block__contents-container">
Expand All @@ -86,7 +99,7 @@ export default function BlockNavigationBlock( {
/>
<BlockNavigationBlockContents
block={ block }
onClick={ onClick }
onClick={ () => onClick( block.clientId ) }
isSelected={ isSelected }
position={ position }
siblingCount={ siblingCount }
Expand Down Expand Up @@ -130,7 +143,7 @@ export default function BlockNavigationBlock( {
</>
) }

{ withBlockNavigationBlockSettings && (
{ withExperimentalFeatures && (
<TreeGridCell
className={ blockNavigationBlockSettingsClassName }
>
Expand All @@ -144,7 +157,24 @@ export default function BlockNavigationBlock( {
onFocus,
} }
disableOpenOnArrowDown
/>
__experimentalSelectBlock={ onClick }
>
{ ( { onClose } ) => (
<MenuGroup>
<MenuItem
onClick={ async () => {
// If clientId is already selected, it won't be focused (see block-wrapper.js)
// This removes the selection first to ensure the focus will always switch.
await selectEditorBlock( null );
await selectEditorBlock( clientId );
onClose();
} }
>
{ __( 'Go to block' ) }
</MenuItem>
</MenuGroup>
) }
</BlockSettingsDropdown>
) }
</TreeGridCell>
) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default function BlockNavigationBranch( props ) {
<Fragment key={ clientId }>
<BlockNavigationBlock
block={ block }
onClick={ () => selectBlock( clientId ) }
onClick={ selectBlock }
isSelected={ selectedBlockClientId === clientId }
level={ level }
position={ position }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,6 @@ $tree-item-height: 36px;
margin-right: 6px;
}

&.is-selected .block-editor-block-icon svg,
&.is-selected:focus .block-editor-block-icon svg {
color: $white;
background: $dark-gray-primary;
box-shadow: 0 0 0 $border-width $dark-gray-primary;
border-radius: $border-width;
}

.block-editor-block-navigation-block__menu-cell,
.block-editor-block-navigation-block__mover-cell,
.block-editor-block-navigation-block__contents-cell {
Expand Down
13 changes: 13 additions & 0 deletions packages/block-editor/src/components/block-settings-menu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
BlockSettingsMenu
============

This is a menu that allows the user to act on the block (duplicate, remove, etc).

# BlockSettingsDropdown

This is the dropdown itself, it covers the bulk of the logic of this component.

## Props

`__experimentalSelectBlock` - A callback. If passed, interacting with dropdown options (such as duplicate) will not update the
editor selection. Instead, every time a selection change should happen the callback will be called with a proper clientId.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { castArray, flow } from 'lodash';
import { castArray, flow, noop } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -15,6 +15,8 @@ import {
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { moreHorizontal } from '@wordpress/icons';

import { Children, cloneElement, useCallback } from '@wordpress/element';
import { serialize } from '@wordpress/blocks';

/**
Expand All @@ -33,7 +35,12 @@ const POPOVER_PROPS = {
isAlternate: true,
};

export function BlockSettingsDropdown( { clientIds, ...props } ) {
export function BlockSettingsDropdown( {
clientIds,
__experimentalSelectBlock,
children,
...props
} ) {
const blockClientIds = castArray( clientIds );
const count = blockClientIds.length;
const firstBlockClientId = blockClientIds[ 0 ];
Expand All @@ -56,8 +63,23 @@ export function BlockSettingsDropdown( { clientIds, ...props } ) {
};
}, [] );

const updateSelection = useCallback(
__experimentalSelectBlock
? async ( clientIdsPromise ) => {
const ids = await clientIdsPromise;
if ( ids && ids[ 0 ] ) {
__experimentalSelectBlock( ids[ 0 ] );
}
}
: noop,
[ __experimentalSelectBlock ]
);

return (
<BlockActions clientIds={ clientIds }>
<BlockActions
clientIds={ clientIds }
__experimentalUpdateSelection={ ! __experimentalSelectBlock }
>
{ ( {
canDuplicate,
canInsertDefaultBlock,
Expand Down Expand Up @@ -103,7 +125,11 @@ export function BlockSettingsDropdown( { clientIds, ...props } ) {
</ClipboardButton>
{ canDuplicate && (
<MenuItem
onClick={ flow( onClose, onDuplicate ) }
onClick={ flow(
onClose,
onDuplicate,
updateSelection
) }
shortcut={ shortcuts.duplicate }
>
{ __( 'Duplicate' ) }
Expand Down Expand Up @@ -142,10 +168,19 @@ export function BlockSettingsDropdown( { clientIds, ...props } ) {
fillProps={ { onClose } }
clientIds={ clientIds }
/>
{ typeof children === 'function'
? children( { onClose } )
: Children.map( ( child ) =>
cloneElement( child, { onClose } )
) }
<MenuGroup>
{ ! isLocked && (
<MenuItem
onClick={ flow( onClose, onRemove ) }
onClick={ flow(
onClose,
onRemove,
updateSelection
) }
shortcut={ shortcuts.remove }
>
{ _n(
Expand Down
Loading

0 comments on commit 4b9fc09

Please sign in to comment.