@wordpress/block-editor
Version:
247 lines (233 loc) • 6.67 kB
JavaScript
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import {
__experimentalTreeGridCell as TreeGridCell,
__experimentalTreeGridItem as TreeGridItem,
MenuGroup,
MenuItem,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { moreVertical } from '@wordpress/icons';
import { useState, useRef, useEffect } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import BlockNavigationLeaf from './leaf';
import {
BlockMoverUpButton,
BlockMoverDownButton,
} from '../block-mover/button';
import BlockNavigationBlockContents from './block-contents';
import BlockSettingsDropdown from '../block-settings-menu/block-settings-dropdown';
import { useBlockNavigationContext } from './context';
import { store as blockEditorStore } from '../../store';
export default function BlockNavigationBlock( {
block,
isSelected,
isBranchSelected,
isLastOfSelectedBranch,
onClick,
position,
level,
rowCount,
siblingBlockCount,
showBlockMovers,
path,
} ) {
const cellRef = useRef( null );
const [ isHovered, setIsHovered ] = useState( false );
const { clientId } = block;
const { isDragging, blockParents } = useSelect(
( select ) => {
const {
isBlockBeingDragged,
isAncestorBeingDragged,
getBlockParents,
} = select( blockEditorStore );
return {
isDragging:
isBlockBeingDragged( clientId ) ||
isAncestorBeingDragged( clientId ),
blockParents: getBlockParents( clientId ),
};
},
[ clientId ]
);
const {
selectBlock: selectEditorBlock,
toggleBlockHighlight,
} = useDispatch( blockEditorStore );
const hasSiblings = siblingBlockCount > 0;
const hasRenderedMovers = showBlockMovers && hasSiblings;
const moverCellClassName = classnames(
'block-editor-block-navigation-block__mover-cell',
{ 'is-visible': isHovered }
);
const {
__experimentalFeatures: withExperimentalFeatures,
__experimentalPersistentListViewFeatures: withExperimentalPersistentListViewFeatures,
isTreeGridMounted,
} = useBlockNavigationContext();
const blockNavigationBlockSettingsClassName = classnames(
'block-editor-block-navigation-block__menu-cell',
{ 'is-visible': isHovered }
);
// If BlockNavigation has experimental features related to the Persistent List View,
// only focus the selected list item on mount; otherwise the list would always
// try to steal the focus from the editor canvas.
useEffect( () => {
if (
withExperimentalPersistentListViewFeatures &&
! isTreeGridMounted &&
isSelected
) {
cellRef.current.focus();
}
}, [] );
// If BlockNavigation has experimental features (such as drag and drop) enabled,
// leave the focus handling as it was before, to avoid accidental regressions.
useEffect( () => {
if ( withExperimentalFeatures && isSelected ) {
cellRef.current.focus();
}
}, [ withExperimentalFeatures, isSelected ] );
const highlightBlock = withExperimentalPersistentListViewFeatures
? toggleBlockHighlight
: () => {};
const onMouseEnter = () => {
setIsHovered( true );
highlightBlock( clientId, true );
};
const onMouseLeave = () => {
setIsHovered( false );
highlightBlock( clientId, false );
};
const classes = classnames( {
'is-selected': isSelected,
'is-branch-selected':
withExperimentalPersistentListViewFeatures && isBranchSelected,
'is-last-of-selected-branch':
withExperimentalPersistentListViewFeatures &&
isLastOfSelectedBranch,
'is-dragging': isDragging,
} );
return (
<BlockNavigationLeaf
className={ classes }
onMouseEnter={ onMouseEnter }
onMouseLeave={ onMouseLeave }
onFocus={ onMouseEnter }
onBlur={ onMouseLeave }
level={ level }
position={ position }
rowCount={ rowCount }
path={ path }
id={ `block-navigation-block-${ clientId }` }
data-block={ clientId }
>
<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">
<BlockNavigationBlockContents
block={ block }
onClick={ () => onClick( block.clientId ) }
isSelected={ isSelected }
position={ position }
siblingBlockCount={ siblingBlockCount }
level={ level }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
</div>
) }
</TreeGridCell>
{ hasRenderedMovers && (
<>
<TreeGridCell
className={ moverCellClassName }
withoutGridItem
>
<TreeGridItem>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockMoverUpButton
orientation="vertical"
clientIds={ [ clientId ] }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
) }
</TreeGridItem>
<TreeGridItem>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockMoverDownButton
orientation="vertical"
clientIds={ [ clientId ] }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
) }
</TreeGridItem>
</TreeGridCell>
</>
) }
{ withExperimentalFeatures && (
<TreeGridCell
className={ blockNavigationBlockSettingsClassName }
>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockSettingsDropdown
clientIds={ [ clientId ] }
icon={ moreVertical }
toggleProps={ {
ref,
tabIndex,
onFocus,
} }
disableOpenOnArrowDown
__experimentalSelectBlock={ onClick }
>
{ ( { onClose } ) => (
<MenuGroup>
<MenuItem
onClick={ async () => {
if ( blockParents.length ) {
// If the block to select is inside a dropdown, we need to open the dropdown.
// Otherwise focus won't transfer to the block.
for ( const parent of blockParents ) {
await selectEditorBlock(
parent
);
}
} else {
// 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>
) }
</BlockNavigationLeaf>
);
}