-
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 all 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,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; | ||
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. I wonder if we could just set the element in the context here instead of using an intermediary "ref". I thought the "ref" was useful for useBlockRef, but it doesn't seem to be the case anyway. 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. We could through |
||
// 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 }; |
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.