From 5c954b8cb0b79cb13b9ee036d381d468c7acdc3d Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:36:01 +1000 Subject: [PATCH 1/8] Use stabilized border support key --- lib/block-supports/border.php | 28 ++++++++-------- lib/class-wp-theme-json-gutenberg.php | 10 +++--- .../global-styles/use-global-styles-output.js | 2 +- packages/block-editor/src/hooks/border.js | 2 +- packages/block-editor/src/hooks/supports.js | 2 +- packages/blocks/src/api/constants.js | 32 +++++++++---------- 6 files changed, 38 insertions(+), 38 deletions(-) diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php index bd4c772675a5e..f890ed84566b7 100644 --- a/lib/block-supports/border.php +++ b/lib/block-supports/border.php @@ -17,7 +17,7 @@ function gutenberg_register_border_support( $block_type ) { $block_type->attributes = array(); } - if ( block_has_support( $block_type, array( '__experimentalBorder' ) ) && ! array_key_exists( 'style', $block_type->attributes ) ) { + if ( block_has_support( $block_type, array( 'border' ) ) && ! array_key_exists( 'style', $block_type->attributes ) ) { $block_type->attributes['style'] = array( 'type' => 'object', ); @@ -52,7 +52,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { if ( gutenberg_has_border_feature_support( $block_type, 'radius' ) && isset( $block_attributes['style']['border']['radius'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'radius' ) + ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'radius' ) ) { $border_radius = $block_attributes['style']['border']['radius']; @@ -67,7 +67,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { if ( gutenberg_has_border_feature_support( $block_type, 'style' ) && isset( $block_attributes['style']['border']['style'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' ) + ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'style' ) ) { $border_block_styles['style'] = $block_attributes['style']['border']['style']; } @@ -76,7 +76,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { if ( $has_border_width_support && isset( $block_attributes['style']['border']['width'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' ) + ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'width' ) ) { $border_width = $block_attributes['style']['border']['width']; @@ -91,7 +91,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { // Border color. if ( $has_border_color_support && - ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' ) + ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'color' ) ) { $preset_border_color = array_key_exists( 'borderColor', $block_attributes ) ? "var:preset|color|{$block_attributes['borderColor']}" : null; $custom_border_color = $block_attributes['style']['border']['color'] ?? null; @@ -103,9 +103,9 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) { $border = $block_attributes['style']['border'][ $side ] ?? null; $border_side_values = array( - 'width' => isset( $border['width'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' ) ? $border['width'] : null, - 'color' => isset( $border['color'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' ) ? $border['color'] : null, - 'style' => isset( $border['style'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' ) ? $border['style'] : null, + 'width' => isset( $border['width'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'width' ) ? $border['width'] : null, + 'color' => isset( $border['color'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'color' ) ? $border['color'] : null, + 'style' => isset( $border['style'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'style' ) ? $border['style'] : null, ); $border_block_styles[ $side ] = $border_side_values; } @@ -129,9 +129,9 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { /** * Checks whether the current block type supports the border feature requested. * - * If the `__experimentalBorder` support flag is a boolean `true` all border + * If the `border` support flag is a boolean `true` all border * support features are available. Otherwise, the specific feature's support - * flag nested under `experimentalBorder` must be enabled for the feature + * flag nested under `border` must be enabled for the feature * to be opted into. * * @param WP_Block_Type $block_type Block type to check for support. @@ -141,17 +141,17 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { * @return boolean Whether or not the feature is supported. */ function gutenberg_has_border_feature_support( $block_type, $feature, $default_value = false ) { - // Check if all border support features have been opted into via `"__experimentalBorder": true`. + // Check if all border support features have been opted into via `"border": true`. if ( $block_type instanceof WP_Block_Type ) { - $block_type_supports_border = $block_type->supports['__experimentalBorder'] ?? $default_value; + $block_type_supports_border = $block_type->supports['border'] ?? $default_value; if ( true === $block_type_supports_border ) { return true; } } // Check if the specific feature has been opted into individually - // via nested flag under `__experimentalBorder`. - return block_has_support( $block_type, array( '__experimentalBorder', $feature ), $default_value ); + // via nested flag under `border`. + return block_has_support( $block_type, array( 'border', $feature ), $default_value ); } // Register the block support. diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index d505916450caf..529c6a715f35c 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -615,10 +615,10 @@ class WP_Theme_JSON_Gutenberg { * @var string[] */ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array( - '__experimentalBorder' => 'border', - 'color' => 'color', - 'spacing' => 'spacing', - 'typography' => 'typography', + 'border' => 'border', + 'color' => 'color', + 'spacing' => 'spacing', + 'typography' => 'typography', ); /** @@ -739,7 +739,7 @@ public static function get_element_class_name( $element ) { * @param string $origin Optional. What source of data this object represents. * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. */ - public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ), $origin = 'theme' ) { + public function __construct( $theme_json = array( 'version' => self::LATEST_SCHEMA ), $origin = 'theme' ) { if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { $origin = 'theme'; } diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index cd4ad0cea50e0..7bdc95d222142 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -47,7 +47,7 @@ const ELEMENT_CLASS_NAMES = { // List of block support features that can have their related styles // generated under their own feature level selector rather than the block's. const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = { - __experimentalBorder: 'border', + border: 'border', color: 'color', spacing: 'spacing', typography: 'typography', diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js index 4ab4c69a41f31..4500444685bef 100644 --- a/packages/block-editor/src/hooks/border.js +++ b/packages/block-editor/src/hooks/border.js @@ -31,7 +31,7 @@ import { import { store as blockEditorStore } from '../store'; import { __ } from '@wordpress/i18n'; -export const BORDER_SUPPORT_KEY = '__experimentalBorder'; +export const BORDER_SUPPORT_KEY = 'border'; export const SHADOW_SUPPORT_KEY = 'shadow'; const getColorByProperty = ( colors, property, value ) => { diff --git a/packages/block-editor/src/hooks/supports.js b/packages/block-editor/src/hooks/supports.js index c0b6bb2cc8b27..102b78bbb96e6 100644 --- a/packages/block-editor/src/hooks/supports.js +++ b/packages/block-editor/src/hooks/supports.js @@ -6,7 +6,7 @@ import { Platform } from '@wordpress/element'; const ALIGN_SUPPORT_KEY = 'align'; const ALIGN_WIDE_SUPPORT_KEY = 'alignWide'; -const BORDER_SUPPORT_KEY = '__experimentalBorder'; +const BORDER_SUPPORT_KEY = 'border'; const COLOR_SUPPORT_KEY = 'color'; const CUSTOM_CLASS_NAME_SUPPORT_KEY = 'customClassName'; const FONT_FAMILY_SUPPORT_KEY = 'typography.fontFamily'; diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 85a359af754db..c9949aa85bd3a 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -58,12 +58,12 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, borderColor: { value: [ 'border', 'color' ], - support: [ '__experimentalBorder', 'color' ], + support: [ 'border', 'color' ], useEngine: true, }, borderRadius: { value: [ 'border', 'radius' ], - support: [ '__experimentalBorder', 'radius' ], + support: [ 'border', 'radius' ], properties: { borderTopLeftRadius: 'topLeft', borderTopRightRadius: 'topRight', @@ -74,72 +74,72 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, borderStyle: { value: [ 'border', 'style' ], - support: [ '__experimentalBorder', 'style' ], + support: [ 'border', 'style' ], useEngine: true, }, borderWidth: { value: [ 'border', 'width' ], - support: [ '__experimentalBorder', 'width' ], + support: [ 'border', 'width' ], useEngine: true, }, borderTopColor: { value: [ 'border', 'top', 'color' ], - support: [ '__experimentalBorder', 'color' ], + support: [ 'border', 'color' ], useEngine: true, }, borderTopStyle: { value: [ 'border', 'top', 'style' ], - support: [ '__experimentalBorder', 'style' ], + support: [ 'border', 'style' ], useEngine: true, }, borderTopWidth: { value: [ 'border', 'top', 'width' ], - support: [ '__experimentalBorder', 'width' ], + support: [ 'border', 'width' ], useEngine: true, }, borderRightColor: { value: [ 'border', 'right', 'color' ], - support: [ '__experimentalBorder', 'color' ], + support: [ 'border', 'color' ], useEngine: true, }, borderRightStyle: { value: [ 'border', 'right', 'style' ], - support: [ '__experimentalBorder', 'style' ], + support: [ 'border', 'style' ], useEngine: true, }, borderRightWidth: { value: [ 'border', 'right', 'width' ], - support: [ '__experimentalBorder', 'width' ], + support: [ 'border', 'width' ], useEngine: true, }, borderBottomColor: { value: [ 'border', 'bottom', 'color' ], - support: [ '__experimentalBorder', 'color' ], + support: [ 'border', 'color' ], useEngine: true, }, borderBottomStyle: { value: [ 'border', 'bottom', 'style' ], - support: [ '__experimentalBorder', 'style' ], + support: [ 'border', 'style' ], useEngine: true, }, borderBottomWidth: { value: [ 'border', 'bottom', 'width' ], - support: [ '__experimentalBorder', 'width' ], + support: [ 'border', 'width' ], useEngine: true, }, borderLeftColor: { value: [ 'border', 'left', 'color' ], - support: [ '__experimentalBorder', 'color' ], + support: [ 'border', 'color' ], useEngine: true, }, borderLeftStyle: { value: [ 'border', 'left', 'style' ], - support: [ '__experimentalBorder', 'style' ], + support: [ 'border', 'style' ], useEngine: true, }, borderLeftWidth: { value: [ 'border', 'left', 'width' ], - support: [ '__experimentalBorder', 'width' ], + support: [ 'border', 'width' ], useEngine: true, }, color: { From 03f745ddccf5df71bcf32b7db7f447afc3b9cd4c Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:22:03 +1000 Subject: [PATCH 2/8] Update stabilization approach to include border support --- lib/class-wp-theme-json-gutenberg.php | 2 +- lib/compat/wordpress-6.8/blocks.php | 83 ++++++++++++++----- packages/blocks/src/api/constants.js | 17 ++-- .../blocks/src/store/process-block-type.js | 74 +++++++++++++---- 4 files changed, 131 insertions(+), 45 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 529c6a715f35c..14b8ed7ca238f 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -739,7 +739,7 @@ public static function get_element_class_name( $element ) { * @param string $origin Optional. What source of data this object represents. * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. */ - public function __construct( $theme_json = array( 'version' => self::LATEST_SCHEMA ), $origin = 'theme' ) { + public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ), $origin = 'theme' ) { if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { $origin = 'theme'; } diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php index 3124dd2a12a61..b057df30158ec 100644 --- a/lib/compat/wordpress-6.8/blocks.php +++ b/lib/compat/wordpress-6.8/blocks.php @@ -6,40 +6,85 @@ */ /** - * Filters the block type arguments during registration to stabilize experimental block supports. + * Filters the block type arguments during registration to stabilize + * experimental block supports. * - * This is a temporary compatibility shim as the approach in core is for this to be handled - * within the WP_Block_Type class rather than requiring a filter. + * This is a temporary compatibility shim as the approach in core is for this + * to be handled within the WP_Block_Type class rather than requiring a filter. * * @param array $args Array of arguments for registering a block type. * @return array Array of arguments for registering a block type. */ function gutenberg_stabilize_experimental_block_supports( $args ) { - if ( empty( $args['supports']['typography'] ) ) { + if ( empty( $args['supports'] ) ) { return $args; } - $experimental_typography_supports_to_stable = array( - '__experimentalFontFamily' => 'fontFamily', - '__experimentalFontStyle' => 'fontStyle', - '__experimentalFontWeight' => 'fontWeight', - '__experimentalLetterSpacing' => 'letterSpacing', - '__experimentalTextDecoration' => 'textDecoration', - '__experimentalTextTransform' => 'textTransform', + $experimental_to_stable_keys = array( + 'typography' => array( + '__experimentalFontFamily' => 'fontFamily', + '__experimentalFontStyle' => 'fontStyle', + '__experimentalFontWeight' => 'fontWeight', + '__experimentalLetterSpacing' => 'letterSpacing', + '__experimentalTextDecoration' => 'textDecoration', + '__experimentalTextTransform' => 'textTransform', + ), + '__experimentalBorder' => 'border', ); - $current_typography_supports = $args['supports']['typography']; - $stable_typography_supports = array(); + $updated_supports = array(); + foreach ( $args['supports'] as $support => $config ) { + // Add the support's config as is when it's not in need of stabilization. + if ( empty( $experimental_to_stable_keys[ $support ] ) ) { + $updated_supports[ $support ] = $config; + continue; + } + + // Stabilize the support's key if needed e.g. __experimentalBorder => border. + if ( is_string( $experimental_to_stable_keys[ $support ] ) ) { + $stabilized_key = $experimental_to_stable_keys[ $support ]; + + // If there is no stabilized key present, use the experimental config as is. + if ( ! array_key_exists( $stabilized_key, $args['supports'] ) ) { + $updated_supports[ $stabilized_key ] = $config; + continue; + } + + // Determine the order of keys, so the last defined can be preferred. + $key_positions = array_flip( array_keys( $args['supports'] ) ); + $experimental_index = $key_positions[ $support ] ?? -1; + $stabilized_index = $key_positions[ $stabilized_key ] ?? -1; + $experimental_first = $experimental_index < $stabilized_index; + + // Update support config, prefer the last defined value. + if ( is_array( $config ) ) { + $updated_supports[ $stabilized_key ] = $experimental_first + ? array_merge( $config, $args['supports'][ $stabilized_key ] ) + : array_merge( $args['supports'][ $stabilized_key ], $config ); + } else { + $updated_supports[ $stabilized_key ] = $experimental_first + ? $args['supports'][ $stabilized_key ] + : $config; + } + + continue; + } - foreach ( $current_typography_supports as $key => $value ) { - if ( array_key_exists( $key, $experimental_typography_supports_to_stable ) ) { - $stable_typography_supports[ $experimental_typography_supports_to_stable[ $key ] ] = $value; - } else { - $stable_typography_supports[ $key ] = $value; + // Stabilize individual support feature keys e.g. __experimentalFontFamily => fontFamily. + if ( is_array( $experimental_to_stable_keys[ $support ] ) ) { + $stable_support_config = array(); + foreach ( $config as $key => $value ) { + if ( array_key_exists( $key, $experimental_to_stable_keys[ $support ] ) ) { + $stable_support_config[ $experimental_to_stable_keys[ $support ][ $key ] ] = $value; + } else { + $stable_support_config[ $key ] = $value; + } + } + $updated_supports[ $support ] = $stable_support_config; } } - $args['supports']['typography'] = $stable_typography_supports; + $args['supports'] = $updated_supports; return $args; } diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index c9949aa85bd3a..8ea32d591adb3 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -298,11 +298,14 @@ export const __EXPERIMENTAL_PATHS_WITH_OVERRIDE = { 'spacing.spacingSizes': true, }; -export const TYPOGRAPHY_SUPPORTS_EXPERIMENTAL_TO_STABLE = { - __experimentalFontFamily: 'fontFamily', - __experimentalFontStyle: 'fontStyle', - __experimentalFontWeight: 'fontWeight', - __experimentalLetterSpacing: 'letterSpacing', - __experimentalTextDecoration: 'textDecoration', - __experimentalTextTransform: 'textTransform', +export const EXPERIMENTAL_TO_STABLE_KEYS = { + typography: { + __experimentalFontFamily: 'fontFamily', + __experimentalFontStyle: 'fontStyle', + __experimentalFontWeight: 'fontWeight', + __experimentalLetterSpacing: 'letterSpacing', + __experimentalTextDecoration: 'textDecoration', + __experimentalTextTransform: 'textTransform', + }, + __experimentalBorder: 'border', }; diff --git a/packages/blocks/src/store/process-block-type.js b/packages/blocks/src/store/process-block-type.js index 98fb044f8faad..e68f4c3d57637 100644 --- a/packages/blocks/src/store/process-block-type.js +++ b/packages/blocks/src/store/process-block-type.js @@ -18,7 +18,7 @@ import { isValidIcon, normalizeIconObject, omit } from '../api/utils'; import { BLOCK_ICON_DEFAULT, DEPRECATED_ENTRY_KEYS, - TYPOGRAPHY_SUPPORTS_EXPERIMENTAL_TO_STABLE, + EXPERIMENTAL_TO_STABLE_KEYS, } from '../api/constants'; /** @typedef {import('../api/registration').WPBlockType} WPBlockType */ @@ -75,24 +75,62 @@ function stabilizeSupports( rawSupports ) { // custom block plugins that rely on immutable supports are not affected. // See: https://github.com/WordPress/gutenberg/pull/66849#issuecomment-2463614281 const newSupports = {}; - for ( const [ key, value ] of Object.entries( rawSupports ) ) { - if ( - key === 'typography' && - typeof value === 'object' && - value !== null - ) { - newSupports.typography = Object.fromEntries( - Object.entries( value ).map( - ( [ typographyKey, typographyValue ] ) => [ - TYPOGRAPHY_SUPPORTS_EXPERIMENTAL_TO_STABLE[ - typographyKey - ] || typographyKey, - typographyValue, - ] - ) + + for ( const [ support, config ] of Object.entries( rawSupports ) ) { + // Add the current support's config as is if it does not need stabilization. + if ( ! EXPERIMENTAL_TO_STABLE_KEYS[ support ] ) { + newSupports[ support ] = config; + continue; + } + + // Stabilize the support's key if needed e.g. __experimentalBorder => border. + if ( typeof EXPERIMENTAL_TO_STABLE_KEYS[ support ] === 'string' ) { + const stabilizedKey = EXPERIMENTAL_TO_STABLE_KEYS[ support ]; + + // If there's no stabilized key present, just use the config as is. + if ( ! Object.hasOwn( rawSupports, stabilizedKey ) ) { + newSupports[ stabilizedKey ] = config; + continue; + } + + // Determine the insertion order for both key. + const entries = Object.entries( rawSupports ); + const experimentalIndex = entries.findIndex( + ( [ key ] ) => key === support ); - } else { - newSupports[ key ] = value; + const stabilizedIndex = entries.findIndex( + ( [ key ] ) => key === stabilizedKey + ); + + if ( typeof config === 'object' && config !== null ) { + newSupports[ stabilizedKey ] = + experimentalIndex < stabilizedIndex + ? { ...config, ...rawSupports[ stabilizedKey ] } + : { ...rawSupports[ stabilizedKey ], ...config }; + } else { + newSupports[ stabilizedKey ] = + experimentalIndex < stabilizedIndex + ? rawSupports[ stabilizedKey ] + : config; + } + continue; + } + + // Stabilize individual support feature keys + // e.g. __experimentalFontFamily => fontFamily. + const featureStabilizationRequired = + typeof EXPERIMENTAL_TO_STABLE_KEYS[ support ] === 'object' && + EXPERIMENTAL_TO_STABLE_KEYS[ support ] !== null; + const hasConfig = typeof config === 'object' && config !== null; + + if ( featureStabilizationRequired && hasConfig ) { + const stableConfig = {}; + for ( const [ key, value ] of Object.entries( config ) ) { + const stableKey = + EXPERIMENTAL_TO_STABLE_KEYS[ support ][ key ] || key; + stableConfig[ stableKey ] = value; + } + newSupports[ support ] = stableConfig; } } From 0377da1ec726729790a87075b2c1da408bf97809 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:28:09 +1000 Subject: [PATCH 3/8] Add unit tests for border support stabilization --- phpunit/block-supports/border-test.php | 108 +++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/phpunit/block-supports/border-test.php b/phpunit/block-supports/border-test.php index 858e4e92cc174..0c320f24ebe4f 100644 --- a/phpunit/block-supports/border-test.php +++ b/phpunit/block-supports/border-test.php @@ -457,6 +457,114 @@ public function test_split_borders_with_named_colors() { 'style' => 'border-top-width:2px;border-top-color:var(--wp--preset--color--red);border-top-style:dashed;border-right-width:0.25rem;border-right-color:var(--wp--preset--color--green);border-right-style:dotted;border-bottom-width:0.5em;border-bottom-color:var(--wp--preset--color--blue);border-bottom-style:solid;border-left-width:1px;border-left-color:var(--wp--preset--color--yellow);border-left-style:solid;', ); + $this->assertSame( $expected, $actual ); + } + /** + * Tests that stabilized border supports will also apply to blocks using + * the experimental syntax, for backwards compatibility with existing blocks. + * + * @covers ::gutenberg_apply_border_support + */ + public function test_should_apply_experimental_border_supports() { + $this->test_block_name = 'test/experimental-border-supports'; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + '__experimentalBorder' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + '__experimentalDefaultControls' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $this->test_block_name ); + $block_atts = array( + 'style' => array( + 'border' => array( + 'color' => '#72aee6', + 'radius' => '10px', + 'style' => 'dashed', + 'width' => '2px', + ), + ), + ); + + $actual = gutenberg_apply_border_support( $block_type, $block_atts ); + $expected = array( + 'class' => 'has-border-color', + 'style' => 'border-color:#72aee6;border-radius:10px;border-style:dashed;border-width:2px;', + ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Tests that stabilized border supports are applied correctly. + * + * @covers ::gutenberg_apply_border_support + */ + public function test_should_apply_stabilized_border_supports() { + $this->test_block_name = 'test/stabilized-border-supports'; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + '__experimentalDefaultControls' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $this->test_block_name ); + $block_atts = array( + 'style' => array( + 'border' => array( + 'color' => '#72aee6', + 'radius' => '10px', + 'style' => 'dashed', + 'width' => '2px', + ), + ), + ); + + $actual = gutenberg_apply_border_support( $block_type, $block_atts ); + $expected = array( + 'class' => 'has-border-color', + 'style' => 'border-color:#72aee6;border-radius:10px;border-style:dashed;border-width:2px;', + ); + $this->assertSame( $expected, $actual ); } } From 50caa72c61698f5b2020068e70fa8d6237c709d0 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:23:59 +1000 Subject: [PATCH 4/8] Update processBlockType tests for border stabilization --- .../src/store/test/process-block-type.js | 273 ++++++++++++++---- 1 file changed, 214 insertions(+), 59 deletions(-) diff --git a/packages/blocks/src/store/test/process-block-type.js b/packages/blocks/src/store/test/process-block-type.js index c82d08cc45d65..c7f487a95301d 100644 --- a/packages/blocks/src/store/test/process-block-type.js +++ b/packages/blocks/src/store/test/process-block-type.js @@ -26,7 +26,7 @@ describe( 'processBlockType', () => { removeFilter( 'blocks.registerBlockType', 'test/filterSupports' ); } ); - it( 'should return the block type with stabilized typography supports', () => { + it( 'should return the block type with stabilized supports', () => { const blockSettings = { ...baseBlockSettings, supports: { @@ -46,6 +46,18 @@ describe( 'processBlockType', () => { textTransform: true, }, }, + __experimentalBorder: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, }, }; @@ -54,25 +66,39 @@ describe( 'processBlockType', () => { blockSettings )( { select } ); - expect( processedBlockType.supports.typography ).toEqual( { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { + expect( processedBlockType.supports ).toEqual( { + typography: { fontSize: true, - fontAppearance: true, + lineHeight: true, + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + }, + border: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, }, } ); } ); - it( 'should return the block type with stable typography supports', () => { + it( 'should return the block type with stable supports', () => { const blockSettings = { ...baseBlockSettings, supports: { @@ -92,6 +118,18 @@ describe( 'processBlockType', () => { textTransform: true, }, }, + __experimentalBorder: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, }, }; @@ -100,20 +138,34 @@ describe( 'processBlockType', () => { blockSettings )( { select } ); - expect( processedBlockType.supports.typography ).toEqual( { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { + expect( processedBlockType.supports ).toEqual( { + typography: { fontSize: true, - fontAppearance: true, + lineHeight: true, + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + }, + border: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, }, } ); } ); @@ -138,6 +190,18 @@ describe( 'processBlockType', () => { textTransform: true, }, }, + __experimentalBorder: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, }, }; @@ -149,6 +213,10 @@ describe( 'processBlockType', () => { settings.supports.typography.__experimentalFontFamily = false; settings.supports.typography.__experimentalFontStyle = false; settings.supports.typography.__experimentalFontWeight = false; + if ( ! settings.supports.__experimentalBorder ) { + settings.supports.__experimentalBorder = {}; + } + settings.supports.__experimentalBorder.radius = false; } return settings; } @@ -159,25 +227,39 @@ describe( 'processBlockType', () => { blockSettings )( { select } ); - expect( processedBlockType.supports.typography ).toEqual( { - fontSize: true, - lineHeight: true, - fontFamily: false, - fontStyle: false, - fontWeight: false, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { + expect( processedBlockType.supports ).toEqual( { + typography: { fontSize: true, - fontAppearance: true, + lineHeight: true, + fontFamily: false, + fontStyle: false, + fontWeight: false, + letterSpacing: true, textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + }, + border: { + color: true, + radius: false, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, }, } ); } ); - it( 'should stabilize experimental typography supports within block deprecations', () => { + it( 'should stabilize experimental supports within block deprecations', () => { const blockSettings = { ...baseBlockSettings, supports: { @@ -197,6 +279,18 @@ describe( 'processBlockType', () => { textTransform: true, }, }, + border: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, }, deprecated: [ { @@ -210,6 +304,18 @@ describe( 'processBlockType', () => { __experimentalTextDecoration: true, __experimentalWritingMode: true, }, + __experimentalBorder: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, }, }, ], @@ -226,16 +332,28 @@ describe( 'processBlockType', () => { blockSettings )( { select } ); - expect( - processedBlockType.deprecated[ 0 ].supports.typography - ).toEqual( { - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, + expect( processedBlockType.deprecated[ 0 ].supports ).toEqual( { + typography: { + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + }, + border: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, } ); } ); @@ -259,6 +377,18 @@ describe( 'processBlockType', () => { textTransform: true, }, }, + border: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, }, deprecated: [ { @@ -272,6 +402,18 @@ describe( 'processBlockType', () => { __experimentalTextDecoration: true, __experimentalWritingMode: true, }, + __experimentalBorder: { + color: true, + radius: true, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, }, }, ], @@ -285,6 +427,7 @@ describe( 'processBlockType', () => { settings.supports.typography.__experimentalFontFamily = false; settings.supports.typography.__experimentalFontStyle = false; settings.supports.typography.__experimentalFontWeight = false; + settings.supports.__experimentalBorder = { radius: false }; } return settings; } @@ -295,16 +438,28 @@ describe( 'processBlockType', () => { blockSettings )( { select } ); - expect( - processedBlockType.deprecated[ 0 ].supports.typography - ).toEqual( { - fontFamily: false, - fontStyle: false, - fontWeight: false, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, + expect( processedBlockType.deprecated[ 0 ].supports ).toEqual( { + typography: { + fontFamily: false, + fontStyle: false, + fontWeight: false, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + }, + border: { + color: true, + radius: false, + style: true, + width: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, } ); } ); } ); From 32df8885c88682193947e185a66aede6ae241aa7 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:00:04 +1000 Subject: [PATCH 5/8] Fix global styles unit test --- .../components/global-styles/test/use-global-styles-output.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index 5022e8ba591db..93e5cc9afdbb3 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -855,7 +855,7 @@ describe( 'global styles renderer', () => { it( 'should return block selectors data with old experimental selectors', () => { const imageSupports = { - __experimentalBorder: { + border: { radius: true, __experimentalSelector: 'img, .crop-area', }, From 611de98df6a3345f6bb2b6810cde873b92d56b74 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:44:58 +1000 Subject: [PATCH 6/8] Make comments match between JS and PHP stabilizations --- packages/blocks/src/store/process-block-type.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/blocks/src/store/process-block-type.js b/packages/blocks/src/store/process-block-type.js index e68f4c3d57637..deb01578f0381 100644 --- a/packages/blocks/src/store/process-block-type.js +++ b/packages/blocks/src/store/process-block-type.js @@ -77,7 +77,7 @@ function stabilizeSupports( rawSupports ) { const newSupports = {}; for ( const [ support, config ] of Object.entries( rawSupports ) ) { - // Add the current support's config as is if it does not need stabilization. + // Add the support's config as is when it's not in need of stabilization. if ( ! EXPERIMENTAL_TO_STABLE_KEYS[ support ] ) { newSupports[ support ] = config; continue; @@ -87,13 +87,13 @@ function stabilizeSupports( rawSupports ) { if ( typeof EXPERIMENTAL_TO_STABLE_KEYS[ support ] === 'string' ) { const stabilizedKey = EXPERIMENTAL_TO_STABLE_KEYS[ support ]; - // If there's no stabilized key present, just use the config as is. + // If there is no stabilized key present, use the experimental config as is. if ( ! Object.hasOwn( rawSupports, stabilizedKey ) ) { newSupports[ stabilizedKey ] = config; continue; } - // Determine the insertion order for both key. + // Determine the order of keys, so the last defined can be preferred. const entries = Object.entries( rawSupports ); const experimentalIndex = entries.findIndex( ( [ key ] ) => key === support @@ -102,6 +102,7 @@ function stabilizeSupports( rawSupports ) { ( [ key ] ) => key === stabilizedKey ); + // Update support config, prefer the last defined value. if ( typeof config === 'object' && config !== null ) { newSupports[ stabilizedKey ] = experimentalIndex < stabilizedIndex From f51cbc00daeb05c4a48451f334344c01cbc488e9 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:46:09 +1000 Subject: [PATCH 7/8] Add border support stabilization to existing backport log --- backport-changelog/6.8/7069.md | 1 + 1 file changed, 1 insertion(+) diff --git a/backport-changelog/6.8/7069.md b/backport-changelog/6.8/7069.md index ea3c717ec3c93..8b4ad69db02e7 100644 --- a/backport-changelog/6.8/7069.md +++ b/backport-changelog/6.8/7069.md @@ -1,3 +1,4 @@ https://github.com/WordPress/wordpress-develop/pull/7069 * https://github.com/WordPress/gutenberg/pull/63401 +* https://github.com/WordPress/gutenberg/pull/66918 From 9c32c4ae41e6fb1bdae0044c622c9c2514f1f3fc Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:17:04 +1000 Subject: [PATCH 8/8] Try improving comments around importance of order --- lib/compat/wordpress-6.8/blocks.php | 11 ++++++++++- packages/blocks/src/store/process-block-type.js | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php index b057df30158ec..0b7031403b191 100644 --- a/lib/compat/wordpress-6.8/blocks.php +++ b/lib/compat/wordpress-6.8/blocks.php @@ -50,7 +50,16 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { continue; } - // Determine the order of keys, so the last defined can be preferred. + /* + * Determine the order of keys, so the last defined can be preferred. + * + * The reason for preferring the last defined key is that after filters + * are applied, the last inserted key is likely the most up-to-date value. + * We cannot determine with certainty which value was "last modified" so + * the insertion order is the best guess. The extreme edge case of multiple + * filters tweaking the same support property will become less over time as + * extenders migrate existing blocks and plugins to stable keys. + */ $key_positions = array_flip( array_keys( $args['supports'] ) ); $experimental_index = $key_positions[ $support ] ?? -1; $stabilized_index = $key_positions[ $stabilized_key ] ?? -1; diff --git a/packages/blocks/src/store/process-block-type.js b/packages/blocks/src/store/process-block-type.js index deb01578f0381..bd7e5ab43800f 100644 --- a/packages/blocks/src/store/process-block-type.js +++ b/packages/blocks/src/store/process-block-type.js @@ -93,7 +93,16 @@ function stabilizeSupports( rawSupports ) { continue; } - // Determine the order of keys, so the last defined can be preferred. + /* + * Determine the order of keys, so the last defined can be preferred. + * + * The reason for preferring the last defined key is that after filters + * are applied, the last inserted key is likely the most up-to-date value. + * We cannot determine with certainty which value was "last modified" so + * the insertion order is the best guess. The extreme edge case of multiple + * filters tweaking the same support property will become less over time as + * extenders migrate existing blocks and plugins to stable keys. + */ const entries = Object.entries( rawSupports ); const experimentalIndex = entries.findIndex( ( [ key ] ) => key === support