From c87f8ac9ac20056ec4ad7386c47cd2c8a9a9d9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Fri, 5 Mar 2021 00:53:33 +0200 Subject: [PATCH 1/9] Block nodes: create useBlockRef for access to block node via ref --- .../components/block-list/block-popover.js | 33 +++++----- .../src/components/block-list/index.js | 16 ++--- .../block-list/use-block-props/index.js | 4 +- .../use-block-props/use-block-nodes.js | 61 ------------------- .../use-block-props/use-block-refs.js | 54 ++++++++++++++++ .../skip-to-selected-block/index.js | 12 ++-- .../writing-flow/use-multi-selection.js | 30 +++++---- .../block-editor/src/hooks/color-panel.js | 13 ++-- .../compose/src/hooks/use-merge-refs/index.js | 11 +++- 9 files changed, 114 insertions(+), 120 deletions(-) delete mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-block-nodes.js create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-block-refs.js 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 91f44d2df09f8..73bf71dbc617b 100644 --- a/packages/block-editor/src/components/block-list/block-popover.js +++ b/packages/block-editor/src/components/block-list/block-popover.js @@ -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'; @@ -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 { useBlockRef } from './use-block-props/use-block-refs'; function selector( select ) { const { @@ -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 @@ -121,6 +113,14 @@ function BlockPopover( { initialToolbarItemIndexRef.current = undefined; }, [ clientId ] ); + const [ selectedElement, setSelectedElement ] = useState(); + const [ lastSelectedElement, setLastSelectedElement ] = useState(); + const [ capturingElement, setCapturingElement ] = useState(); + + useBlockRef( clientId, setSelectedElement ); + useBlockRef( lastClientId, setLastSelectedElement ); + useBlockRef( capturingClientId, setCapturingElement ); + if ( ! shouldShowBreadcrumb && ! shouldShowContextualToolbar && @@ -130,32 +130,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, }; } @@ -174,6 +170,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 diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 352df1fd8a4f3..f19c46e9d8d60 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -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'; /** @@ -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(); @@ -53,7 +49,7 @@ export default function BlockList( { className, __experimentalLayout } ) { }, [] ); return ( - + <> { insertionPoint }
- - - +
-
+ ); } diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index 627f848ebf841..ca4d03a7560e6 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -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 { useRegisteredBlockRefs } from './use-block-refs'; import { store as blockEditorStore } from '../../../store'; /** @@ -106,7 +106,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { useFocusFirstElement( clientId ), // Must happen after focus because we check for focus in the block. useScrollIntoView( clientId ), - useBlockNodes( clientId ), useFocusHandler( clientId ), useEventHandlers( clientId ), useNavModeExit( clientId ), @@ -117,6 +116,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { enableAnimation, triggerAnimationOnChange: index, } ), + ...useRegisteredBlockRefs( clientId ), ] ); return { diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-nodes.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-nodes.js deleted file mode 100644 index 49542364e2cfe..0000000000000 --- a/packages/block-editor/src/components/block-list/use-block-props/use-block-nodes.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * External dependencies - */ -import { omit } from 'lodash'; - -/** - * WordPress dependencies - */ -import { useContext } from '@wordpress/element'; -import { useRefEffect } from '@wordpress/compose'; -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { SetBlockNodes } from '../'; -import { store as blockEditorStore } from '../../../store'; - -export function useBlockNodes( clientId ) { - const setBlockNodes = useContext( SetBlockNodes ); - // 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. - const isNodeNeeded = useSelect( - ( select ) => { - const { - isBlockSelected, - isFirstMultiSelectedBlock, - getLastMultiSelectedBlockClientId, - } = select( blockEditorStore ); - return ( - isBlockSelected( clientId ) || - isFirstMultiSelectedBlock( clientId ) || - getLastMultiSelectedBlockClientId() === clientId - ); - } - // To do: figure out why tests are failing when dependencies are added. - // This data was originally retrieved with `withSelect` in `block.js`. - // For some reason, adding `clientId` as a dependency results in - // `toolbar-roving-tabindex.test.js` e2e test failures. - ); - - return useRefEffect( - ( node ) => { - if ( ! isNodeNeeded ) { - return; - } - - setBlockNodes( ( nodes ) => ( { - ...nodes, - [ clientId ]: node, - } ) ); - - return () => { - setBlockNodes( ( nodes ) => omit( nodes, clientId ) ); - }; - }, - [ isNodeNeeded, clientId, setBlockNodes ] - ); -} diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-refs.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-refs.js new file mode 100644 index 0000000000000..4bf85c76e5023 --- /dev/null +++ b/packages/block-editor/src/components/block-list/use-block-props/use-block-refs.js @@ -0,0 +1,54 @@ +/** + * WordPress dependencies + */ +import { useEffect, useReducer } from '@wordpress/element'; + +const store = { + add( clientId, ref ) { + store[ clientId ] = store[ clientId ] || new Set(); + store[ clientId ].add( ref ); + + if ( listeners[ clientId ] ) { + listeners[ clientId ](); + } + }, + remove( clientId, ref ) { + store[ clientId ].delete( ref ); + + if ( listeners[ clientId ] ) { + listeners[ clientId ](); + } + }, +}; + +const listeners = { + add( clientId, fn ) { + listeners[ clientId ] = fn; + }, + remove( clientId ) { + delete listeners[ clientId ]; + }, +}; + +function useUpdate( object, key, value ) { + useEffect( () => { + if ( ! key ) { + return; + } + + object.add( key, value ); + return () => { + object.remove( key, value ); + }; + }, [ key, value ] ); +} + +export function useRegisteredBlockRefs( clientId ) { + const [ , forceRender ] = useReducer( ( s ) => ! s ); + useUpdate( listeners, clientId, forceRender ); + return store[ clientId ] ? Array.from( store[ clientId ] ) : []; +} + +export function useBlockRef( clientId, ref ) { + useUpdate( store, clientId, ref ); +} diff --git a/packages/block-editor/src/components/skip-to-selected-block/index.js b/packages/block-editor/src/components/skip-to-selected-block/index.js index e5c54524dc46e..5b5a21af6d25c 100644 --- a/packages/block-editor/src/components/skip-to-selected-block/index.js +++ b/packages/block-editor/src/components/skip-to-selected-block/index.js @@ -4,22 +4,22 @@ import { withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; +import { useRef } from '@wordpress/element'; /** * Internal dependencies */ -import { getBlockDOMNode } from '../../utils/dom'; import { store as blockEditorStore } from '../../store'; +import { useBlockRef } from '../block-list/use-block-props/use-block-refs'; const SkipToSelectedBlock = ( { selectedBlockClientId } ) => { + const ref = useRef(); const onClick = () => { - const selectedBlockElement = getBlockDOMNode( - selectedBlockClientId, - document - ); - selectedBlockElement.focus(); + ref.current.focus(); }; + useBlockRef( selectedBlockClientId, ref ); + return selectedBlockClientId ? (