Skip to content

Commit

Permalink
Block editor: create useBlockRef and useBlockElement for better access (
Browse files Browse the repository at this point in the history
#29573)

* Block nodes: create useBlockRef for access to block node via ref

* Try layout effect

* Adjust useMergeRef to not depend on ref order

* wip

* Rewrite

* Fix multi select

* Revert useMergeRefs

* Fix roving toolbar reset

* Add inline docs
  • Loading branch information
ellatrix authored Apr 27, 2021
1 parent 686ee9c commit 7fc3896
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 165 deletions.
36 changes: 13 additions & 23 deletions packages/block-editor/src/components/block-list/block-popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import {
useState,
useCallback,
useContext,
useRef,
useEffect,
} from '@wordpress/element';
import { useState, useCallback, useRef, useEffect } from '@wordpress/element';
import { isUnmodifiedDefaultBlock } from '@wordpress/blocks';
import { Popover } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
Expand All @@ -27,9 +21,8 @@ import { getScrollContainer } from '@wordpress/dom';
import BlockSelectionButton from './block-selection-button';
import BlockContextualToolbar from './block-contextual-toolbar';
import Inserter from '../inserter';
import { BlockNodes } from './';
import { getBlockDOMNode } from '../../utils/dom';
import { store as blockEditorStore } from '../../store';
import { __unstableUseBlockElement as useBlockElement } from './use-block-props/use-block-refs';

function selector( select ) {
const {
Expand Down Expand Up @@ -71,7 +64,6 @@ function BlockPopover( {
const isLargeViewport = useViewportMatch( 'medium' );
const [ isToolbarForced, setIsToolbarForced ] = useState( false );
const [ isInserterShown, setIsInserterShown ] = useState( false );
const blockNodes = useContext( BlockNodes );
const { stopTyping } = useDispatch( blockEditorStore );

// Controls when the side inserter on empty lines should
Expand Down Expand Up @@ -115,11 +107,9 @@ function BlockPopover( {
// to it when re-mounting.
const initialToolbarItemIndexRef = useRef();

useEffect( () => {
// Resets the index whenever the active block changes so this is not
// persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169
initialToolbarItemIndexRef.current = undefined;
}, [ clientId ] );
const selectedElement = useBlockElement( clientId );
const lastSelectedElement = useBlockElement( lastClientId );
const capturingElement = useBlockElement( capturingClientId );

if (
! shouldShowBreadcrumb &&
Expand All @@ -130,32 +120,28 @@ function BlockPopover( {
return null;
}

let node = blockNodes[ clientId ];
let node = selectedElement;

if ( ! node ) {
return null;
}

const { ownerDocument } = node;

if ( capturingClientId ) {
node = getBlockDOMNode( capturingClientId, ownerDocument );
node = capturingElement;
}

let anchorRef = node;

if ( hasMultiSelection ) {
const bottomNode = blockNodes[ lastClientId ];

// Wait to render the popover until the bottom reference is available
// as well.
if ( ! bottomNode ) {
if ( ! lastSelectedElement ) {
return null;
}

anchorRef = {
top: node,
bottom: bottomNode,
bottom: lastSelectedElement,
};
}

Expand All @@ -174,6 +160,7 @@ function BlockPopover( {
const popoverPosition = showEmptyBlockSideInserter
? 'top left right'
: 'top right left';
const { ownerDocument } = node;
const stickyBoundaryElement = showEmptyBlockSideInserter
? undefined
: // The sticky boundary element should be the boundary at which the
Expand Down Expand Up @@ -235,6 +222,9 @@ function BlockPopover( {
__experimentalOnIndexChange={ ( index ) => {
initialToolbarItemIndexRef.current = index;
} }
// Resets the index whenever the active block changes so
// this is not persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169
key={ clientId }
/>
) }
{ shouldShowBreadcrumb && (
Expand Down
16 changes: 4 additions & 12 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { AsyncModeProvider, useSelect } from '@wordpress/data';
import { useRef, createContext, useState } from '@wordpress/element';
import { useRef } from '@wordpress/element';
import { useViewportMatch, useMergeRefs } from '@wordpress/compose';

/**
Expand All @@ -22,12 +22,8 @@ import { store as blockEditorStore } from '../../store';
import { usePreParsePatterns } from '../../utils/pre-parse-patterns';
import { LayoutProvider, defaultLayout } from './layout';

export const BlockNodes = createContext();
export const SetBlockNodes = createContext();

export default function BlockList( { className, __experimentalLayout } ) {
const ref = useRef();
const [ blockNodes, setBlockNodes ] = useState( {} );
const insertionPoint = useInsertionPoint( ref );
usePreParsePatterns();

Expand All @@ -53,7 +49,7 @@ export default function BlockList( { className, __experimentalLayout } ) {
}, [] );

return (
<BlockNodes.Provider value={ blockNodes }>
<>
{ insertionPoint }
<BlockPopover />
<div
Expand All @@ -69,13 +65,9 @@ export default function BlockList( { className, __experimentalLayout } ) {
}
) }
>
<SetBlockNodes.Provider value={ setBlockNodes }>
<BlockListItems
__experimentalLayout={ __experimentalLayout }
/>
</SetBlockNodes.Provider>
<BlockListItems __experimentalLayout={ __experimentalLayout } />
</div>
</BlockNodes.Provider>
</>
);
}

Expand Down
15 changes: 5 additions & 10 deletions packages/block-editor/src/components/block-list/insertion-point.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { isRTL } from '@wordpress/i18n';
* Internal dependencies
*/
import Inserter from '../inserter';
import { getBlockDOMNode } from '../../utils/dom';
import { store as blockEditorStore } from '../../store';
import { __unstableUseBlockElement as useBlockElement } from './use-block-props/use-block-refs';

function InsertionPointInserter( {
clientId,
Expand Down Expand Up @@ -53,17 +53,14 @@ function InsertionPointPopover( {
isInserterShown,
isInserterForced,
setIsInserterForced,
containerRef,
showInsertionPoint,
} ) {
const { selectBlock } = useDispatch( blockEditorStore );
const ref = useRef();

const {
previousElement,
nextElement,
orientation,
isHidden,
previousClientId,
nextClientId,
rootClientId,
} = useSelect(
Expand All @@ -77,7 +74,6 @@ function InsertionPointPopover( {
hasMultiSelection,
getSettings,
} = select( blockEditorStore );
const { ownerDocument } = containerRef.current;
const targetRootClientId = clientId
? getBlockRootClientId( clientId )
: selectedRootClientId;
Expand All @@ -100,8 +96,7 @@ function InsertionPointPopover( {
'vertical';

return {
previousElement: getBlockDOMNode( previous, ownerDocument ),
nextElement: getBlockDOMNode( next, ownerDocument ),
previousClientId: previous,
nextClientId: next,
isHidden:
hasReducedUI ||
Expand All @@ -116,7 +111,8 @@ function InsertionPointPopover( {
},
[ clientId, selectedRootClientId ]
);

const previousElement = useBlockElement( previousClientId );
const nextElement = useBlockElement( nextClientId );
const style = useMemo( () => {
if ( ! previousElement ) {
return {};
Expand Down Expand Up @@ -411,7 +407,6 @@ export default function useInsertionPoint( ref ) {
setIsInserterShown( value );
}
} }
containerRef={ ref }
showInsertionPoint={ isInserterVisible }
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import { useBlockMovingModeClassNames } from './use-block-moving-mode-class-name
import { useFocusHandler } from './use-focus-handler';
import { useEventHandlers } from './use-selected-block-event-handlers';
import { useNavModeExit } from './use-nav-mode-exit';
import { useBlockNodes } from './use-block-nodes';
import { useScrollIntoView } from './use-scroll-into-view';
import { useBlockRefProvider } from './use-block-refs';
import { store as blockEditorStore } from '../../../store';

/**
Expand Down Expand Up @@ -106,7 +106,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
useFocusFirstElement( clientId ),
// Must happen after focus because we check for focus in the block.
useScrollIntoView( clientId ),
useBlockNodes( clientId ),
useBlockRefProvider( clientId ),
useFocusHandler( clientId ),
useEventHandlers( clientId ),
useNavModeExit( clientId ),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* WordPress dependencies
*/
import {
useCallback,
useContext,
useLayoutEffect,
useMemo,
useRef,
useState,
} from '@wordpress/element';

/**
* Internal dependencies
*/
import { BlockRefs } from '../../provider/block-refs-provider';

/** @typedef {import('@wordpress/element').RefCallback} RefCallback */
/** @typedef {import('@wordpress/element').RefObject} RefObject */

/**
* Provides a ref to the BlockRefs context.
*
* @param {string} clientId The client ID of the element ref.
*
* @return {RefCallback} Ref callback.
*/
export function useBlockRefProvider( clientId ) {
const { refs, callbacks } = useContext( BlockRefs );
const ref = useRef();
useLayoutEffect( () => {
refs.set( clientId, ref );
return () => {
refs.delete( clientId );
};
}, [] );
return useCallback( ( element ) => {
// Update the ref in the provider.
ref.current = element;
// Call any update functions.
callbacks.forEach( ( id, setElement ) => {
if ( clientId === id ) {
setElement( element );
}
} );
}, [] );
}

/**
* Gets a ref pointing to the current block element. Continues to return a
* stable ref even if the block client ID changes.
*
* @param {string} clientId The client ID to get a ref for.
*
* @return {RefObject} A ref containing the element.
*/
function useBlockRef( clientId ) {
const { refs } = useContext( BlockRefs );
const freshClientId = useRef();
freshClientId.current = clientId;
// Always return an object, even if no ref exists for a given client ID, so
// that `current` works at a later point.
return useMemo(
() => ( {
get current() {
return refs.get( freshClientId.current )?.current || null;
},
} ),
[]
);
}

/**
* Return the element for a given client ID. Updates whenever the element
* changes, becomes available, or disappears.
*
* @param {string} clientId The client ID to an element for.
*
* @return {Element|null} The block's wrapper element.
*/
function useBlockElement( clientId ) {
const { callbacks } = useContext( BlockRefs );
const ref = useBlockRef( clientId );
const [ element, setElement ] = useState( null );

useLayoutEffect( () => {
if ( ! clientId ) {
return;
}

callbacks.set( setElement, clientId );
return () => {
callbacks.delete( setElement );
};
}, [ clientId ] );

return ref.current || element;
}

export { useBlockRef as __unstableUseBlockRef };
export { useBlockElement as __unstableUseBlockElement };
Loading

0 comments on commit 7fc3896

Please sign in to comment.