Skip to content

Commit

Permalink
Block: use context to provide selected element (#19782)
Browse files Browse the repository at this point in the history
* Block: use context to provide selected element

* Include multi selection nodes

* Add comment
  • Loading branch information
ellatrix authored Jan 23, 2020
1 parent 26b8015 commit 89c2b44
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 93 deletions.
34 changes: 24 additions & 10 deletions packages/block-editor/src/components/block-list/block-popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand All @@ -29,6 +30,7 @@ function selector( select ) {
isTyping,
isCaretWithinFormattedText,
getSettings,
getLastMultiSelectedBlockClientId,
} = select( 'core/block-editor' );
return {
isNavigationMode: isNavigationMode(),
Expand All @@ -37,6 +39,7 @@ function selector( select ) {
isCaretWithinFormattedText: isCaretWithinFormattedText(),
hasMultiSelection: hasMultiSelection(),
hasFixedToolbar: getSettings().hasFixedToolbar,
lastClientId: getLastMultiSelectedBlockClientId(),
};
}

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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 );
}
Expand All @@ -116,17 +134,16 @@ 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 (
<Popover
noArrow
animate={ false }
position={ popoverPosition }
focusOnMount={ false }
anchorRef={ node }
anchorRef={ anchorRef }
className="block-editor-block-list__block-popover"
__unstableSticky={ showEmptyBlockSideInserter ? false : popoverIsSticky }
__unstableSticky={ ! showEmptyBlockSideInserter }
__unstableSlotName="block-toolbar"
// Allow subpixel positioning for the block movement animation.
__unstableAllowVerticalSubpixelPosition={ moverDirection !== 'horizontal' && node }
Expand Down Expand Up @@ -188,7 +205,6 @@ function wrapperSelector( select ) {
getSelectedBlockClientId,
getFirstMultiSelectedBlockClientId,
getBlockRootClientId,
__unstableGetSelectedMountedBlock,
__unstableGetBlockWithoutInnerBlocks,
getBlockParents,
getBlockListSettings,
Expand All @@ -213,7 +229,7 @@ function wrapperSelector( select ) {
// This will be the top most ancestor because getBlockParents() returns tree from top -> bottom
const topmostAncestorWithCaptureDescendantsToolbarsIndex = findIndex( ancestorBlockListSettings, [ '__experimentalCaptureToolbars', true ] );

let capturingClientId = clientId;
let capturingClientId;

if ( topmostAncestorWithCaptureDescendantsToolbarsIndex !== -1 ) {
capturingClientId = blockParentsClientIds[ topmostAncestorWithCaptureDescendantsToolbarsIndex ];
Expand All @@ -222,7 +238,6 @@ function wrapperSelector( select ) {
return {
clientId,
rootClientId: getBlockRootClientId( clientId ),
isMounted: __unstableGetSelectedMountedBlock() === clientId,
name,
align: attributes.align,
isValid,
Expand All @@ -242,7 +257,6 @@ export default function WrappedBlockPopover() {
const {
clientId,
rootClientId,
isMounted,
name,
align,
isValid,
Expand All @@ -251,7 +265,7 @@ export default function WrappedBlockPopover() {
capturingClientId,
} = selected;

if ( ! name || ! isMounted ) {
if ( ! name ) {
return null;
}

Expand Down
26 changes: 17 additions & 9 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -28,7 +28,6 @@ import {
withDispatch,
withSelect,
useSelect,
useDispatch,
} from '@wordpress/data';
import { withViewportMatch } from '@wordpress/viewport';
import { compose, pure, ifCondition } from '@wordpress/compose';
Expand All @@ -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,
Expand All @@ -54,6 +53,7 @@ function BlockListBlock( {
isMultiSelected,
isPartOfMultiSelection,
isFirstMultiSelected,
isLastMultiSelected,
isTypingWithinBlock,
isEmptyDefaultBlock,
isAncestorOfSelectedBlock,
Expand All @@ -78,25 +78,31 @@ 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 ) => {
return {
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 );
Expand Down Expand Up @@ -331,6 +337,7 @@ const applyWithSelect = withSelect(
isAncestorMultiSelected,
isBlockMultiSelected,
isFirstMultiSelectedBlock,
getLastMultiSelectedBlockClientId,
isTyping,
getBlockMode,
isSelectionEnabled,
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 15 additions & 12 deletions packages/block-editor/src/components/block-list/root-container.js
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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 {
Expand Down Expand Up @@ -80,17 +81,19 @@ function RootContainer( { children, className }, ref ) {
selectedBlockClientId={ selectedBlockClientId }
containerRef={ ref }
>
<BlockPopover />
<div
ref={ ref }
className={ className }
onFocus={ onFocus }
onDragStart={ onDragStart }
>
<Context.Provider value={ onSelectionStart }>
{ children }
</Context.Provider>
</div>
<BlockNodes.Provider value={ useState( {} ) }>
<BlockPopover />
<div
ref={ ref }
className={ className }
onFocus={ onFocus }
onDragStart={ onDragStart }
>
<Context.Provider value={ onSelectionStart }>
{ children }
</Context.Provider>
</div>
</BlockNodes.Provider>
</InsertionPoint>
);
}
Expand Down
12 changes: 0 additions & 12 deletions packages/block-editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
20 changes: 0 additions & 20 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1347,32 +1347,13 @@ 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;
}

// 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,
Expand All @@ -1392,5 +1373,4 @@ export default combineReducers( {
lastBlockAttributesChange,
isNavigationMode,
automaticChangeStatus,
selectedMountedBlock,
} );
11 changes: 0 additions & 11 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
22 changes: 20 additions & 2 deletions packages/components/src/popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 ) {
Expand Down
Loading

0 comments on commit 89c2b44

Please sign in to comment.