Skip to content
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: new blockEditor.useBlockProps filter #48884

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 94 additions & 10 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ import {
} from '@wordpress/compose';
import {
createContext,
useState,
useMemo,
useCallback,
useLayoutEffect,
useContext,
useRef,
useReducer,
useId,
} from '@wordpress/element';

/**
Expand All @@ -41,13 +45,92 @@ import {
DEFAULT_BLOCK_EDIT_CONTEXT,
} from '../block-edit/context';

const elementContext = createContext();
const componentsContext = createContext( { render() {}, unmount() {} } );

export const IntersectionObserver = createContext();
const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap();

function useRootPortal( component ) {
const components = useContext( componentsContext );
const id = useId();

// Run every time the component rerenders.
useLayoutEffect( () => {
if ( component ) {
components.render( id, component );
} else {
components.unmount( id );
}
}, [ components, id, component ] );

// Run only on unmount.
useLayoutEffect( () => () => components.unmount( id ), [ components, id ] );
}

function Component( { id, componentsById, renderById } ) {
const [ , forceRender ] = useReducer( () => ( {} ) );
useLayoutEffect( () => {
renderById.current.set( id, forceRender );
}, [ id, renderById ] );
return componentsById.current.get( id );
}

function Components( { componentsById, renderById, renderAll } ) {
const [ , forceRender ] = useReducer( () => ( {} ) );

useLayoutEffect( () => {
renderAll.current = forceRender;
}, [ renderAll ] );

return Array.from( componentsById.current.keys() ).map( ( key ) => (
<Component
key={ key }
id={ key }
componentsById={ componentsById }
renderById={ renderById }
/>
) );
}

function ComponentRenderer( { children } ) {
const componentsById = useRef( new Map() );
const renderById = useRef( new Map() );
const renderAll = useRef( () => {} );
return (
<componentsContext.Provider
value={ useMemo(
() => ( {
render( id, component ) {
if ( componentsById.current.has( id ) ) {
componentsById.current.set( id, component );
renderById.current.get( id )();
} else {
componentsById.current.set( id, component );
renderAll.current();
}
},
unmount( id ) {
if ( componentsById.current.has( id ) ) {
componentsById.current.delete( id );
renderById.current.delete( id );
renderAll.current();
}
},
} ),
[]
) }
>
<Components
componentsById={ componentsById }
renderById={ renderById }
renderAll={ renderAll }
/>
{ children }
</componentsContext.Provider>
);
}

function Root( { className, ...settings } ) {
const [ element, setElement ] = useState();
const isLargeViewport = useViewportMatch( 'medium' );
const { isOutlineMode, isFocusMode, editorMode } = useSelect(
( select ) => {
Expand Down Expand Up @@ -105,7 +188,6 @@ function Root( { className, ...settings } ) {
ref: useMergeRefs( [
useBlockSelectionClearer(),
useInBetweenInserter(),
setElement,
] ),
className: classnames( 'is-root-container', className, {
'is-outline-mode': isOutlineMode,
Expand All @@ -116,11 +198,13 @@ function Root( { className, ...settings } ) {
settings
);
return (
<elementContext.Provider value={ element }>
<IntersectionObserver.Provider value={ intersectionObserver }>
<div { ...innerBlocksProps } />
</IntersectionObserver.Provider>
</elementContext.Provider>
<IntersectionObserver.Provider value={ intersectionObserver }>
<div { ...innerBlocksProps }>
<ComponentRenderer>
{ innerBlocksProps.children }
</ComponentRenderer>
</div>
</IntersectionObserver.Provider>
);
}

Expand All @@ -135,7 +219,7 @@ export default function BlockList( settings ) {
);
}

BlockList.__unstableElementContext = elementContext;
BlockList.useRootPortal = useRootPortal;

function Items( {
placeholder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { useMergeRefs, useDisabled } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import warning from '@wordpress/warning';
import { applyFilters } from '@wordpress/hooks';

/**
* Internal dependencies
Expand Down Expand Up @@ -75,6 +76,8 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
isPartOfSelection,
adjustScrolling,
enableAnimation,
blockType,
attributes,
} = useSelect(
( select ) => {
const {
Expand All @@ -95,22 +98,24 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
isBlockMultiSelected( clientId ) ||
isAncestorMultiSelected( clientId );
const blockName = getBlockName( clientId );
const blockType = getBlockType( blockName );
const attributes = getBlockAttributes( clientId );
const match = getActiveBlockVariation( blockName, attributes );
const _blockType = getBlockType( blockName );
const _attributes = getBlockAttributes( clientId );
const match = getActiveBlockVariation( blockName, _attributes );

return {
index: getBlockIndex( clientId ),
mode: getBlockMode( clientId ),
name: blockName,
blockApiVersion: blockType?.apiVersion || 1,
blockTitle: match?.title || blockType?.title,
blockApiVersion: _blockType?.apiVersion || 1,
blockTitle: match?.title || _blockType?.title,
isPartOfSelection: isSelected || isPartOfMultiSelection,
adjustScrolling:
isSelected || isFirstMultiSelectedBlock( clientId ),
enableAnimation:
! isTyping() &&
getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD,
blockType: _blockType,
attributes: _attributes,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use context for this, I'm not sure what's best for performance.

};
},
[ clientId ]
Expand Down Expand Up @@ -147,9 +152,16 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
);
}

const filteredWrapperProps = applyFilters(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach suffers from the hooks rules errors as filters get added/removed over time. I think it's probably going to create a lot of bugs.

'blockEditor.useBlockProps',
wrapperProps,
blockType,
attributes
);

return {
tabIndex: 0,
...wrapperProps,
...filteredWrapperProps,
...props,
ref: mergedRefs,
id: `block-${ clientId }${ htmlSuffix }`,
Expand All @@ -166,13 +178,13 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
} ),
className,
props.className,
wrapperProps.className,
filteredWrapperProps.className,
useBlockClassNames( clientId ),
useBlockDefaultClassName( clientId ),
useBlockCustomClassName( clientId ),
useBlockMovingModeClassNames( clientId )
),
style: { ...wrapperProps.style, ...props.style },
style: { ...filteredWrapperProps.style, ...props.style },
};
}

Expand Down
54 changes: 23 additions & 31 deletions packages/block-editor/src/hooks/align.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,38 +175,30 @@ export const withToolbarControls = createHigherOrderComponent(
/**
* Override the default block element to add alignment wrapper props.
*
* @param {Function} BlockListBlock Original component.
*
* @return {Function} Wrapped component.
* @param {Object} props Additional props applied to edit element.
* @param {Object} blockType Block type.
* @param {Object} attributes Block attributes.
*/
export const withDataAlign = createHigherOrderComponent(
( BlockListBlock ) => ( props ) => {
const { name, attributes } = props;
const { align } = attributes;
const blockAllowedAlignments = getValidAlignments(
getBlockSupport( name, 'align' ),
hasBlockSupport( name, 'alignWide', true )
);
const validAlignments = useAvailableAlignments(
blockAllowedAlignments
);

// If an alignment is not assigned, there's no need to go through the
// effort to validate or assign its value.
if ( align === undefined ) {
return <BlockListBlock { ...props } />;
}

let wrapperProps = props.wrapperProps;
if (
validAlignments.some( ( alignment ) => alignment.name === align )
) {
wrapperProps = { ...wrapperProps, 'data-align': align };
}
export const useDataAlign = ( props, blockType, attributes ) => {
const { align } = attributes;
const blockAllowedAlignments = getValidAlignments(
getBlockSupport( blockType, 'align' ),
hasBlockSupport( blockType, 'alignWide', true )
);
const validAlignments = useAvailableAlignments( blockAllowedAlignments );

// If an alignment is not assigned, there's no need to go through the
// effort to validate or assign its value.
if ( align === undefined ) {
return props;
}

return <BlockListBlock { ...props } wrapperProps={ wrapperProps } />;
if ( validAlignments.some( ( alignment ) => alignment.name === align ) ) {
return { ...props, 'data-align': align };
}
);

return props;
};

/**
* Override props assigned to save component to inject alignment class name if
Expand Down Expand Up @@ -243,9 +235,9 @@ addFilter(
addAttribute
);
addFilter(
'editor.BlockListBlock',
'blockEditor.useBlockProps',
'core/editor/align/with-data-align',
withDataAlign
useDataAlign
);
addFilter(
'editor.BlockEdit',
Expand Down
Loading