@gechiui/block-editor
Version:
115 lines (103 loc) • 2.74 kB
JavaScript
/**
* GeChiUI dependencies
*/
import {
useContext,
useLayoutEffect,
useMemo,
useRef,
useState,
} from '@gechiui/element';
import { useRefEffect } from '@gechiui/compose';
/**
* Internal dependencies
*/
import { BlockRefs } from '../../provider/block-refs-provider';
/** @typedef {import('@gechiui/element').RefCallback} RefCallback */
/** @typedef {import('@gechiui/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( ref, clientId );
return () => {
refs.delete( ref );
};
}, [ clientId ] );
return useRefEffect(
( element ) => {
// Update the ref in the provider.
ref.current = element;
// Call any update functions.
callbacks.forEach( ( id, setElement ) => {
if ( clientId === id ) {
setElement( element );
}
} );
},
[ clientId ]
);
}
/**
* 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() {
let element = null;
// Multiple refs may be created for a single block. Find the
// first that has an element set.
for ( const [ ref, id ] of refs.entries() ) {
if ( id === freshClientId.current && ref.current ) {
element = ref.current;
}
}
return element;
},
} ),
[]
);
}
/**
* 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 };