From 89c2b444079efa1b63e1ead407072f8dbd1216a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Thu, 23 Jan 2020 12:20:22 +0100 Subject: [PATCH] Block: use context to provide selected element (#19782) * Block: use context to provide selected element * Include multi selection nodes * Add comment --- .../components/block-list/block-popover.js | 34 +++++++++++++------ .../src/components/block-list/block.js | 26 +++++++++----- .../components/block-list/root-container.js | 27 ++++++++------- packages/block-editor/src/store/actions.js | 12 ------- packages/block-editor/src/store/reducer.js | 20 ----------- packages/block-editor/src/store/selectors.js | 11 ------ packages/components/src/popover/index.js | 22 ++++++++++-- packages/components/src/popover/utils.js | 20 ++--------- 8 files changed, 79 insertions(+), 93 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-popover.js b/packages/block-editor/src/components/block-list/block-popover.js index cb563c3319f21..2919019d849a9 100644 --- a/packages/block-editor/src/components/block-list/block-popover.js +++ b/packages/block-editor/src/components/block-list/block-popover.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useState, useCallback } from '@wordpress/element'; +import { useState, useCallback, useContext } from '@wordpress/element'; import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; import { Popover } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; @@ -20,6 +20,7 @@ import { useViewportMatch } from '@wordpress/compose'; import BlockBreadcrumb from './breadcrumb'; import BlockContextualToolbar from './block-contextual-toolbar'; import Inserter from '../inserter'; +import { BlockNodes } from './root-container'; function selector( select ) { const { @@ -29,6 +30,7 @@ function selector( select ) { isTyping, isCaretWithinFormattedText, getSettings, + getLastMultiSelectedBlockClientId, } = select( 'core/block-editor' ); return { isNavigationMode: isNavigationMode(), @@ -37,6 +39,7 @@ function selector( select ) { isCaretWithinFormattedText: isCaretWithinFormattedText(), hasMultiSelection: hasMultiSelection(), hasFixedToolbar: getSettings().hasFixedToolbar, + lastClientId: getLastMultiSelectedBlockClientId(), }; } @@ -57,10 +60,12 @@ function BlockPopover( { isCaretWithinFormattedText, hasMultiSelection, hasFixedToolbar, + lastClientId, } = useSelect( selector, [] ); const isLargeViewport = useViewportMatch( 'medium' ); const [ isToolbarForced, setIsToolbarForced ] = useState( false ); const [ isInserterShown, setIsInserterShown ] = useState( false ); + const [ blockNodes ] = useContext( BlockNodes ); const showEmptyBlockSideInserter = ! isNavigationMode && isEmptyDefaultBlock && isValid; const shouldShowBreadcrumb = isNavigationMode; @@ -92,7 +97,11 @@ function BlockPopover( { return null; } - let node = document.getElementById( 'block-' + capturingClientId ); + let node = blockNodes[ clientId ]; + + if ( capturingClientId ) { + node = document.getElementById( 'block-' + capturingClientId ); + } if ( ! node ) { return null; @@ -103,6 +112,15 @@ function BlockPopover( { node = node.querySelector( '.is-block-content' ) || node; } + let anchorRef = node; + + if ( hasMultiSelection ) { + anchorRef = { + top: blockNodes[ clientId ], + bottom: blockNodes[ lastClientId ], + }; + } + function onFocus() { setIsInserterShown( true ); } @@ -116,7 +134,6 @@ function BlockPopover( { // position in the right corner. // To do: refactor `Popover` to make this prop clearer. const popoverPosition = showEmptyBlockSideInserter ? 'top left right' : 'top right left'; - const popoverIsSticky = hasMultiSelection ? '.wp-block.is-multi-selected' : true; return ( bottom const topmostAncestorWithCaptureDescendantsToolbarsIndex = findIndex( ancestorBlockListSettings, [ '__experimentalCaptureToolbars', true ] ); - let capturingClientId = clientId; + let capturingClientId; if ( topmostAncestorWithCaptureDescendantsToolbarsIndex !== -1 ) { capturingClientId = blockParentsClientIds[ topmostAncestorWithCaptureDescendantsToolbarsIndex ]; @@ -222,7 +238,6 @@ function wrapperSelector( select ) { return { clientId, rootClientId: getBlockRootClientId( clientId ), - isMounted: __unstableGetSelectedMountedBlock() === clientId, name, align: attributes.align, isValid, @@ -242,7 +257,6 @@ export default function WrappedBlockPopover() { const { clientId, rootClientId, - isMounted, name, align, isValid, @@ -251,7 +265,7 @@ export default function WrappedBlockPopover() { capturingClientId, } = selected; - if ( ! name || ! isMounted ) { + if ( ! name ) { return null; } diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 8902227bbd561..0d674919bc4df 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { first, last } from 'lodash'; +import { first, last, omit } from 'lodash'; import { animated } from 'react-spring/web.cjs'; /** @@ -28,7 +28,6 @@ import { withDispatch, withSelect, useSelect, - useDispatch, } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { compose, pure, ifCondition } from '@wordpress/compose'; @@ -43,7 +42,7 @@ import BlockCrashBoundary from './block-crash-boundary'; import BlockHtml from './block-html'; import { isInsideRootBlock } from '../../utils/dom'; import useMovingAnimation from './moving-animation'; -import { Context } from './root-container'; +import { Context, BlockNodes } from './root-container'; function BlockListBlock( { mode, @@ -54,6 +53,7 @@ function BlockListBlock( { isMultiSelected, isPartOfMultiSelection, isFirstMultiSelected, + isLastMultiSelected, isTypingWithinBlock, isEmptyDefaultBlock, isAncestorOfSelectedBlock, @@ -78,6 +78,7 @@ function BlockListBlock( { hasSelectedUI = true, } ) { const onSelectionStart = useContext( Context ); + const [ , setBlockNodes ] = useContext( BlockNodes ); // In addition to withSelect, we should favor using useSelect in this component going forward // to avoid leaking new props to the public API (editor.BlockListBlock filter) const { isDraggingBlocks } = useSelect( ( select ) => { @@ -85,18 +86,23 @@ function BlockListBlock( { isDraggingBlocks: select( 'core/block-editor' ).isDraggingBlocks(), }; }, [] ); - const { - __unstableSetSelectedMountedBlock, - } = useDispatch( 'core/block-editor' ); // Reference of the wrapper const wrapper = useRef( null ); + // Provide the selected node, or the first and last nodes of a multi- + // selection, so it can be used to position the contextual block toolbar. + // We only provide what is necessary, and remove the nodes again when they + // are no longer selected. useLayoutEffect( () => { - if ( isSelected || isFirstMultiSelected ) { - __unstableSetSelectedMountedBlock( clientId ); + if ( isSelected || isFirstMultiSelected || isLastMultiSelected ) { + const node = wrapper.current; + setBlockNodes( ( nodes ) => ( { ...nodes, [ clientId ]: node } ) ); + return () => { + setBlockNodes( ( nodes ) => omit( nodes, clientId ) ); + }; } - }, [ isSelected, isFirstMultiSelected ] ); + }, [ isSelected, isFirstMultiSelected, isLastMultiSelected ] ); // Handling the error state const [ hasError, setErrorState ] = useState( false ); @@ -331,6 +337,7 @@ const applyWithSelect = withSelect( isAncestorMultiSelected, isBlockMultiSelected, isFirstMultiSelectedBlock, + getLastMultiSelectedBlockClientId, isTyping, getBlockMode, isSelectionEnabled, @@ -361,6 +368,7 @@ const applyWithSelect = withSelect( isPartOfMultiSelection: isBlockMultiSelected( clientId ) || isAncestorMultiSelected( clientId ), isFirstMultiSelected: isFirstMultiSelectedBlock( clientId ), + isLastMultiSelected: getLastMultiSelectedBlockClientId() === clientId, // We only care about this prop when the block is selected // Thus to avoid unnecessary rerenders we avoid updating the prop if the block is not selected. diff --git a/packages/block-editor/src/components/block-list/root-container.js b/packages/block-editor/src/components/block-list/root-container.js index 5542b3edfe80a..84985f8fd7c07 100644 --- a/packages/block-editor/src/components/block-list/root-container.js +++ b/packages/block-editor/src/components/block-list/root-container.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { createContext, forwardRef } from '@wordpress/element'; +import { createContext, forwardRef, useState } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; /** @@ -15,6 +15,7 @@ import BlockPopover from './block-popover'; /** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */ export const Context = createContext(); +export const BlockNodes = createContext(); function selector( select ) { const { @@ -80,17 +81,19 @@ function RootContainer( { children, className }, ref ) { selectedBlockClientId={ selectedBlockClientId } containerRef={ ref } > - -
- - { children } - -
+ + +
+ + { children } + +
+
); } diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 07106b6459777..b6a6552e1f019 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -930,15 +930,3 @@ export function * insertAfterBlock( clientId ) { const firstSelectedIndex = yield select( 'core/block-editor', 'getBlockIndex', clientId, rootClientId ); yield insertDefaultBlock( {}, rootClientId, firstSelectedIndex + 1 ); } - -/** - * Sets the client ID for the mounted and selected block. - * - * @param {Element} clientId The block's client ID. - */ -export function __unstableSetSelectedMountedBlock( clientId ) { - return { - type: 'SET_SELECTED_MOUNTED_BLOCK', - clientId, - }; -} diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 2471be0a1e23a..d7b3799a61a0d 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1347,7 +1347,6 @@ export function automaticChangeStatus( state, action ) { return; // Undoing an automatic change should still be possible after mouse // move. - case 'SET_SELECTED_MOUNTED_BLOCK': case 'STOP_TYPING': return state; } @@ -1355,24 +1354,6 @@ export function automaticChangeStatus( state, action ) { // Reset the state by default (for any action not handled). } -/** - * Reducer returning selected and mounted block. This state is useful for - * components rendering and positioning controls around the block's node. - * - * @param {boolean} state Current state. - * @param {Object} action Dispatched action. - * - * @return {boolean} Updated state. - */ -export function selectedMountedBlock( state, action ) { - switch ( action.type ) { - case 'SET_SELECTED_MOUNTED_BLOCK': - return action.clientId; - } - - return state; -} - export default combineReducers( { blocks, isTyping, @@ -1392,5 +1373,4 @@ export default combineReducers( { lastBlockAttributesChange, isNavigationMode, automaticChangeStatus, - selectedMountedBlock, } ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index ceff4cb964436..8c4d6c6395e7d 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1526,14 +1526,3 @@ export function isNavigationMode( state ) { export function didAutomaticChange( state ) { return !! state.automaticChangeStatus; } - -/** - * Gets the selected block's DOM node. - * - * @param {Object} state Global application state. - * - * @return {Element} The selected block's DOM node. - */ -export function __unstableGetSelectedMountedBlock( state ) { - return state.selectedMountedBlock; -} diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 82cb3874db2fb..a97c964e06597 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -62,7 +62,25 @@ function computeAnchorRect( return getRectangleFromRange( anchorRef ); } - const rect = anchorRef.getBoundingClientRect(); + if ( anchorRef instanceof window.Element ) { + const rect = anchorRef.getBoundingClientRect(); + + if ( shouldAnchorIncludePadding ) { + return rect; + } + + return withoutPadding( rect, anchorRef ); + } + + const { top, bottom } = anchorRef; + const topRect = top.getBoundingClientRect(); + const bottomRect = bottom.getBoundingClientRect(); + const rect = new window.DOMRect( + topRect.left, + topRect.top, + topRect.width, + bottomRect.bottom - topRect.top + ); if ( shouldAnchorIncludePadding ) { return rect; @@ -301,7 +319,7 @@ const Popover = ( { yAxis, contentHeight, contentWidth, - } = computePopoverPosition( anchor, contentRect.current, position, __unstableSticky, anchorRef, relativeOffsetTop ); + } = computePopoverPosition( anchor, contentRect.current, position, __unstableSticky, containerRef.current, relativeOffsetTop ); if ( typeof popoverTop === 'number' && typeof popoverLeft === 'number' ) { if ( subpixels && __unstableAllowVerticalSubpixelPosition ) { diff --git a/packages/components/src/popover/utils.js b/packages/components/src/popover/utils.js index f729247c98d7f..187f3c292a564 100644 --- a/packages/components/src/popover/utils.js +++ b/packages/components/src/popover/utils.js @@ -128,27 +128,13 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis, cor const { height } = contentSize; if ( sticky ) { - let topEl = anchorRef; - let bottomEl = anchorRef; - - if ( typeof sticky === 'string' ) { - const elements = document.querySelectorAll( sticky ); - - if ( elements.length ) { - topEl = elements[ 0 ]; - bottomEl = elements[ elements.length - 1 ]; - } - } - - const scrollContainerEl = getScrollContainer( topEl ) || document.body; + const scrollContainerEl = getScrollContainer( anchorRef ) || document.body; const scrollRect = scrollContainerEl.getBoundingClientRect(); - const topRect = topEl.getBoundingClientRect(); - const bottomRect = bottomEl.getBoundingClientRect(); - if ( topRect.top - height <= scrollRect.top ) { + if ( anchorRect.top - height <= scrollRect.top ) { return { yAxis, - popoverTop: Math.min( bottomRect.bottom - relativeOffsetTop, scrollRect.top + height - relativeOffsetTop ), + popoverTop: Math.min( anchorRect.bottom - relativeOffsetTop, scrollRect.top + height - relativeOffsetTop ), }; } }