Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block: use context to provide selected element #19782

Merged
merged 3 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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