diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js
index 82a237625e6a77..aac68e2e6faa88 100644
--- a/packages/block-editor/src/components/block-list/block.js
+++ b/packages/block-editor/src/components/block-list/block.js
@@ -48,7 +48,7 @@ import BlockInsertionPoint from './insertion-point';
import IgnoreNestedEvents from '../ignore-nested-events';
import InserterWithShortcuts from '../inserter-with-shortcuts';
import Inserter from '../inserter';
-import HoverArea from './hover-area';
+import useHoveredArea from './hover-area';
import { isInsideRootBlock } from '../../utils/dom';
/**
@@ -79,6 +79,7 @@ function BlockListBlock( {
isParentOfSelectedBlock,
isDraggable,
isSelectionEnabled,
+ isRTL,
className,
name,
isValid,
@@ -105,13 +106,14 @@ function BlockListBlock( {
const wrapper = useRef( null );
useEffect( () => {
blockRef( wrapper.current, clientId );
- // We need to rerender to trigger a rerendering of HoverArea.
- rerender();
}, [] );
// Reference to the block edit node
const blockNodeRef = useRef();
+ // Hovered area of the block
+ const hoverArea = useHoveredArea( wrapper );
+
// Keep track of touchstart to disable hover on iOS
const hadTouchStart = useRef( false );
const onTouchStart = () => {
@@ -333,240 +335,236 @@ function BlockListBlock( {
}
};
+ // Rendering the output
+ const isHovered = isBlockHovered && ! isPartOfMultiSelection;
+ const blockType = getBlockType( name );
+ // translators: %s: Type of block (i.e. Text, Image etc)
+ const blockLabel = sprintf( __( 'Block: %s' ), blockType.title );
+ // The block as rendered in the editor is composed of general block UI
+ // (mover, toolbar, wrapper) and the display of the block content.
+
+ const isUnregisteredBlock = name === getUnregisteredTypeHandlerName();
+
+ // If the block is selected and we're typing the block should not appear.
+ // Empty paragraph blocks should always show up as unselected.
+ const showInserterShortcuts = ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid;
+ const showEmptyBlockSideInserter = ( isSelected || isHovered || isLast ) && isEmptyDefaultBlock && isValid;
+ const shouldAppearSelected =
+ ! isFocusMode &&
+ ! showEmptyBlockSideInserter &&
+ isSelected &&
+ ! isTypingWithinBlock;
+ const shouldAppearHovered =
+ ! isFocusMode &&
+ ! hasFixedToolbar &&
+ isHovered &&
+ ! isEmptyDefaultBlock;
+ // We render block movers and block settings to keep them tabbale even if hidden
+ const shouldRenderMovers =
+ ( isSelected || hoverArea === ( isRTL ? 'right' : 'left' ) ) &&
+ ! showEmptyBlockSideInserter &&
+ ! isPartOfMultiSelection &&
+ ! isTypingWithinBlock;
+ const shouldShowBreadcrumb =
+ ! isFocusMode && isHovered && ! isEmptyDefaultBlock;
+ const shouldShowContextualToolbar =
+ ! hasFixedToolbar &&
+ ! showEmptyBlockSideInserter &&
+ (
+ ( isSelected && ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) ||
+ isFirstMultiSelected
+ );
+ const shouldShowMobileToolbar = shouldAppearSelected;
+
+ // Insertion point can only be made visible if the block is at the
+ // the extent of a multi-selection, or not in a multi-selection.
+ const shouldShowInsertionPoint =
+ ( isPartOfMultiSelection && isFirstMultiSelected ) ||
+ ! isPartOfMultiSelection;
+
+ // The wp-block className is important for editor styles.
+ // Generate the wrapper class names handling the different states of the block.
+ const wrapperClassName = classnames(
+ 'wp-block editor-block-list__block block-editor-block-list__block',
+ {
+ 'has-warning': ! isValid || !! hasError || isUnregisteredBlock,
+ 'is-selected': shouldAppearSelected,
+ 'is-multi-selected': isPartOfMultiSelection,
+ 'is-hovered': shouldAppearHovered,
+ 'is-reusable': isReusableBlock( blockType ),
+ 'is-dragging': isDragging,
+ 'is-typing': isTypingWithinBlock,
+ 'is-focused': isFocusMode && ( isSelected || isParentOfSelectedBlock ),
+ 'is-focus-mode': isFocusMode,
+ },
+ className
+ );
+
+ // Determine whether the block has props to apply to the wrapper.
+ let blockWrapperProps = wrapperProps;
+ if ( blockType.getEditWrapperProps ) {
+ blockWrapperProps = {
+ ...blockWrapperProps,
+ ...blockType.getEditWrapperProps( attributes ),
+ };
+ }
+ const blockElementId = `block-${ clientId }`;
+
+ // We wrap the BlockEdit component in a div that hides it when editing in
+ // HTML mode. This allows us to render all of the ancillary pieces
+ // (InspectorControls, etc.) which are inside `BlockEdit` but not
+ // `BlockHTML`, even in HTML mode.
+ let blockEdit = (
+
+ );
+ if ( mode !== 'visual' ) {
+ blockEdit =
{ blockEdit }
;
+ }
+
+ // Disable reasons:
+ //
+ // jsx-a11y/mouse-events-have-key-events:
+ // - onMouseOver is explicitly handling hover effects
+ //
+ // jsx-a11y/no-static-element-interactions:
+ // - Each block can be selected by clicking on it
+
+ /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
+
return (
-
- { ( { hoverArea } ) => {
- const isHovered = isBlockHovered && ! isPartOfMultiSelection;
- const blockType = getBlockType( name );
- // translators: %s: Type of block (i.e. Text, Image etc)
- const blockLabel = sprintf( __( 'Block: %s' ), blockType.title );
- // The block as rendered in the editor is composed of general block UI
- // (mover, toolbar, wrapper) and the display of the block content.
-
- const isUnregisteredBlock = name === getUnregisteredTypeHandlerName();
-
- // If the block is selected and we're typing the block should not appear.
- // Empty paragraph blocks should always show up as unselected.
- const showInserterShortcuts = ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid;
- const showEmptyBlockSideInserter = ( isSelected || isHovered || isLast ) && isEmptyDefaultBlock && isValid;
- const shouldAppearSelected =
- ! isFocusMode &&
- ! showEmptyBlockSideInserter &&
+
+ { shouldShowInsertionPoint && (
+
+ ) }
+
+ { isFirstMultiSelected && (
+
+ ) }
+
+ { shouldRenderMovers && (
+
+ ) }
+ { shouldShowBreadcrumb && (
+
+ ) }
+ { ( shouldShowContextualToolbar || isForcingContextualToolbar.current ) && (
+
+ ) }
+ {
+ ! shouldShowContextualToolbar &&
isSelected &&
- ! isTypingWithinBlock;
- const shouldAppearHovered =
- ! isFocusMode &&
- ! hasFixedToolbar &&
- isHovered &&
- ! isEmptyDefaultBlock;
- // We render block movers and block settings to keep them tabbale even if hidden
- const shouldRenderMovers =
- ( isSelected || hoverArea === 'left' ) &&
- ! showEmptyBlockSideInserter &&
- ! isPartOfMultiSelection &&
- ! isTypingWithinBlock;
- const shouldShowBreadcrumb =
- ! isFocusMode && isHovered && ! isEmptyDefaultBlock;
- const shouldShowContextualToolbar =
! hasFixedToolbar &&
- ! showEmptyBlockSideInserter &&
- ( ( isSelected &&
- ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) ||
- isFirstMultiSelected );
- const shouldShowMobileToolbar = shouldAppearSelected;
-
- // Insertion point can only be made visible if the block is at the
- // the extent of a multi-selection, or not in a multi-selection.
- const shouldShowInsertionPoint =
- ( isPartOfMultiSelection && isFirstMultiSelected ) ||
- ! isPartOfMultiSelection;
-
- // The wp-block className is important for editor styles.
- // Generate the wrapper class names handling the different states of the block.
- const wrapperClassName = classnames(
- 'wp-block editor-block-list__block block-editor-block-list__block',
- {
- 'has-warning': ! isValid || !! hasError || isUnregisteredBlock,
- 'is-selected': shouldAppearSelected,
- 'is-multi-selected': isPartOfMultiSelection,
- 'is-hovered': shouldAppearHovered,
- 'is-reusable': isReusableBlock( blockType ),
- 'is-dragging': isDragging,
- 'is-typing': isTypingWithinBlock,
- 'is-focused':
- isFocusMode && ( isSelected || isParentOfSelectedBlock ),
- 'is-focus-mode': isFocusMode,
- },
- className
- );
-
- // Determine whether the block has props to apply to the wrapper.
- let blockWrapperProps = wrapperProps;
- if ( blockType.getEditWrapperProps ) {
- blockWrapperProps = {
- ...blockWrapperProps,
- ...blockType.getEditWrapperProps( attributes ),
- };
+ ! isEmptyDefaultBlock && (
+
+ )
}
- const blockElementId = `block-${ clientId }`;
-
- // We wrap the BlockEdit component in a div that hides it when editing in
- // HTML mode. This allows us to render all of the ancillary pieces
- // (InspectorControls, etc.) which are inside `BlockEdit` but not
- // `BlockHTML`, even in HTML mode.
- let blockEdit = (
-
+
+ { isValid && blockEdit }
+ { isValid && mode === 'html' && (
+
+ ) }
+ { ! isValid && [
+ ,
+
+ { getSaveElement( blockType, attributes ) }
+
,
+ ] }
+
+ { shouldShowMobileToolbar && (
+
+ ) }
+ { !! hasError && }
+
+
+ { showInserterShortcuts && (
+
+
- );
- if ( mode !== 'visual' ) {
- blockEdit =
{ blockEdit }
;
- }
-
- // Disable reasons:
- //
- // jsx-a11y/mouse-events-have-key-events:
- // - onMouseOver is explicitly handling hover effects
- //
- // jsx-a11y/no-static-element-interactions:
- // - Each block can be selected by clicking on it
-
- /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
-
- return (
-
- { shouldShowInsertionPoint && (
-
- ) }
-
- { isFirstMultiSelected && (
-
- ) }
-
- { shouldRenderMovers && (
-
- ) }
- { shouldShowBreadcrumb && (
-
- ) }
- { ( shouldShowContextualToolbar ||
- isForcingContextualToolbar.current ) && (
-
- ) }
- { ! shouldShowContextualToolbar &&
- isSelected &&
- ! hasFixedToolbar &&
- ! isEmptyDefaultBlock && (
-
- ) }
-
-
- { isValid && blockEdit }
- { isValid && mode === 'html' && (
-
- ) }
- { ! isValid && [
- ,
-
- { getSaveElement( blockType, attributes ) }
-
,
- ] }
-
- { shouldShowMobileToolbar && (
-
- ) }
- { !! hasError && }
-
-
- { showInserterShortcuts && (
-
-
-
- ) }
- { showEmptyBlockSideInserter && (
-
-
-
- ) }
-
- );
- /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
- } }
-
+
+ ) }
+ { showEmptyBlockSideInserter && (
+
+
+
+ ) }
+
);
+ /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
}
const applyWithSelect = withSelect(
@@ -590,7 +588,7 @@ const applyWithSelect = withSelect(
} = select( 'core/block-editor' );
const block = __unstableGetBlockWithoutInnerBlocks( clientId );
const isSelected = isBlockSelected( clientId );
- const { hasFixedToolbar, focusMode } = getSettings();
+ const { hasFixedToolbar, focusMode, isRTL } = getSettings();
const templateLock = getTemplateLock( rootClientId );
const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true );
const index = getBlockIndex( clientId, rootClientId );
@@ -620,6 +618,7 @@ const applyWithSelect = withSelect(
isFocusMode: focusMode && isLargeViewport,
hasFixedToolbar: hasFixedToolbar && isLargeViewport,
isLast: index === blockOrder.length - 1,
+ isRTL,
// Users of the editor.BlockListBlock filter used to be able to access the block prop
// Ideally these blocks would rely on the clientId prop only.
diff --git a/packages/block-editor/src/components/block-list/hover-area.js b/packages/block-editor/src/components/block-list/hover-area.js
index a79b0bcd9b088b..36664e3c9d797c 100644
--- a/packages/block-editor/src/components/block-list/hover-area.js
+++ b/packages/block-editor/src/components/block-list/hover-area.js
@@ -1,82 +1,41 @@
/**
* WordPress dependencies
*/
-import { Component } from '@wordpress/element';
-import { withSelect } from '@wordpress/data';
+import { useState, useEffect } from '@wordpress/element';
-class HoverArea extends Component {
- constructor() {
- super( ...arguments );
- this.state = {
- hoverArea: null,
- };
- this.onMouseLeave = this.onMouseLeave.bind( this );
- this.onMouseMove = this.onMouseMove.bind( this );
- }
-
- componentWillUnmount() {
- if ( this.props.container ) {
- this.toggleListeners( this.props.container, false );
- }
- }
-
- componentDidMount() {
- if ( this.props.container ) {
- this.toggleListeners( this.props.container );
- }
- }
-
- componentDidUpdate( prevProps ) {
- if ( prevProps.container === this.props.container ) {
- return;
- }
- if ( prevProps.container ) {
- this.toggleListeners( prevProps.container, false );
- }
- if ( this.props.container ) {
- this.toggleListeners( this.props.container, true );
- }
- }
+const useHoveredArea = ( wrapper ) => {
+ const [ hoveredArea, setHoveredArea ] = useState( null );
- toggleListeners( container, shouldListnerToEvents = true ) {
- const method = shouldListnerToEvents ? 'addEventListener' : 'removeEventListener';
- container[ method ]( 'mousemove', this.onMouseMove );
- container[ method ]( 'mouseleave', this.onMouseLeave );
- }
-
- onMouseLeave() {
- if ( this.state.hoverArea ) {
- this.setState( { hoverArea: null } );
- }
- }
+ useEffect( () => {
+ const onMouseLeave = () => {
+ if ( hoveredArea ) {
+ setHoveredArea( null );
+ }
+ };
- onMouseMove( event ) {
- const { isRTL, container } = this.props;
- const { width, left, right } = container.getBoundingClientRect();
+ const onMouseMove = ( event ) => {
+ const { width, left, right } = wrapper.current.getBoundingClientRect();
- let hoverArea = null;
- if ( ( event.clientX - left ) < width / 3 ) {
- hoverArea = isRTL ? 'right' : 'left';
- } else if ( ( right - event.clientX ) < width / 3 ) {
- hoverArea = isRTL ? 'left' : 'right';
- }
+ let newHoveredArea = null;
+ if ( ( event.clientX - left ) < width / 3 ) {
+ newHoveredArea = 'left';
+ } else if ( ( right - event.clientX ) < width / 3 ) {
+ newHoveredArea = 'right';
+ }
- if ( hoverArea !== this.state.hoverArea ) {
- this.setState( { hoverArea } );
- }
- }
+ setHoveredArea( newHoveredArea );
+ };
- render() {
- const { hoverArea } = this.state;
- const { children } = this.props;
+ wrapper.current.addEventListener( 'mousemove', onMouseMove );
+ wrapper.current.addEventListener( 'mouseleave', onMouseLeave );
- return children( { hoverArea } );
- }
-}
+ return () => {
+ wrapper.current.removeEventListener( 'mousemove', onMouseMove );
+ wrapper.current.removeEventListener( 'mouseleave', onMouseLeave );
+ };
+ }, [] );
-export default withSelect( ( select ) => {
- return {
- isRTL: select( 'core/block-editor' ).getSettings().isRTL,
- };
-} )( HoverArea );
+ return hoveredArea;
+};
+export default useHoveredArea;