From a65a9f5ac524755e5bedd4075a70a2bf50edddcf Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:27:04 +1000 Subject: [PATCH 01/25] Allow block style variations to be registered across multiple block types --- lib/compat/wordpress-6.5/blocks.php | 41 +++++++++++++++++++++++++++++ lib/load.php | 1 + 2 files changed, 42 insertions(+) create mode 100644 lib/compat/wordpress-6.5/blocks.php diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php new file mode 100644 index 0000000000000..b1a8b4762986e --- /dev/null +++ b/lib/compat/wordpress-6.5/blocks.php @@ -0,0 +1,41 @@ +register( $name, $style_properties ) ) { + $result = false; + } + } + + return $result; +} diff --git a/lib/load.php b/lib/load.php index d413334227ee7..e5788e924f9d8 100644 --- a/lib/load.php +++ b/lib/load.php @@ -101,6 +101,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.4/kses.php'; // WordPress 6.5 compat. +require __DIR__ . '/compat/wordpress-6.5/blocks.php'; require __DIR__ . '/compat/wordpress-6.5/block-patterns.php'; require __DIR__ . '/compat/wordpress-6.5/class-wp-navigation-block-renderer.php'; require __DIR__ . '/compat/wordpress-6.5/kses.php'; From 8da9d1f4381d064c1b2e3da2ee3de26c0beb0427 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:29:16 +1000 Subject: [PATCH 02/25] Extend theme.json schema for extended block style variations Block style variations can now also contain inner block type and element styles. Currently the inner block styles are not recursive in that they do not support nested style variations. --- schemas/json/theme.json | 389 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 386 insertions(+), 3 deletions(-) diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 10695f493c40d..c56f1a84bae0e 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -2194,20 +2194,403 @@ "$ref": "#/definitions/stylesElementsPropertiesComplete" }, "variations": { - "$ref": "#/definitions/stylesVariationPropertiesComplete" + "$ref": "#/definitions/stylesVariationsPropertiesComplete" } }, "additionalProperties": false } ] }, - "stylesVariationPropertiesComplete": { + "stylesVariationsPropertiesComplete": { "type": "object", "patternProperties": { "^[a-z][a-z0-9-]*$": { - "$ref": "#/definitions/stylesPropertiesComplete" + "$ref": "#/definitions/stylesVariationPropertiesComplete" } } + }, + "stylesVariationPropertiesComplete": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stylesProperties" + }, + { + "properties": { + "border": {}, + "color": {}, + "dimensions": {}, + "spacing": {}, + "typography": {}, + "filter": {}, + "shadow": {}, + "outline": {}, + "css": {}, + "elements": { + "$ref": "#/definitions/stylesElementsPropertiesComplete" + }, + "blocks": { + "$ref": "#/definitions/stylesVariationBlocksPropertiesComplete" + } + }, + "additionalProperties": false + } + ] + }, + "stylesVariationBlocksPropertiesComplete": { + "type": "object", + "properties": { + "core/archives": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/audio": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/avatar": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/block": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/button": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/buttons": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/calendar": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/categories": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/code": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/column": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/columns": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comment-author-avatar": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comment-author-name": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comment-content": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comment-date": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comment-edit-link": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comment-reply-link": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comments": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comments-pagination": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comments-pagination-next": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comments-pagination-numbers": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comments-pagination-previous": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comments-title": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/comment-template": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/cover": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/details": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/embed": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/file": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/freeform": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/gallery": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/group": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/heading": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/home-link": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/html": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/image": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/latest-comments": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/latest-posts": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/list": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/list-item": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/loginout": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/media-text": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/missing": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/more": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/navigation": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/navigation-link": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/navigation-submenu": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/nextpage": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/page-list": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/page-list-item": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/paragraph": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-author": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-author-biography": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-author-name": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-comment": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-comments-count": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-comments-form": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-comments-link": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-content": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-date": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-excerpt": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-featured-image": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-navigation-link": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-template": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-terms": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-time-to-read": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/post-title": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/preformatted": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/pullquote": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/query": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/query-no-results": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/query-pagination": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/query-pagination-next": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/query-pagination-numbers": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/query-pagination-previous": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/query-title": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/quote": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/read-more": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/rss": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/search": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/separator": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/shortcode": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/site-logo": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/site-tagline": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/site-title": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/social-link": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/social-links": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/spacer": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/table": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/table-of-contents": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/tag-cloud": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/template-part": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/term-description": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/text-columns": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/verse": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/video": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/widget-area": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/legacy-widget": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, + "core/widget-group": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + } + }, + "patternProperties": { + "^[a-z][a-z0-9-]*/[a-z][a-z0-9-]*$": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + } + }, + "additionalProperties": false + }, + "stylesVariationBlockPropertiesComplete": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stylesProperties" + }, + { + "properties": { + "border": {}, + "color": {}, + "dimensions": {}, + "spacing": {}, + "typography": {}, + "filter": {}, + "shadow": {}, + "outline": {}, + "css": {}, + "elements": { + "$ref": "#/definitions/stylesElementsPropertiesComplete" + }, + "blocks": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stylesProperties" + }, + { + "properties": { + "border": {}, + "color": {}, + "dimensions": {}, + "spacing": {}, + "typography": {}, + "filter": {}, + "shadow": {}, + "outline": {}, + "css": {}, + "elements": { + "$ref": "#/definitions/stylesElementsPropertiesComplete" + } + }, + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + ] } }, "type": "object", From 01d1843c926e985cb22d744580500d8f82360777 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:36:00 +1000 Subject: [PATCH 03/25] Update PHP processing of block style variations in theme.json Includes: - absorbing block style variations from the block styles registry into theme.json - updating metadata and stylesheet generation from variations to support inner block and element styles --- lib/class-wp-theme-json-gutenberg.php | 205 +++++++++++++++--- ...class-wp-theme-json-resolver-gutenberg.php | 150 +++++++------ phpunit/class-wp-theme-json-resolver-test.php | 77 +++++++ phpunit/class-wp-theme-json-test.php | 201 ++++++++++++++++- 4 files changed, 537 insertions(+), 96 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index aa8de83df9597..aadf704647582 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -844,6 +844,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n foreach ( $valid_block_names as $block ) { // Build the schema for each block style variation. $style_variation_names = array(); + if ( ! empty( $input['styles']['blocks'][ $block ]['variations'] ) && is_array( $input['styles']['blocks'][ $block ]['variations'] ) && @@ -855,9 +856,14 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n ); } + $schema_styles_variations = array(); + $block_style_variation_styles = static::VALID_STYLES; + $block_style_variation_styles['blocks'] = null; + $block_style_variation_styles['elements'] = null; + $schema_styles_variations = array(); if ( ! empty( $style_variation_names ) ) { - $schema_styles_variations = array_fill_keys( $style_variation_names, $styles_non_top_level ); + $schema_styles_variations = array_fill_keys( $style_variation_names, $block_style_variation_styles ); } $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; @@ -980,12 +986,34 @@ protected static function prepend_to_selector( $selector, $to_prepend ) { */ protected static function get_blocks_metadata() { // NOTE: the compat/6.1 version of this method in Gutenberg did not have these changes. - $registry = WP_Block_Type_Registry::get_instance(); - $blocks = $registry->get_all_registered(); + $registry = WP_Block_Type_Registry::get_instance(); + $blocks = $registry->get_all_registered(); + $style_registry = WP_Block_Styles_Registry::get_instance(); // Is there metadata for all currently registered blocks? $blocks = array_diff_key( $blocks, static::$blocks_metadata ); + if ( empty( $blocks ) ) { + // New block styles may have been registered within WP_Block_Styles_Registry. + // Update block metadata for any new block style variations. + $registered_styles = $style_registry->get_all_registered(); + foreach ( static::$blocks_metadata as $block_name => $block_metadata ) { + if ( ! empty( $registered_styles[ $block_name ] ) ) { + $style_selectors = $block_metadata['styleVariations'] ?? array(); + + foreach ( $registered_styles[ $block_name ] as $block_style ) { + if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { + $style_selectors[ $block_style['name'] ] = static::append_to_selector( + '.is-style-' . $block_style['name'] . '.is-style-' . $block_style['name'], + $block_metadata['selector'] + ); + } + } + + static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; + } + } + return static::$blocks_metadata; } @@ -1009,7 +1037,7 @@ protected static function get_blocks_metadata() { if ( $duotone_support ) { $root_selector = wp_get_block_css_selector( $block_type ); - $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); + $duotone_selector = static::scope_selector( $root_selector, $duotone_support ); } } @@ -1018,11 +1046,20 @@ protected static function get_blocks_metadata() { } // If the block has style variations, append their selectors to the block metadata. + $style_selectors = array(); if ( ! empty( $block_type->styles ) ) { - $style_selectors = array(); foreach ( $block_type->styles as $style ) { $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } + } + + // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. + $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); + foreach ( $registered_styles as $style ) { + $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'] . '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + } + + if ( ! empty( $style_selectors ) ) { static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } } @@ -1195,7 +1232,8 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' } if ( in_array( 'styles', $types, true ) ) { - if ( false !== $root_style_key ) { + $skip_root_layout_styles = $options['skip_root_layout_styles'] ?? false; + if ( false !== $root_style_key && ! $skip_root_layout_styles ) { $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ] ); } $stylesheet .= $this->get_block_classes( $style_nodes ); @@ -1748,6 +1786,10 @@ protected static function compute_preset_classes( $settings, $selector, $origins * @return string Scoped selector. */ public static function scope_selector( $scope, $selector ) { + if ( ! $selector || ! $scope ) { + return $selector; + } + $scopes = explode( ',', $scope ); $selectors = explode( ',', $selector ); @@ -2380,38 +2422,85 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { } foreach ( $theme_json['styles']['blocks'] as $name => $node ) { - $selector = null; - if ( isset( $selectors[ $name ]['selector'] ) ) { - $selector = $selectors[ $name ]['selector']; - } + $selector = $selectors[ $name ]['selector'] ?? null; + $duotone_selector = $selectors[ $name ]['duotone'] ?? null; + $feature_selectors = $selectors[ $name ]['selectors'] ?? null; + $variations = $node['variations'] ?? array(); + $variation_selectors = array(); + $variation_nodes = array(); + + // TODO: Should we be supporting recursive variations and block type styles? + foreach ( $variations as $variation => $variation_node ) { + $variation_selector = $selectors[ $name ]['styleVariations'][ $variation ]; + $variation_selectors[] = array( + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), + 'selector' => $variation_selector, + ); - $duotone_selector = null; - if ( isset( $selectors[ $name ]['duotone'] ) ) { - $duotone_selector = $selectors[ $name ]['duotone']; - } + $variation_blocks = $variation_node['blocks'] ?? array(); + $variation_elements = $variation_node['elements'] ?? array(); - $feature_selectors = null; - if ( isset( $selectors[ $name ]['selectors'] ) ) { - $feature_selectors = $selectors[ $name ]['selectors']; - } + foreach ( $variation_blocks as $variation_block => $variation_block_node ) { + $variation_block_selector = static::scope_selector( $variation_selector, $selectors[ $variation_block ]['selector'] ?? null ); + $variation_duotone_selector = static::scope_selector( $variation_selector, $selectors[ $variation_block ]['duotone'] ?? null ); + $variation_feature_selectors = $selectors[ $variation_block ]['selectors'] ?? null; - $variation_selectors = array(); - if ( isset( $node['variations'] ) ) { - foreach ( $node['variations'] as $variation => $node ) { - $variation_selectors[] = array( - 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), - 'selector' => $selectors[ $name ]['styleVariations'][ $variation ], + if ( $variation_feature_selectors ) { + foreach ( $variation_feature_selectors as $feature => $feature_selector ) { + if ( is_string( $feature_selector ) ) { + $variation_feature_selectors[ $feature ] = static::scope_selector( $variation_selector, $feature_selector ); + } + + if ( is_array( $feature_selector ) ) { + foreach ( $feature_selector as $subfeature => $subfeature_selector ) { + $variation_feature_selectors[ $feature ][ $subfeature ] = static::scope_selector( $variation_selector, $subfeature_selector ); + } + } + } + } + + $variation_nodes[] = array( + 'name' => $variation_block, + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'blocks', $variation_block ), + 'selector' => $variation_block_selector, + 'selectors' => $variation_feature_selectors, + 'duotone' => $variation_duotone_selector, ); } + + foreach ( $variation_elements as $variation_element => $variation_element_node ) { + // TODO: See if there is a way to clean up the generation of element selectors. + // The following code varies from standard block element selectors only to avoid + // that the $selectors[ $name ]['elements'][ $variation_element ] value would + // nest the block's root selector. + $nodes[] = array( + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'elements', $variation_element ), + 'selector' => static::scope_selector( $variation_selector, static::ELEMENTS[ $variation_element ] ), + ); + + // Handle any pseudo selectors for the element. + if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $variation_element ] ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $variation_element ] as $pseudo_selector ) { + if ( isset( $variation_element_node[ $pseudo_selector ] ) ) { + $pseudo_element_selector = static::append_to_selector( static::ELEMENTS[ $variation_element ], $pseudo_selector ); + $nodes[] = array( + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'elements', $variation_element ), + 'selector' => static::scope_selector( $variation_selector, $pseudo_element_selector ), + ); + } + } + } + } } $nodes[] = array( - 'name' => $name, - 'path' => array( 'styles', 'blocks', $name ), - 'selector' => $selector, - 'selectors' => $feature_selectors, - 'duotone' => $duotone_selector, - 'variations' => $variation_selectors, + 'name' => $name, + 'path' => array( 'styles', 'blocks', $name ), + 'selector' => $selector, + 'selectors' => $feature_selectors, + 'duotone' => $duotone_selector, + 'variations' => $variation_selectors, + 'variation_nodes' => $variation_nodes, ); if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { @@ -2490,6 +2579,28 @@ static function ( $split_selector ) use ( $clean_style_variation_selector ) { } } + $style_variation_block_declarations = array(); + if ( ! empty( $block_metadata['variation_nodes'] ) ) { + foreach ( $block_metadata['variation_nodes'] as $variation_node ) { + $style_variation_block_node = _wp_array_get( $this->theme_json, $variation_node['path'], array() ); + $variation_block_declarations = static::get_feature_declarations_for_node( $variation_node, $style_variation_block_node ); + + foreach ( $variation_block_declarations as $current_selector => $new_declarations ) { + $style_variation_block_declarations[ $current_selector ] = $new_declarations; + } + + // Note that `get_feature_declarations_for_node` will also unset + // feature values so they aren't duplicated in declarations via + // the call below. + $style_variation_block_declarations[ $variation_node['selector'] ] = static::compute_style_properties( + $style_variation_block_node, + $settings, + null, + $this->theme_json + ); + } + } + /* * Get a reference to element name from path. * $block_metadata['path'] = array( 'styles','elements','link' ); @@ -2591,6 +2702,11 @@ static function ( $pseudo_selector ) use ( $selector ) { $block_rules .= static::to_ruleset( $style_variation_selector, $individual_style_variation_declarations ); } + // 7. Generate and append the block style variations for inner blocks and elements. + foreach ( $style_variation_block_declarations as $style_variation_block_selector => $indvidial_style_variation_block_declaration ) { + $block_rules .= static::to_ruleset( $style_variation_block_selector, $indvidial_style_variation_block_declaration ); + } + return $block_rules; } @@ -3229,6 +3345,35 @@ public function get_raw_data() { return $this->theme_json; } + /** + * Converts block styles registered through the `WP_Block_Styles_Registry` + * with a style object, into theme.json format. + * + * @since 6.5.0 + * + * @return array Styles configuration adhering to the theme.json schema. + */ + public static function get_from_block_styles_registry() { + $variations_data = array(); + $registry = WP_Block_Styles_Registry::get_instance(); + $styles = $registry->get_all_registered(); + + foreach ( $styles as $block_name => $variations ) { + foreach ( $variations as $variation_name => $variation ) { + if ( ! empty( $variation['style_data'] ) ) { + $variations_data[ $block_name ]['variations'][ $variation_name ] = $variation['style_data']; + } + } + } + + return array( + 'version' => static::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => $variations_data, + ), + ); + } + /** * Transforms the given editor settings according the * add_theme_support format to the theme.json format. diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 189d411db2257..0ad947957215f 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -217,19 +217,22 @@ protected static function has_same_registered_blocks( $origin ) { * Returns the theme's data. * * Data from theme.json will be backfilled from existing - * theme supports, if any. Note that if the same data - * is present in theme.json and in theme supports, - * the theme.json takes precedence. + * theme supports and block style variations, if any. + * + * Note that if the same data is present in theme.json and in theme supports + * or registered block styles, the theme.json takes precedence. * * @since 5.8.0 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. + * @since 6.5.0 Theme data will now also include block style variations that were registered with a style object. * * @param array $deprecated Deprecated. Not used. * @param array $options { * Options arguments. * - * @type bool $with_supports Whether to include theme supports in the data. Default true. + * @type bool $with_supports Whether to include theme supports in the data. Default true. + * @type bool $with_style_variations Whether to include block style variations in the data. Default true. * } * @return WP_Theme_JSON Entity that holds theme data. */ @@ -238,7 +241,13 @@ public static function get_theme_data( $deprecated = array(), $options = array() _deprecated_argument( __METHOD__, '5.9.0' ); } - $options = wp_parse_args( $options, array( 'with_supports' => true ) ); + $options = wp_parse_args( + $options, + array( + 'with_supports' => true, + 'with_block_style_variations' => true, + ) + ); if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) { $wp_theme = wp_get_theme(); @@ -286,74 +295,91 @@ public static function get_theme_data( $deprecated = array(), $options = array() } - if ( ! $options['with_supports'] ) { + if ( ! $options['with_supports'] && ! $options['with_block_style_variations'] ) { return static::$theme; } - /* - * We want the presets and settings declared in theme.json - * to override the ones declared via theme supports. - * So we take theme supports, transform it to theme.json shape - * and merge the static::$theme upon that. - */ - $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); - if ( ! wp_theme_has_theme_json() ) { - if ( ! isset( $theme_support_data['settings']['color'] ) ) { - $theme_support_data['settings']['color'] = array(); - } + $theme_support_data = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array(), + ); - $default_palette = false; - if ( current_theme_supports( 'default-color-palette' ) ) { - $default_palette = true; - } - if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) { - // If the theme does not have any palette, we still want to show the core one. - $default_palette = true; - } - $theme_support_data['settings']['color']['defaultPalette'] = $default_palette; + if ( $options['with_supports'] ) { + /* + * We want the presets and settings declared in theme.json + * to override the ones declared via theme supports. + * So we take theme supports, transform it to theme.json shape + * and merge any block style variations from WP_Block_Styles_Registry + * before merging the static::$theme upon that. + */ + $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); + if ( ! wp_theme_has_theme_json() ) { + if ( ! isset( $theme_support_data['settings']['color'] ) ) { + $theme_support_data['settings']['color'] = array(); + } - $default_gradients = false; - if ( current_theme_supports( 'default-gradient-presets' ) ) { - $default_gradients = true; - } - if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) { - // If the theme does not have any gradients, we still want to show the core ones. - $default_gradients = true; - } - $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; - - // Allow themes to enable all border settings via theme_support. - if ( current_theme_supports( 'border' ) ) { - $theme_support_data['settings']['border']['color'] = true; - $theme_support_data['settings']['border']['radius'] = true; - $theme_support_data['settings']['border']['style'] = true; - $theme_support_data['settings']['border']['width'] = true; - } + $default_palette = false; + if ( current_theme_supports( 'default-color-palette' ) ) { + $default_palette = true; + } + if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) { + // If the theme does not have any palette, we still want to show the core one. + $default_palette = true; + } + $theme_support_data['settings']['color']['defaultPalette'] = $default_palette; - // Allow themes to enable link colors via theme_support. - if ( current_theme_supports( 'link-color' ) ) { - $theme_support_data['settings']['color']['link'] = true; - } - if ( current_theme_supports( 'experimental-link-color' ) ) { - _doing_it_wrong( - current_theme_supports( 'experimental-link-color' ), - __( '`experimental-link-color` is no longer supported. Use `link-color` instead.', 'gutenberg' ), - '6.3.0' - ); - } + $default_gradients = false; + if ( current_theme_supports( 'default-gradient-presets' ) ) { + $default_gradients = true; + } + if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) { + // If the theme does not have any gradients, we still want to show the core ones. + $default_gradients = true; + } + $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; + + // Allow themes to enable all border settings via theme_support. + if ( current_theme_supports( 'border' ) ) { + $theme_support_data['settings']['border']['color'] = true; + $theme_support_data['settings']['border']['radius'] = true; + $theme_support_data['settings']['border']['style'] = true; + $theme_support_data['settings']['border']['width'] = true; + } + + // Allow themes to enable link colors via theme_support. + if ( current_theme_supports( 'link-color' ) ) { + $theme_support_data['settings']['color']['link'] = true; + } + if ( current_theme_supports( 'experimental-link-color' ) ) { + _doing_it_wrong( + current_theme_supports( 'experimental-link-color' ), + __( '`experimental-link-color` is no longer supported. Use `link-color` instead.', 'gutenberg' ), + '6.3.0' + ); + } - // BEGIN EXPERIMENTAL. - // Allow themes to enable appearance tools via theme_support. - // This feature was backported for WordPress 6.2 as of https://core.trac.wordpress.org/ticket/56487 - // and then reverted as of https://core.trac.wordpress.org/ticket/57649 - // Not to backport until the issues are resolved. - if ( current_theme_supports( 'appearance-tools' ) ) { - $theme_support_data['settings']['appearanceTools'] = true; + // BEGIN EXPERIMENTAL. + // Allow themes to enable appearance tools via theme_support. + // This feature was backported for WordPress 6.2 as of https://core.trac.wordpress.org/ticket/56487 + // and then reverted as of https://core.trac.wordpress.org/ticket/57649 + // Not to backport until the issues are resolved. + if ( current_theme_supports( 'appearance-tools' ) ) { + $theme_support_data['settings']['appearanceTools'] = true; + } + // END EXPERIMENTAL. } - // END EXPERIMENTAL. } + $with_theme_supports = new WP_Theme_JSON_Gutenberg( $theme_support_data ); + + if ( $options['with_block_style_variations'] ) { + $block_style_variations_data = WP_Theme_JSON_Gutenberg::get_from_block_styles_registry(); + $with_block_style_variations = new WP_Theme_JSON_Gutenberg( $block_style_variations_data ); + $with_theme_supports->merge( $with_block_style_variations ); + } + $with_theme_supports->merge( static::$theme ); + return $with_theme_supports; } diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index eda8caede7bfe..4bd5f460bad96 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -226,6 +226,83 @@ public function test_add_theme_supports_are_loaded_for_themes_without_theme_json $this->assertSame( $color_palette, $settings['color']['palette']['theme'] ); } + public function test_add_registered_block_styles_to_theme_data() { + switch_theme( 'block-theme' ); + + $variation_styles_data = array( + 'color' => array( + 'background' => 'darkslateblue', + 'text' => 'lavender', + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'text' => 'violet', + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'fuchsia', + ), + ':hover' => array( + 'color' => array( + 'text' => 'deeppink', + ), + ), + ), + ), + ); + + register_block_style( + 'core/group', + array( + 'name' => 'my-variation', + 'style_data' => $variation_styles_data, + ) + ); + + $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_raw_data(); + $group_styles = $theme_json['styles']['blocks']['core/group'] ?? array(); + $expected = array( + 'variations' => array( + 'my-variation' => $variation_styles_data, + ), + ); + + $this->assertSameSetsWithIndex( $group_styles, $expected ); + + unregister_block_style( 'core/group', 'my-variation' ); + } + + public function test_registered_block_styles_not_added_to_theme_data_when_option_is_false() { + switch_theme( 'block-theme' ); + + $variation_styles_data = array( + 'color' => array( + 'background' => 'darkslateblue', + 'text' => 'lavender', + ), + ); + + register_block_style( + 'core/group', + array( + 'name' => 'my-variation', + 'style_data' => $variation_styles_data, + ) + ); + + $options = array( 'with_block_style_variations' => false ); + $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( null, $options )->get_raw_data(); + $group_styles = $theme_json['styles']['blocks']['core/group'] ?? array(); + + $this->assertArrayNotHasKey( 'variations', $group_styles ); + + unregister_block_style( 'core/group', 'my-variation' ); + } + /** * Recursively applies ksort to an array. */ diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 89900d45893d9..706678a23f20d 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -35,6 +35,19 @@ public static function set_up_before_class() { } static::$user_id = self::factory()->user->create(); + + // Register some block styles to test block style variations that have + // been registered through `WP_Block_Styles_Registry` as opposed to + // through block.json. + register_block_style( 'core/quote', array( 'name' => 'custom' ) ); + register_block_style( 'core/group', array( 'name' => 'custom' ) ); + } + + public static function tear_down_after_class() { + unregister_block_style( 'core/quote', 'custom' ); + unregister_block_style( 'core/group', 'custom' ); + + parent::tear_down_after_class(); } public function test_get_stylesheet_generates_layout_styles() { @@ -204,6 +217,35 @@ public function test_get_stylesheet_skips_layout_styles() { ); } + public function test_get_stylesheet_skips_root_layout_styles() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'background' => 'deepskyblue', + ), + ), + ), + ), + ), + 'blocks' + ); + $stylesheet = $theme_json->get_stylesheet( + array( 'styles' ), + array( 'custom' ), + array( 'skip_root_layout_styles' => true ) + ); + + // All Layout styles should be skipped. + $this->assertEquals( + '.wp-block-group{background-color: deepskyblue;}', + $stylesheet + ); + } + public function test_get_stylesheet() { $theme_json = new WP_Theme_JSON_Gutenberg( array( @@ -282,6 +324,46 @@ public function test_get_stylesheet() { 'spacing' => array( 'padding' => '24px', ), + 'variations' => array( + 'custom' => array( + 'color' => array( + 'background' => 'darkslateblue', + 'text' => 'white', + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'text' => 'fuchsia', + ), + ), + 'core/image' => array( + 'border' => array( + 'color' => 'darkorange', + 'style' => 'solid', + 'width' => '5px', + ), + ), + ), + 'elements' => array( + 'button' => array( + 'color' => array( + 'background' => 'deeppink', + 'text' => '#f0f0f0', + ), + ':hover' => array( + 'color' => array( + 'background' => 'fuchsia', + ), + ), + ), + 'h2' => array( + 'color' => array( + 'text' => 'deepskyblue', + ), + ), + ), + ), + ), ), 'core/heading' => array( 'color' => array( @@ -337,7 +419,7 @@ public function test_get_stylesheet() { ); $variables = 'body{--wp--preset--color--grey: grey;--wp--preset--font-size--small: 14px;--wp--preset--font-size--big: 41px;--wp--preset--font-family--arial: Arial, serif;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}'; - $styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}'; + $styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.is-style-custom.is-style-custom.wp-block-group .wp-element-button, .is-style-custom.is-style-custom.wp-block-group .wp-block-button__link{background-color: deeppink;color: #f0f0f0;}.is-style-custom.is-style-custom.wp-block-group .wp-element-button:hover, .is-style-custom.is-style-custom.wp-block-group .wp-block-button__link:hover{background-color: fuchsia;}.is-style-custom.is-style-custom.wp-block-group h2{color: deepskyblue;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.is-style-custom.is-style-custom.wp-block-group{background-color: darkslateblue;color: white;}.is-style-custom.is-style-custom.wp-block-group .wp-block-heading{color: fuchsia;}.is-style-custom.is-style-custom.wp-block-group .wp-block-image img, .is-style-custom.is-style-custom.wp-block-group .wp-block-image .wp-block-image__crop-area, .is-style-custom.is-style-custom.wp-block-group .wp-block-image .components-placeholder{border-color: darkorange;border-width: 5px;border-style: solid;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}'; $presets = '.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-big-font-size{font-size: var(--wp--preset--font-size--big) !important;}.has-arial-font-family{font-family: var(--wp--preset--font-family--arial) !important;}'; $all = $variables . $styles . $presets; @@ -1534,6 +1616,11 @@ public function test_sanitize_for_unregistered_style_variations() { 'background' => 'hotpink', ), ), + 'custom' => array( + 'color' => array( + 'background' => 'magenta', + ), + ), ), ), ), @@ -1548,11 +1635,16 @@ public function test_sanitize_for_unregistered_style_variations() { 'blocks' => array( 'core/quote' => array( 'variations' => array( - 'plain' => array( + 'plain' => array( 'color' => array( 'background' => 'hotpink', ), ), + 'custom' => array( + 'color' => array( + 'background' => 'magenta', + ), + ), ), ), ), @@ -1643,6 +1735,32 @@ public function data_sanitize_for_block_with_style_variations() { ), ), ), + '1 variation via registry with invalid properties' => array( + 'theme_json_variations' => array( + 'variations' => array( + 'custom' => array( + 'color' => array( + 'background' => 'magenta', + ), + 'invalidProperty1' => 'value1', + 'invalidProperty2' => 'value2', + ), + ), + ), + 'expected_sanitized' => array( + 'blocks' => array( + 'core/quote' => array( + 'variations' => array( + 'custom' => array( + 'color' => array( + 'background' => 'magenta', + ), + ), + ), + ), + ), + ), + ), ); } @@ -1862,8 +1980,16 @@ public function data_get_styles_for_block_with_style_variations() { 'styles' => '.is-style-plain.is-style-plain.wp-block-quote{background-color: hotpink;}', ); + $custom = array( + 'metadata' => array( + 'path' => array( 'styles', 'blocks', 'core/quote', 'variations', 'custom' ), + 'selector' => '.is-style-custom.is-style-custom.wp-block-quote', + ), + 'styles' => '.is-style-custom.is-style-custom.wp-block-quote{background-color: magenta;}', + ); + return array( - '1 variation with 1 invalid property' => array( + 'variation with 1 valid property' => array( 'theme_json_variations' => array( 'variations' => array( 'plain' => array( @@ -1876,7 +2002,7 @@ public function data_get_styles_for_block_with_style_variations() { 'metadata_variation' => array( $plain['metadata'] ), 'expected' => $plain['styles'], ), - '1 variation with 2 invalid properties' => array( + 'variation with 2 invalid properties' => array( 'theme_json_variations' => array( 'variations' => array( 'plain' => array( @@ -1891,6 +2017,19 @@ public function data_get_styles_for_block_with_style_variations() { 'metadata_variation' => array( $plain['metadata'] ), 'expected' => $plain['styles'], ), + 'variation via WP_Block_Styles_Registry' => array( + 'theme_json_variations' => array( + 'variations' => array( + 'custom' => array( + 'color' => array( + 'background' => 'magenta', + ), + ), + ), + ), + 'metadata_variation' => array( $custom['metadata'] ), + 'expected' => $custom['styles'], + ), ); } @@ -1922,6 +2061,60 @@ public function test_block_style_variations() { $this->assertSameSetsWithIndex( $expected, $actual ); } + public function test_block_style_variations_from_block_styles_registry() { + $variation_styles_data = array( + 'color' => array( + 'background' => 'darkslateblue', + 'text' => 'lavender', + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'text' => 'violet', + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'fuchsia', + ), + ':hover' => array( + 'color' => array( + 'text' => 'deeppink', + ), + ), + ), + ), + ); + + register_block_style( + 'core/group', + array( + 'name' => 'my-variation', + 'style_data' => $variation_styles_data, + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'variations' => array( + 'my-variation' => $variation_styles_data, + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON_Gutenberg::get_from_block_styles_registry(); + $this->assertSameSetsWithIndex( $expected, $actual ); + + unregister_block_style( 'core/group', 'my-variation' ); + } + public function test_block_style_variations_with_invalid_properties() { wp_set_current_user( static::$administrator_id ); From 883c4678795e3e121987007b80ab0ec9f250f4e7 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:36:17 +1000 Subject: [PATCH 04/25] Update JS processing of block style variations --- .../test/use-global-styles-output.js | 121 +++++++++++++++++ .../global-styles/use-global-styles-output.js | 122 +++++++++++++++--- .../src/components/global-styles/utils.js | 4 + 3 files changed, 232 insertions(+), 15 deletions(-) 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 b05381a8325b0..bfdf48eb3aaa6 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 @@ -28,6 +28,35 @@ describe( 'global styles renderer', () => { text: 'red', }, blocks: { + 'core/group': { + color: { + background: 'linen', + }, + variations: { + foo: { + color: 'aliceblue', + blocks: { + 'core/heading': { + typography: { + fontSize: '3em', + }, + }, + }, + elements: { + link: { + color: { + text: 'darkcyan', + }, + ':hover': { + color: { + text: 'darkturqoise', + }, + }, + }, + }, + }, + }, + }, 'core/heading': { color: { background: 'blue', @@ -89,6 +118,12 @@ describe( 'global styles renderer', () => { }, }; const blockSelectors = { + 'core/group': { + selector: '.my-group', + styleVariationSelectors: { + foo: '.is-style-foo.my-group', + }, + }, 'core/heading': { selector: '.my-heading1, .my-heading2', }, @@ -129,6 +164,44 @@ describe( 'global styles renderer', () => { }, selector: ELEMENTS.link, }, + { + selector: + '.is-style-foo.my-group .my-heading1, .is-style-foo.my-group .my-heading2', + styles: { + typography: { + fontSize: '3em', + }, + }, + }, + { + selector: '.is-style-foo.my-group a', + styles: { + color: { + text: 'darkcyan', + }, + ':hover': { + color: { + text: 'darkturqoise', + }, + }, + }, + }, + { + selector: '.my-group', + styles: { + color: { + background: 'linen', + }, + variations: { + foo: { + color: 'aliceblue', + }, + }, + }, + styleVariationSelectors: { + foo: '.is-style-foo.my-group', + }, + }, { styles: { color: { @@ -549,6 +622,44 @@ describe( 'global styles renderer', () => { }, }, }, + 'core/group': { + variations: { + bar: { + color: { + background: 'midnightblue', + text: 'lightskyblue', + }, + blocks: { + 'core/heading': { + color: { + text: 'royalblue', + }, + }, + 'core/image': { + border: { + color: 'darkcyan', + style: 'dashed', + width: '5px', + }, + }, + }, + elements: { + h2: { + color: { + text: 'turquoise', + }, + }, + button: { + color: { + background: 'midnightblue', + text: 'powderblue', + }, + ':hover': {}, + }, + }, + }, + }, + }, }, }, }; @@ -567,11 +678,21 @@ describe( 'global styles renderer', () => { foo: '.is-style-foo.wp-image', }, }, + 'core/group': { + selector: '.wp-group', + styleVariationSelectors: { + bar: '.is-style-bar.wp-group', + }, + }, + 'core/heading': { + selector: '.wp-heading', + }, }; expect( toStyles( Object.freeze( tree ), blockSelectors ) ).toEqual( 'body {margin: 0;}body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-constrained > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-constrained > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-constrained > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) { max-width: var(--wp--style--global--content-size); margin-left: auto !important; margin-right: auto !important; }body .is-layout-constrained > .alignwide { max-width: var(--wp--style--global--wide-size); }body .is-layout-flex { display:flex; }body .is-layout-flex { flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }body .is-layout-grid { display:grid; }body .is-layout-grid > * { margin: 0; }' + '.is-style-foo.wp-image.wp-image-spacing{padding-top: 2px;}.is-style-foo.wp-image.wp-image-border-color{border-color: blue;}.is-style-foo.wp-image{color: blue;}' + + '.is-style-bar.wp-group .wp-heading{color: royalblue;}.is-style-bar.wp-group .wp-image-border-color{border-color: darkcyan;}.is-style-bar.wp-group .wp-image-border{border-style: dashed;border-width: 5px;}.is-style-bar.wp-group h2{color: turquoise;}.is-style-bar.wp-group .wp-element-button, .is-style-bar.wp-group .wp-block-button__link{color: powderblue;background-color: midnightblue;}.is-style-bar.wp-group{color: lightskyblue;background-color: midnightblue;}' + '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' ); } ); 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 1cd63ef4d03f0..55bc89175cc67 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 @@ -611,6 +611,33 @@ function pickStyleKeys( treeToPickFrom ) { return Object.fromEntries( clonedEntries ); } +function scopeFeatureSelectors( scope, selectors ) { + if ( ! scope || ! selectors ) { + return; + } + + const featureSelectors = JSON.parse( JSON.stringify( selectors ) ); + + Object.entries( selectors ).forEach( ( [ feature, selector ] ) => { + if ( typeof selector === 'string' ) { + featureSelectors[ feature ] = scopeSelector( scope, selector ); + } + + if ( typeof selector === 'object' ) { + Object.entries( selector ).forEach( + ( [ subfeature, subfeatureSelector ] ) => { + featureSelectors[ feature ][ subfeature ] = scopeSelector( + scope, + subfeatureSelector + ); + } + ); + } + } ); + + return featureSelectors; +} + export const getNodesWithStyles = ( tree, blockSelectors ) => { const nodes = []; @@ -643,14 +670,78 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => { if ( node?.variations ) { const variations = {}; - Object.keys( node.variations ).forEach( ( variation ) => { - variations[ variation ] = pickStyleKeys( - node.variations[ variation ] - ); - } ); + + Object.entries( node.variations ).forEach( + ( [ variationName, variation ] ) => { + variations[ variationName ] = + pickStyleKeys( variation ); + const variationSelector = + blockSelectors[ blockName ].styleVariationSelectors[ + variationName + ]; + + // Process the variations inner block type styles. + Object.entries( variation.blocks ?? {} ).forEach( + ( [ + variationBlockName, + variationBlockStyles, + ] ) => { + const variationBlockSelector = scopeSelector( + variationSelector, + blockSelectors[ variationBlockName ] + .selector + ); + const variationDuotoneSelector = scopeSelector( + variationSelector, + blockSelectors[ variationBlockName ] + .duotoneSelector + ); + const variationFeatureSelectors = + scopeFeatureSelectors( + variationSelector, + blockSelectors[ variationBlockName ] + .featureSelectors + ); + + // TODO: Do we need to delay pushing these nodes so they come after the original block's node? + nodes.push( { + selector: variationBlockSelector, + duotoneSelector: variationDuotoneSelector, + featureSelectors: variationFeatureSelectors, + fallbackGapValue: + blockSelectors[ variationBlockName ] + .fallbackGapValue, + hasLayoutSupport: + blockSelectors[ variationBlockName ] + .hasLayoutSupport, + styles: pickStyleKeys( + variationBlockStyles + ), + } ); + } + ); + + // Process the variations inner element styles. + Object.entries( variation.elements ?? {} ).forEach( + ( [ element, elementStyles ] ) => { + if ( elementStyles && ELEMENTS[ element ] ) { + nodes.push( { + styles: elementStyles, + selector: scopeSelector( + variationSelector, + ELEMENTS[ element ] + ), + } ); + } + } + ); + } + ); + blockStyles.variations = variations; } - if ( blockStyles && blockSelectors?.[ blockName ]?.selector ) { + + if ( blockSelectors?.[ blockName ]?.selector ) { nodes.push( { duotoneSelector: blockSelectors[ blockName ].duotoneSelector, @@ -843,6 +934,7 @@ export const toStyles = ( ( [ styleVariationName, styleVariationSelector ] ) => { const styleVariations = styles?.variations?.[ styleVariationName ]; + if ( styleVariations ) { // If the block uses any custom selectors for block support, add those first. if ( featureSelectors ) { @@ -860,6 +952,7 @@ export const toStyles = ( baseSelector, styleVariationSelector ); + const rules = declarations.join( ';' ); ruleset += `${ cssSelector }{${ rules };}`; @@ -876,6 +969,7 @@ export const toStyles = ( useRootPaddingAlign, tree ); + if ( styleVariationDeclarations.length ) { ruleset += `${ styleVariationSelector }{${ styleVariationDeclarations.join( ';' @@ -1069,13 +1163,12 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const blockStyleVariations = getBlockStyles( name ); const styleVariationSelectors = {}; - if ( blockStyleVariations?.length ) { - blockStyleVariations.forEach( ( variation ) => { - const styleVariationSelector = `.is-style-${ variation.name }${ selector }`; - styleVariationSelectors[ variation.name ] = - styleVariationSelector; - } ); - } + + blockStyleVariations?.forEach( ( variation ) => { + const styleVariationSelector = `.is-style-${ variation.name }${ selector }`; + styleVariationSelectors[ variation.name ] = styleVariationSelector; + } ); + // For each block support feature add any custom selectors. const featureSelectors = getSelectorsConfig( blockType, selector ); @@ -1088,8 +1181,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { hasLayoutSupport, name, selector, - styleVariationSelectors: Object.keys( styleVariationSelectors ) - .length + styleVariationSelectors: blockStyleVariations?.length ? styleVariationSelectors : undefined, }; diff --git a/packages/block-editor/src/components/global-styles/utils.js b/packages/block-editor/src/components/global-styles/utils.js index 34964d3c92905..08e38592cfa86 100644 --- a/packages/block-editor/src/components/global-styles/utils.js +++ b/packages/block-editor/src/components/global-styles/utils.js @@ -387,6 +387,10 @@ export function getValueFromVariable( features, blockName, variable ) { * @return {string} Scoped selector. */ export function scopeSelector( scope, selector ) { + if ( ! scope || ! selector ) { + return selector; + } + const scopes = scope.split( ',' ); const selectors = selector.split( ',' ); From 5f17722db77dc5c3b535c81a23567e3d29f07e78 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:07:39 +1000 Subject: [PATCH 05/25] Allow non-core block style variations to be configured within Global Styles This initial approach will restrict the block style variations configurable within Global Styles to core block styles and those registered and possessing matching styles within the base theme.json. --- .../global-styles/variations-panel.js | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/variations-panel.js b/packages/edit-site/src/components/global-styles/variations-panel.js index 823e27038defb..3b2e475064805 100644 --- a/packages/edit-site/src/components/global-styles/variations-panel.js +++ b/packages/edit-site/src/components/global-styles/variations-panel.js @@ -2,16 +2,26 @@ * WordPress dependencies */ import { store as blocksStore } from '@wordpress/blocks'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; import { __experimentalItemGroup as ItemGroup } from '@wordpress/components'; + /** * Internal dependencies */ - import { NavigationButtonAsItem } from './navigation-button'; +import { unlock } from '../../lock-unlock'; + +const { useGlobalStyle } = unlock( blockEditorPrivateApis ); -function getCoreBlockStyles( blockStyles ) { - return blockStyles?.filter( ( style ) => style.source === 'block' ); +// Only core block styles (source === block) or block styles with +// a matching theme.json style variation will be configurable via +// Global Styles. +function getFilteredBlockStyles( blockStyles, variations ) { + return blockStyles?.filter( + ( style ) => + style.source === 'block' || variations.includes( style.name ) + ); } export function useBlockVariations( name ) { @@ -22,8 +32,10 @@ export function useBlockVariations( name ) { }, [ name ] ); - const coreBlockStyles = getCoreBlockStyles( blockStyles ); - return coreBlockStyles; + const [ variations ] = useGlobalStyle( 'variations', name, 'base' ); + const variationNames = Object.keys( variations ?? {} ); + + return getFilteredBlockStyles( blockStyles, variationNames ); } export function VariationsPanel( { name } ) { From a4d54d65cbf1376f5b04539615a08bb2aa635c66 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:46:17 +1000 Subject: [PATCH 06/25] Remove no longer required option to skip root layout styles --- lib/class-wp-theme-json-gutenberg.php | 3 +-- phpunit/class-wp-theme-json-test.php | 29 --------------------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index aadf704647582..09e10f6559112 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1232,8 +1232,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' } if ( in_array( 'styles', $types, true ) ) { - $skip_root_layout_styles = $options['skip_root_layout_styles'] ?? false; - if ( false !== $root_style_key && ! $skip_root_layout_styles ) { + if ( false !== $root_style_key ) { $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ] ); } $stylesheet .= $this->get_block_classes( $style_nodes ); diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 706678a23f20d..07158ee172d44 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -217,35 +217,6 @@ public function test_get_stylesheet_skips_layout_styles() { ); } - public function test_get_stylesheet_skips_root_layout_styles() { - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/group' => array( - 'color' => array( - 'background' => 'deepskyblue', - ), - ), - ), - ), - ), - 'blocks' - ); - $stylesheet = $theme_json->get_stylesheet( - array( 'styles' ), - array( 'custom' ), - array( 'skip_root_layout_styles' => true ) - ); - - // All Layout styles should be skipped. - $this->assertEquals( - '.wp-block-group{background-color: deepskyblue;}', - $stylesheet - ); - } - public function test_get_stylesheet() { $theme_json = new WP_Theme_JSON_Gutenberg( array( From e5b519690c1caccb3d7bd6be8fc44c93f56bf6d0 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:48:33 +1000 Subject: [PATCH 07/25] Remove unnecessary comments --- lib/class-wp-theme-json-gutenberg.php | 5 ----- .../src/components/global-styles/use-global-styles-output.js | 1 - 2 files changed, 6 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 09e10f6559112..d808fcc0cac38 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2428,7 +2428,6 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { $variation_selectors = array(); $variation_nodes = array(); - // TODO: Should we be supporting recursive variations and block type styles? foreach ( $variations as $variation => $variation_node ) { $variation_selector = $selectors[ $name ]['styleVariations'][ $variation ]; $variation_selectors[] = array( @@ -2468,10 +2467,6 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { } foreach ( $variation_elements as $variation_element => $variation_element_node ) { - // TODO: See if there is a way to clean up the generation of element selectors. - // The following code varies from standard block element selectors only to avoid - // that the $selectors[ $name ]['elements'][ $variation_element ] value would - // nest the block's root selector. $nodes[] = array( 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'elements', $variation_element ), 'selector' => static::scope_selector( $variation_selector, static::ELEMENTS[ $variation_element ] ), 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 55bc89175cc67..b1f6f395d9861 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 @@ -703,7 +703,6 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => { .featureSelectors ); - // TODO: Do we need to delay pushing these nodes so they come after the original block's node? nodes.push( { selector: variationBlockSelector, duotoneSelector: variationDuotoneSelector, From b66a1cbe7735e69e1c7f7efb38f6ec3ea4c1a1c7 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 2 Jan 2024 19:11:52 +1000 Subject: [PATCH 08/25] Fix typo and remove stray comment --- lib/class-wp-theme-json-gutenberg.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index d808fcc0cac38..3bc7711fa43e8 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2583,9 +2583,6 @@ static function ( $split_selector ) use ( $clean_style_variation_selector ) { $style_variation_block_declarations[ $current_selector ] = $new_declarations; } - // Note that `get_feature_declarations_for_node` will also unset - // feature values so they aren't duplicated in declarations via - // the call below. $style_variation_block_declarations[ $variation_node['selector'] ] = static::compute_style_properties( $style_variation_block_node, $settings, @@ -2697,8 +2694,8 @@ static function ( $pseudo_selector ) use ( $selector ) { } // 7. Generate and append the block style variations for inner blocks and elements. - foreach ( $style_variation_block_declarations as $style_variation_block_selector => $indvidial_style_variation_block_declaration ) { - $block_rules .= static::to_ruleset( $style_variation_block_selector, $indvidial_style_variation_block_declaration ); + foreach ( $style_variation_block_declarations as $style_variation_block_selector => $individial_style_variation_block_declaration ) { + $block_rules .= static::to_ruleset( $style_variation_block_selector, $individial_style_variation_block_declaration ); } return $block_rules; From 4744dc3f3d3b061be1360b3f4c2ced06aceca300 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 3 Jan 2024 14:13:56 +1000 Subject: [PATCH 09/25] Ensure test block style variation is unregistered before assertions --- phpunit/class-wp-theme-json-resolver-test.php | 8 ++++---- phpunit/class-wp-theme-json-test.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 4bd5f460bad96..aff82bc145b81 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -271,9 +271,9 @@ public function test_add_registered_block_styles_to_theme_data() { ), ); - $this->assertSameSetsWithIndex( $group_styles, $expected ); - unregister_block_style( 'core/group', 'my-variation' ); + + $this->assertSameSetsWithIndex( $group_styles, $expected ); } public function test_registered_block_styles_not_added_to_theme_data_when_option_is_false() { @@ -298,9 +298,9 @@ public function test_registered_block_styles_not_added_to_theme_data_when_option $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( null, $options )->get_raw_data(); $group_styles = $theme_json['styles']['blocks']['core/group'] ?? array(); - $this->assertArrayNotHasKey( 'variations', $group_styles ); - unregister_block_style( 'core/group', 'my-variation' ); + + $this->assertArrayNotHasKey( 'variations', $group_styles ); } /** diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 07158ee172d44..dee808ad0933d 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -2081,9 +2081,9 @@ public function test_block_style_variations_from_block_styles_registry() { ); $actual = WP_Theme_JSON_Gutenberg::get_from_block_styles_registry(); - $this->assertSameSetsWithIndex( $expected, $actual ); unregister_block_style( 'core/group', 'my-variation' ); + $this->assertSameSetsWithIndex( $expected, $actual ); } public function test_block_style_variations_with_invalid_properties() { From 1130551d6865b860f18daf7fd2c86b7c02d4b41f Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 3 Jan 2024 19:28:11 +1000 Subject: [PATCH 10/25] Pay the price for my codespell ceasing to function --- lib/class-wp-theme-json-gutenberg.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 3bc7711fa43e8..6020aeffc1a95 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2694,8 +2694,8 @@ static function ( $pseudo_selector ) use ( $selector ) { } // 7. Generate and append the block style variations for inner blocks and elements. - foreach ( $style_variation_block_declarations as $style_variation_block_selector => $individial_style_variation_block_declaration ) { - $block_rules .= static::to_ruleset( $style_variation_block_selector, $individial_style_variation_block_declaration ); + foreach ( $style_variation_block_declarations as $style_variation_block_selector => $individual_style_variation_block_declaration ) { + $block_rules .= static::to_ruleset( $style_variation_block_selector, $individual_style_variation_block_declaration ); } return $block_rules; From d49e029eb1e14e70f8344a6dc3a5b354a405eb9e Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:55:38 +1000 Subject: [PATCH 11/25] Prevent variation elements and pseudo selectors from being removed --- lib/class-wp-theme-json-gutenberg.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 6020aeffc1a95..1a970b4965181 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3157,6 +3157,29 @@ public static function remove_insecure_properties( $theme_json ) { } $variation_output = static::remove_insecure_styles( $variation_input ); + + // Process a variation's elements and element pseudo selector styles. + if ( isset( $variation_input['elements'] ) ) { + foreach ( $valid_element_names as $element_name ) { + $element_input = $variation_input['elements'][ $element_name ] ?? null; + if ( $element_input ) { + $element_output = static::remove_insecure_styles( $element_input ); + + if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { + if ( isset( $element_input[ $pseudo_selector ] ) ) { + $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); + } + } + } + + if ( ! empty( $element_output ) ) { + _wp_array_set( $variation_output, array( 'elements', $element_name ), $element_output ); + } + } + } + } + if ( ! empty( $variation_output ) ) { _wp_array_set( $sanitized, $variation['path'], $variation_output ); } From 7582c5f9dcbf0f83e855741900af0c48d8cadcb3 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:34:07 +1000 Subject: [PATCH 12/25] Fix block style variation schemas and sanitization --- lib/class-wp-theme-json-gutenberg.php | 27 ++++-- phpunit/class-wp-theme-json-test.php | 129 ++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 1a970b4965181..8b671c7a2d4ea 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -841,8 +841,25 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_blocks = array(); $schema_settings_blocks = array(); + + // Generate blocks schema without variations. They will be added later + // as each variation can support inner block styles which will need a + // schema for variation block styles that don't contain variations + // i.e. the variable block styles schema will be the same as the + // `$schema_styles_blocks` generated here. + foreach ( $valid_block_names as $block ) { + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = $styles_non_top_level; + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + } + + // Generate block style variations schema including nested block styles + // schema as generated above. + $block_style_variation_styles = static::VALID_STYLES; + $block_style_variation_styles['blocks'] = $schema_styles_blocks; + $block_style_variation_styles['elements'] = $schema_styles_elements; + foreach ( $valid_block_names as $block ) { - // Build the schema for each block style variation. $style_variation_names = array(); if ( @@ -856,19 +873,11 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n ); } - $schema_styles_variations = array(); - $block_style_variation_styles = static::VALID_STYLES; - $block_style_variation_styles['blocks'] = null; - $block_style_variation_styles['elements'] = null; - $schema_styles_variations = array(); if ( ! empty( $style_variation_names ) ) { $schema_styles_variations = array_fill_keys( $style_variation_names, $block_style_variation_styles ); } - $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; - $schema_styles_blocks[ $block ] = $styles_non_top_level; - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index dee808ad0933d..55ca56a3da871 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -2137,6 +2137,135 @@ public function test_block_style_variations_with_invalid_properties() { $this->assertSameSetsWithIndex( $expected, $actual ); } + public function test_block_styles_with_invalid_elements() { + wp_set_current_user( static::$administrator_id ); + + $partially_invalid_elements = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'deepskyblue', + ), + 'invalid' => array( + 'value' => 'should be stripped', + ), + ':hover' => array( + 'color' => array( + 'text' => 'cyan', + ), + 'invalid' => array( + 'value' => 'should be stripped', + ), + ), + ), + 'invalid' => array( + 'value' => 'should be stripped', + ), + ), + ), + ), + ), + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'deepskyblue', + ), + ':hover' => array( + 'color' => array( + 'text' => 'cyan', + ), + ), + ), + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $partially_invalid_elements ); + + $this->assertSameSetsWithIndex( $expected, $actual ); + } + public function test_block_style_variations_with_elements_and_invalid_properties() { + wp_set_current_user( static::$administrator_id ); + + $partially_invalid_variation = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/quote' => array( + 'variations' => array( + 'plain' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'deepskyblue', + ), + 'invalid' => array( + 'value' => 'should be stripped', + ), + ':hover' => array( + 'color' => array( + 'text' => 'cyan', + ), + 'invalid' => array( + 'value' => 'should be stripped', + ), + ), + ), + 'invalid' => array( + 'value' => 'should be stripped', + ), + ), + ), + ), + ), + ), + ), + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/quote' => array( + 'variations' => array( + 'plain' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'deepskyblue', + ), + ':hover' => array( + 'color' => array( + 'text' => 'cyan', + ), + ), + ), + ), + ), + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $partially_invalid_variation ); + + $this->assertSameSetsWithIndex( $expected, $actual ); + } + public function test_update_separator_declarations() { // If only background is defined, test that includes border-color to the style so it is applied on the front end. $theme_json = new WP_Theme_JSON_Gutenberg( From 7426a3def90ef56a5b1c8a48e0d1545f1d6dd7c6 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Jan 2024 16:21:50 +1000 Subject: [PATCH 13/25] Try reducing the main block style variation selector specificity --- lib/class-wp-theme-json-gutenberg.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 8b671c7a2d4ea..db81335237683 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1013,7 +1013,7 @@ protected static function get_blocks_metadata() { foreach ( $registered_styles[ $block_name ] as $block_style ) { if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { $style_selectors[ $block_style['name'] ] = static::append_to_selector( - '.is-style-' . $block_style['name'] . '.is-style-' . $block_style['name'], + '.is-style-' . $block_style['name'], $block_metadata['selector'] ); } @@ -1065,7 +1065,7 @@ protected static function get_blocks_metadata() { // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); foreach ( $registered_styles as $style ) { - $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'] . '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } if ( ! empty( $style_selectors ) ) { From f48dd575284a291e61549d81e4d210bc2f1a1bd2 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Jan 2024 16:26:47 +1000 Subject: [PATCH 14/25] Bump block instance elements specificity Think change is still up for debate. If accepted it may be a breaking change that needs plenty of communication. --- lib/block-supports/elements.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 85dbd7d39797c..ac5fe6b03e361 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -135,19 +135,20 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { } $class_name = wp_get_elements_class_name( $block ); + $class_name = ".$class_name.$class_name"; $element_types = array( 'button' => array( - 'selector' => ".$class_name .wp-element-button, .$class_name .wp-block-button__link", + 'selector' => "$class_name .wp-element-button, $class_name .wp-block-button__link", 'skip' => $skip_button_color_serialization, ), 'link' => array( - 'selector' => ".$class_name a", - 'hover_selector' => ".$class_name a:hover", + 'selector' => "$class_name a", + 'hover_selector' => "$class_name a:hover", 'skip' => $skip_link_color_serialization, ), 'heading' => array( - 'selector' => ".$class_name h1, .$class_name h2, .$class_name h3, .$class_name h4, .$class_name h5, .$class_name h6", + 'selector' => "$class_name h1, $class_name h2, $class_name h3, $class_name h4, $class_name h5, $class_name h6", 'skip' => $skip_heading_color_serialization, 'elements' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ), ), @@ -190,7 +191,7 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { gutenberg_style_engine_get_styles( $element_style_object, array( - 'selector' => ".$class_name $element", + 'selector' => "$class_name $element", 'context' => 'block-supports', ) ); From c47aa41a5cfa3416e571cfe20f07dcebfea78244 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 10 Jan 2024 10:25:49 +1000 Subject: [PATCH 15/25] Update theme.json and element unit test for reduction in element specificity --- phpunit/block-supports/elements-test.php | 20 ++++++++++---------- phpunit/class-wp-theme-json-test.php | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/phpunit/block-supports/elements-test.php b/phpunit/block-supports/elements-test.php index efea11887b620..ced216e96ef28 100644 --- a/phpunit/block-supports/elements-test.php +++ b/phpunit/block-supports/elements-test.php @@ -261,7 +261,7 @@ public function data_elements_block_support_styles() { 'elements_styles' => array( 'button' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} .wp-element-button, .wp-elements-[a-f0-9]{32} .wp-block-button__link' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-element-button, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-block-button__link' . $color_css_rules . '$/', ), 'link element styles are applied' => array( 'color_settings' => array( 'link' => true ), @@ -273,15 +273,15 @@ public function data_elements_block_support_styles() { ), ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} a' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} a:hover' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a:hover' . $color_css_rules . '$/', ), 'generic heading element styles are applied' => array( 'color_settings' => array( 'heading' => true ), 'elements_styles' => array( 'heading' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} h1, .wp-elements-[a-f0-9]{32} h2, .wp-elements-[a-f0-9]{32} h3, .wp-elements-[a-f0-9]{32} h4, .wp-elements-[a-f0-9]{32} h5, .wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', ), 'individual heading element styles are applied' => array( 'color_settings' => array( 'heading' => true ), @@ -293,12 +293,12 @@ public function data_elements_block_support_styles() { 'h5' => array( 'color' => $color_styles ), 'h6' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} h1' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h2' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h3' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h4' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h5' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', ), ); } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 55ca56a3da871..e6627fbb4aea4 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -390,7 +390,7 @@ public function test_get_stylesheet() { ); $variables = 'body{--wp--preset--color--grey: grey;--wp--preset--font-size--small: 14px;--wp--preset--font-size--big: 41px;--wp--preset--font-family--arial: Arial, serif;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}'; - $styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.is-style-custom.is-style-custom.wp-block-group .wp-element-button, .is-style-custom.is-style-custom.wp-block-group .wp-block-button__link{background-color: deeppink;color: #f0f0f0;}.is-style-custom.is-style-custom.wp-block-group .wp-element-button:hover, .is-style-custom.is-style-custom.wp-block-group .wp-block-button__link:hover{background-color: fuchsia;}.is-style-custom.is-style-custom.wp-block-group h2{color: deepskyblue;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.is-style-custom.is-style-custom.wp-block-group{background-color: darkslateblue;color: white;}.is-style-custom.is-style-custom.wp-block-group .wp-block-heading{color: fuchsia;}.is-style-custom.is-style-custom.wp-block-group .wp-block-image img, .is-style-custom.is-style-custom.wp-block-group .wp-block-image .wp-block-image__crop-area, .is-style-custom.is-style-custom.wp-block-group .wp-block-image .components-placeholder{border-color: darkorange;border-width: 5px;border-style: solid;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}'; + $styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.is-style-custom.wp-block-group .wp-element-button, .is-style-custom.wp-block-group .wp-block-button__link{background-color: deeppink;color: #f0f0f0;}.is-style-custom.wp-block-group .wp-element-button:hover, .is-style-custom.wp-block-group .wp-block-button__link:hover{background-color: fuchsia;}.is-style-custom.wp-block-group h2{color: deepskyblue;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.is-style-custom.wp-block-group{background-color: darkslateblue;color: white;}.is-style-custom.wp-block-group .wp-block-heading{color: fuchsia;}.is-style-custom.wp-block-group .wp-block-image img, .is-style-custom.wp-block-group .wp-block-image .wp-block-image__crop-area, .is-style-custom.wp-block-group .wp-block-image .components-placeholder{border-color: darkorange;border-width: 5px;border-style: solid;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}'; $presets = '.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-big-font-size{font-size: var(--wp--preset--font-size--big) !important;}.has-arial-font-family{font-family: var(--wp--preset--font-family--arial) !important;}'; $all = $variables . $styles . $presets; @@ -1946,17 +1946,17 @@ public function data_get_styles_for_block_with_style_variations() { $plain = array( 'metadata' => array( 'path' => array( 'styles', 'blocks', 'core/quote', 'variations', 'plain' ), - 'selector' => '.is-style-plain.is-style-plain.wp-block-quote', + 'selector' => '.is-style-plain.wp-block-quote', ), - 'styles' => '.is-style-plain.is-style-plain.wp-block-quote{background-color: hotpink;}', + 'styles' => '.is-style-plain.wp-block-quote{background-color: hotpink;}', ); $custom = array( 'metadata' => array( 'path' => array( 'styles', 'blocks', 'core/quote', 'variations', 'custom' ), - 'selector' => '.is-style-custom.is-style-custom.wp-block-quote', + 'selector' => '.is-style-custom.wp-block-quote', ), - 'styles' => '.is-style-custom.is-style-custom.wp-block-quote{background-color: magenta;}', + 'styles' => '.is-style-custom.wp-block-quote{background-color: magenta;}', ); return array( From 9aca87ef860ab1b9a13c3e2ff809cfea31401503 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:03:25 +1000 Subject: [PATCH 16/25] Reinstate processing of variation > block > elements in block node generation --- lib/class-wp-theme-json-gutenberg.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index db81335237683..69ac5f15611e1 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2466,6 +2466,26 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { } } + $variation_block_elements = $variation_block_node['elements'] ?? array(); + foreach ( $variation_block_elements as $variation_element => $variation_element_node ) { + $nodes[] = array( + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'blocks', $variation_block, 'elements', $variation_element ), + 'selector' => static::scope_selector( $variation_block_selector, static::ELEMENTS[ $variation_element ] ), + ); + + if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $variation_element ] ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $variation_element ] as $pseudo_selector ) { + if ( isset( $variation_element_node[ $pseudo_selector ] ) ) { + $pseudo_element_selector = static::append_to_selector( static::ELEMENTS[ $variation_element ], $pseudo_selector ); + $nodes[] = array( + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'blocks', $variation_block, 'elements', $variation_element ), + 'selector' => static::scope_selector( $variation_block_selector, $pseudo_element_selector ), + ); + } + } + } + } + $variation_nodes[] = array( 'name' => $variation_block, 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'blocks', $variation_block ), From a00347711a87a4b9d0e947a28b6d911cf5a25429 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:29:40 +1000 Subject: [PATCH 17/25] Bump element styles specificity again for overcoming variation styles This ensures that element styles placed upon a block instance by the user take precedence over element styles for a block type within a block style variation e.g. `styles.blocks.core/group.variations.custom.blocks.core/media-text.elements` --- lib/block-supports/elements.php | 7 ++++++- packages/block-editor/src/hooks/style.js | 6 +++++- phpunit/block-supports/elements-test.php | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index ac5fe6b03e361..c277e11012f71 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -134,8 +134,13 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { return null; } + // The class name is duplicated twice to provide the required specificity + // to overcome default block styles, global block styles, and block style + // variation styles including elements styles on block types within a + // block style variation. + // See: https://github.com/WordPress/gutenberg/pull/56540 $class_name = wp_get_elements_class_name( $block ); - $class_name = ".$class_name.$class_name"; + $class_name = ".$class_name.$class_name.$class_name"; $element_types = array( 'button' => array( diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 7221de63456cd..fe1e25ae4b90f 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -368,7 +368,11 @@ function useBlockProps( { name, style } ) { // The .editor-styles-wrapper selector is required on elements styles. As it is // added to all other editor styles, not providing it causes reset and global // styles to override element styles because of higher specificity. - const baseElementSelector = `.editor-styles-wrapper .${ blockElementsContainerIdentifier }`; + // The block elements class is duplicated to provide the required + // specificity to correctly take precedence over block variation styles, + // including those for elements within a variation's block type styles. + // See: https://github.com/WordPress/gutenberg/pull/56540 + const baseElementSelector = `.editor-styles-wrapper .${ blockElementsContainerIdentifier }.${ blockElementsContainerIdentifier }`; const blockElementStyles = style?.elements; const styles = useMemo( () => { diff --git a/phpunit/block-supports/elements-test.php b/phpunit/block-supports/elements-test.php index ced216e96ef28..20ceca7e6e91e 100644 --- a/phpunit/block-supports/elements-test.php +++ b/phpunit/block-supports/elements-test.php @@ -261,7 +261,7 @@ public function data_elements_block_support_styles() { 'elements_styles' => array( 'button' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-element-button, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-block-button__link' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-element-button, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-block-button__link' . $color_css_rules . '$/', ), 'link element styles are applied' => array( 'color_settings' => array( 'link' => true ), @@ -273,15 +273,15 @@ public function data_elements_block_support_styles() { ), ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a:hover' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a:hover' . $color_css_rules . '$/', ), 'generic heading element styles are applied' => array( 'color_settings' => array( 'heading' => true ), 'elements_styles' => array( 'heading' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', ), 'individual heading element styles are applied' => array( 'color_settings' => array( 'heading' => true ), @@ -293,12 +293,12 @@ public function data_elements_block_support_styles() { 'h5' => array( 'color' => $color_styles ), 'h6' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', ), ); } From 562a9fa6bbf1e4f02ce8c0e962de2bd18cf32cd0 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:15:26 +1000 Subject: [PATCH 18/25] Add element styles for inner blocks within a variation to site editor --- .../test/use-global-styles-output.js | 41 +++++++++++++++++++ .../global-styles/use-global-styles-output.js | 26 ++++++++++++ 2 files changed, 67 insertions(+) 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 bfdf48eb3aaa6..81e31941e2a80 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 @@ -41,6 +41,23 @@ describe( 'global styles renderer', () => { fontSize: '3em', }, }, + 'core/media-text': { + color: { + text: 'maroon', + }, + elements: { + link: { + color: { + text: 'firebrick', + }, + ':hover': { + color: { + text: 'coral', + }, + }, + }, + }, + }, }, elements: { link: { @@ -131,6 +148,9 @@ describe( 'global styles renderer', () => { selector: '.my-image', featureSelectors: '.my-image img, .my-image .crop-area', }, + 'core/media-text': { + selector: '.media-text', + }, }; expect( getNodesWithStyles( tree, blockSelectors ) ).toEqual( [ @@ -173,6 +193,27 @@ describe( 'global styles renderer', () => { }, }, }, + { + selector: '.is-style-foo.my-group .media-text', + styles: { + color: { + text: 'maroon', + }, + }, + }, + { + selector: '.is-style-foo.my-group .media-text a', + styles: { + color: { + text: 'firebrick', + }, + ':hover': { + color: { + text: 'coral', + }, + }, + }, + }, { selector: '.is-style-foo.my-group a', styles: { 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 b1f6f395d9861..4634a9224f66b 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 @@ -717,6 +717,32 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => { variationBlockStyles ), } ); + + // Process element styles for the inner blocks + // of the variation. + Object.entries( + variationBlockStyles.elements ?? {} + ).forEach( + ( [ + variationBlockElement, + variationBlockElementStyles, + ] ) => { + if ( + variationBlockElementStyles && + ELEMENTS[ variationBlockElement ] + ) { + nodes.push( { + styles: variationBlockElementStyles, + selector: scopeSelector( + variationBlockSelector, + ELEMENTS[ + variationBlockElement + ] + ), + } ); + } + } + ); } ); From f09833f2c469c1a62f775702ce1582c1dab6f756 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:37:00 +1000 Subject: [PATCH 19/25] Revert "Bump element styles specificity again for overcoming variation styles" This reverts commit a00347711a87a4b9d0e947a28b6d911cf5a25429. --- lib/block-supports/elements.php | 7 +------ packages/block-editor/src/hooks/style.js | 6 +----- phpunit/block-supports/elements-test.php | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index c277e11012f71..ac5fe6b03e361 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -134,13 +134,8 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { return null; } - // The class name is duplicated twice to provide the required specificity - // to overcome default block styles, global block styles, and block style - // variation styles including elements styles on block types within a - // block style variation. - // See: https://github.com/WordPress/gutenberg/pull/56540 $class_name = wp_get_elements_class_name( $block ); - $class_name = ".$class_name.$class_name.$class_name"; + $class_name = ".$class_name.$class_name"; $element_types = array( 'button' => array( diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index fe1e25ae4b90f..7221de63456cd 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -368,11 +368,7 @@ function useBlockProps( { name, style } ) { // The .editor-styles-wrapper selector is required on elements styles. As it is // added to all other editor styles, not providing it causes reset and global // styles to override element styles because of higher specificity. - // The block elements class is duplicated to provide the required - // specificity to correctly take precedence over block variation styles, - // including those for elements within a variation's block type styles. - // See: https://github.com/WordPress/gutenberg/pull/56540 - const baseElementSelector = `.editor-styles-wrapper .${ blockElementsContainerIdentifier }.${ blockElementsContainerIdentifier }`; + const baseElementSelector = `.editor-styles-wrapper .${ blockElementsContainerIdentifier }`; const blockElementStyles = style?.elements; const styles = useMemo( () => { diff --git a/phpunit/block-supports/elements-test.php b/phpunit/block-supports/elements-test.php index 20ceca7e6e91e..ced216e96ef28 100644 --- a/phpunit/block-supports/elements-test.php +++ b/phpunit/block-supports/elements-test.php @@ -261,7 +261,7 @@ public function data_elements_block_support_styles() { 'elements_styles' => array( 'button' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-element-button, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-block-button__link' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-element-button, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} .wp-block-button__link' . $color_css_rules . '$/', ), 'link element styles are applied' => array( 'color_settings' => array( 'link' => true ), @@ -273,15 +273,15 @@ public function data_elements_block_support_styles() { ), ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a:hover' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} a:hover' . $color_css_rules . '$/', ), 'generic heading element styles are applied' => array( 'color_settings' => array( 'heading' => true ), 'elements_styles' => array( 'heading' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5, .wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', ), 'individual heading element styles are applied' => array( 'color_settings' => array( 'heading' => true ), @@ -293,12 +293,12 @@ public function data_elements_block_support_styles() { 'h5' => array( 'color' => $color_styles ), 'h6' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5' . $color_css_rules . - '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h1' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h2' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h3' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h4' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h5' . $color_css_rules . + '.wp-elements-[a-f0-9]{32}.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', ), ); } From 29180f7264c409aa72391e6e3d9bdac4d1febe9d Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:54:48 +1000 Subject: [PATCH 20/25] Remove elements and incorrect blocks properties from variations schema --- schemas/json/theme.json | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/schemas/json/theme.json b/schemas/json/theme.json index c56f1a84bae0e..30016a16c38f9 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -2558,35 +2558,7 @@ "filter": {}, "shadow": {}, "outline": {}, - "css": {}, - "elements": { - "$ref": "#/definitions/stylesElementsPropertiesComplete" - }, - "blocks": { - "type": "object", - "allOf": [ - { - "$ref": "#/definitions/stylesProperties" - }, - { - "properties": { - "border": {}, - "color": {}, - "dimensions": {}, - "spacing": {}, - "typography": {}, - "filter": {}, - "shadow": {}, - "outline": {}, - "css": {}, - "elements": { - "$ref": "#/definitions/stylesElementsPropertiesComplete" - } - }, - "additionalProperties": false - } - ] - } + "css": {} }, "additionalProperties": false } From 6413ecd6bb0687af04f5d2042e67e967902f800f Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:29:25 +1000 Subject: [PATCH 21/25] Remove variation > block > elements from theme.json data --- lib/class-wp-theme-json-gutenberg.php | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 69ac5f15611e1..d408600ef8501 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -842,15 +842,18 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_blocks = array(); $schema_settings_blocks = array(); - // Generate blocks schema without variations. They will be added later - // as each variation can support inner block styles which will need a - // schema for variation block styles that don't contain variations - // i.e. the variable block styles schema will be the same as the - // `$schema_styles_blocks` generated here. + // Generate a schema for blocks. + // - Block styles can contain `elements` & `variations` definitions. + // - Variations can contain styles for inner `blocks`. + // - Variations definitions cannot be nested. + // - Variations inner block styles cannot contain `elements`. + // + // As each variation needs a `blocks` schema but without `elements` and + // inner `blocks`, the overall schema will be generated in multiple + // passes. foreach ( $valid_block_names as $block ) { - $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; - $schema_styles_blocks[ $block ] = $styles_non_top_level; - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = $styles_non_top_level; } // Generate block style variations schema including nested block styles @@ -879,6 +882,10 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n } $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; + + // The element styles schema can now be added for this block to the + // styles.blocks.$block schema. + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; } $schema['styles'] = static::VALID_STYLES; From 4f561789ac9bca218fb1d2bc8745c99e603af5ed Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:31:24 +1000 Subject: [PATCH 22/25] Revert "Add element styles for inner blocks within a variation to site editor" This reverts commit 562a9fa6bbf1e4f02ce8c0e962de2bd18cf32cd0. --- .../test/use-global-styles-output.js | 41 ------------------- .../global-styles/use-global-styles-output.js | 26 ------------ 2 files changed, 67 deletions(-) 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 81e31941e2a80..bfdf48eb3aaa6 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 @@ -41,23 +41,6 @@ describe( 'global styles renderer', () => { fontSize: '3em', }, }, - 'core/media-text': { - color: { - text: 'maroon', - }, - elements: { - link: { - color: { - text: 'firebrick', - }, - ':hover': { - color: { - text: 'coral', - }, - }, - }, - }, - }, }, elements: { link: { @@ -148,9 +131,6 @@ describe( 'global styles renderer', () => { selector: '.my-image', featureSelectors: '.my-image img, .my-image .crop-area', }, - 'core/media-text': { - selector: '.media-text', - }, }; expect( getNodesWithStyles( tree, blockSelectors ) ).toEqual( [ @@ -193,27 +173,6 @@ describe( 'global styles renderer', () => { }, }, }, - { - selector: '.is-style-foo.my-group .media-text', - styles: { - color: { - text: 'maroon', - }, - }, - }, - { - selector: '.is-style-foo.my-group .media-text a', - styles: { - color: { - text: 'firebrick', - }, - ':hover': { - color: { - text: 'coral', - }, - }, - }, - }, { selector: '.is-style-foo.my-group a', styles: { 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 4634a9224f66b..b1f6f395d9861 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 @@ -717,32 +717,6 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => { variationBlockStyles ), } ); - - // Process element styles for the inner blocks - // of the variation. - Object.entries( - variationBlockStyles.elements ?? {} - ).forEach( - ( [ - variationBlockElement, - variationBlockElementStyles, - ] ) => { - if ( - variationBlockElementStyles && - ELEMENTS[ variationBlockElement ] - ) { - nodes.push( { - styles: variationBlockElementStyles, - selector: scopeSelector( - variationBlockSelector, - ELEMENTS[ - variationBlockElement - ] - ), - } ); - } - } - ); } ); From f092ad722465469528fcb8e2a01b9fdec1e6e0e1 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:31:35 +1000 Subject: [PATCH 23/25] Revert "Reinstate processing of variation > block > elements in block node generation" This reverts commit 9aca87ef860ab1b9a13c3e2ff809cfea31401503. --- lib/class-wp-theme-json-gutenberg.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index d408600ef8501..58d29c14e7c02 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2473,26 +2473,6 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { } } - $variation_block_elements = $variation_block_node['elements'] ?? array(); - foreach ( $variation_block_elements as $variation_element => $variation_element_node ) { - $nodes[] = array( - 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'blocks', $variation_block, 'elements', $variation_element ), - 'selector' => static::scope_selector( $variation_block_selector, static::ELEMENTS[ $variation_element ] ), - ); - - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $variation_element ] ) ) { - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $variation_element ] as $pseudo_selector ) { - if ( isset( $variation_element_node[ $pseudo_selector ] ) ) { - $pseudo_element_selector = static::append_to_selector( static::ELEMENTS[ $variation_element ], $pseudo_selector ); - $nodes[] = array( - 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'blocks', $variation_block, 'elements', $variation_element ), - 'selector' => static::scope_selector( $variation_block_selector, $pseudo_element_selector ), - ); - } - } - } - } - $variation_nodes[] = array( 'name' => $variation_block, 'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'blocks', $variation_block ), From 15270b601794aacfd8986f4d1d9f94709db86293 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:19:48 +1000 Subject: [PATCH 24/25] Fix block style selectors when root block selector is an element --- lib/class-wp-theme-json-gutenberg.php | 8 ++++---- .../components/global-styles/use-global-styles-output.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 58d29c14e7c02..cdf6b472f9f11 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1020,8 +1020,8 @@ protected static function get_blocks_metadata() { foreach ( $registered_styles[ $block_name ] as $block_style ) { if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { $style_selectors[ $block_style['name'] ] = static::append_to_selector( - '.is-style-' . $block_style['name'], - $block_metadata['selector'] + $block_metadata['selector'], + '.is-style-' . $block_style['name'] ); } } @@ -1065,14 +1065,14 @@ protected static function get_blocks_metadata() { $style_selectors = array(); if ( ! empty( $block_type->styles ) ) { foreach ( $block_type->styles as $style ) { - $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + $style_selectors[ $style['name'] ] = static::append_to_selector( static::$blocks_metadata[ $block_name ]['selector'], '.is-style-' . $style['name'] ); } } // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); foreach ( $registered_styles as $style ) { - $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + $style_selectors[ $style['name'] ] = static::append_to_selector( static::$blocks_metadata[ $block_name ]['selector'], '.is-style-' . $style['name'] ); } if ( ! empty( $style_selectors ) ) { 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 b1f6f395d9861..99f2cd8801d84 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 @@ -1164,7 +1164,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const styleVariationSelectors = {}; blockStyleVariations?.forEach( ( variation ) => { - const styleVariationSelector = `.is-style-${ variation.name }${ selector }`; + const styleVariationSelector = `${ selector }.is-style-${ variation.name }`; styleVariationSelectors[ variation.name ] = styleVariationSelector; } ); From 6fb1cfbb5157e983abf018e919ae1f1992eada7b Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 16 Jan 2024 18:52:44 +1000 Subject: [PATCH 25/25] Revert "Fix block style selectors when root block selector is an element" This reverts commit 15270b601794aacfd8986f4d1d9f94709db86293. --- lib/class-wp-theme-json-gutenberg.php | 8 ++++---- .../components/global-styles/use-global-styles-output.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index cdf6b472f9f11..58d29c14e7c02 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1020,8 +1020,8 @@ protected static function get_blocks_metadata() { foreach ( $registered_styles[ $block_name ] as $block_style ) { if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { $style_selectors[ $block_style['name'] ] = static::append_to_selector( - $block_metadata['selector'], - '.is-style-' . $block_style['name'] + '.is-style-' . $block_style['name'], + $block_metadata['selector'] ); } } @@ -1065,14 +1065,14 @@ protected static function get_blocks_metadata() { $style_selectors = array(); if ( ! empty( $block_type->styles ) ) { foreach ( $block_type->styles as $style ) { - $style_selectors[ $style['name'] ] = static::append_to_selector( static::$blocks_metadata[ $block_name ]['selector'], '.is-style-' . $style['name'] ); + $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } } // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); foreach ( $registered_styles as $style ) { - $style_selectors[ $style['name'] ] = static::append_to_selector( static::$blocks_metadata[ $block_name ]['selector'], '.is-style-' . $style['name'] ); + $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } if ( ! empty( $style_selectors ) ) { 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 99f2cd8801d84..b1f6f395d9861 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 @@ -1164,7 +1164,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const styleVariationSelectors = {}; blockStyleVariations?.forEach( ( variation ) => { - const styleVariationSelector = `${ selector }.is-style-${ variation.name }`; + const styleVariationSelector = `.is-style-${ variation.name }${ selector }`; styleVariationSelectors[ variation.name ] = styleVariationSelector; } );