diff --git a/docs/reference-guides/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md index ba047160f047de..eb8e820643ca23 100644 --- a/docs/reference-guides/data/data-core-blocks.md +++ b/docs/reference-guides/data/data-core-blocks.md @@ -85,6 +85,7 @@ _Parameters_ - _state_ `Object`: Data state. - _name_ `string`: Block type name. +- _attributes_ `[Object]`: Block attributes. If provided, will try to find and include styles for specific block variations. _Returns_ @@ -246,6 +247,19 @@ _Returns_ - `(WPBlockVariation[]|void)`: Block variations. +### getBlockVariationStyles + +Returns block variation styles by block name. + +_Parameters_ + +- _state_ `Object`: Data state. +- _name_ `string`: Block type name. + +_Returns_ + +- `Array?`: Block Styles. + ### getCategories Returns all the available block categories. diff --git a/lib/compat/wordpress-6.3/script-loader.php b/lib/compat/wordpress-6.3/script-loader.php index 8a8e41ef2be919..d5b14b87126997 100644 --- a/lib/compat/wordpress-6.3/script-loader.php +++ b/lib/compat/wordpress-6.3/script-loader.php @@ -13,3 +13,44 @@ remove_action( 'wp_body_open', 'wp_global_styles_render_svg_filters' ); remove_action( 'in_admin_header', 'wp_global_styles_render_svg_filters' ); + +/** + * Function responsible for enqueuing the assets required for block styles functionality on the editor. + * + * @since 5.3.0 + * @since 6.3.0 Support styles for block variations. + */ +function gutenberg_enqueue_editor_block_styles_assets() { + $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered(); + + $register_script_lines = array( '( function() {' ); + foreach ( $block_styles as $block_name => $styles ) { + foreach ( $styles as $style_properties ) { + $block_style = array( + 'name' => $style_properties['name'], + 'label' => $style_properties['label'], + ); + if ( isset( $style_properties['is_default'] ) ) { + $block_style['isDefault'] = $style_properties['is_default']; + } + if ( isset( $style_properties['variations'] ) ) { + $block_style['variations'] = $style_properties['variations']; + } + $register_script_lines[] = sprintf( + ' wp.blocks.registerBlockStyle( \'%s\', %s );', + $block_name, + wp_json_encode( $block_style ) + ); + } + } + $register_script_lines[] = '} )();'; + $inline_script = implode( "\n", $register_script_lines ); + + wp_register_script( 'wp-block-styles', false, array( 'wp-blocks' ), true, true ); + wp_add_inline_script( 'wp-block-styles', $inline_script ); + wp_enqueue_script( 'wp-block-styles' ); +} + +// Remove the Core action hook to avoid handling editor block style assets twice. +remove_action( 'enqueue_block_editor_assets', 'enqueue_editor_block_styles_assets' ); +add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_editor_block_styles_assets' ); diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 199f4f4628b4bf..89cb70b02bc3e7 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -314,10 +314,14 @@ const BlockInspectorSingleBlock = ( { clientId, blockName } ) => { const hasBlockStyles = useSelect( ( select ) => { const { getBlockStyles } = select( blocksStore ); - const blockStyles = getBlockStyles( blockName ); - return blockStyles && blockStyles.length > 0; + const { getBlockAttributes } = select( blockEditorStore ); + const blockStyles = getBlockStyles( + blockName, + getBlockAttributes( clientId ) + ); + return blockStyles?.length > 0; }, - [ blockName ] + [ clientId, blockName ] ); const blockInformation = useBlockDisplayInformation( clientId ); 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 ca6bb4355f52db..9a118f874d4625 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 @@ -34,6 +34,7 @@ import { useEventHandlers } from './use-selected-block-event-handlers'; import { useNavModeExit } from './use-nav-mode-exit'; import { useBlockRefProvider } from './use-block-refs'; import { useIntersectionObserver } from './use-intersection-observer'; +import useCleanBlockStyles from './use-clean-block-styles'; import { store as blockEditorStore } from '../../../store'; import useBlockOverlayActive from '../../block-content-overlay'; @@ -116,6 +117,9 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { [ clientId ] ); + // This hook has side effects. Removes any lingering block variation styles. + useCleanBlockStyles( clientId ); + const hasOverlay = useBlockOverlayActive( clientId ); // translators: %s: Type of block (i.e. Text, Image etc) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-clean-block-styles.js b/packages/block-editor/src/components/block-list/use-block-props/use-clean-block-styles.js new file mode 100644 index 00000000000000..c02d508ec5e244 --- /dev/null +++ b/packages/block-editor/src/components/block-list/use-block-props/use-clean-block-styles.js @@ -0,0 +1,72 @@ +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../../store'; + +/** + * This hook checks if any applied block variation style is set, + * but the respective block variation is not active now. + * + * In this case this hook performs a side effect to remove + * that style(class name) and doesn't create an `undo` step. + * + * @param {string} clientId The block client ID. + * + */ +export default function useCleanBlockStyles( clientId ) { + const { updateBlockAttributes } = useDispatch( blockEditorStore ); + const { __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); + const { styleToRemove, blockClassNames } = useSelect( + ( select ) => { + const { getBlockAttributes, getBlockName } = + select( blockEditorStore ); + const { getActiveBlockVariation, getBlockVariationStyles } = + select( blocksStore ); + const attributes = getBlockAttributes( clientId ); + if ( ! attributes.className ) { + return {}; + } + // Here we are checking if the block has a block style for a specific + // block variation. If it does and the block variation is no longer active, + // we need to remove the style class name. + const blockName = getBlockName( clientId ); + const match = getActiveBlockVariation( blockName, attributes ); + const blockVariationStyles = getBlockVariationStyles( blockName ); + return { + styleToRemove: blockVariationStyles?.find( + ( { name: styleName, variations } ) => + attributes.className.includes( + `is-style-${ styleName }` + ) && + ( ! match || ! variations.includes( match.name ) ) + )?.name, + blockClassNames: attributes.className, + }; + }, + [ clientId ] + ); + useEffect( () => { + if ( styleToRemove === undefined ) { + return; + } + const updatedClassNames = blockClassNames + .split( ' ' ) + .filter( + ( blockClassName ) => + ! blockClassName.includes( `is-style-${ styleToRemove }` ) + ) + .join( ' ' ); + __unstableMarkNextChangeAsNotPersistent(); + updateBlockAttributes( clientId, { + className: updatedClassNames, + } ); + }, [ clientId, styleToRemove, blockClassNames ] ); +} diff --git a/packages/block-editor/src/components/block-styles/use-styles-for-block.js b/packages/block-editor/src/components/block-styles/use-styles-for-block.js index 6d73dca557b058..6338562cb44751 100644 --- a/packages/block-editor/src/components/block-styles/use-styles-for-block.js +++ b/packages/block-editor/src/components/block-styles/use-styles-for-block.js @@ -65,7 +65,7 @@ export default function useStylesForBlocks( { clientId, onSwitch } ) { return { block, blockType, - styles: getBlockStyles( block.name ), + styles: getBlockStyles( block.name, block.attributes ), className: block.attributes.className || '', }; }; diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 696b097757d335..00992611ee1b63 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -49,10 +49,13 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { const rootClientId = getBlockRootClientId( Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds ); - const [ { name: firstBlockName } ] = blocks; + const [ + { name: firstBlockName, attributes: firstBlockAttributes }, + ] = blocks; const _isSingleBlockSelected = blocks.length === 1; const styles = - _isSingleBlockSelected && getBlockStyles( firstBlockName ); + _isSingleBlockSelected && + getBlockStyles( firstBlockName, firstBlockAttributes ); let _icon; if ( _isSingleBlockSelected ) { _icon = blockInformation?.icon; // Take into account active block variations. diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index bf70dbc0c261c5..05045e7d5e56ac 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -116,8 +116,9 @@ export function getBlockType( state, name ) { /** * Returns block styles by block name. * - * @param {Object} state Data state. - * @param {string} name Block type name. + * @param {Object} state Data state. + * @param {string} name Block type name. + * @param {Object} [attributes] Block attributes. If provided, will try to find and include styles for specific block variations. * * @example * ```js @@ -143,9 +144,48 @@ export function getBlockType( state, name ) { * * @return {Array?} Block Styles. */ -export function getBlockStyles( state, name ) { - return state.blockStyles[ name ]; -} +export const getBlockStyles = createSelector( + ( state, name, attributes ) => { + const mainBlockStyles = state.blockStyles[ name ]?.filter( + ( { variations } ) => ! variations + ); + // If block attributes are provided, try to find styles for + // any active block variation. + if ( ! attributes ) { + return mainBlockStyles; + } + const activeVariation = getActiveBlockVariation( + state, + name, + attributes + ); + if ( ! activeVariation ) { + return mainBlockStyles; + } + const activeVariationStyles = state.blockStyles[ name ]?.filter( + ( { variations } ) => variations?.includes( activeVariation.name ) + ); + return [ ...activeVariationStyles, ...mainBlockStyles ]; + }, + ( state, name ) => [ state.blockStyles[ name ] ] +); + +/** + * Returns block variation styles by block name. + * + * @param {Object} state Data state. + * @param {string} name Block type name. + * + * @return {Array?} Block Styles. + */ +export const getBlockVariationStyles = createSelector( + ( state, name ) => { + return state.blockStyles[ name ]?.filter( + ( { variations } ) => !! variations + ); + }, + ( state, name ) => [ state.blockStyles[ name ] ] +); /** * Returns block variations by block name. diff --git a/schemas/json/block.json b/schemas/json/block.json index 5b92a654fbc4a5..402212838e9d5b 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -552,6 +552,12 @@ "isDefault": { "type": "boolean", "default": false + }, + "variations": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ "name", "label" ],