From 34b0c3999e1288dd057cfb9d6778d3032256d1ba Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:28:11 +1000 Subject: [PATCH 1/6] Try stabilizing default controls as top-level block type property --- lib/compat/wordpress-6.8/blocks.php | 39 ++++- phpunit/block-supports/border-test.php | 217 +++++++++++++++++++++---- 2 files changed, 217 insertions(+), 39 deletions(-) diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php index e0f5082bfce8dc..6fd2034b2b0e00 100644 --- a/lib/compat/wordpress-6.8/blocks.php +++ b/lib/compat/wordpress-6.8/blocks.php @@ -21,10 +21,7 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { } $experimental_supports_map = array( '__experimentalBorder' => 'border' ); - $common_experimental_properties = array( - '__experimentalDefaultControls' => 'defaultControls', - '__experimentalSkipSerialization' => 'skipSerialization', - ); + $common_experimental_properties = array( '__experimentalSkipSerialization' => 'skipSerialization' ); $experimental_support_properties = array( 'typography' => array( '__experimentalFontFamily' => 'fontFamily', @@ -35,9 +32,11 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { '__experimentalTextTransform' => 'textTransform', ), ); - $done = array(); + $done = array(); + $default_controls = array(); $updated_supports = array(); + foreach ( $args['supports'] as $support => $config ) { /* * If this support config has already been stabilized, skip it. @@ -50,6 +49,12 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { $stable_support_key = $experimental_supports_map[ $support ] ?? $support; + // Extract default controls config as it is being moved to top-level property. + if ( is_array( $config ) && ! empty( $config['__experimentalDefaultControls'] ) ) { + $default_controls[ $stable_support_key ] = $config['__experimentalDefaultControls']; + unset( $config['__experimentalDefaultControls'] ); + } + /* * Use the support's config as is when it's not in need of stabilization. * @@ -77,6 +82,11 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { $stable_config = array(); foreach ( $unstable_config as $key => $value ) { + // Skip `__experimentalDefaultControls` as they are handled separately. + if ( '__experimentalDefaultControls' === $key ) { + continue; + } + // Get stable key from support-specific map, common properties map, or keep original. $stable_key = $experimental_support_properties[ $stable_support_key ][ $key ] ?? $common_experimental_properties[ $key ] ?? @@ -120,6 +130,14 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { ( $key_positions[ $support ] ?? PHP_INT_MAX ) < ( $key_positions[ $stable_support_key ] ?? PHP_INT_MAX ); + // Extract default controls as they are relocated to top-level property. + if ( is_array( $args['supports'][ $stable_support_key ] ) && ! empty( $args['supports'][ $stable_support_key ]['__experimentalDefaultControls'] ) ) { + $controls = $args['supports'][ $stable_support_key ]['__experimentalDefaultControls']; + $default_controls[ $stable_support_key ] = $experimental_first ? + array_merge( $default_controls[ $stable_support_key ] ?? array(), $controls ) : + array_merge( $controls, $default_controls[ $stable_support_key ] ?? array() ); + } + /* * To merge the alternative support config effectively, it also needs to be * stabilized before merging to keep stabilized and experimental flags in @@ -143,7 +161,18 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { $updated_supports[ $stable_support_key ] = $stable_config; } + $final_default_controls = $args['defaultControls'] ?? array(); + foreach ( $default_controls as $support_key => $stabilized_default_controls ) { + // Only add experimental controls if no top-level controls exist for this support key. + if ( ! isset( $final_default_controls[ $support_key ] ) && ! empty( $stabilized_default_controls ) ) { + $final_default_controls[ $support_key ] = $stabilized_default_controls; + } + } + $args['supports'] = $updated_supports; + if ( ! empty( $final_default_controls ) ) { + $args['defaultControls'] = $final_default_controls; + } return $args; } diff --git a/phpunit/block-supports/border-test.php b/phpunit/block-supports/border-test.php index 510633b48aab59..9e798fe3f2df30 100644 --- a/phpunit/block-supports/border-test.php +++ b/phpunit/block-supports/border-test.php @@ -592,7 +592,7 @@ public function test_should_stabilize_border_supports() { $actual = gutenberg_stabilize_experimental_block_supports( $block_type_args ); $expected = array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -601,12 +601,14 @@ public function test_should_stabilize_border_supports() { 'skipSerialization' => true, // Has to be kept due to core's `wp_should_skip_block_supports_serialization` only checking the experimental flag until 6.8. '__experimentalSkipSerialization' => true, - 'defaultControls' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - ), + ), + ), + 'defaultControls' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, ), ), ); @@ -640,12 +642,6 @@ public function test_should_stabilize_border_supports_using_order_based_merge() * key but stable serialization and default control keys. */ 'skipSerialization' => false, - 'defaultControls' => array( - 'color' => true, - 'radius' => false, - 'style' => true, - 'width' => true, - ), ); $stable_border_config = array( 'color' => true, @@ -653,12 +649,6 @@ public function test_should_stabilize_border_supports_using_order_based_merge() 'style' => false, 'width' => true, 'skipSerialization' => false, - 'defaultControls' => array( - 'color' => true, - 'radius' => false, - 'style' => false, - 'width' => true, - ), /* * The following simulates theme/plugin filtering using stable `border` key @@ -682,7 +672,7 @@ public function test_should_stabilize_border_supports_using_order_based_merge() $actual = gutenberg_stabilize_experimental_block_supports( $experimental_first_args ); $expected = array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -690,13 +680,14 @@ public function test_should_stabilize_border_supports_using_order_based_merge() 'width' => true, 'skipSerialization' => true, '__experimentalSkipSerialization' => true, - 'defaultControls' => array( - 'color' => false, - 'radius' => false, - 'style' => false, - 'width' => false, - ), - + ), + ), + 'defaultControls' => array( + 'border' => array( + 'color' => false, + 'radius' => false, + 'style' => false, + 'width' => false, ), ), ); @@ -711,7 +702,7 @@ public function test_should_stabilize_border_supports_using_order_based_merge() $actual = gutenberg_stabilize_experimental_block_supports( $stable_first_args ); $expected = array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -719,18 +710,176 @@ public function test_should_stabilize_border_supports_using_order_based_merge() 'width' => true, 'skipSerialization' => false, '__experimentalSkipSerialization' => false, - 'defaultControls' => array( - 'color' => true, - 'radius' => false, - 'style' => true, - 'width' => true, - ), + ), + ), + 'defaultControls' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, ), ), ); $this->assertSame( $expected, $actual, 'Merged stabilized border block support config does not match when stable keys are first.' ); } + /** + * Tests that default controls are handled correctly when stabilizing border block supports. + * + * @dataProvider data_border_default_controls + * + * @param array $input Input block type arguments. + * @param array $expected Expected stabilized configuration. + */ + public function test_should_stabilize_border_default_controls( array $input, array $expected ) { + $actual = gutenberg_stabilize_experimental_block_supports( $input ); + $this->assertSame( $expected, $actual ); + } + + /** + * Data provider for border default controls stabilization scenarios. + * + * @return array[] Test scenarios with input arguments and expected results. + */ + public function data_border_default_controls() { + return array( + 'top-level defaultControls override experimental defaults' => array( + 'input' => array( + 'supports' => array( + '__experimentalBorder' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + '__experimentalSkipSerialization' => true, + '__experimentalDefaultControls' => array( + 'color' => false, + 'radius' => false, + 'style' => false, + 'width' => false, + ), + ), + ), + 'defaultControls' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + ), + ), + 'expected' => array( + 'supports' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + 'skipSerialization' => true, + '__experimentalSkipSerialization' => true, + ), + ), + 'defaultControls' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + ), + ), + ), + 'fall back to experimental defaults when top-level lacks border config' => array( + 'input' => array( + 'supports' => array( + '__experimentalBorder' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + '__experimentalSkipSerialization' => true, + '__experimentalDefaultControls' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + ), + ), + 'defaultControls' => array( + 'typography' => array( + 'fontSize' => true, + ), + ), + ), + 'expected' => array( + 'supports' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + 'skipSerialization' => true, + '__experimentalSkipSerialization' => true, + ), + ), + 'defaultControls' => array( + 'typography' => array( + 'fontSize' => true, + ), + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + ), + ), + ), + 'use experimental defaults when no top-level defaultControls' => array( + 'input' => array( + 'supports' => array( + '__experimentalBorder' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + '__experimentalSkipSerialization' => true, + '__experimentalDefaultControls' => array( + 'color' => false, + 'radius' => false, + 'style' => false, + 'width' => false, + ), + ), + ), + ), + 'expected' => array( + 'supports' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + 'skipSerialization' => true, + '__experimentalSkipSerialization' => true, + ), + ), + 'defaultControls' => array( + 'border' => array( + 'color' => false, + 'radius' => false, + 'style' => false, + 'width' => false, + ), + ), + ), + ), + ); + } + /** * Tests that boolean border support configurations are handled correctly. * From ea9bc9dd9fdc4ca1d06b08f0fefe63c79aeab365 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:33:43 +1000 Subject: [PATCH 2/6] Stabilize __experimentalDefaultControls to top level property in JS --- packages/blocks/src/api/constants.js | 1 - .../blocks/src/store/process-block-type.js | 68 ++++++++++--- .../src/store/test/process-block-type.js | 95 +++++++++---------- 3 files changed, 98 insertions(+), 66 deletions(-) diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index aaf6558c47bada..fd6c60bce5451c 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -303,7 +303,6 @@ export const EXPERIMENTAL_SUPPORTS_MAP = { }; export const COMMON_EXPERIMENTAL_PROPERTIES = { - __experimentalDefaultControls: 'defaultControls', __experimentalSkipSerialization: 'skipSerialization', }; diff --git a/packages/blocks/src/store/process-block-type.js b/packages/blocks/src/store/process-block-type.js index 0ca28a3c3e2070..bc2cec52f987be 100644 --- a/packages/blocks/src/store/process-block-type.js +++ b/packages/blocks/src/store/process-block-type.js @@ -79,6 +79,11 @@ function mergeBlockVariations( function stabilizeSupportConfig( unstableConfig, stableSupportKey ) { const stableConfig = {}; for ( const [ key, value ] of Object.entries( unstableConfig ) ) { + // Skip `__experimentalDefaultControls` as they are handled separately. + if ( key === '__experimentalDefaultControls' ) { + continue; + } + // Get stable key from support-specific map, common properties map, or keep original. const stableKey = EXPERIMENTAL_SUPPORT_PROPERTIES[ stableSupportKey ]?.[ key ] ?? @@ -105,22 +110,29 @@ function stabilizeSupportConfig( unstableConfig, stableSupportKey ) { /** * Stabilizes experimental block supports by converting experimental keys and properties - * to their stable equivalents. + * to their stable equivalents. This includes moving `__experimentalDefaultControls` config + * to a top-level `defaultControls` property if it isn't already present on the block type. * - * @param {Object|undefined} rawSupports The block supports configuration to stabilize. - * @return {Object|undefined} The stabilized block supports configuration. + * @param {WPBlockType|undefined} blockType The block supports configuration to stabilize. + * @return {Object|undefined} The stabilized block supports and default controls configuration. */ -function stabilizeSupports( rawSupports ) { - if ( ! rawSupports ) { - return rawSupports; +function stabilizeSupports( blockType ) { + if ( ! blockType.supports ) { + return { + supports: blockType.supports, + defaultControls: blockType.defaultControls, + }; } + const { supports: rawSupports, defaultControls } = blockType ?? {}; + /* * Create a new object to avoid mutating the original. This ensures that * custom block plugins that rely on immutable supports are not affected. * See: https://github.com/WordPress/gutenberg/pull/66849#issuecomment-2463614281 */ const newSupports = {}; + const newDefaultControls = {}; const done = {}; for ( const [ support, config ] of Object.entries( rawSupports ) ) { @@ -136,6 +148,15 @@ function stabilizeSupports( rawSupports ) { const stableSupportKey = EXPERIMENTAL_SUPPORTS_MAP[ support ] ?? support; + // Extract default controls config as it is being moved to top-level property. + if ( + typeof config === 'object' && + config?.__experimentalDefaultControls + ) { + newDefaultControls[ stableSupportKey ] = + config.__experimentalDefaultControls; + } + /* * Use the support's config as is when it's not in need of stabilization. * A support does not need stabilization if: @@ -190,6 +211,18 @@ function stabilizeSupports( rawSupports ) { ( keyPositions[ support ] ?? Number.MAX_VALUE ) < ( keyPositions[ stableSupportKey ] ?? Number.MAX_VALUE ); + // Extract default controls as they are relocated to top-level property. + const controls = + rawSupports[ stableSupportKey ]?.__experimentalDefaultControls; + if ( controls ) { + newDefaultControls[ stableSupportKey ] = experimentalFirst + ? { ...newDefaultControls[ stableSupportKey ], ...controls } + : { + ...controls, + ...newDefaultControls[ stableSupportKey ], + }; + } + if ( isPlainObject( rawSupports[ stableSupportKey ] ) ) { /* * To merge the alternative support config effectively, it also needs to be @@ -214,7 +247,13 @@ function stabilizeSupports( rawSupports ) { } } - return newSupports; + return { + supports: newSupports, + defaultControls: { + ...newDefaultControls, + ...defaultControls, + }, + }; } /** @@ -258,7 +297,9 @@ export const processBlockType = }; // Stabilize any experimental supports before applying filters. - blockType.supports = stabilizeSupports( blockType.supports ); + let stabilizedConfig = stabilizeSupports( blockType ); + blockType.supports = stabilizedConfig.supports; + blockType.defaultControls = stabilizedConfig.defaultControls; const settings = applyFilters( 'blocks.registerBlockType', @@ -269,7 +310,9 @@ export const processBlockType = // Re-stabilize any experimental supports after applying filters. // This ensures that any supports updated by filters are also stabilized. - blockType.supports = stabilizeSupports( blockType.supports ); + stabilizedConfig = stabilizeSupports( blockType ); + blockType.supports = stabilizedConfig.supports; + blockType.defaultControls = stabilizedConfig.defaultControls; if ( settings.description && @@ -285,7 +328,7 @@ export const processBlockType = // Stabilize any experimental supports before applying filters. let filteredDeprecation = { ...deprecation, - supports: stabilizeSupports( deprecation.supports ), + supports: stabilizeSupports( deprecation ).supports, }; filteredDeprecation = // Only keep valid deprecation keys. @@ -305,9 +348,8 @@ export const processBlockType = ); // Re-stabilize any experimental supports after applying filters. // This ensures that any supports updated by filters are also stabilized. - filteredDeprecation.supports = stabilizeSupports( - filteredDeprecation.supports - ); + filteredDeprecation.supports = + stabilizeSupports( filteredDeprecation ).supports; return Object.fromEntries( Object.entries( filteredDeprecation ).filter( ( [ key ] ) => diff --git a/packages/blocks/src/store/test/process-block-type.js b/packages/blocks/src/store/test/process-block-type.js index 82b2c1ad3080d7..be1c33454ac611 100644 --- a/packages/blocks/src/store/test/process-block-type.js +++ b/packages/blocks/src/store/test/process-block-type.js @@ -77,23 +77,25 @@ describe( 'processBlockType', () => { textTransform: true, textDecoration: true, __experimentalWritingMode: true, - defaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, }, border: { color: true, radius: true, style: true, width: true, - defaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, + }, + } ); + expect( processedBlockType.defaultControls ).toMatchObject( { + typography: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + border: { + color: true, + radius: true, + style: true, + width: true, }, } ); } ); @@ -145,6 +147,11 @@ describe( 'processBlockType', () => { settings.supports.__experimentalBorder = {}; } settings.supports.__experimentalBorder.radius = false; + // This default controls config will be ignored due to initial + // stabilization pass before filters stabilizing the original + // experimental default control values. + settings.supports.__experimentalBorder.__experimentalDefaultControls = + { radius: false }; } return settings; } @@ -166,23 +173,25 @@ describe( 'processBlockType', () => { textTransform: true, textDecoration: true, __experimentalWritingMode: true, - defaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, }, border: { color: true, radius: false, style: true, width: true, - defaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, + }, + } ); + expect( processedBlockType.defaultControls ).toMatchObject( { + typography: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + border: { + color: true, + radius: true, // Matches the original stabilized value before filters. + style: true, + width: true, }, } ); } ); @@ -261,6 +270,8 @@ describe( 'processBlockType', () => { deprecatedBlockSettings )( { select } ); + // Note: defaultControls are stripped for stabilized deprecation supports as + // they aren't needed. expect( processedBlockType.deprecated[ 0 ].supports ).toMatchObject( { typography: { @@ -272,18 +283,6 @@ describe( 'processBlockType', () => { textDecoration: true, __experimentalWritingMode: true, }, - border: { - color: true, - radius: true, - style: true, - width: true, - defaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, } ); } ); @@ -313,6 +312,8 @@ describe( 'processBlockType', () => { deprecatedBlockSettings )( { select } ); + // Note: defaultControls are stripped for stabilized deprecation supports as + // they aren't needed. expect( processedBlockType.deprecated[ 0 ].supports ).toMatchObject( { typography: { @@ -324,18 +325,6 @@ describe( 'processBlockType', () => { textDecoration: true, __experimentalWritingMode: true, }, - border: { - color: true, - radius: false, - style: true, - width: true, - defaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, } ); } ); @@ -370,21 +359,23 @@ describe( 'processBlockType', () => { expect( processedBlockType.supports ).toMatchObject( { typography: { fontSize: true, - defaultControls: { - fontSize: true, - }, skipSerialization: true, __experimentalSkipSerialization: true, }, spacing: { padding: true, - defaultControls: { - padding: true, - }, skipSerialization: true, __experimentalSkipSerialization: true, }, } ); + expect( processedBlockType.defaultControls ).toMatchObject( { + typography: { + fontSize: true, + }, + spacing: { + padding: true, + }, + } ); } ); it( 'should merge experimental and stable keys in order of definition', () => { From 53634790da3afac2a7a7a202bae05b8304f617b5 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:04:20 +1000 Subject: [PATCH 3/6] Add until for retrieving default controls for a block --- packages/blocks/README.md | 10 ++++++++++ packages/blocks/src/api/index.js | 1 + packages/blocks/src/api/registration.js | 16 ++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index f4805e1c60b381..eb06a31c42c7fd 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -167,6 +167,16 @@ _Returns_ - `string`: The block's default class. +### getBlockDefaultControls + +Returns a block type's default controls config for a feature, if defined. + +_Parameters_ + +- _name_ `(string|Object)`: Block name or type object +- _feature_ `string`: Feature to retrieve +- _defaultControls_ `*`: Default config to use if not explicitly defined. + ### getBlockFromExample Create a block object from the example API. diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index a03a58d8f9b21c..f5590a82785d97 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -130,6 +130,7 @@ export { getBlockTypes, getBlockSupport, hasBlockSupport, + getBlockDefaultControls, getBlockVariations, isReusableBlock, isTemplatePart, diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 2f4bab2b5f2589..32925ebc48eea4 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -10,6 +10,7 @@ import warning from '@wordpress/warning'; */ import i18nBlockSchema from './i18n-block.json'; import { store as blocksStore } from '../store'; +import { getValueFromObjectPath } from '../store/utils'; import { unlock } from '../lock-unlock'; /** @@ -553,6 +554,21 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { ); } +/** + * Returns a block type's default controls config for a feature, if defined. + * + * @param {(string|Object)} name Block name or type object + * @param {string} feature Feature to retrieve + * @param {*} defaultControls Default config to use if not explicitly defined. + */ +export function getBlockDefaultControls( name, feature, defaultControls ) { + return getValueFromObjectPath( + select( blocksStore )?.getBlockType( name )?.defaultControls, + feature, + defaultControls + ); +} + /** * Determines whether or not the given block is a reusable block. This is a * special block type that is used to point to a global block stored via the From 15ca5710da9df39b0edb046a9a7a7e132db6f766 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:13:07 +1000 Subject: [PATCH 4/6] Update block supports to use getBlockDefaultControls --- packages/block-editor/src/hooks/border.js | 10 +++++++--- packages/block-editor/src/hooks/color.js | 5 ++--- packages/block-editor/src/hooks/dimensions.js | 8 +++----- packages/block-editor/src/hooks/typography.js | 5 ++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js index 14b3dbf7669b3a..b480c11cf5fe00 100644 --- a/packages/block-editor/src/hooks/border.js +++ b/packages/block-editor/src/hooks/border.js @@ -6,7 +6,11 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { hasBlockSupport, getBlockSupport } from '@wordpress/blocks'; +import { + hasBlockSupport, + getBlockSupport, + getBlockDefaultControls, +} from '@wordpress/blocks'; import { __experimentalHasSplitBorders as hasSplitBorders } from '@wordpress/components'; import { Platform, useCallback, useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; @@ -161,8 +165,8 @@ export function BorderPanel( { clientId, name, setAttributes, settings } ) { } const defaultControls = { - ...getBlockSupport( name, [ BORDER_SUPPORT_KEY, 'defaultControls' ] ), - ...getBlockSupport( name, [ SHADOW_SUPPORT_KEY, 'defaultControls' ] ), + ...getBlockDefaultControls( name, [ BORDER_SUPPORT_KEY ] ), + ...getBlockDefaultControls( name, [ SHADOW_SUPPORT_KEY ] ), }; return ( diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index 2fecc10a311984..9d2b38952e012c 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -7,7 +7,7 @@ import clsx from 'clsx'; * WordPress dependencies */ import { addFilter } from '@wordpress/hooks'; -import { getBlockSupport } from '@wordpress/blocks'; +import { getBlockSupport, getBlockDefaultControls } from '@wordpress/blocks'; import { useMemo, Platform, useCallback } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; @@ -288,9 +288,8 @@ export function ColorEdit( { clientId, name, setAttributes, settings } ) { return null; } - const defaultControls = getBlockSupport( name, [ + const defaultControls = getBlockDefaultControls( name, [ COLOR_SUPPORT_KEY, - 'defaultControls', ] ); const enableContrastChecking = diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index c98cc34e4272c8..54061575dd9943 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -8,7 +8,7 @@ import clsx from 'clsx'; */ import { Platform, useState, useEffect, useCallback } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; -import { getBlockSupport } from '@wordpress/blocks'; +import { getBlockSupport, getBlockDefaultControls } from '@wordpress/blocks'; import deprecated from '@wordpress/deprecated'; /** @@ -86,13 +86,11 @@ export function DimensionsPanel( { clientId, name, setAttributes, settings } ) { return null; } - const defaultDimensionsControls = getBlockSupport( name, [ + const defaultDimensionsControls = getBlockDefaultControls( name, [ DIMENSIONS_SUPPORT_KEY, - 'defaultControls', ] ); - const defaultSpacingControls = getBlockSupport( name, [ + const defaultSpacingControls = getBlockDefaultControls( name, [ SPACING_SUPPORT_KEY, - 'defaultControls', ] ); const defaultControls = { ...defaultDimensionsControls, diff --git a/packages/block-editor/src/hooks/typography.js b/packages/block-editor/src/hooks/typography.js index 160894eac4e610..33bb62a992f56a 100644 --- a/packages/block-editor/src/hooks/typography.js +++ b/packages/block-editor/src/hooks/typography.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; +import { hasBlockSupport, getBlockDefaultControls } from '@wordpress/blocks'; import { useMemo, useCallback } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; @@ -131,9 +131,8 @@ export function TypographyPanel( { clientId, name, setAttributes, settings } ) { return null; } - const defaultControls = getBlockSupport( name, [ + const defaultControls = getBlockDefaultControls( name, [ TYPOGRAPHY_SUPPORT_KEY, - 'defaultControls', ] ); return ( From 06a44a96303ec7b7823ba0737099d009bde78678 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:20:28 +1000 Subject: [PATCH 5/6] Stumble about in the weeds trying to make top level defaultControls work --- lib/compat/wordpress-6.8/blocks.php | 4 +-- packages/block-directory/src/store/actions.js | 1 + packages/blocks/src/api/constants.js | 1 + packages/blocks/src/api/registration.js | 1 + packages/blocks/src/store/reducer.js | 14 ++++++++ phpunit/block-supports/border-test.php | 32 +++++++++---------- 6 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php index 6fd2034b2b0e00..9186538a54198c 100644 --- a/lib/compat/wordpress-6.8/blocks.php +++ b/lib/compat/wordpress-6.8/blocks.php @@ -161,7 +161,7 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { $updated_supports[ $stable_support_key ] = $stable_config; } - $final_default_controls = $args['defaultControls'] ?? array(); + $final_default_controls = $args['default_controls'] ?? array(); foreach ( $default_controls as $support_key => $stabilized_default_controls ) { // Only add experimental controls if no top-level controls exist for this support key. if ( ! isset( $final_default_controls[ $support_key ] ) && ! empty( $stabilized_default_controls ) ) { @@ -171,7 +171,7 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { $args['supports'] = $updated_supports; if ( ! empty( $final_default_controls ) ) { - $args['defaultControls'] = $final_default_controls; + $args['default_controls'] = $final_default_controls; } return $args; diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js index 6e8ec13002acb4..f2d92709d7bc1b 100644 --- a/packages/block-directory/src/store/actions.js +++ b/packages/block-directory/src/store/actions.js @@ -101,6 +101,7 @@ export const installBlockType = 'styles', 'example', 'variations', + 'default_controls', ]; await apiFetch( { path: addQueryArgs( `/wp/v2/block-types/${ name }`, { diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index fd6c60bce5451c..aaf6558c47bada 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -303,6 +303,7 @@ export const EXPERIMENTAL_SUPPORTS_MAP = { }; export const COMMON_EXPERIMENTAL_PROPERTIES = { + __experimentalDefaultControls: 'defaultControls', __experimentalSkipSerialization: 'skipSerialization', }; diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 32925ebc48eea4..fa84a6b15019b6 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -173,6 +173,7 @@ function getBlockSettingsFromMetadata( { textdomain, ...metadata } ) { 'variations', 'blockHooks', 'allowedBlocks', + 'defaultControls', ]; const settings = Object.fromEntries( diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index ac652b91890319..5240109621f150 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -93,6 +93,20 @@ function bootstrappedBlockTypes( state = {}, action ) { allowedBlocks: blockType.allowedBlocks, }; } + + // The `defaultControls` prop is not yet included in the server provided + // definitions and needs to be polyfilled. This can be removed when the + // minimum supported WordPress is >= 6.6. + if ( + serverDefinition.defaultControls === undefined && + blockType.defaultControls + ) { + newDefinition = { + ...serverDefinition, + ...newDefinition, + defaultControls: blockType.defaultControls, + }; + } } else { newDefinition = Object.fromEntries( Object.entries( blockType ) diff --git a/phpunit/block-supports/border-test.php b/phpunit/block-supports/border-test.php index 9e798fe3f2df30..7e7e85ca990f9e 100644 --- a/phpunit/block-supports/border-test.php +++ b/phpunit/block-supports/border-test.php @@ -592,7 +592,7 @@ public function test_should_stabilize_border_supports() { $actual = gutenberg_stabilize_experimental_block_supports( $block_type_args ); $expected = array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -603,7 +603,7 @@ public function test_should_stabilize_border_supports() { '__experimentalSkipSerialization' => true, ), ), - 'defaultControls' => array( + 'default_controls' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -672,7 +672,7 @@ public function test_should_stabilize_border_supports_using_order_based_merge() $actual = gutenberg_stabilize_experimental_block_supports( $experimental_first_args ); $expected = array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -682,7 +682,7 @@ public function test_should_stabilize_border_supports_using_order_based_merge() '__experimentalSkipSerialization' => true, ), ), - 'defaultControls' => array( + 'default_controls' => array( 'border' => array( 'color' => false, 'radius' => false, @@ -702,7 +702,7 @@ public function test_should_stabilize_border_supports_using_order_based_merge() $actual = gutenberg_stabilize_experimental_block_supports( $stable_first_args ); $expected = array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -712,7 +712,7 @@ public function test_should_stabilize_border_supports_using_order_based_merge() '__experimentalSkipSerialization' => false, ), ), - 'defaultControls' => array( + 'default_controls' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -746,7 +746,7 @@ public function data_border_default_controls() { return array( 'top-level defaultControls override experimental defaults' => array( 'input' => array( - 'supports' => array( + 'supports' => array( '__experimentalBorder' => array( 'color' => true, 'radius' => true, @@ -761,7 +761,7 @@ public function data_border_default_controls() { ), ), ), - 'defaultControls' => array( + 'default_controls' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -771,7 +771,7 @@ public function data_border_default_controls() { ), ), 'expected' => array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -781,7 +781,7 @@ public function data_border_default_controls() { '__experimentalSkipSerialization' => true, ), ), - 'defaultControls' => array( + 'default_controls' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -793,7 +793,7 @@ public function data_border_default_controls() { ), 'fall back to experimental defaults when top-level lacks border config' => array( 'input' => array( - 'supports' => array( + 'supports' => array( '__experimentalBorder' => array( 'color' => true, 'radius' => true, @@ -808,14 +808,14 @@ public function data_border_default_controls() { ), ), ), - 'defaultControls' => array( + 'default_controls' => array( 'typography' => array( 'fontSize' => true, ), ), ), 'expected' => array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -825,7 +825,7 @@ public function data_border_default_controls() { '__experimentalSkipSerialization' => true, ), ), - 'defaultControls' => array( + 'default_controls' => array( 'typography' => array( 'fontSize' => true, ), @@ -857,7 +857,7 @@ public function data_border_default_controls() { ), ), 'expected' => array( - 'supports' => array( + 'supports' => array( 'border' => array( 'color' => true, 'radius' => true, @@ -867,7 +867,7 @@ public function data_border_default_controls() { '__experimentalSkipSerialization' => true, ), ), - 'defaultControls' => array( + 'default_controls' => array( 'border' => array( 'color' => false, 'radius' => false, From 0f4914acd5e1d9d6c5db6670fbe81349688a94d8 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:08:03 +1000 Subject: [PATCH 6/6] Try and limit the required core updates --- lib/compat/wordpress-6.8/blocks.php | 20 ++++++ ...enberg-rest-block-types-controller-6-8.php | 68 +++++++++++++++++++ lib/load.php | 1 + 3 files changed, 89 insertions(+) create mode 100644 lib/compat/wordpress-6.8/class-gutenberg-rest-block-types-controller-6-8.php diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php index 9186538a54198c..81010c7c9c1623 100644 --- a/lib/compat/wordpress-6.8/blocks.php +++ b/lib/compat/wordpress-6.8/blocks.php @@ -178,3 +178,23 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { } add_filter( 'register_block_type_args', 'gutenberg_stabilize_experimental_block_supports', PHP_INT_MAX, 1 ); + +/** + * Ensure the `defaultControls` property, set via block.json metadata, or + * stabilized block supports filter, is included within the block type's settings. + * + * Note: This should be removed when the minimum required WP version is >= 6.8. + * + * @param array $settings Current block type settings. + * @param array $metadata Block metadata as read in via block.json. + * + * @return array Filtered block type settings. + */ +function gutenberg_add_default_controls_property_to_block_type_settings( $settings, $metadata ) { + if ( ! isset( $settings['default_controls'] ) && isset( $metadata['defaultControls'] ) ) { + $settings['default_controls'] = $metadata['defaultControls']; + } + return $settings; +} + +add_filter( 'block_type_metadata_settings', 'gutenberg_add_default_controls_property_to_block_type_settings', PHP_INT_MAX, 2 ); diff --git a/lib/compat/wordpress-6.8/class-gutenberg-rest-block-types-controller-6-8.php b/lib/compat/wordpress-6.8/class-gutenberg-rest-block-types-controller-6-8.php new file mode 100644 index 00000000000000..bf413e799ef640 --- /dev/null +++ b/lib/compat/wordpress-6.8/class-gutenberg-rest-block-types-controller-6-8.php @@ -0,0 +1,68 @@ + __( 'UI controls to display by default.' ), + 'type' => 'object', + 'default' => array(), + 'properties' => array(), + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ); + return $schema; + } + + /** + * Add default_controls to the response. + * + * @param WP_Block_Type $block_type Block type object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $block_type, $request ) { + $response = parent::prepare_item_for_response( $block_type, $request ); + $default_controls = $block_type->default_controls ?? $this->default_controls_schema['default']; + $data = $response->get_data(); + $data['default_controls'] = rest_sanitize_value_from_schema( + $default_controls, + array( + 'description' => __( 'UI controls to display by default.' ), + 'type' => 'object', + 'default' => array(), + 'properties' => array(), + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ) + ); + $response->set_data( $data ); + return $response; + } +} + + +add_action( + 'rest_api_init', + function () { + $controller = new Gutenberg_REST_Block_Types_Controller_6_8(); + $controller->register_routes(); + } +); diff --git a/lib/load.php b/lib/load.php index 26af78f3173c53..e2e3f67cd0b417 100644 --- a/lib/load.php +++ b/lib/load.php @@ -43,6 +43,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.8 compat. require __DIR__ . '/compat/wordpress-6.8/block-comments.php'; + require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-block-types-controller-6-8.php'; require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php'; require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-post-types-controller-6-8.php'; require __DIR__ . '/compat/wordpress-6.8/rest-api.php';