-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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 editor: create useBlockRef and useBlockElement for better access #29573
Changes from 8 commits
c87f8ac
4381da0
41e0da5
902c9c6
abc0e1a
56e2eea
8786f8a
5628271
9703d88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 ( | ||
<BlockNodes.Provider value={ blockNodes }> | ||
<> | ||
{ insertionPoint } | ||
<BlockPopover /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After this and #31134, we can move the popover and insertion point out of the BlockList component. |
||
<div | ||
|
@@ -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> | ||
</> | ||
); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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( | ||
|
@@ -77,7 +74,6 @@ function InsertionPointPopover( { | |
hasMultiSelection, | ||
getSettings, | ||
} = select( blockEditorStore ); | ||
const { ownerDocument } = containerRef.current; | ||
const targetRootClientId = clientId | ||
? getBlockRootClientId( clientId ) | ||
: selectedRootClientId; | ||
|
@@ -100,8 +96,7 @@ function InsertionPointPopover( { | |
'vertical'; | ||
|
||
return { | ||
previousElement: getBlockDOMNode( previous, ownerDocument ), | ||
nextElement: getBlockDOMNode( next, ownerDocument ), | ||
previousClientId: previous, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that previously the element wouldn't update if it changed because it's memoized by client ID. |
||
nextClientId: next, | ||
isHidden: | ||
hasReducedUI || | ||
|
@@ -116,7 +111,8 @@ function InsertionPointPopover( { | |
}, | ||
[ clientId, selectedRootClientId ] | ||
); | ||
|
||
const previousElement = useBlockElement( previousClientId ); | ||
const nextElement = useBlockElement( nextClientId ); | ||
const style = useMemo( () => { | ||
if ( ! previousElement ) { | ||
return {}; | ||
|
@@ -411,7 +407,6 @@ export default function useInsertionPoint( ref ) { | |
setIsInserterShown( value ); | ||
} | ||
} } | ||
containerRef={ ref } | ||
showInsertionPoint={ isInserterVisible } | ||
/> | ||
) | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
useCallback, | ||
useContext, | ||
useLayoutEffect, | ||
useMemo, | ||
useRef, | ||
useState, | ||
} from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { BlockRefs } from '../../provider/block-refs-provider'; | ||
|
||
export function useBlockRefProvider( clientId ) { | ||
const { refs, callbacks } = useContext( BlockRefs ); | ||
const ref = useRef(); | ||
useLayoutEffect( () => { | ||
refs.set( clientId, ref ); | ||
return () => { | ||
refs.delete( clientId ); | ||
}; | ||
}, [] ); | ||
return useCallback( ( node ) => { | ||
ref.current = node; | ||
callbacks.forEach( ( id, callback ) => { | ||
if ( clientId === id ) { | ||
callback( node ); | ||
} | ||
} ); | ||
}, [] ); | ||
} | ||
|
||
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; | ||
}, | ||
} ), | ||
[] | ||
); | ||
} | ||
|
||
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 }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { createContext, useMemo } from '@wordpress/element'; | ||
|
||
export const BlockRefs = createContext(); | ||
|
||
export function BlockRefsProvider( { children } ) { | ||
const value = useMemo( | ||
() => ( { refs: new Map(), callbacks: new Map() } ), | ||
[] | ||
); | ||
return ( | ||
<BlockRefs.Provider value={ value }>{ children }</BlockRefs.Provider> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remounting the component is better because the block element may change immediately from one to the other. Previously the block node would be undefined between client ID changes.