From 10fe5d645191bac88e0fbb1e7c28830c6a86448c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Tue, 7 Mar 2023 14:20:07 +0200 Subject: [PATCH 1/3] Block editor: new filter --- .../src/components/block-list/index.js | 68 ++++++- .../block-list/use-block-props/index.js | 28 ++- packages/block-editor/src/hooks/align.js | 54 +++--- packages/block-editor/src/hooks/border.js | 105 +++++------ packages/block-editor/src/hooks/color.js | 109 ++++++----- packages/block-editor/src/hooks/duotone.js | 82 ++++----- packages/block-editor/src/hooks/font-size.js | 89 ++++----- packages/block-editor/src/hooks/layout.js | 50 ++--- packages/block-editor/src/hooks/position.js | 99 +++++----- packages/block-editor/src/hooks/style.js | 172 +++++++++--------- .../block-library/src/gallery/gap-styles.js | 10 +- 11 files changed, 428 insertions(+), 438 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 2193cedcf1c10..c0d311c254cba 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -19,9 +19,14 @@ import { } from '@wordpress/compose'; import { createContext, - useState, useMemo, useCallback, + useLayoutEffect, + useContext, + useRef, + Fragment, + createElement, + useReducer, } from '@wordpress/element'; /** @@ -41,13 +46,55 @@ import { DEFAULT_BLOCK_EDIT_CONTEXT, } from '../block-edit/context'; -const elementContext = createContext(); +const componentsContext = createContext(); export const IntersectionObserver = createContext(); const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap(); +function useRootPortal( component ) { + const components = useContext( componentsContext ); + + useLayoutEffect( () => { + if ( ! component ) return; + components.add( component ); + return () => { + components.delete( component ); + }; + } ); +} + +function Components( { subscriber, components } ) { + const [ , forceRender ] = useReducer( () => ( {} ) ); + subscriber.current = forceRender; + return createElement( Fragment, null, ...components.current ); +} + +function ComponentRenderer( { children } ) { + const subscriber = useRef( () => {} ); + const components = useRef( new Set() ); + return ( + ( { + add( component ) { + components.current.add( component ); + subscriber.current(); + }, + delete( component ) { + components.current.delete( component ); + subscriber.current(); + }, + } ), + [] + ) } + > + + { children } + + ); +} + function Root( { className, ...settings } ) { - const [ element, setElement ] = useState(); const isLargeViewport = useViewportMatch( 'medium' ); const { isOutlineMode, isFocusMode, editorMode } = useSelect( ( select ) => { @@ -105,7 +152,6 @@ function Root( { className, ...settings } ) { ref: useMergeRefs( [ useBlockSelectionClearer(), useInBetweenInserter(), - setElement, ] ), className: classnames( 'is-root-container', className, { 'is-outline-mode': isOutlineMode, @@ -116,11 +162,13 @@ function Root( { className, ...settings } ) { settings ); return ( - - -
- - + +
+ + { innerBlocksProps.children } + +
+
); } @@ -135,7 +183,7 @@ export default function BlockList( settings ) { ); } -BlockList.__unstableElementContext = elementContext; +BlockList.useRootPortal = useRootPortal; function Items( { placeholder, 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 ca6bb4355f52d..ed96039a0c2fa 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 @@ -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 @@ -75,6 +76,8 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isPartOfSelection, adjustScrolling, enableAnimation, + blockType, + attributes, } = useSelect( ( select ) => { const { @@ -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, }; }, [ clientId ] @@ -147,9 +152,16 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { ); } + const filteredWrapperProps = applyFilters( + 'blockEditor.useBlockProps', + wrapperProps, + blockType, + attributes + ); + return { tabIndex: 0, - ...wrapperProps, + ...filteredWrapperProps, ...props, ref: mergedRefs, id: `block-${ clientId }${ htmlSuffix }`, @@ -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 }, }; } diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index 8d5c7f850e89b..60a7eed577083 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -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 ; - } - - 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 ; + 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 @@ -243,9 +235,9 @@ addFilter( addAttribute ); addFilter( - 'editor.BlockListBlock', + 'blockEditor.useBlockProps', 'core/editor/align/with-data-align', - withDataAlign + useDataAlign ); addFilter( 'editor.BlockEdit', diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js index a5f08b64515b0..aa2072a880b92 100644 --- a/packages/block-editor/src/hooks/border.js +++ b/packages/block-editor/src/hooks/border.js @@ -8,7 +8,6 @@ import classnames from 'classnames'; */ import { getBlockSupport } from '@wordpress/blocks'; import { __experimentalHasSplitBorders as hasSplitBorders } from '@wordpress/components'; -import { createHigherOrderComponent } from '@wordpress/compose'; import { Platform, useCallback, useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; @@ -331,66 +330,58 @@ function addEditProps( settings ) { * This adds inline styles for color palette colors. * Ideally, this is not needed and themes should load their palettes on the editor. * - * @param {Function} BlockListBlock Original component. - * - * @return {Function} Wrapped component. + * @param {Object} props Additional props applied to save element. + * @param {Object} blockType Block type definition. + * @param {Object} attributes Block's attributes. */ -export const withBorderColorPaletteStyles = createHigherOrderComponent( - ( BlockListBlock ) => ( props ) => { - const { name, attributes } = props; - const { borderColor, style } = attributes; - const { colors } = useMultipleOriginColorsAndGradients(); - - if ( - ! hasBorderSupport( name, 'color' ) || - shouldSkipSerialization( name, BORDER_SUPPORT_KEY, 'color' ) - ) { - return ; - } +export const useBorderColorPaletteStyles = ( props, blockType, attributes ) => { + const { borderColor, style } = attributes; + const { colors } = useMultipleOriginColorsAndGradients(); - const { color: borderColorValue } = getMultiOriginColor( { - colors, - namedColor: borderColor, - } ); - const { color: borderTopColor } = getMultiOriginColor( { - colors, - namedColor: getColorSlugFromVariable( style?.border?.top?.color ), - } ); - const { color: borderRightColor } = getMultiOriginColor( { - colors, - namedColor: getColorSlugFromVariable( style?.border?.right?.color ), - } ); + if ( + ! hasBorderSupport( blockType, 'color' ) || + shouldSkipSerialization( blockType, BORDER_SUPPORT_KEY, 'color' ) + ) { + return props; + } - const { color: borderBottomColor } = getMultiOriginColor( { - colors, - namedColor: getColorSlugFromVariable( - style?.border?.bottom?.color - ), - } ); - const { color: borderLeftColor } = getMultiOriginColor( { - colors, - namedColor: getColorSlugFromVariable( style?.border?.left?.color ), - } ); + const { color: borderColorValue } = getMultiOriginColor( { + colors, + namedColor: borderColor, + } ); + const { color: borderTopColor } = getMultiOriginColor( { + colors, + namedColor: getColorSlugFromVariable( style?.border?.top?.color ), + } ); + const { color: borderRightColor } = getMultiOriginColor( { + colors, + namedColor: getColorSlugFromVariable( style?.border?.right?.color ), + } ); - const extraStyles = { - borderTopColor: borderTopColor || borderColorValue, - borderRightColor: borderRightColor || borderColorValue, - borderBottomColor: borderBottomColor || borderColorValue, - borderLeftColor: borderLeftColor || borderColorValue, - }; + const { color: borderBottomColor } = getMultiOriginColor( { + colors, + namedColor: getColorSlugFromVariable( style?.border?.bottom?.color ), + } ); + const { color: borderLeftColor } = getMultiOriginColor( { + colors, + namedColor: getColorSlugFromVariable( style?.border?.left?.color ), + } ); - let wrapperProps = props.wrapperProps; - wrapperProps = { - ...props.wrapperProps, - style: { - ...props.wrapperProps?.style, - ...extraStyles, - }, - }; + const extraStyles = { + borderTopColor: borderTopColor || borderColorValue, + borderRightColor: borderRightColor || borderColorValue, + borderBottomColor: borderBottomColor || borderColorValue, + borderLeftColor: borderLeftColor || borderColorValue, + }; - return ; - } -); + return { + ...props, + style: { + ...props.style, + ...extraStyles, + }, + }; +}; addFilter( 'blocks.registerBlockType', @@ -411,7 +402,7 @@ addFilter( ); addFilter( - 'editor.BlockListBlock', + 'blockEditor.useBlockProps', 'core/border/with-border-color-palette-styles', - withBorderColorPaletteStyles + useBorderColorPaletteStyles ); diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index cd94b6b1bcaa3..47793ba07cc6c 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -10,7 +10,6 @@ import { addFilter } from '@wordpress/hooks'; import { getBlockSupport } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; import { useRef, useEffect, useMemo, Platform } from '@wordpress/element'; -import { createHigherOrderComponent } from '@wordpress/compose'; /** * Internal dependencies @@ -536,64 +535,58 @@ export function ColorEdit( props ) { * This adds inline styles for color palette colors. * Ideally, this is not needed and themes should load their palettes on the editor. * - * @param {Function} BlockListBlock Original component. - * - * @return {Function} Wrapped component. + * @param {Object} props Additional props applied to save element. + * @param {Object} blockType Block type. + * @param {Object} attributes Block attributes. */ -export const withColorPaletteStyles = createHigherOrderComponent( - ( BlockListBlock ) => ( props ) => { - const { name, attributes } = props; - const { backgroundColor, textColor } = attributes; - const userPalette = useSetting( 'color.palette.custom' ); - const themePalette = useSetting( 'color.palette.theme' ); - const defaultPalette = useSetting( 'color.palette.default' ); - const colors = useMemo( - () => [ - ...( userPalette || [] ), - ...( themePalette || [] ), - ...( defaultPalette || [] ), - ], - [ userPalette, themePalette, defaultPalette ] - ); - if ( - ! hasColorSupport( name ) || - shouldSkipSerialization( name, COLOR_SUPPORT_KEY ) - ) { - return ; - } - const extraStyles = {}; - - if ( - textColor && - ! shouldSkipSerialization( name, COLOR_SUPPORT_KEY, 'text' ) - ) { - extraStyles.color = getColorObjectByAttributeValues( - colors, - textColor - )?.color; - } - if ( - backgroundColor && - ! shouldSkipSerialization( name, COLOR_SUPPORT_KEY, 'background' ) - ) { - extraStyles.backgroundColor = getColorObjectByAttributeValues( - colors, - backgroundColor - )?.color; - } - - let wrapperProps = props.wrapperProps; - wrapperProps = { - ...props.wrapperProps, - style: { - ...extraStyles, - ...props.wrapperProps?.style, - }, - }; +export const useColorPaletteStyles = ( props, blockType, attributes ) => { + const { backgroundColor, textColor } = attributes; + const userPalette = useSetting( 'color.palette.custom' ); + const themePalette = useSetting( 'color.palette.theme' ); + const defaultPalette = useSetting( 'color.palette.default' ); + const colors = useMemo( + () => [ + ...( userPalette || [] ), + ...( themePalette || [] ), + ...( defaultPalette || [] ), + ], + [ userPalette, themePalette, defaultPalette ] + ); + if ( + ! hasColorSupport( blockType ) || + shouldSkipSerialization( blockType, COLOR_SUPPORT_KEY ) + ) { + return props; + } + const extraStyles = {}; - return ; + if ( + textColor && + ! shouldSkipSerialization( blockType, COLOR_SUPPORT_KEY, 'text' ) + ) { + extraStyles.color = getColorObjectByAttributeValues( + colors, + textColor + )?.color; } -); + if ( + backgroundColor && + ! shouldSkipSerialization( blockType, COLOR_SUPPORT_KEY, 'background' ) + ) { + extraStyles.backgroundColor = getColorObjectByAttributeValues( + colors, + backgroundColor + )?.color; + } + + return { + ...props, + style: { + ...extraStyles, + ...props.style, + }, + }; +}; const MIGRATION_PATHS = { linkColor: [ [ 'style', 'elements', 'link', 'color', 'text' ] ], @@ -642,9 +635,9 @@ addFilter( ); addFilter( - 'editor.BlockListBlock', + 'blockEditor.useBlockProps', 'core/color/with-color-palette-styles', - withColorPaletteStyles + useColorPaletteStyles ); addFilter( diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index b56d9f64b4e83..df2d840188246 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -11,7 +11,7 @@ import namesPlugin from 'colord/plugins/names'; import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; -import { useMemo, useContext, createPortal } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; /** @@ -249,20 +249,12 @@ function scopeSelector( scope, selector ) { return selectorsScoped.join( ', ' ); } -function BlockDuotoneStyles( { name, duotoneStyle, id } ) { +function ConditionalInlineDuotone( { blockType, id, duotoneStyle } ) { const duotonePalette = useMultiOriginPresets( { presetSetting: 'color.duotone', defaultSetting: 'color.defaultDuotone', } ); - const element = useContext( BlockList.__unstableElementContext ); - - // Portals cannot exist without a container. - // Guard against empty Duotone styles. - if ( ! element || ! duotoneStyle ) { - return null; - } - let colors = duotoneStyle; if ( ! Array.isArray( colors ) && colors !== 'unset' ) { @@ -270,7 +262,7 @@ function BlockDuotoneStyles( { name, duotoneStyle, id } ) { } const duotoneSupportSelectors = getBlockSupport( - name, + blockType, 'color.__experimentalDuotone' ); @@ -282,55 +274,47 @@ function BlockDuotoneStyles( { name, duotoneStyle, id } ) { duotoneSupportSelectors ); - return createPortal( + return ( , - element + /> ); } /** * Override the default block element to include duotone styles. * - * @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. */ -const withDuotoneStyles = createHigherOrderComponent( - ( BlockListBlock ) => ( props ) => { - const duotoneSupport = getBlockSupport( - props.name, - 'color.__experimentalDuotone' - ); +const useDuotoneStyles = ( props, blockType, attributes ) => { + const duotoneSupport = getBlockSupport( + blockType, + 'color.__experimentalDuotone' + ); - const id = `wp-duotone-${ useInstanceId( BlockListBlock ) }`; - const className = duotoneSupport - ? classnames( props?.className, id ) - : props?.className; - const duotoneStyle = props?.attributes?.style?.color?.duotone; + const id = `wp-duotone-${ useInstanceId( useDuotoneStyles ) }`; - // CAUTION: code added before this line will be executed - // for all blocks, not just those that support duotone. Code added - // above this line should be carefully evaluated for its impact on - // performance. - return ( - <> - { duotoneSupport && duotoneStyle && ( - - ) } - - - ); - }, - 'withDuotoneStyles' -); + BlockList.useRootPortal( + duotoneSupport && ( + + ) + ); + + return { + ...props, + className: duotoneSupport + ? classnames( props.className, id ) + : props.className, + }; +}; addFilter( 'blocks.registerBlockType', @@ -343,7 +327,7 @@ addFilter( withDuotoneControls ); addFilter( - 'editor.BlockListBlock', + 'blockEditor.useBlockProps', 'core/editor/duotone/with-styles', - withDuotoneStyles + useDuotoneStyles ); diff --git a/packages/block-editor/src/hooks/font-size.js b/packages/block-editor/src/hooks/font-size.js index e03b73c56331d..046e53507f9ad 100644 --- a/packages/block-editor/src/hooks/font-size.js +++ b/packages/block-editor/src/hooks/font-size.js @@ -4,7 +4,6 @@ import { addFilter } from '@wordpress/hooks'; import { hasBlockSupport } from '@wordpress/blocks'; import TokenList from '@wordpress/token-list'; -import { createHigherOrderComponent } from '@wordpress/compose'; import { select } from '@wordpress/data'; /** @@ -177,57 +176,45 @@ export function useIsFontSizeDisabled( { name: blockName } = {} ) { * Ideally, this is not needed and themes load the font-size classes on the * editor. * - * @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. */ -const withFontSizeInlineStyles = createHigherOrderComponent( - ( BlockListBlock ) => ( props ) => { - const fontSizes = useSetting( 'typography.fontSizes' ); - const { - name: blockName, - attributes: { fontSize, style }, - wrapperProps, - } = props; - - // Only add inline styles if the block supports font sizes, - // doesn't skip serialization of font sizes, - // doesn't already have an inline font size, - // and does have a class to extract the font size from. - if ( - ! hasBlockSupport( blockName, FONT_SIZE_SUPPORT_KEY ) || - shouldSkipSerialization( - blockName, - TYPOGRAPHY_SUPPORT_KEY, - 'fontSize' - ) || - ! fontSize || - style?.typography?.fontSize - ) { - return ; - } +const useFontSizeInlineStyles = ( props, blockType, attributes ) => { + const fontSizes = useSetting( 'typography.fontSizes' ); + const { fontSize, style } = attributes; - const fontSizeValue = getFontSize( - fontSizes, - fontSize, - style?.typography?.fontSize - ).size; - - const newProps = { - ...props, - wrapperProps: { - ...wrapperProps, - style: { - fontSize: fontSizeValue, - ...wrapperProps?.style, - }, - }, - }; + // Only add inline styles if the block supports font sizes, + // doesn't skip serialization of font sizes, + // doesn't already have an inline font size, + // and does have a class to extract the font size from. + if ( + ! hasBlockSupport( blockType, FONT_SIZE_SUPPORT_KEY ) || + shouldSkipSerialization( + blockType, + TYPOGRAPHY_SUPPORT_KEY, + 'fontSize' + ) || + ! fontSize || + style?.typography?.fontSize + ) { + return props; + } - return ; - }, - 'withFontSizeInlineStyles' -); + const fontSizeValue = getFontSize( + fontSizes, + fontSize, + style?.typography?.fontSize + ).size; + + return { + ...props, + style: { + fontSize: fontSizeValue, + ...props?.style, + }, + }; +}; const MIGRATION_PATHS = { fontSize: [ [ 'fontSize' ], [ 'style', 'typography', 'fontSize' ] ], @@ -338,9 +325,9 @@ addFilter( addFilter( 'blocks.registerBlockType', 'core/font/addEditProps', addEditProps ); addFilter( - 'editor.BlockListBlock', + 'blockEditor.useBlockProps', 'core/font-size/with-font-size-inline-styles', - withFontSizeInlineStyles + useFontSizeInlineStyles ); addFilter( diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index aa2d278aee582..70e3a668f93df 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -18,7 +18,6 @@ import { PanelBody, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useContext, createPortal } from '@wordpress/element'; /** * Internal dependencies @@ -364,7 +363,6 @@ export const withLayoutStyles = createHigherOrderComponent( hasLayoutBlockSupport && ! disableLayoutStyles; const id = useInstanceId( BlockListBlock ); const defaultThemeLayout = useSetting( 'layout' ) || {}; - const element = useContext( BlockList.__unstableElementContext ); const { layout } = attributes; const { default: defaultBlockLayout } = getBlockSupport( name, layoutBlockSupportKey ) || {}; @@ -405,26 +403,23 @@ export const withLayoutStyles = createHigherOrderComponent( layoutClasses ); - return ( - <> - { shouldRenderLayoutStyles && - element && - !! css && - createPortal( - , - element - ) } - - + ) + ); + + return ( + ); } ); @@ -449,7 +444,6 @@ export const withChildLayoutStyles = createHigherOrderComponent( const shouldRenderChildLayoutStyles = hasChildLayout && ! disableLayoutStyles; - const element = useContext( BlockList.__unstableElementContext ); const id = useInstanceId( BlockListBlock ); const selector = `.wp-container-content-${ id }`; @@ -472,15 +466,11 @@ export const withChildLayoutStyles = createHigherOrderComponent( shouldRenderChildLayoutStyles && !! css, // Only attach a container class if there is generated CSS to be attached. } ); - return ( - <> - { shouldRenderChildLayoutStyles && - element && - !! css && - createPortal( , element ) } - - + BlockList.useRootPortal( + shouldRenderChildLayoutStyles && !! css && ); + + return ; } ); diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 2c5589c1920bb..3b767a4d437a9 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -14,12 +14,7 @@ import { } from '@wordpress/components'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; -import { - useContext, - useMemo, - createPortal, - Platform, -} from '@wordpress/element'; +import { useMemo, Platform } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; /** @@ -338,61 +333,57 @@ export const withInspectorControls = createHigherOrderComponent( /** * Override the default block element to add the position styles. * - * @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 withPositionStyles = createHigherOrderComponent( - ( BlockListBlock ) => ( props ) => { - const { name, attributes } = props; - const hasPositionBlockSupport = hasBlockSupport( - name, - POSITION_SUPPORT_KEY - ); - const allowPositionStyles = - hasPositionBlockSupport && ! useIsPositionDisabled( props ); - - const id = useInstanceId( BlockListBlock ); - const element = useContext( BlockList.__unstableElementContext ); - - // Higher specificity to override defaults in editor UI. - const positionSelector = `.wp-container-${ id }.wp-container-${ id }`; - - // Get CSS string for the current position values. - let css; - if ( allowPositionStyles ) { - css = - getPositionCSS( { - selector: positionSelector, - style: attributes?.style, - } ) || ''; - } +export const usePositionStyles = ( props, blockType, attributes ) => { + const hasPositionBlockSupport = hasBlockSupport( + blockType, + POSITION_SUPPORT_KEY + ); + const allowPositionStyles = ! useIsPositionDisabled( props ); + const id = useInstanceId( usePositionStyles ); + + // Higher specificity to override defaults in editor UI. + const positionSelector = `.wp-container-${ id }.wp-container-${ id }`; + + // Get CSS string for the current position values. + let css; + if ( allowPositionStyles ) { + css = + getPositionCSS( { + selector: positionSelector, + style: attributes?.style, + } ) || ''; + } - // Attach a `wp-container-` id-based class name. - const className = classnames( props?.className, { - [ `wp-container-${ id }` ]: allowPositionStyles && !! css, // Only attach a container class if there is generated CSS to be attached. - [ `is-position-${ attributes?.style?.position?.type }` ]: - allowPositionStyles && - !! css && - !! attributes?.style?.position?.type, - } ); + // Attach a `wp-container-` id-based class name. + const className = classnames( props?.className, { + [ `wp-container-${ id }` ]: allowPositionStyles && !! css, // Only attach a container class if there is generated CSS to be attached. + [ `is-position-${ attributes?.style?.position?.type }` ]: + allowPositionStyles && + !! css && + !! attributes?.style?.position?.type, + } ); - return ( - <> - { allowPositionStyles && - element && - !! css && - createPortal( , element ) } - - - ); - } -); + BlockList.useRootPortal( + hasPositionBlockSupport && allowPositionStyles && !! css && ( + + ) + ); + + return { + ...props, + className, + }; +}; addFilter( - 'editor.BlockListBlock', + 'blockEditor.useBlockProps', 'core/editor/position/with-position-styles', - withPositionStyles + usePositionStyles ); addFilter( 'editor.BlockEdit', diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index b6fe7b8188c2b..703b912984c83 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useContext, useMemo, createPortal } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { getBlockSupport, @@ -365,89 +365,99 @@ export const withBlockControls = createHigherOrderComponent( 'withToolbarControls' ); +function ElementStyles( { + blockType, + rawElementsStyles, + blockElementsContainerIdentifier, +} ) { + const skipLinkColorSerialization = shouldSkipSerialization( + blockType, + COLOR_SUPPORT_KEY, + 'link' + ); + const styles = useMemo( () => { + const elementCssRules = []; + if ( + rawElementsStyles && + Object.keys( rawElementsStyles ).length > 0 + ) { + // Remove values based on whether serialization has been skipped for a specific style. + const filteredElementsStyles = { + ...rawElementsStyles, + link: { + ...rawElementsStyles.link, + color: ! skipLinkColorSerialization + ? rawElementsStyles.link?.color + : undefined, + }, + }; + + for ( const [ elementName, elementStyles ] of Object.entries( + filteredElementsStyles + ) ) { + const cssRule = compileCSS( elementStyles, { + // The .editor-styles-wrapper selector is required on elements styles. As it is + // added to all other editor styles, not providing it causes reset and global + // styles to override element styles because of higher specificity. + selector: `.editor-styles-wrapper .${ blockElementsContainerIdentifier } ${ ELEMENTS[ elementName ] }`, + } ); + if ( !! cssRule ) { + elementCssRules.push( cssRule ); + } + } + } + return elementCssRules.length > 0 ? elementCssRules : undefined; + }, [ + rawElementsStyles, + blockElementsContainerIdentifier, + skipLinkColorSerialization, + ] ); + + if ( ! styles ) { + return null; + } + + return ( + ; - }; - - return gap && styleElement - ? createPortal( , styleElement ) - : null; + BlockList.useRootPortal( gap ? : null ); } From 704f13af5e6658b92f4761721433727ebb344dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Tue, 7 Mar 2023 20:18:58 +0200 Subject: [PATCH 2/3] Fix useRootPortal --- .../src/components/block-list/index.js | 76 ++++++++++++++----- packages/block-editor/src/hooks/duotone.js | 7 +- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index c0d311c254cba..0c284f50049b4 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -24,9 +24,8 @@ import { useLayoutEffect, useContext, useRef, - Fragment, - createElement, useReducer, + useId, } from '@wordpress/element'; /** @@ -53,42 +52,79 @@ const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap(); function useRootPortal( component ) { const components = useContext( componentsContext ); + const id = useId(); + // Run every time the component rerenders. useLayoutEffect( () => { - if ( ! component ) return; - components.add( component ); - return () => { - components.delete( component ); - }; - } ); + 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( { subscriber, components } ) { +function Components( { componentsById, renderById, renderAll } ) { const [ , forceRender ] = useReducer( () => ( {} ) ); - subscriber.current = forceRender; - return createElement( Fragment, null, ...components.current ); + + useLayoutEffect( () => { + renderAll.current = forceRender; + }, [ renderAll ] ); + + return Array.from( componentsById.current.keys() ).map( ( key ) => ( + + ) ); } function ComponentRenderer( { children } ) { - const subscriber = useRef( () => {} ); - const components = useRef( new Set() ); + const componentsById = useRef( new Map() ); + const renderById = useRef( new Map() ); + const renderAll = useRef( () => {} ); return ( ( { - add( component ) { - components.current.add( component ); - subscriber.current(); + 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(); + } }, - delete( component ) { - components.current.delete( component ); - subscriber.current(); + unmount( id ) { + if ( componentsById.current.has( id ) ) { + componentsById.current.delete( id ); + renderById.current.delete( id ); + renderAll.current(); + } }, } ), [] ) } > - + { children } ); diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index df2d840188246..52cd2ece75300 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -297,9 +297,10 @@ const useDuotoneStyles = ( props, blockType, attributes ) => { ); const id = `wp-duotone-${ useInstanceId( useDuotoneStyles ) }`; + const duotoneStyle = attributes?.style?.color?.duotone; BlockList.useRootPortal( - duotoneSupport && ( + duotoneSupport && duotoneStyle && ( { ) ); + // CAUTION: code added before this line will be executed + // for all blocks, not just those that support duotone. Code added + // above this line should be carefully evaluated for its impact on + // performance. return { ...props, className: duotoneSupport From 82008bc460fc5ef337e667a792e6872ee327b6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Tue, 7 Mar 2023 21:13:40 +0200 Subject: [PATCH 3/3] Fix unit tests --- .../src/components/block-list/index.js | 2 +- packages/block-editor/src/hooks/test/align.js | 55 +++++++++---------- packages/block-editor/src/hooks/test/color.js | 17 ++++-- 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 0c284f50049b4..bcd11e1f49b6a 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -45,7 +45,7 @@ import { DEFAULT_BLOCK_EDIT_CONTEXT, } from '../block-edit/context'; -const componentsContext = createContext(); +const componentsContext = createContext( { render() {}, unmount() {} } ); export const IntersectionObserver = createContext(); const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap(); diff --git a/packages/block-editor/src/hooks/test/align.js b/packages/block-editor/src/hooks/test/align.js index d6a9b3aba65ce..a2a262775d9c9 100644 --- a/packages/block-editor/src/hooks/test/align.js +++ b/packages/block-editor/src/hooks/test/align.js @@ -23,7 +23,7 @@ import BlockEditorProvider from '../../components/provider'; import { getValidAlignments, withToolbarControls, - withDataAlign, + useDataAlign, addAssignedAlign, } from '../align'; @@ -219,7 +219,7 @@ describe( 'align', () => { } ); } ); - describe( 'withDataAlign', () => { + describe( 'useDataAlign', () => { it( 'should render with wrapper props', () => { registerBlockType( 'core/foo', { ...blockSettings, @@ -229,21 +229,20 @@ describe( 'align', () => { }, } ); - const EnhancedComponent = withDataAlign( ( { wrapperProps } ) => ( -