From 5958faab8a64e558f48f5bc76d5c933938c51f8a Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 24 May 2024 18:05:12 +1000 Subject: [PATCH 01/52] Fix list block's has-background padding --- packages/block-library/src/list/style.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/list/style.scss b/packages/block-library/src/list/style.scss index badf1b9e560ebd..11f7c4888a5eff 100644 --- a/packages/block-library/src/list/style.scss +++ b/packages/block-library/src/list/style.scss @@ -1,8 +1,8 @@ ol, ul { box-sizing: border-box; +} - &.has-background { - padding: $block-bg-padding--v $block-bg-padding--h; - } +:root :where(ul.has-background, ol.has-background) { + padding: $block-bg-padding--v $block-bg-padding--h; } From 23537e64f2e549cea010bf35a99fde251f069994 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 24 May 2024 18:05:26 +1000 Subject: [PATCH 02/52] Fix paragraph block's has-padding style --- packages/block-library/src/paragraph/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/paragraph/style.scss b/packages/block-library/src/paragraph/style.scss index 34960bdb2fd589..7bd8c77e85de83 100644 --- a/packages/block-library/src/paragraph/style.scss +++ b/packages/block-library/src/paragraph/style.scss @@ -38,7 +38,8 @@ p.has-drop-cap.has-background { overflow: hidden; } -p.has-background { +// Specificity is reduced to 0-1-0 so global styles can override this. +:root :where(p.has-background) { padding: $block-bg-padding--v $block-bg-padding--h; } From 16de34e6cccf7e3df13e77dda84a519b33227e71 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:34:38 +1000 Subject: [PATCH 03/52] Make global styles data available in block editor --- lib/block-editor-settings.php | 1 + .../class-wp-rest-block-editor-settings-controller.php | 2 +- .../editor/src/components/provider/use-block-editor-settings.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/block-editor-settings.php b/lib/block-editor-settings.php index 53668e114e04cb..ba726ffe2ad5b9 100644 --- a/lib/block-editor-settings.php +++ b/lib/block-editor-settings.php @@ -78,6 +78,7 @@ function gutenberg_get_block_editor_settings( $settings ) { $settings['styles'] = array_merge( $global_styles, get_block_editor_theme_styles() ); + $settings['__experimentalStyles'] = gutenberg_get_global_styles(); $settings['__experimentalFeatures'] = gutenberg_get_global_settings(); // These settings may need to be updated based on data coming from theme.json sources. if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) { diff --git a/lib/experimental/class-wp-rest-block-editor-settings-controller.php b/lib/experimental/class-wp-rest-block-editor-settings-controller.php index 2c4bf29bc21a73..2d0537ca0891c9 100644 --- a/lib/experimental/class-wp-rest-block-editor-settings-controller.php +++ b/lib/experimental/class-wp-rest-block-editor-settings-controller.php @@ -155,7 +155,7 @@ public function get_item_schema() { '__experimentalStyles' => array( 'description' => __( 'Styles consolidated from core, theme, and user origins.', 'gutenberg' ), 'type' => 'object', - 'context' => array( 'mobile' ), + 'context' => array( 'post-editor', 'site-editor', 'widgets-editor', 'mobile' ), ), '__experimentalEnableQuoteBlockV2' => array( diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 2917c6905e3f0d..bf0f6159f2ff96 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -41,6 +41,7 @@ const BLOCK_EDITOR_SETTINGS = [ '__experimentalBlockDirectory', '__experimentalDiscussionSettings', '__experimentalFeatures', + '__experimentalStyles', '__experimentalGlobalStylesBaseStyles', '__unstableGalleryWithImageBlocks', 'alignWide', From 6549ff35308669247a95b411bfd9013a4d669e49 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:57:32 +1000 Subject: [PATCH 04/52] Update util to retrieve style variations from specified directory --- lib/class-wp-theme-json-resolver-gutenberg.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 0d6aa7bd73fd3a..95ca466140e23f 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -720,14 +720,17 @@ private static function recursively_iterate_json( $dir ) { * Returns the style variations defined by the theme (parent and child). * * @since 6.2.0 Returns parent theme variations if theme is a child. + * @since 6.6.0 Added configurable directory to allow block style variations + * to reside in a different directory to theme style variations. * + * @param string $dir Directory to search for variation partials. * @return array */ - public static function get_style_variations() { + public static function get_style_variations( $dir = 'styles' ) { $variation_files = array(); $variations = array(); - $base_directory = get_stylesheet_directory() . '/styles'; - $template_directory = get_template_directory() . '/styles'; + $base_directory = get_stylesheet_directory() . '/' . $dir; + $template_directory = get_template_directory() . '/' . $dir; if ( is_dir( $base_directory ) ) { $variation_files = static::recursively_iterate_json( $base_directory ); } From 7ea4780c4f498d16d1ca470be245ad46fb99feaa Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:07:47 +1000 Subject: [PATCH 05/52] Add filters to resolve shared block style variation definitions --- lib/block-supports/variations.php | 184 ++++++++++++++++++++++++++++++ lib/load.php | 1 + 2 files changed, 185 insertions(+) create mode 100644 lib/block-supports/variations.php diff --git a/lib/block-supports/variations.php b/lib/block-supports/variations.php new file mode 100644 index 00000000000000..5ae717ba59245f --- /dev/null +++ b/lib/block-supports/variations.php @@ -0,0 +1,184 @@ + $variation ) { + $supported_blocks = $variation['supportedBlockTypes'] ?? array(); + + /* + * Standalone theme.json partial files for block style variations + * will have their styles under a top-level property by the same name. + * Variations defined within an existing theme.json or theme style + * variation will themselves already be the required styles data. + */ + $variation_data = $variation['styles'] ?? $variation; + + /* + * Block style variations read in via standalone theme.json partials + * need to have their name set to the kebab case version of their title. + */ + $variation_name = $have_named_variations ? $key : _wp_to_kebab_case( $variation['title'] ); + + if ( empty( $variation_data ) ) { + continue; + } + + foreach ( $supported_blocks as $block_type ) { + $registered_styles = $registry->get_registered_styles_for_block( $block_type ); + + if ( ! array_key_exists( $variation_name, $registered_styles ) ) { + gutenberg_register_block_style( + $block_type, + array( + 'name' => $variation_name, + 'label' => $variation['title'] ?? $variation_name, + ) + ); + } + + $path = array( $block_type, 'variations', $variation_name ); + _wp_array_set( $variations_data, $path, $variation_data ); + } + } + + return $variations_data; +} + +/** + * Merges variations data with existing theme.json data ensuring that the + * current theme.json data values take precedence. + * + * @param array $variations_data Block style variations data keyed by block type. + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * @param string $origin Origin for the theme.json data. + * + * @return WP_Theme_JSON_Gutenberg The merged theme.json data. + */ +function gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, $origin = 'theme' ) { + if ( empty( $variations_data ) ) { + return $theme_json; + } + + $variations_theme_json_data = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( 'blocks' => $variations_data ), + ); + + $variations_theme_json = new WP_Theme_JSON_Data_Gutenberg( $variations_theme_json_data, $origin ); + + /* + * Merge the current theme.json data over shared variation data so that + * any explicit per block variation values take precedence. + */ + return $variations_theme_json->update_with( $theme_json->get_data() ); +} + +/** + * Merges any shared block style variation definitions from a theme style + * variation into their appropriate block type within theme json styles. Any + * custom user selections already made will take precedence over the shared + * style variation value. + * + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * + * @return WP_Theme_JSON_Data_Gutenberg + */ +function gutenberg_resolve_block_style_variations_from_theme_style_variation( $theme_json ) { + $theme_json_data = $theme_json->get_data(); + $shared_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); + $variations_data = gutenberg_resolve_and_register_block_style_variations( $shared_variations ); + + return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, 'user' ); +} + +/** + * Merges block style variation data sourced from standalone partial + * theme.json files. + * + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * + * @return WP_Theme_JSON_Data_Gutenberg + */ +function gutenberg_resolve_block_style_variations_from_theme_json_partials( $theme_json ) { + $block_style_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( '/block-styles' ); + $variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations ); + + return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); +} + +/** + * Merges shared block style variations registered within the + * `styles.blocks.variations` property of the primary theme.json file. + * + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * + * @return WP_Theme_JSON_Data_Gutenberg + */ +function gutenberg_resolve_block_style_variations_from_primary_theme_json( $theme_json ) { + $theme_json_data = $theme_json->get_data(); + $block_style_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); + $variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations ); + + return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); +} + +/** + * Merges block style variations registered via the block styles registry with a + * style object, under their appropriate block types within theme.json styles. + * Any variation values defined within the theme.json specific to a block type + * will take precedence over these shared definitions. + * + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * + * @return WP_Theme_JSON_Data_Gutenberg + */ +function gutenberg_resolve_block_style_variations_from_styles_registry( $theme_json ) { + $registry = WP_Block_Styles_Registry::get_instance(); + $styles = $registry->get_all_registered(); + $variations_data = array(); + + foreach ( $styles as $block_type => $variations ) { + foreach ( $variations as $variation_name => $variation ) { + if ( ! empty( $variation['style_data'] ) ) { + $path = array( $block_type, 'variations', $variation_name ); + _wp_array_set( $variations_data, $path, $variation['style_data'] ); + } + } + } + + return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( 'variation', array() ); + +// Resolve block style variations from all their potential sources. The order here is deliberate. +add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_primary_theme_json', 10, 1 ); +add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_theme_json_partials', 10, 1 ); +add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_styles_registry', 10, 1 ); + +add_filter( 'wp_theme_json_data_user', 'gutenberg_resolve_block_style_variations_from_theme_style_variation', 10, 1 ); diff --git a/lib/load.php b/lib/load.php index 357935b4137794..5b0c8fd64ab902 100644 --- a/lib/load.php +++ b/lib/load.php @@ -232,6 +232,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/duotone.php'; require __DIR__ . '/block-supports/shadow.php'; require __DIR__ . '/block-supports/background.php'; +require __DIR__ . '/block-supports/variations.php'; // Data views. require_once __DIR__ . '/experimental/data-views.php'; From 93fa1f87c19685f93258e89446f22c58d6dbf0de Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:16:52 +1000 Subject: [PATCH 06/52] Add new top-level theme.json property to identify supported block types for style variations --- lib/class-wp-theme-json-gutenberg.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 75b9ef60ff1fc8..3d459cadd3b3cf 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -353,6 +353,7 @@ class WP_Theme_JSON_Gutenberg { 'patterns', 'settings', 'styles', + 'supportedBlockTypes', 'templateParts', 'title', 'version', From 2c73a6220daa5feaea524ed4757288f64d762e37 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:21:39 +1000 Subject: [PATCH 07/52] Support skipping root layout styles from theme.json stylesheet generation --- lib/class-wp-theme-json-gutenberg.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 3d459cadd3b3cf..65a976b93bc26f 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1175,8 +1175,10 @@ public function get_settings() { * - `presets`: only the classes for the presets. * @param array $origins A list of origins to include. By default it includes VALID_ORIGINS. * @param array $options An array of options for now used for internal purposes only (may change without notice). - * The options currently supported are 'scope' that makes sure all style are scoped to a given selector, - * and root_selector which overwrites and forces a given selector to be used on the root node. + * The options currently supported are: + * - 'scope' that makes sure all style are scoped to a given selector + * - `root_selector` which overwrites and forces a given selector to be used on the root node + * - `skip_root_layout_styles` which omits root layout styles from the generated stylesheet. * @return string The resulting stylesheet. */ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null, $options = array() ) { @@ -1229,7 +1231,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' } if ( in_array( 'styles', $types, true ) ) { - if ( false !== $root_style_key ) { + if ( false !== $root_style_key && empty( $options['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 ); From 3af0d247fe73595946f59a44b3e6fc91c5a48f08 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:30:29 +1000 Subject: [PATCH 08/52] Add variations from block style registry to block metadata in theme.json --- lib/class-wp-theme-json-gutenberg.php | 34 ++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 65a976b93bc26f..c4e5f6ace99b75 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1014,12 +1014,31 @@ 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::get_block_style_variation_selector( $block_style['name'], $block_metadata['selector'] ); + } + } + + static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; + } + } return static::$blocks_metadata; } @@ -1052,11 +1071,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::get_block_style_variation_selector( $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::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + } + + if ( ! empty( $style_selectors ) ) { static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } } From d966c388daed5084045989b93e032369e5833dd2 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:38:25 +1000 Subject: [PATCH 09/52] Update theme.json sanitization to support extended block style variations --- lib/class-wp-theme-json-gutenberg.php | 32 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index c4e5f6ace99b75..d1da933642e3ac 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -875,6 +875,27 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_blocks = array(); $schema_settings_blocks = array(); + + /* + * Generate a schema for blocks. + * - Block styles can contain `elements` & `variations` definitions. + * - Variations definitions cannot be nested. + * - Variations can contain styles for inner `blocks`. + * - Variation inner `blocks` styles can contain `elements`. + * + * As each variation needs a `blocks` schema but further nested + * 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; + } + + $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(); @@ -891,12 +912,9 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $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; - $schema_styles_blocks[ $block ] = $styles_non_top_level; - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; } @@ -907,6 +925,12 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema['settings']['blocks'] = $schema_settings_blocks; $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA ); + /* + * Shared block style variations can be registered from the theme.json data so we can't + * validate them against pre-registered block style variations. + */ + $schema['styles']['blocks']['variations'] = null; + // Remove anything that's not present in the schema. foreach ( array( 'styles', 'settings' ) as $subtree ) { if ( ! isset( $input[ $subtree ] ) ) { From daa82d3c7b1d0d22567ee6dfd4a4e20d5fc26d60 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:40:41 +1000 Subject: [PATCH 10/52] Prevent block style variation element styles from being removed for users without unfiltered html caps --- 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 d1da933642e3ac..e5c6a1b12134f9 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3279,6 +3279,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 6274c28c43013db85ca76ecab0815238518a8f91 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:55:28 +1000 Subject: [PATCH 11/52] Resolve user origin shared block style variation definitions e.g. from theme style variation Add setNestedValue util to editor package --- .../global-styles-provider/index.js | 51 +++++++++++++++++-- packages/editor/src/utils/set-nested-value.js | 39 ++++++++++++++ 2 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 packages/editor/src/utils/set-nested-value.js diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 9e4ba24e7311fe..e06cae27f16058 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -16,6 +16,7 @@ import { useMemo, useCallback } from '@wordpress/element'; * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import setNestedValue from '../../utils/set-nested-value'; const { GlobalStylesContext, cleanEmptyObject } = unlock( blockEditorPrivateApis @@ -30,6 +31,37 @@ export function mergeBaseAndUserConfigs( base, user ) { } ); } +function resolveBlockStyleVariations( userConfig ) { + const sharedVariations = userConfig.styles?.blocks?.variations; + + if ( ! sharedVariations ) { + return userConfig; + } + + const variationsConfig = JSON.parse( JSON.stringify( userConfig ) ); + + Object.entries( sharedVariations ).forEach( + ( [ variationName, variation ] ) => { + if ( ! variation?.supportedBlockTypes ) { + return; + } + + variation.supportedBlockTypes.forEach( ( blockName ) => { + const path = [ + 'styles', + 'blocks', + blockName, + 'variations', + variationName, + ]; + setNestedValue( variationsConfig, path, variation ); + } ); + } + ); + + return deepmerge( variationsConfig, userConfig ); +} + function useGlobalStylesUserConfig() { const { globalStylesId, isReady, settings, styles, _links } = useSelect( ( select ) => { @@ -128,24 +160,33 @@ export function useGlobalStylesContext() { const [ isUserConfigReady, userConfig, setUserConfig ] = useGlobalStylesUserConfig(); const [ isBaseConfigReady, baseConfig ] = useGlobalStylesBaseConfig(); + + const userConfigWithVariations = useMemo( () => { + if ( ! userConfig ) { + return userConfig; + } + return resolveBlockStyleVariations( userConfig ); + }, [ userConfig ] ); + const mergedConfig = useMemo( () => { - if ( ! baseConfig || ! userConfig ) { + if ( ! baseConfig || ! userConfigWithVariations ) { return {}; } - return mergeBaseAndUserConfigs( baseConfig, userConfig ); - }, [ userConfig, baseConfig ] ); + + return mergeBaseAndUserConfigs( baseConfig, userConfigWithVariations ); + }, [ userConfigWithVariations, baseConfig ] ); const context = useMemo( () => { return { isReady: isUserConfigReady && isBaseConfigReady, - user: userConfig, + user: userConfigWithVariations, base: baseConfig, merged: mergedConfig, setUserConfig, }; }, [ mergedConfig, - userConfig, + userConfigWithVariations, baseConfig, setUserConfig, isUserConfigReady, diff --git a/packages/editor/src/utils/set-nested-value.js b/packages/editor/src/utils/set-nested-value.js new file mode 100644 index 00000000000000..ec684e751cd041 --- /dev/null +++ b/packages/editor/src/utils/set-nested-value.js @@ -0,0 +1,39 @@ +/** + * Sets the value at path of object. + * If a portion of path doesn’t exist, it’s created. + * Arrays are created for missing index properties while objects are created + * for all other missing properties. + * + * This function intentionally mutates the input object. + * + * Inspired by _.set(). + * + * @see https://lodash.com/docs/4.17.15#set + * + * @todo Needs to be deduplicated with its copy in `@wordpress/core-data`. + * + * @param {Object} object Object to modify + * @param {Array} path Path of the property to set. + * @param {*} value Value to set. + */ +export default function setNestedValue( object, path, value ) { + if ( ! object || typeof object !== 'object' ) { + return object; + } + + path.reduce( ( acc, key, idx ) => { + if ( acc[ key ] === undefined ) { + if ( Number.isInteger( path[ idx + 1 ] ) ) { + acc[ key ] = []; + } else { + acc[ key ] = {}; + } + } + if ( idx === path.length - 1 ) { + acc[ key ] = value; + } + return acc[ key ]; + }, object ); + + return object; +} From cf79868f89bf7e6ee6b2649a8c66ee71cabe628b Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:31:51 +1000 Subject: [PATCH 12/52] Update theme.json schema for extended block style variations --- schemas/json/theme.json | 416 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 413 insertions(+), 3 deletions(-) diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 1443685ff83cb6..f58214c6f774c4 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -1927,6 +1927,9 @@ "stylesBlocksPropertiesComplete": { "type": "object", "properties": { + "variations": { + "$ref": "#/definitions/stylesBlocksSharedVariationProperties" + }, "core/archives": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2250,20 +2253,420 @@ "$ref": "#/definitions/stylesElementsPropertiesComplete" }, "variations": { - "$ref": "#/definitions/stylesVariationPropertiesComplete" + "$ref": "#/definitions/stylesVariationsPropertiesComplete" } }, "additionalProperties": false } ] }, - "stylesVariationPropertiesComplete": { + "stylesBlocksSharedVariationProperties": { "type": "object", "patternProperties": { "^[a-z][a-z0-9-]*$": { - "$ref": "#/definitions/stylesPropertiesComplete" + "$ref": "#/definitions/stylesSharedVariationProperties" + } + } + }, + "stylesSharedVariationProperties": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stylesProperties" + }, + { + "properties": { + "supportedBlockTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "border": {}, + "color": {}, + "dimensions": {}, + "spacing": {}, + "typography": {}, + "filter": {}, + "shadow": {}, + "outline": {}, + "css": {}, + "elements": { + "$ref": "#/definitions/stylesElementsPropertiesComplete" + }, + "blocks": { + "$ref": "#/definitions/stylesVariationBlocksPropertiesComplete" + } + }, + "additionalProperties": false + } + ] + }, + "stylesVariationsPropertiesComplete": { + "type": "object", + "patternProperties": { + "^[a-z][a-z0-9-]*$": { + "$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" + } + }, + "additionalProperties": false + } + ] } }, "type": "object", @@ -2285,6 +2688,13 @@ "type": "string", "description": "Description of the global styles variation." }, + "supportedBlockTypes": { + "type": "array", + "description": "List of block types that can use the block style variation this theme.json file represents.", + "items": { + "type": "string" + } + }, "settings": { "description": "Settings for the block editor and individual blocks. These include things like:\n- Which customization options should be available to the user. \n- The default colors, font sizes... available to the user. \n- CSS custom properties and class names used in styles.\n- And the default layout of the editor (widths and available alignments).", "type": "object", From 24b14fa5226fb139834c7381126c028d1d558c9b Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:00:05 +1000 Subject: [PATCH 13/52] Allow non-core block style variations in global styles variations panel --- .../variations/variations-panel.js | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/variations/variations-panel.js b/packages/edit-site/src/components/global-styles/variations/variations-panel.js index 7e52498e0a4385..6c03821705619f 100644 --- a/packages/edit-site/src/components/global-styles/variations/variations-panel.js +++ b/packages/edit-site/src/components/global-styles/variations/variations-panel.js @@ -2,16 +2,25 @@ * 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 +31,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 35a1d2ff5dd058ae149619888c06654b0aed6cac Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:02:57 +1000 Subject: [PATCH 14/52] Generate per-application variation styles and enforce unique classname --- lib/block-supports/variations.php | 157 ++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/lib/block-supports/variations.php b/lib/block-supports/variations.php index 5ae717ba59245f..3cc6509c6ae034 100644 --- a/lib/block-supports/variations.php +++ b/lib/block-supports/variations.php @@ -6,6 +6,152 @@ * @package gutenberg */ +/** + * Get the class name for this application of this block's variation styles. + * + * @param array $block Block object. + * @param string $variation Slug for the variation. + * + * @return string The unique class name. + */ +function gutenberg_get_variation_class_name( $block, $variation ) { + return 'is-style-' . $variation . '--' . md5( serialize( $block ) ); +} + +/** + * Determine a block style variation name from a CSS class string. + * + * @param string $class_string CSS class string to look for a variation in. + * + * @return string|null The variation name if found. + */ +function gutenberg_get_variation_name_from_class( $class_string ) { + if ( ! is_string( $class_string ) ) { + return null; + } + + preg_match( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches ); + return $matches ? $matches[1] : null; +} + +/** + * Render the block style variation's styles. + * + * In the case of nested blocks with variations applies, we want the parent + * variation's styles to be rendered before their descendants. This solves the + * issue of a block type being styled in both the parent and descendant: we want + * the descendant style to take priority, and this is done by loading it after, + * in the DOM order. This is why the variation stylesheet generation is in a + * different filter. + * + * @param array $parsed_block The parsed block. + * + * @return array The same parsed block with variation classname added if appropriate. + */ +function gutenberg_render_variation_support_styles( $parsed_block ) { + $classes = $parsed_block['attrs']['className'] ?? null; + $variation = gutenberg_get_variation_name_from_class( $classes ); + + if ( ! $variation ) { + return $parsed_block; + } + + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $theme_json = $tree->get_raw_data(); + $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array(); + + if ( empty( $variation_data ) ) { + return $parsed_block; + } + + $config = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => $variation_data, + ); + + $class_name = gutenberg_get_variation_class_name( $parsed_block, $variation ); + $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; + + $class_name = ".$class_name"; + + if ( ! is_admin() ) { + remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); + } + + $variation_theme_json = new WP_Theme_JSON_Gutenberg( $config, 'blocks' ); + $variation_styles = $variation_theme_json->get_stylesheet( + array( 'styles' ), + array( 'custom' ), + array( + 'root_selector' => $class_name, + 'skip_root_layout_styles' => true, + 'scope' => $class_name, + ) + ); + + if ( ! is_admin() ) { + add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); + } + + if ( empty( $variation_styles ) ) { + return $parsed_block; + } + + wp_register_style( 'variation-styles', false, array( 'global-styles' ) ); + wp_add_inline_style( 'variation-styles', $variation_styles ); + + /* + * Add variation instance class name to block's className string so it can + * be enforced in the block markup via render_block filter. + */ + _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name ); + + return $parsed_block; +} + +/** + * Ensure the variation block support class name generated and added to + * block attributes in the `render_block_data` filter gets applied to the + * block's markup. + * + * @see gutenberg_render_variation_support_styles + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * + * @return string Filtered block content. + */ +function gutenberg_render_variation_class_name( $block_content, $block ) { + if ( ! $block_content || empty( $block['attrs']['className'] ) ) { + return $block_content; + } + + /* + * Matches a class prefixed by `is-style`, followed by the + * variation slug, then `--`, and finally a hash. + * + * See `gutenberg_get_variation_class_name` for class generation. + */ + preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches ); + + if ( empty( $matches ) ) { + return $block_content; + } + + $tags = new WP_HTML_Tag_Processor( $block_content ); + + if ( $tags->next_tag() ) { + /* + * Ensure the variation instance class name set in the + * `render_block_data` filter is applied in markup. + * See `gutenberg_render_variation_support_styles`. + */ + $tags->add_class( $matches[0] ); + } + + return $tags->get_updated_html(); +} + /** * Collects block style variation data for merging with theme.json data. * As each block style variation is processed it is registered if it hasn't @@ -173,9 +319,20 @@ function gutenberg_resolve_block_style_variations_from_styles_registry( $theme_j return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); } +/** + * Enqueues styles for block style variations. + */ +function gutenberg_enqueue_variation_styles() { + wp_enqueue_style( 'variation-styles' ); +} + // Register the block support. WP_Block_Supports::get_instance()->register( 'variation', array() ); +add_filter( 'render_block_data', 'gutenberg_render_variation_support_styles', 10, 2 ); +add_filter( 'render_block', 'gutenberg_render_variation_class_name', 10, 2 ); +add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_variation_styles', 1 ); + // Resolve block style variations from all their potential sources. The order here is deliberate. add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_primary_theme_json', 10, 1 ); add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_theme_json_partials', 10, 1 ); From a1ea2e32d1865f3c7fdad82fa87c1f91a300de56 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:27:48 +1000 Subject: [PATCH 15/52] Update useGlobalStylesOutput hook's node generation to support extended block style variations --- .../global-styles/use-global-styles-output.js | 129 +++++++++++++++++- 1 file changed, 123 insertions(+), 6 deletions(-) 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 06d9400416eebb..1044425468d7b6 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 @@ -614,6 +614,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 = []; @@ -646,14 +673,104 @@ 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 variation's inner element styles. + // This comes before the inner block styles so the + // element styles within the block type styles take + // precedence over these. + Object.entries( variation?.elements ?? {} ).forEach( + ( [ element, elementStyles ] ) => { + if ( elementStyles && ELEMENTS[ element ] ) { + nodes.push( { + styles: elementStyles, + selector: scopeSelector( + variationSelector, + ELEMENTS[ element ] + ), + } ); + } + } + ); + + // 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 + ); + + nodes.push( { + selector: variationBlockSelector, + duotoneSelector: variationDuotoneSelector, + featureSelectors: variationFeatureSelectors, + fallbackGapValue: + blockSelectors[ variationBlockName ] + .fallbackGapValue, + hasLayoutSupport: + blockSelectors[ variationBlockName ] + .hasLayoutSupport, + styles: pickStyleKeys( + 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 + ] + ), + } ); + } + } + ); + } + ); + } + ); blockStyles.variations = variations; } - if ( blockStyles && blockSelectors?.[ blockName ]?.selector ) { + + if ( blockSelectors?.[ blockName ]?.selector ) { nodes.push( { duotoneSelector: blockSelectors[ blockName ].duotoneSelector, From b7219e54fc082fbe744788819b77151059590bb4 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:51:03 +1000 Subject: [PATCH 16/52] Allow opting out of certain sets of styles when generating JS global styles stylesheet --- .../src/components/global-styles/use-global-styles-output.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1044425468d7b6..9aedc3fee71df6 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 @@ -1092,7 +1092,7 @@ export const toStyles = ( return; } - // `selector` maybe provided in a form + // `selector` may be provided in a form // where block level selectors have sub element // selectors appended to them as a comma separated // string. From 785d9cdefb67278b873dc0aed5e722721cd747b2 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:55:49 +1000 Subject: [PATCH 17/52] Support instanced variation selectors via getBlockSelectors --- .../global-styles/use-global-styles-output.js | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) 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 9aedc3fee71df6..baae9669e0c46d 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 @@ -1196,7 +1196,11 @@ const getSelectorsConfig = ( blockType, rootSelector ) => { return config; }; -export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { +export const getBlockSelectors = ( + blockTypes, + getBlockStyles, + variationInstanceId +) => { const result = {}; blockTypes.forEach( ( blockType ) => { const name = blockType.name; @@ -1226,16 +1230,19 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const blockStyleVariations = getBlockStyles( name ); const styleVariationSelectors = {}; - if ( blockStyleVariations?.length ) { - blockStyleVariations.forEach( ( variation ) => { - const styleVariationSelector = getBlockStyleVariationSelector( - variation.name, - selector - ); - styleVariationSelectors[ variation.name ] = - styleVariationSelector; - } ); - } + blockStyleVariations?.forEach( ( variation ) => { + const variationSuffix = variationInstanceId + ? `-${ variationInstanceId }` + : ''; + const variationName = `${ variation.name }${ variationSuffix }`; + const styleVariationSelector = getBlockStyleVariationSelector( + variationName, + selector + ); + + styleVariationSelectors[ variationName ] = styleVariationSelector; + } ); + // For each block support feature add any custom selectors. const featureSelectors = getSelectorsConfig( blockType, selector ); @@ -1248,8 +1255,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { hasLayoutSupport, name, selector, - styleVariationSelectors: Object.keys( styleVariationSelectors ) - .length + styleVariationSelectors: blockStyleVariations?.length ? styleVariationSelectors : undefined, }; From e4c21ca475bc7cd4e1b8bfa68a2ab1003f2f2c96 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:43:21 +1000 Subject: [PATCH 18/52] Add block support hook for variations in the editor --- .../src/components/global-styles/index.js | 2 + packages/block-editor/src/hooks/index.js | 2 + packages/block-editor/src/hooks/utils.js | 2 +- packages/block-editor/src/hooks/variation.js | 132 ++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 packages/block-editor/src/hooks/variation.js diff --git a/packages/block-editor/src/components/global-styles/index.js b/packages/block-editor/src/components/global-styles/index.js index 0e9aeb4c9c84ec..062df0a5606e90 100644 --- a/packages/block-editor/src/components/global-styles/index.js +++ b/packages/block-editor/src/components/global-styles/index.js @@ -8,6 +8,8 @@ export { export { getBlockCSSSelector } from './get-block-css-selector'; export { getLayoutStyles, + getBlockSelectors, + toStyles, useGlobalStylesOutput, useGlobalStylesOutputWithConfig, } from './use-global-styles-output'; diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 4a59c2faa0073f..ce47cb5e824412 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -24,6 +24,7 @@ import fontSize from './font-size'; import textAlign from './text-align'; import border from './border'; import position from './position'; +import variation from './variation'; import layout from './layout'; import childLayout from './layout-child'; import contentLockUI from './content-lock-ui'; @@ -61,6 +62,7 @@ createBlockListBlockFilter( [ fontSize, border, position, + variation, childLayout, ] ); createBlockSaveFilter( [ diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 391287afb6ba09..4beb5c9122f537 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -568,7 +568,7 @@ export function createBlockListBlockFilter( features ) { useBlockProps, } = feature; - const neededProps = {}; + const neededProps = { clientId: props.clientId }; for ( const key of attributeKeys ) { if ( props.attributes[ key ] ) { neededProps[ key ] = props.attributes[ key ]; diff --git a/packages/block-editor/src/hooks/variation.js b/packages/block-editor/src/hooks/variation.js new file mode 100644 index 00000000000000..fa34ba44c0f8c4 --- /dev/null +++ b/packages/block-editor/src/hooks/variation.js @@ -0,0 +1,132 @@ +/** + * WordPress dependencies + */ +import { getBlockTypes, store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; +import { useContext, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { + GlobalStylesContext, + toStyles, + getBlockSelectors, +} from '../components/global-styles'; +import { useStyleOverride } from './utils'; +import { store as blockEditorStore } from '../store'; + +function getVariationNameFromClass( className ) { + const match = className?.match( /\bis-style-(?!default)(\S+)\b/ ); + return match ? match[ 1 ] : null; +} + +function useBlockSyleVariation( name, variation, clientId ) { + const { user: userStyles } = useContext( GlobalStylesContext ); + const { globalSettings, globalStyles } = useSelect( ( select ) => { + const { getSettings } = select( blockEditorStore ); + return { + globalSettings: getSettings().__experimentalFeatures, + globalStyles: getSettings().__experimentalStyles, + }; + }, [] ); + + return useMemo( () => { + const styles = userStyles?.styles ?? globalStyles; + const variationStyles = + styles?.blocks?.[ name ]?.variations?.[ variation ]; + + return { + settings: userStyles?.settings ?? globalSettings, + // The variation style data is all that is needed to generate + // the styles for the current application to a block. The variation + // name is updated to match the instance specific class name. + styles: { + blocks: { + [ name ]: { + variations: { + [ `${ variation }-${ clientId }` ]: variationStyles, + }, + }, + }, + }, + }; + }, [ + userStyles, + globalSettings, + globalStyles, + variation, + clientId, + name, + ] ); +} + +// Rather than leveraging `useInstanceId` here, the `clientId` is used. +// This is so that the variation style override's ID is predictable +// when the order of applied style variations changes. +function useBlockProps( { name, className, clientId } ) { + const variation = getVariationNameFromClass( className ); + const variationClass = `is-style-${ variation }-${ clientId }`; + + const getBlockStyles = useSelect( ( select ) => { + return select( blocksStore ).getBlockStyles; + }, [] ); + + const { settings, styles } = useBlockSyleVariation( + name, + variation, + clientId + ); + + const variationStyles = useMemo( () => { + if ( ! variation ) { + return; + } + + const variationConfig = { settings, styles }; + const blockSelectors = getBlockSelectors( + getBlockTypes(), + getBlockStyles, + clientId + ); + const hasBlockGapSupport = false; + const hasFallbackGapSupport = true; + const disableLayoutStyles = true; + const isTemplate = true; + + return toStyles( + variationConfig, + blockSelectors, + hasBlockGapSupport, + hasFallbackGapSupport, + disableLayoutStyles, + isTemplate, + { + blockGap: false, + blockStyles: true, + layoutStyles: false, + marginReset: false, + presets: false, + rootPadding: false, + } + ); + }, [ variation, settings, styles, getBlockStyles, clientId ] ); + + useStyleOverride( { + id: `variation-${ clientId }`, + css: variationStyles, + __unstableType: 'variation', + // The clientId will be stored with the override and used to ensure + // the order of overrides matches the order of blocks so that the + // correct CSS cascade is maintained. + clientId, + } ); + + return variation ? { className: variationClass } : {}; +} + +export default { + hasSupport: () => true, + attributeKeys: [ 'className' ], + useBlockProps, +}; From c8ff6e75cc495fe86fc6474fa7f0a1f400e4fc5c Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:22:29 +1000 Subject: [PATCH 19/52] Sort style overrides by the rendered order of associated blocks --- .../src/components/editor-styles/index.js | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/editor-styles/index.js b/packages/block-editor/src/components/editor-styles/index.js index 34c4a90020e3be..5e350fb1ebfbb5 100644 --- a/packages/block-editor/src/components/editor-styles/index.js +++ b/packages/block-editor/src/components/editor-styles/index.js @@ -68,14 +68,32 @@ function useDarkThemeBodyClassName( styles, scope ) { } function EditorStyles( { styles, scope } ) { - const overrides = useSelect( - ( select ) => unlock( select( blockEditorStore ) ).getStyleOverrides(), + const { overrides, clientIds } = useSelect( + ( select ) => ( { + overrides: unlock( select( blockEditorStore ) ).getStyleOverrides(), + clientIds: select( blockEditorStore ).getClientIdsWithDescendants(), + } ), [] ); + const [ transformedStyles, transformedSvgs ] = useMemo( () => { + const clientIdMap = clientIds.reduce( ( acc, clientId, index ) => { + acc[ clientId ] = index; + return acc; + }, {} ); + + // Sort overrides to match the order of blocks they relate to. + // This is useful to maintain the correct CSS cascade order for + // nested block style variations. + const sortedOverrides = [ ...overrides ].sort( ( a, b ) => { + const aIndex = clientIdMap[ a[ 1 ].clientId ] ?? -1; + const bIndex = clientIdMap[ b[ 1 ].clientId ] ?? -1; + return aIndex - bIndex; + } ); + const _styles = Object.values( styles ?? [] ); - for ( const [ id, override ] of overrides ) { + for ( const [ id, override ] of sortedOverrides ) { const index = _styles.findIndex( ( { id: _id } ) => id === _id ); const overrideWithId = { ...override, id }; if ( index === -1 ) { @@ -95,7 +113,7 @@ function EditorStyles( { styles, scope } ) { .map( ( style ) => style.assets ) .join( '' ), ]; - }, [ styles, overrides, scope ] ); + }, [ styles, overrides, scope, clientIds ] ); return ( <> From 0f63b37ca387f7e39664a26aeeab5582da9fd0c8 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:39:55 +1000 Subject: [PATCH 20/52] Add test theme and child theme with block style variations --- .../block-styles/block-style-variation-a.json | 10 ++++++++++ .../style.css | 8 ++++++++ .../theme.json | 4 ++++ .../block-styles/block-style-variation-a.json | 10 ++++++++++ .../block-styles/block-style-variation-b.json | 10 ++++++++++ 5 files changed, 42 insertions(+) create mode 100644 phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json create mode 100644 phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css create mode 100644 phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json create mode 100644 phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json create mode 100644 phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json new file mode 100644 index 00000000000000..1daaac0062b9c6 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "supportedBlockTypes": [ "core/group", "core/columns", "core/media-text" ], + "styles": { + "color": { + "background": "darkcyan", + "text": "aliceblue" + } + } +} diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css new file mode 100644 index 00000000000000..c1cc20aaf1f101 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css @@ -0,0 +1,8 @@ +/* +Theme Name: Block Theme Child With Block Style Variations Theme +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Template: block-theme +Version: 1.0.0 +Text Domain: block-theme-child-with-block-style-variations +*/ diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json new file mode 100644 index 00000000000000..0da29ef16fd679 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 2 +} diff --git a/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json new file mode 100644 index 00000000000000..0ba8417049eb0a --- /dev/null +++ b/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "supportedBlockTypes": [ "core/group", "core/columns" ], + "styles": { + "color": { + "background": "indigo", + "text": "plum" + } + } +} diff --git a/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json b/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json new file mode 100644 index 00000000000000..6133b3e9f8d591 --- /dev/null +++ b/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "supportedBlockTypes": [ "core/group", "core/columns" ], + "styles": { + "color": { + "background": "midnightblue", + "text": "lightblue" + } + } +} From 8786df0a80c67eb03b4230af0a277a22c0eaf76e Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:48:33 +1000 Subject: [PATCH 21/52] Update theme.json resolver unit tests for get_style_variations --- phpunit/class-wp-theme-json-resolver-test.php | 164 +++++++++++------- 1 file changed, 106 insertions(+), 58 deletions(-) diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 9ba170cd785d22..6aad6029129252 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -981,95 +981,143 @@ public function data_get_merged_data_returns_origin() { } /** - * Tests that get_style_variations returns all variations, including parent theme variations if the theme is a child, - * and that the child variation overwrites the parent variation of the same name. + * Tests that `get_style_variations` returns all the appropriate variations, + * including parent variations if the theme is a child, and that the child + * variation overwrites the parent variation of the same name. * - * @covers WP_Theme_JSON_Resolver_Gutenberg::get_style_variations + * Note: This covers both theme style variations (`/styles`) and block style + * variations (`/block-styles`). + * + * @covers WP_Theme_JSON_Resolver::get_style_variations + * + * @dataProvider data_get_style_variations + * + * @param string $theme Name of the theme to use. + * @param string $dir The directory to retrieve variation json files from. + * @param array $expected_variations Collection of expected variations. */ - public function test_get_style_variations_returns_all_variations() { - // Switch to a child theme. - switch_theme( 'block-theme-child' ); + public function test_get_style_variations( $theme, $dir, $expected_variations ) { + switch_theme( $theme ); wp_set_current_user( self::$administrator_id ); - $actual_settings = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); - $expected_settings = array( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'title' => 'variation-a', - 'settings' => array( - 'blocks' => array( - 'core/paragraph' => array( - 'color' => array( - 'palette' => array( - 'theme' => array( - array( - 'slug' => 'dark', - 'name' => 'Dark', - 'color' => '#010101', + $actual_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( $dir ); + + wp_recursive_ksort( $actual_variations ); + wp_recursive_ksort( $expected_variations ); + + $this->assertSame( $expected_variations, $actual_variations ); + } + + /** + * Data provider for test_get_style_variations + * + * @return array + */ + public function data_get_style_variations() { + return array( + 'theme_style_variations' => array( + 'theme' => 'block-theme-child', + 'dir' => 'styles', + 'expected_variations' => array( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'title' => 'variation-a', + 'settings' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'dark', + 'name' => 'Dark', + 'color' => '#010101', + ), + ), ), ), ), ), ), ), - ), - ), - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'title' => 'variation-b', - 'settings' => array( - 'blocks' => array( - 'core/post-title' => array( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'title' => 'variation-b', + 'settings' => array( + 'blocks' => array( + 'core/post-title' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'dark', + 'name' => 'Dark', + 'color' => '#010101', + ), + ), + ), + ), + ), + ), + ), + ), + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'title' => 'Block theme variation', + 'settings' => array( 'color' => array( 'palette' => array( 'theme' => array( array( - 'slug' => 'dark', - 'name' => 'Dark', - 'color' => '#010101', + 'slug' => 'foreground', + 'name' => 'Foreground', + 'color' => '#3F67C6', ), ), ), ), ), + 'styles' => array( + 'blocks' => array( + 'core/post-title' => array( + 'typography' => array( + 'fontWeight' => '700', + ), + ), + ), + ), ), ), ), - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'title' => 'Block theme variation', - 'settings' => array( - 'color' => array( - 'palette' => array( - 'theme' => array( - array( - 'slug' => 'foreground', - 'name' => 'Foreground', - 'color' => '#3F67C6', - ), + 'block_style_variations' => array( + 'theme' => 'block-theme-child-with-block-style-variations', + 'dir' => 'block-styles', + 'expected_variations' => array( + array( + 'supportedBlockTypes' => array( 'core/group', 'core/columns', 'core/media-text' ), + 'version' => 2, + 'title' => 'block-style-variation-a', + 'styles' => array( + 'color' => array( + 'background' => 'darkcyan', + 'text' => 'aliceblue', ), ), ), - ), - 'styles' => array( - 'blocks' => array( - 'core/post-title' => array( - 'typography' => array( - 'fontWeight' => '700', + array( + 'supportedBlockTypes' => array( 'core/group', 'core/columns' ), + 'version' => 2, + 'title' => 'block-style-variation-b', + 'styles' => array( + 'color' => array( + 'background' => 'midnightblue', + 'text' => 'lightblue', ), ), ), ), ), ); - - wp_recursive_ksort( $actual_settings ); - wp_recursive_ksort( $expected_settings ); - - $this->assertSame( - $expected_settings, - $actual_settings - ); } public function test_theme_shadow_presets_do_not_override_default_shadow_presets() { From 5206c337020ee0ef234e78b923791fc1ed8aa29b Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:25:56 +1000 Subject: [PATCH 22/52] Test for variations support resolving variation definitions into theme.json --- phpunit/block-supports/variations-test.php | 126 +++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 phpunit/block-supports/variations-test.php diff --git a/phpunit/block-supports/variations-test.php b/phpunit/block-supports/variations-test.php new file mode 100644 index 00000000000000..db842a39c9ee35 --- /dev/null +++ b/phpunit/block-supports/variations-test.php @@ -0,0 +1,126 @@ +theme_root = realpath( dirname( __DIR__ ) . '/data/themedir1' ); + + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + public function tear_down() { + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + + // Reset data between tests. + _gutenberg_clean_theme_json_caches(); + parent::tear_down(); + } + + public function filter_set_theme_root() { + return $this->theme_root; + } + + /** + * Tests that block style variations registered via either + * `gutenberg_register_block_style` with a style object, or a standalone + * block style variation file within `/block-styles`, are added to the + * theme data. + */ + 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, + // The following variations are registered automatically from + // their respective JSON files within the theme's `block-styles` + // directory. + 'block-style-variation-a' => array( + 'color' => array( + 'background' => 'indigo', + 'text' => 'plum', + ), + ), + 'block-style-variation-b' => array( + 'color' => array( + 'background' => 'midnightblue', + 'text' => 'lightblue', + ), + ), + ), + ); + + unregister_block_style( 'core/group', 'my-variation' ); + + $this->assertSameSetsWithIndex( $group_styles, $expected ); + } +} From 6f83b555d03eacb3a1b907ed6cb854de0f6e8d43 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:30:05 +1000 Subject: [PATCH 23/52] Clean up retrieval of settings and styles --- packages/block-editor/src/hooks/variation.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/hooks/variation.js b/packages/block-editor/src/hooks/variation.js index fa34ba44c0f8c4..7cce3f9b1182ed 100644 --- a/packages/block-editor/src/hooks/variation.js +++ b/packages/block-editor/src/hooks/variation.js @@ -24,10 +24,11 @@ function getVariationNameFromClass( className ) { function useBlockSyleVariation( name, variation, clientId ) { const { user: userStyles } = useContext( GlobalStylesContext ); const { globalSettings, globalStyles } = useSelect( ( select ) => { - const { getSettings } = select( blockEditorStore ); + const { __experimentalFeatures, __experimentalStyles } = + select( blockEditorStore ).getSettings(); return { - globalSettings: getSettings().__experimentalFeatures, - globalStyles: getSettings().__experimentalStyles, + globalSettings: __experimentalFeatures, + globalStyles: __experimentalStyles, }; }, [] ); From 611498c1551134e76dcf8dd97cfbd1e0beb07a1b Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:34:19 +1000 Subject: [PATCH 24/52] Use cloneDeep util --- packages/editor/src/components/global-styles-provider/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index e06cae27f16058..3658d44a9cf256 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -16,6 +16,7 @@ import { useMemo, useCallback } from '@wordpress/element'; * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import cloneDeep from '../../utils/clone-deep'; import setNestedValue from '../../utils/set-nested-value'; const { GlobalStylesContext, cleanEmptyObject } = unlock( @@ -38,7 +39,7 @@ function resolveBlockStyleVariations( userConfig ) { return userConfig; } - const variationsConfig = JSON.parse( JSON.stringify( userConfig ) ); + const variationsConfig = cloneDeep( userConfig ); Object.entries( sharedVariations ).forEach( ( [ variationName, variation ] ) => { From 6be673c570cc7e4118d73790fe4c0fc4ef893bbe Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:41:51 +1000 Subject: [PATCH 25/52] Register block style variations defined in user origin --- .../global-styles-provider/index.js | 80 ++++++++++++------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 3658d44a9cf256..8029077f2c8f2f 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -7,6 +7,7 @@ import { isPlainObject } from 'is-plain-object'; /** * WordPress dependencies */ +import { registerBlockStyle, store as blocksStore } from '@wordpress/blocks'; import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect, useDispatch } from '@wordpress/data'; @@ -32,35 +33,57 @@ export function mergeBaseAndUserConfigs( base, user ) { } ); } -function resolveBlockStyleVariations( userConfig ) { - const sharedVariations = userConfig.styles?.blocks?.variations; +// Resolves shared block style variation definitions from the user origin +// under their respective block types and registers the block style if required. +function useResolvedBlockStyleVariationsConfig( userConfig ) { + const getBlockStyles = useSelect( ( select ) => { + return select( blocksStore ).getBlockStyles; + }, [] ); - if ( ! sharedVariations ) { - return userConfig; - } + return useMemo( () => { + const sharedVariations = userConfig?.styles?.blocks?.variations; - const variationsConfig = cloneDeep( userConfig ); + if ( ! sharedVariations ) { + return userConfig; + } - Object.entries( sharedVariations ).forEach( - ( [ variationName, variation ] ) => { - if ( ! variation?.supportedBlockTypes ) { - return; + const variationsConfig = cloneDeep( userConfig ); + + Object.entries( sharedVariations ).forEach( + ( [ variationName, variation ] ) => { + if ( ! variation?.supportedBlockTypes ) { + return; + } + + variation.supportedBlockTypes.forEach( ( blockName ) => { + // Register any block style variations that have been added + // by a theme style variation and are not already registered. + const blockStyles = getBlockStyles( blockName ); + const registeredBlockStyle = blockStyles.find( + ( { name } ) => name === variationName + ); + + if ( ! registeredBlockStyle ) { + registerBlockStyle( blockName, { + name: variationName, + label: variationName, + } ); + } + + const path = [ + 'styles', + 'blocks', + blockName, + 'variations', + variationName, + ]; + setNestedValue( variationsConfig, path, variation ); + } ); } + ); - variation.supportedBlockTypes.forEach( ( blockName ) => { - const path = [ - 'styles', - 'blocks', - blockName, - 'variations', - variationName, - ]; - setNestedValue( variationsConfig, path, variation ); - } ); - } - ); - - return deepmerge( variationsConfig, userConfig ); + return deepmerge( variationsConfig, userConfig ); + }, [ userConfig, getBlockStyles ] ); } function useGlobalStylesUserConfig() { @@ -161,13 +184,8 @@ export function useGlobalStylesContext() { const [ isUserConfigReady, userConfig, setUserConfig ] = useGlobalStylesUserConfig(); const [ isBaseConfigReady, baseConfig ] = useGlobalStylesBaseConfig(); - - const userConfigWithVariations = useMemo( () => { - if ( ! userConfig ) { - return userConfig; - } - return resolveBlockStyleVariations( userConfig ); - }, [ userConfig ] ); + const userConfigWithVariations = + useResolvedBlockStyleVariationsConfig( userConfig ); const mergedConfig = useMemo( () => { if ( ! baseConfig || ! userConfigWithVariations ) { From 723ed633042e4584591b27c9bc31d83b104b8e9c Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:44:45 +1000 Subject: [PATCH 26/52] Make PHP function, block support, and register styles naming consistent --- lib/block-supports/variations.php | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/block-supports/variations.php b/lib/block-supports/variations.php index 3cc6509c6ae034..ae4de3922f0792 100644 --- a/lib/block-supports/variations.php +++ b/lib/block-supports/variations.php @@ -10,11 +10,11 @@ * Get the class name for this application of this block's variation styles. * * @param array $block Block object. - * @param string $variation Slug for the variation. + * @param string $variation Slug for the block style variation. * * @return string The unique class name. */ -function gutenberg_get_variation_class_name( $block, $variation ) { +function gutenberg_get_block_style_variation_class_name( $block, $variation ) { return 'is-style-' . $variation . '--' . md5( serialize( $block ) ); } @@ -23,9 +23,9 @@ function gutenberg_get_variation_class_name( $block, $variation ) { * * @param string $class_string CSS class string to look for a variation in. * - * @return string|null The variation name if found. + * @return string|null The block style variation name if found. */ -function gutenberg_get_variation_name_from_class( $class_string ) { +function gutenberg_get_block_style_variation_name_from_class( $class_string ) { if ( ! is_string( $class_string ) ) { return null; } @@ -46,11 +46,11 @@ function gutenberg_get_variation_name_from_class( $class_string ) { * * @param array $parsed_block The parsed block. * - * @return array The same parsed block with variation classname added if appropriate. + * @return array The parsed block with block style variation classname added. */ -function gutenberg_render_variation_support_styles( $parsed_block ) { +function gutenberg_render_block_style_variation_support_styles( $parsed_block ) { $classes = $parsed_block['attrs']['className'] ?? null; - $variation = gutenberg_get_variation_name_from_class( $classes ); + $variation = gutenberg_get_block_style_variation_name_from_class( $classes ); if ( ! $variation ) { return $parsed_block; @@ -69,7 +69,7 @@ function gutenberg_render_variation_support_styles( $parsed_block ) { 'styles' => $variation_data, ); - $class_name = gutenberg_get_variation_class_name( $parsed_block, $variation ); + $class_name = gutenberg_get_block_style_variation_class_name( $parsed_block, $variation ); $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; $class_name = ".$class_name"; @@ -97,8 +97,8 @@ function gutenberg_render_variation_support_styles( $parsed_block ) { return $parsed_block; } - wp_register_style( 'variation-styles', false, array( 'global-styles' ) ); - wp_add_inline_style( 'variation-styles', $variation_styles ); + wp_register_style( 'block-style-variation-styles', false, array( 'global-styles' ) ); + wp_add_inline_style( 'block-style-variation-styles', $variation_styles ); /* * Add variation instance class name to block's className string so it can @@ -114,14 +114,14 @@ function gutenberg_render_variation_support_styles( $parsed_block ) { * block attributes in the `render_block_data` filter gets applied to the * block's markup. * - * @see gutenberg_render_variation_support_styles + * @see gutenberg_render_block_style_variation_support_styles * * @param string $block_content Rendered block content. * @param array $block Block object. * * @return string Filtered block content. */ -function gutenberg_render_variation_class_name( $block_content, $block ) { +function gutenberg_render_block_style_variation_class_name( $block_content, $block ) { if ( ! $block_content || empty( $block['attrs']['className'] ) ) { return $block_content; } @@ -130,7 +130,7 @@ function gutenberg_render_variation_class_name( $block_content, $block ) { * Matches a class prefixed by `is-style`, followed by the * variation slug, then `--`, and finally a hash. * - * See `gutenberg_get_variation_class_name` for class generation. + * See `gutenberg_get_block_style_variation_class_name` for class generation. */ preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches ); @@ -144,7 +144,7 @@ function gutenberg_render_variation_class_name( $block_content, $block ) { /* * Ensure the variation instance class name set in the * `render_block_data` filter is applied in markup. - * See `gutenberg_render_variation_support_styles`. + * See `gutenberg_render_block_style_variation_support_styles`. */ $tags->add_class( $matches[0] ); } @@ -322,16 +322,16 @@ function gutenberg_resolve_block_style_variations_from_styles_registry( $theme_j /** * Enqueues styles for block style variations. */ -function gutenberg_enqueue_variation_styles() { - wp_enqueue_style( 'variation-styles' ); +function gutenberg_enqueue_block_style_variation_styles() { + wp_enqueue_style( 'block-style-variation-styles' ); } // Register the block support. -WP_Block_Supports::get_instance()->register( 'variation', array() ); +WP_Block_Supports::get_instance()->register( 'block-style-variation', array() ); -add_filter( 'render_block_data', 'gutenberg_render_variation_support_styles', 10, 2 ); -add_filter( 'render_block', 'gutenberg_render_variation_class_name', 10, 2 ); -add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_variation_styles', 1 ); +add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles', 10, 2 ); +add_filter( 'render_block', 'gutenberg_render_block_style_variation_class_name', 10, 2 ); +add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_style_variation_styles', 1 ); // Resolve block style variations from all their potential sources. The order here is deliberate. add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_primary_theme_json', 10, 1 ); From 09426ff52471fbd8eb54f6c8804e0acf440c2299 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:54:54 +1000 Subject: [PATCH 27/52] Rename block style variation PHP block support files --- .../{variations.php => block-style-variations.php} | 0 lib/load.php | 2 +- ...ons-test.php => block-style-variations-test.php} | 13 ++++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) rename lib/block-supports/{variations.php => block-style-variations.php} (100%) rename phpunit/block-supports/{variations-test.php => block-style-variations-test.php} (90%) diff --git a/lib/block-supports/variations.php b/lib/block-supports/block-style-variations.php similarity index 100% rename from lib/block-supports/variations.php rename to lib/block-supports/block-style-variations.php diff --git a/lib/load.php b/lib/load.php index 5b0c8fd64ab902..6179ade9a2288e 100644 --- a/lib/load.php +++ b/lib/load.php @@ -232,7 +232,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/duotone.php'; require __DIR__ . '/block-supports/shadow.php'; require __DIR__ . '/block-supports/background.php'; -require __DIR__ . '/block-supports/variations.php'; +require __DIR__ . '/block-supports/block-style-variations.php'; // Data views. require_once __DIR__ . '/experimental/data-views.php'; diff --git a/phpunit/block-supports/variations-test.php b/phpunit/block-supports/block-style-variations-test.php similarity index 90% rename from phpunit/block-supports/variations-test.php rename to phpunit/block-supports/block-style-variations-test.php index db842a39c9ee35..073f137a9e2090 100644 --- a/phpunit/block-supports/variations-test.php +++ b/phpunit/block-supports/block-style-variations-test.php @@ -1,12 +1,12 @@ array( 'my-variation' => $variation_styles_data, - // The following variations are registered automatically from - // their respective JSON files within the theme's `block-styles` - // directory. + + /* + * The following block style variations are registered + * automatically from their respective JSON files within the + * theme's `block-styles` directory. + */ 'block-style-variation-a' => array( 'color' => array( 'background' => 'indigo', From cefe0cb5bd12466e20cc1ce664c5cb98cb2e03a7 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:36:48 +1000 Subject: [PATCH 28/52] Rename JS variation block support to block style variation --- .../src/hooks/{variation.js => block-style-variation.js} | 0 packages/block-editor/src/hooks/index.js | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/block-editor/src/hooks/{variation.js => block-style-variation.js} (100%) diff --git a/packages/block-editor/src/hooks/variation.js b/packages/block-editor/src/hooks/block-style-variation.js similarity index 100% rename from packages/block-editor/src/hooks/variation.js rename to packages/block-editor/src/hooks/block-style-variation.js diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index ce47cb5e824412..89e6819c1d0314 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -24,7 +24,7 @@ import fontSize from './font-size'; import textAlign from './text-align'; import border from './border'; import position from './position'; -import variation from './variation'; +import blockStyleVariation from './block-style-variation'; import layout from './layout'; import childLayout from './layout-child'; import contentLockUI from './content-lock-ui'; @@ -62,7 +62,7 @@ createBlockListBlockFilter( [ fontSize, border, position, - variation, + blockStyleVariation, childLayout, ] ); createBlockSaveFilter( [ From fca19815bc10fe9e06273650d9160fcc06d3d123 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:43:12 +1000 Subject: [PATCH 29/52] Fix typo --- lib/block-supports/block-style-variations.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index ae4de3922f0792..ac2581a05310da 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -37,7 +37,7 @@ function gutenberg_get_block_style_variation_name_from_class( $class_string ) { /** * Render the block style variation's styles. * - * In the case of nested blocks with variations applies, we want the parent + * In the case of nested blocks with variations applied, we want the parent * variation's styles to be rendered before their descendants. This solves the * issue of a block type being styled in both the parent and descendant: we want * the descendant style to take priority, and this is done by loading it after, From 24f1863523070bad0c7b89bb0fdf9af60216ba74 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:52:57 +1000 Subject: [PATCH 30/52] Tweaks to resolve and register util function --- lib/block-supports/block-style-variations.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index ac2581a05310da..a6fa87b3b57870 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -170,7 +170,7 @@ function gutenberg_resolve_and_register_block_style_variations( $variations ) { } $registry = WP_Block_Styles_Registry::get_instance(); - $have_named_variations = array_keys( $variations ) !== range( 0, count( $variations ) - 1 ); + $have_named_variations = ! wp_is_numeric_array( $variations ); foreach ( $variations as $key => $variation ) { $supported_blocks = $variation['supportedBlockTypes'] ?? array(); @@ -183,29 +183,32 @@ function gutenberg_resolve_and_register_block_style_variations( $variations ) { */ $variation_data = $variation['styles'] ?? $variation; + if ( empty( $variation_data ) ) { + continue; + } + /* * Block style variations read in via standalone theme.json partials * need to have their name set to the kebab case version of their title. */ - $variation_name = $have_named_variations ? $key : _wp_to_kebab_case( $variation['title'] ); - - if ( empty( $variation_data ) ) { - continue; - } + $variation_name = $have_named_variations ? $key : _wp_to_kebab_case( $variation['title'] ); + $variation_label = $variation['title'] ?? $variation_name; foreach ( $supported_blocks as $block_type ) { $registered_styles = $registry->get_registered_styles_for_block( $block_type ); + // Register block style variation if it hasn't already been registered. if ( ! array_key_exists( $variation_name, $registered_styles ) ) { gutenberg_register_block_style( $block_type, array( 'name' => $variation_name, - 'label' => $variation['title'] ?? $variation_name, + 'label' => $variation_label, ) ); } + // Add block style variation data under current block type. $path = array( $block_type, 'variations', $variation_name ); _wp_array_set( $variations_data, $path, $variation_data ); } From c54d3a3d0cf8782418d560281e09500cf1d68ea8 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 15 Apr 2024 18:04:38 +1000 Subject: [PATCH 31/52] Consolidate block style variations under theme styles directory --- lib/block-supports/block-style-variations.php | 2 +- ...class-wp-theme-json-resolver-gutenberg.php | 35 +++++++++++++++---- phpunit/class-wp-theme-json-resolver-test.php | 13 ++++--- .../block-style-variation-a.json | 1 + .../block-style-variation-a.json | 1 + .../block-style-variation-b.json | 1 + 6 files changed, 38 insertions(+), 15 deletions(-) rename phpunit/data/themedir1/block-theme-child-with-block-style-variations/{block-styles => styles}/block-style-variation-a.json (88%) rename phpunit/data/themedir1/block-theme/{block-styles => styles}/block-style-variation-a.json (87%) rename phpunit/data/themedir1/block-theme/{block-styles => styles}/block-style-variation-b.json (87%) diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index a6fa87b3b57870..ecdc4e1498c557 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -273,7 +273,7 @@ function gutenberg_resolve_block_style_variations_from_theme_style_variation( $t * @return WP_Theme_JSON_Data_Gutenberg */ function gutenberg_resolve_block_style_variations_from_theme_json_partials( $theme_json ) { - $block_style_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( '/block-styles' ); + $block_style_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' ); $variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations ); return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 95ca466140e23f..e886d9047ca0fd 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -716,21 +716,42 @@ private static function recursively_iterate_json( $dir ) { return $nested_json_files; } + /** + * Determines if a supplied style variation matches the provided scope. + * + * For backwards compatibility, if a variation does not explicitly define + * a scope, it is assumed to be a theme style variation. + * + * @param array $variation Theme.json shaped style variation object. + * @param string $scope Scope to check e.g. theme, block etc. + * @return boolean + */ + private static function style_variation_has_scope( $variation, $scope ) { + $scopes = $variation['scope'] ?? array(); + + if ( count( $scopes ) === 0 ) { + return 'theme' === $scope; + } + + return in_array( $scope, $scopes, true ); + } + /** * Returns the style variations defined by the theme (parent and child). * * @since 6.2.0 Returns parent theme variations if theme is a child. - * @since 6.6.0 Added configurable directory to allow block style variations - * to reside in a different directory to theme style variations. + * @since 6.6.0 Added configurable scope parameter to allow filtering + * theme.json partial files by the scope to which they + * can be applied e.g. theme vs block etc. * - * @param string $dir Directory to search for variation partials. + * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc. * @return array */ - public static function get_style_variations( $dir = 'styles' ) { + public static function get_style_variations( $scope = 'theme' ) { $variation_files = array(); $variations = array(); - $base_directory = get_stylesheet_directory() . '/' . $dir; - $template_directory = get_template_directory() . '/' . $dir; + $base_directory = get_stylesheet_directory() . '/styles'; + $template_directory = get_template_directory() . '/styles'; if ( is_dir( $base_directory ) ) { $variation_files = static::recursively_iterate_json( $base_directory ); } @@ -749,7 +770,7 @@ public static function get_style_variations( $dir = 'styles' ) { ksort( $variation_files ); foreach ( $variation_files as $path => $file ) { $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); - if ( is_array( $decoded_file ) ) { + if ( is_array( $decoded_file ) && static::style_variation_has_scope( $decoded_file, $scope ) ) { $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->get_raw_data(); if ( empty( $variation['title'] ) ) { diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 6aad6029129252..8a68c7eed30611 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -985,22 +985,21 @@ public function data_get_merged_data_returns_origin() { * including parent variations if the theme is a child, and that the child * variation overwrites the parent variation of the same name. * - * Note: This covers both theme style variations (`/styles`) and block style - * variations (`/block-styles`). + * Note: This covers both theme and block style variations. * * @covers WP_Theme_JSON_Resolver::get_style_variations * * @dataProvider data_get_style_variations * * @param string $theme Name of the theme to use. - * @param string $dir The directory to retrieve variation json files from. + * @param string $scope Scope to filter variations by e.g. theme vs block. * @param array $expected_variations Collection of expected variations. */ - public function test_get_style_variations( $theme, $dir, $expected_variations ) { + public function test_get_style_variations( $theme, $scope, $expected_variations ) { switch_theme( $theme ); wp_set_current_user( self::$administrator_id ); - $actual_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( $dir ); + $actual_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( $scope ); wp_recursive_ksort( $actual_variations ); wp_recursive_ksort( $expected_variations ); @@ -1017,7 +1016,7 @@ public function data_get_style_variations() { return array( 'theme_style_variations' => array( 'theme' => 'block-theme-child', - 'dir' => 'styles', + 'scope' => 'theme', 'expected_variations' => array( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, @@ -1091,7 +1090,7 @@ public function data_get_style_variations() { ), 'block_style_variations' => array( 'theme' => 'block-theme-child-with-block-style-variations', - 'dir' => 'block-styles', + 'scope' => 'block', 'expected_variations' => array( array( 'supportedBlockTypes' => array( 'core/group', 'core/columns', 'core/media-text' ), diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json similarity index 88% rename from phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json rename to phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json index 1daaac0062b9c6..017bb2148d4771 100644 --- a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json @@ -1,5 +1,6 @@ { "version": 2, + "scope": [ "block" ], "supportedBlockTypes": [ "core/group", "core/columns", "core/media-text" ], "styles": { "color": { diff --git a/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json similarity index 87% rename from phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json rename to phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json index 0ba8417049eb0a..92aed03bb35005 100644 --- a/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json +++ b/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json @@ -1,5 +1,6 @@ { "version": 2, + "scope": [ "block" ], "supportedBlockTypes": [ "core/group", "core/columns" ], "styles": { "color": { diff --git a/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json b/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json similarity index 87% rename from phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json rename to phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json index 6133b3e9f8d591..d6afc57b5bad41 100644 --- a/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json +++ b/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json @@ -1,5 +1,6 @@ { "version": 2, + "scope": [ "block" ], "supportedBlockTypes": [ "core/group", "core/columns" ], "styles": { "color": { From db0402ce10deb4dad2daab4f56c73d5bac72788e Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:25:15 +1000 Subject: [PATCH 32/52] Fix references to /block-styles in comments --- phpunit/block-supports/block-style-variations-test.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/phpunit/block-supports/block-style-variations-test.php b/phpunit/block-supports/block-style-variations-test.php index 073f137a9e2090..99d87d4736bd1a 100644 --- a/phpunit/block-supports/block-style-variations-test.php +++ b/phpunit/block-supports/block-style-variations-test.php @@ -56,8 +56,7 @@ public function filter_set_theme_root() { /** * Tests that block style variations registered via either * `gutenberg_register_block_style` with a style object, or a standalone - * block style variation file within `/block-styles`, are added to the - * theme data. + * block style variation file within `/styles`, are added to the theme data. */ public function test_add_registered_block_styles_to_theme_data() { switch_theme( 'block-theme' ); @@ -105,7 +104,7 @@ public function test_add_registered_block_styles_to_theme_data() { /* * The following block style variations are registered * automatically from their respective JSON files within the - * theme's `block-styles` directory. + * theme's `/styles` directory. */ 'block-style-variation-a' => array( 'color' => array( From 800d452ff69fccb55afd39ee9d756e69371e965f Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:54:13 +1000 Subject: [PATCH 33/52] Switch scoping of variations to check supportedBlockTypes --- lib/class-wp-theme-json-resolver-gutenberg.php | 16 ++++++++++------ .../styles/block-style-variation-a.json | 1 - .../styles/block-style-variation-a.json | 1 - .../styles/block-style-variation-b.json | 1 - 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index e886d9047ca0fd..d78eda9be0ef51 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -719,21 +719,25 @@ private static function recursively_iterate_json( $dir ) { /** * Determines if a supplied style variation matches the provided scope. * - * For backwards compatibility, if a variation does not explicitly define - * a scope, it is assumed to be a theme style variation. + * For backwards compatibility, if a variation does not define any scope + * related property, e.g. `supportedBlockTypes`, it is assumed to be a + * theme style variation. * * @param array $variation Theme.json shaped style variation object. * @param string $scope Scope to check e.g. theme, block etc. + * * @return boolean */ private static function style_variation_has_scope( $variation, $scope ) { - $scopes = $variation['scope'] ?? array(); + if ( 'block' === $scope ) { + return isset( $variation['supportedBlockTypes'] ); + } - if ( count( $scopes ) === 0 ) { - return 'theme' === $scope; + if ( 'theme' === $scope ) { + return ! isset( $variation['supportedBlockTypes'] ); } - return in_array( $scope, $scopes, true ); + return false; } /** diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json index 017bb2148d4771..1daaac0062b9c6 100644 --- a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json @@ -1,6 +1,5 @@ { "version": 2, - "scope": [ "block" ], "supportedBlockTypes": [ "core/group", "core/columns", "core/media-text" ], "styles": { "color": { diff --git a/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json index 92aed03bb35005..0ba8417049eb0a 100644 --- a/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json +++ b/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json @@ -1,6 +1,5 @@ { "version": 2, - "scope": [ "block" ], "supportedBlockTypes": [ "core/group", "core/columns" ], "styles": { "color": { diff --git a/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json b/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json index d6afc57b5bad41..6133b3e9f8d591 100644 --- a/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json +++ b/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json @@ -1,6 +1,5 @@ { "version": 2, - "scope": [ "block" ], "supportedBlockTypes": [ "core/group", "core/columns" ], "styles": { "color": { From 9108b714ae47963467d153b79bcbe94f815655f7 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:55:56 +1000 Subject: [PATCH 34/52] Move scopeFeatureSelectors to utils and add docblock --- .../global-styles/use-global-styles-output.js | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) 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 baae9669e0c46d..82f09c47905d2c 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 @@ -21,6 +21,7 @@ import { ROOT_BLOCK_SELECTOR, ROOT_CSS_PROPERTIES_SELECTOR, scopeSelector, + scopeFeatureSelectors, appendToSelector, getBlockStyleVariationSelector, } from './utils'; @@ -614,33 +615,6 @@ 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 = []; From a524bfb831f4c89caf8f2ad63edccddb06c02035 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:25:06 +1000 Subject: [PATCH 35/52] Make comment docblock --- .../src/components/global-styles-provider/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 8029077f2c8f2f..a81cb0c3d7bb10 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -33,8 +33,13 @@ export function mergeBaseAndUserConfigs( base, user ) { } ); } -// Resolves shared block style variation definitions from the user origin -// under their respective block types and registers the block style if required. +/** + * Resolves shared block style variation definitions from the user origin + * under their respective block types and registers the block style if required. + * + * @param {Object} userConfig Current user origin global styles data. + * @return {Object} Updated global styles data. + */ function useResolvedBlockStyleVariationsConfig( userConfig ) { const getBlockStyles = useSelect( ( select ) => { return select( blocksStore ).getBlockStyles; From 1cede2ab53cf8a8d76a5810ce18731247f1122aa Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:29:33 +1000 Subject: [PATCH 36/52] Use shorthand for retrieving getBlockStyles from blocksStore --- packages/block-editor/src/hooks/block-style-variation.js | 4 +--- .../editor/src/components/global-styles-provider/index.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js index 7cce3f9b1182ed..39568012ec2cb9 100644 --- a/packages/block-editor/src/hooks/block-style-variation.js +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -69,9 +69,7 @@ function useBlockProps( { name, className, clientId } ) { const variation = getVariationNameFromClass( className ); const variationClass = `is-style-${ variation }-${ clientId }`; - const getBlockStyles = useSelect( ( select ) => { - return select( blocksStore ).getBlockStyles; - }, [] ); + const { getBlockStyles } = useSelect( blocksStore ); const { settings, styles } = useBlockSyleVariation( name, diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index a81cb0c3d7bb10..26c6ace290acfe 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -41,9 +41,7 @@ export function mergeBaseAndUserConfigs( base, user ) { * @return {Object} Updated global styles data. */ function useResolvedBlockStyleVariationsConfig( userConfig ) { - const getBlockStyles = useSelect( ( select ) => { - return select( blocksStore ).getBlockStyles; - }, [] ); + const { getBlockStyles } = useSelect( blocksStore ); return useMemo( () => { const sharedVariations = userConfig?.styles?.blocks?.variations; From 402a234a94cb58c250155955e9995f32914864b8 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:55:39 +1000 Subject: [PATCH 37/52] Split out user origin based block style registration to useEffect --- .../global-styles-provider/index.js | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 26c6ace290acfe..3496096c4d5664 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -11,7 +11,7 @@ import { registerBlockStyle, store as blocksStore } from '@wordpress/blocks'; import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect, useDispatch } from '@wordpress/data'; -import { useMemo, useCallback } from '@wordpress/element'; +import { useEffect, useMemo, useCallback } from '@wordpress/element'; /** * Internal dependencies @@ -43,15 +43,15 @@ export function mergeBaseAndUserConfigs( base, user ) { function useResolvedBlockStyleVariationsConfig( userConfig ) { const { getBlockStyles } = useSelect( blocksStore ); - return useMemo( () => { + // Register any block style variations that have been added + // by a theme style variation and are not already registered. + useEffect( () => { const sharedVariations = userConfig?.styles?.blocks?.variations; if ( ! sharedVariations ) { - return userConfig; + return; } - const variationsConfig = cloneDeep( userConfig ); - Object.entries( sharedVariations ).forEach( ( [ variationName, variation ] ) => { if ( ! variation?.supportedBlockTypes ) { @@ -59,8 +59,6 @@ function useResolvedBlockStyleVariationsConfig( userConfig ) { } variation.supportedBlockTypes.forEach( ( blockName ) => { - // Register any block style variations that have been added - // by a theme style variation and are not already registered. const blockStyles = getBlockStyles( blockName ); const registeredBlockStyle = blockStyles.find( ( { name } ) => name === variationName @@ -72,7 +70,27 @@ function useResolvedBlockStyleVariationsConfig( userConfig ) { label: variationName, } ); } + } ); + } + ); + }, [ userConfig?.styles?.blocks?.variations, getBlockStyles ] ); + + const updatedConfig = useMemo( () => { + const sharedVariations = userConfig?.styles?.blocks?.variations; + + if ( ! sharedVariations ) { + return userConfig; + } + + const variationsConfig = cloneDeep( userConfig ); + Object.entries( sharedVariations ).forEach( + ( [ variationName, variation ] ) => { + if ( ! variation?.supportedBlockTypes ) { + return; + } + + variation.supportedBlockTypes.forEach( ( blockName ) => { const path = [ 'styles', 'blocks', @@ -86,7 +104,9 @@ function useResolvedBlockStyleVariationsConfig( userConfig ) { ); return deepmerge( variationsConfig, userConfig ); - }, [ userConfig, getBlockStyles ] ); + }, [ userConfig ] ); + + return updatedConfig; } function useGlobalStylesUserConfig() { From a8690c4fc1bb40126867e099226d96ea380d9404 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:34:47 +1000 Subject: [PATCH 38/52] Fix issue with application of variations in the site editor --- .../block-editor/src/hooks/block-style-variation.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js index 39568012ec2cb9..e7691b2aea63e3 100644 --- a/packages/block-editor/src/hooks/block-style-variation.js +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -22,7 +22,12 @@ function getVariationNameFromClass( className ) { } function useBlockSyleVariation( name, variation, clientId ) { - const { user: userStyles } = useContext( GlobalStylesContext ); + // Prefer global styles data in GlobalStylesContext, which are available + // if in the site editor. Otherwise fall back to whatever is in the + // editor settings and available in the post editor. + // This can be updated once the global styles data is consistently + // available across the editors. + const { merged: mergedConfig } = useContext( GlobalStylesContext ); const { globalSettings, globalStyles } = useSelect( ( select ) => { const { __experimentalFeatures, __experimentalStyles } = select( blockEditorStore ).getSettings(); @@ -33,12 +38,12 @@ function useBlockSyleVariation( name, variation, clientId ) { }, [] ); return useMemo( () => { - const styles = userStyles?.styles ?? globalStyles; + const styles = mergedConfig?.styles ?? globalStyles; const variationStyles = styles?.blocks?.[ name ]?.variations?.[ variation ]; return { - settings: userStyles?.settings ?? globalSettings, + settings: mergedConfig?.settings ?? globalSettings, // The variation style data is all that is needed to generate // the styles for the current application to a block. The variation // name is updated to match the instance specific class name. @@ -53,7 +58,7 @@ function useBlockSyleVariation( name, variation, clientId ) { }, }; }, [ - userStyles, + mergedConfig, globalSettings, globalStyles, variation, From 2b97374d9cdfb94b24d49465fc1337e3dc24609a Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:34:46 +1000 Subject: [PATCH 39/52] Rename supportedBlockTypes to blockTypes --- lib/block-supports/block-style-variations.php | 2 +- lib/class-wp-theme-json-gutenberg.php | 2 +- lib/class-wp-theme-json-resolver-gutenberg.php | 8 ++++---- .../components/global-styles-provider/index.js | 8 ++++---- phpunit/class-wp-theme-json-resolver-test.php | 16 ++++++++-------- .../styles/block-style-variation-a.json | 2 +- .../styles/block-style-variation-a.json | 2 +- .../styles/block-style-variation-b.json | 2 +- schemas/json/theme.json | 4 ++-- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index ecdc4e1498c557..a717bac4a21bab 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -173,7 +173,7 @@ function gutenberg_resolve_and_register_block_style_variations( $variations ) { $have_named_variations = ! wp_is_numeric_array( $variations ); foreach ( $variations as $key => $variation ) { - $supported_blocks = $variation['supportedBlockTypes'] ?? array(); + $supported_blocks = $variation['blockTypes'] ?? array(); /* * Standalone theme.json partial files for block style variations diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index e5c6a1b12134f9..99de056143dd69 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -348,12 +348,12 @@ class WP_Theme_JSON_Gutenberg { * @var string[] */ const VALID_TOP_LEVEL_KEYS = array( + 'blockTypes', 'customTemplates', 'description', 'patterns', 'settings', 'styles', - 'supportedBlockTypes', 'templateParts', 'title', 'version', diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index d78eda9be0ef51..f13b1966b72de5 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -720,8 +720,8 @@ private static function recursively_iterate_json( $dir ) { * Determines if a supplied style variation matches the provided scope. * * For backwards compatibility, if a variation does not define any scope - * related property, e.g. `supportedBlockTypes`, it is assumed to be a - * theme style variation. + * related property, e.g. `blockTypes`, it is assumed to be a theme style + * variation. * * @param array $variation Theme.json shaped style variation object. * @param string $scope Scope to check e.g. theme, block etc. @@ -730,11 +730,11 @@ private static function recursively_iterate_json( $dir ) { */ private static function style_variation_has_scope( $variation, $scope ) { if ( 'block' === $scope ) { - return isset( $variation['supportedBlockTypes'] ); + return isset( $variation['blockTypes'] ); } if ( 'theme' === $scope ) { - return ! isset( $variation['supportedBlockTypes'] ); + return ! isset( $variation['blockTypes'] ); } return false; diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 3496096c4d5664..cf2bee6dada6cf 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -54,11 +54,11 @@ function useResolvedBlockStyleVariationsConfig( userConfig ) { Object.entries( sharedVariations ).forEach( ( [ variationName, variation ] ) => { - if ( ! variation?.supportedBlockTypes ) { + if ( ! variation?.blockTypes ) { return; } - variation.supportedBlockTypes.forEach( ( blockName ) => { + variation.blockTypes.forEach( ( blockName ) => { const blockStyles = getBlockStyles( blockName ); const registeredBlockStyle = blockStyles.find( ( { name } ) => name === variationName @@ -86,11 +86,11 @@ function useResolvedBlockStyleVariationsConfig( userConfig ) { Object.entries( sharedVariations ).forEach( ( [ variationName, variation ] ) => { - if ( ! variation?.supportedBlockTypes ) { + if ( ! variation?.blockTypes ) { return; } - variation.supportedBlockTypes.forEach( ( blockName ) => { + variation.blockTypes.forEach( ( blockName ) => { const path = [ 'styles', 'blocks', diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 8a68c7eed30611..23e2ebfcd673cd 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -1093,10 +1093,10 @@ public function data_get_style_variations() { 'scope' => 'block', 'expected_variations' => array( array( - 'supportedBlockTypes' => array( 'core/group', 'core/columns', 'core/media-text' ), - 'version' => 2, - 'title' => 'block-style-variation-a', - 'styles' => array( + 'blockTypes' => array( 'core/group', 'core/columns', 'core/media-text' ), + 'version' => 2, + 'title' => 'block-style-variation-a', + 'styles' => array( 'color' => array( 'background' => 'darkcyan', 'text' => 'aliceblue', @@ -1104,10 +1104,10 @@ public function data_get_style_variations() { ), ), array( - 'supportedBlockTypes' => array( 'core/group', 'core/columns' ), - 'version' => 2, - 'title' => 'block-style-variation-b', - 'styles' => array( + 'blockTypes' => array( 'core/group', 'core/columns' ), + 'version' => 2, + 'title' => 'block-style-variation-b', + 'styles' => array( 'color' => array( 'background' => 'midnightblue', 'text' => 'lightblue', diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json index 1daaac0062b9c6..660e4d9a684a8a 100644 --- a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json @@ -1,6 +1,6 @@ { "version": 2, - "supportedBlockTypes": [ "core/group", "core/columns", "core/media-text" ], + "blockTypes": [ "core/group", "core/columns", "core/media-text" ], "styles": { "color": { "background": "darkcyan", diff --git a/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json index 0ba8417049eb0a..03b3b0a93d809e 100644 --- a/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json +++ b/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json @@ -1,6 +1,6 @@ { "version": 2, - "supportedBlockTypes": [ "core/group", "core/columns" ], + "blockTypes": [ "core/group", "core/columns" ], "styles": { "color": { "background": "indigo", diff --git a/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json b/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json index 6133b3e9f8d591..161a19bdf4d576 100644 --- a/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json +++ b/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json @@ -1,6 +1,6 @@ { "version": 2, - "supportedBlockTypes": [ "core/group", "core/columns" ], + "blockTypes": [ "core/group", "core/columns" ], "styles": { "color": { "background": "midnightblue", diff --git a/schemas/json/theme.json b/schemas/json/theme.json index f58214c6f774c4..6a3ec7e81d3947 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -2276,7 +2276,7 @@ }, { "properties": { - "supportedBlockTypes": { + "blockTypes": { "type": "array", "items": { "type": "string" @@ -2688,7 +2688,7 @@ "type": "string", "description": "Description of the global styles variation." }, - "supportedBlockTypes": { + "blockTypes": { "type": "array", "description": "List of block types that can use the block style variation this theme.json file represents.", "items": { From 92809eaa186edfb0335aad2d89f35a125acef558 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Sat, 20 Apr 2024 15:44:15 +1000 Subject: [PATCH 40/52] Fix merging of theme style variations config --- packages/editor/src/components/global-styles-provider/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index cf2bee6dada6cf..bb70fce156be9a 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -17,7 +17,6 @@ import { useEffect, useMemo, useCallback } from '@wordpress/element'; * Internal dependencies */ import { unlock } from '../../lock-unlock'; -import cloneDeep from '../../utils/clone-deep'; import setNestedValue from '../../utils/set-nested-value'; const { GlobalStylesContext, cleanEmptyObject } = unlock( @@ -82,7 +81,7 @@ function useResolvedBlockStyleVariationsConfig( userConfig ) { return userConfig; } - const variationsConfig = cloneDeep( userConfig ); + const variationsConfig = {}; Object.entries( sharedVariations ).forEach( ( [ variationName, variation ] ) => { From fca03a005b072a8ccd1e158dec65c181670a4042 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 22 Apr 2024 18:09:26 +1000 Subject: [PATCH 41/52] Try optimisation of variation resolution in global styles provider --- .../global-styles-provider/index.js | 71 ++++++++++--------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index bb70fce156be9a..04a42ab7af819c 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -41,19 +41,21 @@ export function mergeBaseAndUserConfigs( base, user ) { */ function useResolvedBlockStyleVariationsConfig( userConfig ) { const { getBlockStyles } = useSelect( blocksStore ); + const sharedVariations = userConfig?.styles?.blocks?.variations; - // Register any block style variations that have been added - // by a theme style variation and are not already registered. - useEffect( () => { - const sharedVariations = userConfig?.styles?.blocks?.variations; - + // Collect block style variation definitions to merge and unregistered + // block styles for automatic registration. + const [ userConfigToMerge, unregisteredStyles ] = useMemo( () => { if ( ! sharedVariations ) { - return; + return []; } + const variationsConfigToMerge = {}; + const unregisteredBlockStyles = []; + Object.entries( sharedVariations ).forEach( ( [ variationName, variation ] ) => { - if ( ! variation?.blockTypes ) { + if ( ! variation?.blockTypes?.length ) { return; } @@ -64,32 +66,15 @@ function useResolvedBlockStyleVariationsConfig( userConfig ) { ); if ( ! registeredBlockStyle ) { - registerBlockStyle( blockName, { - name: variationName, - label: variationName, - } ); + unregisteredBlockStyles.push( [ + blockName, + { + name: variationName, + label: variationName, + }, + ] ); } - } ); - } - ); - }, [ userConfig?.styles?.blocks?.variations, getBlockStyles ] ); - - const updatedConfig = useMemo( () => { - const sharedVariations = userConfig?.styles?.blocks?.variations; - if ( ! sharedVariations ) { - return userConfig; - } - - const variationsConfig = {}; - - Object.entries( sharedVariations ).forEach( - ( [ variationName, variation ] ) => { - if ( ! variation?.blockTypes ) { - return; - } - - variation.blockTypes.forEach( ( blockName ) => { const path = [ 'styles', 'blocks', @@ -97,13 +82,31 @@ function useResolvedBlockStyleVariationsConfig( userConfig ) { 'variations', variationName, ]; - setNestedValue( variationsConfig, path, variation ); + setNestedValue( variationsConfigToMerge, path, variation ); } ); } ); - return deepmerge( variationsConfig, userConfig ); - }, [ userConfig ] ); + return [ variationsConfigToMerge, unregisteredBlockStyles ]; + }, [ sharedVariations, getBlockStyles ] ); + + // Automatically register missing block styles from variations. + useEffect( + () => + unregisteredStyles?.forEach( ( unregisteredStyle ) => + registerBlockStyle( ...unregisteredStyle ) + ), + [ unregisteredStyles ] + ); + + // Merge shared block style variation definitions into overall user config. + const updatedConfig = useMemo( () => { + if ( ! userConfigToMerge ) { + return userConfig; + } + + return deepmerge( userConfigToMerge, userConfig ); + }, [ userConfigToMerge, userConfig ] ); return updatedConfig; } From 00c22e58d8fbd24e9d82baf4da78d42f9f8d77f6 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:45:21 +1000 Subject: [PATCH 42/52] Move sorting of style overrides into getStyleOverrides selector --- .../src/components/editor-styles/index.js | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/packages/block-editor/src/components/editor-styles/index.js b/packages/block-editor/src/components/editor-styles/index.js index 5e350fb1ebfbb5..2ede3e45566ed1 100644 --- a/packages/block-editor/src/components/editor-styles/index.js +++ b/packages/block-editor/src/components/editor-styles/index.js @@ -68,32 +68,15 @@ function useDarkThemeBodyClassName( styles, scope ) { } function EditorStyles( { styles, scope } ) { - const { overrides, clientIds } = useSelect( - ( select ) => ( { - overrides: unlock( select( blockEditorStore ) ).getStyleOverrides(), - clientIds: select( blockEditorStore ).getClientIdsWithDescendants(), - } ), + const overrides = useSelect( + ( select ) => unlock( select( blockEditorStore ) ).getStyleOverrides(), [] ); const [ transformedStyles, transformedSvgs ] = useMemo( () => { - const clientIdMap = clientIds.reduce( ( acc, clientId, index ) => { - acc[ clientId ] = index; - return acc; - }, {} ); - - // Sort overrides to match the order of blocks they relate to. - // This is useful to maintain the correct CSS cascade order for - // nested block style variations. - const sortedOverrides = [ ...overrides ].sort( ( a, b ) => { - const aIndex = clientIdMap[ a[ 1 ].clientId ] ?? -1; - const bIndex = clientIdMap[ b[ 1 ].clientId ] ?? -1; - return aIndex - bIndex; - } ); - const _styles = Object.values( styles ?? [] ); - for ( const [ id, override ] of sortedOverrides ) { + for ( const [ id, override ] of overrides ) { const index = _styles.findIndex( ( { id: _id } ) => id === _id ); const overrideWithId = { ...override, id }; if ( index === -1 ) { @@ -113,7 +96,7 @@ function EditorStyles( { styles, scope } ) { .map( ( style ) => style.assets ) .join( '' ), ]; - }, [ styles, overrides, scope, clientIds ] ); + }, [ styles, overrides, scope ] ); return ( <> From afe5c982067fc11eeeede6913cf3a92cc328a6df Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:49:59 +1000 Subject: [PATCH 43/52] Undo whitespace change --- packages/block-editor/src/components/editor-styles/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-editor/src/components/editor-styles/index.js b/packages/block-editor/src/components/editor-styles/index.js index 2ede3e45566ed1..34c4a90020e3be 100644 --- a/packages/block-editor/src/components/editor-styles/index.js +++ b/packages/block-editor/src/components/editor-styles/index.js @@ -72,7 +72,6 @@ function EditorStyles( { styles, scope } ) { ( select ) => unlock( select( blockEditorStore ) ).getStyleOverrides(), [] ); - const [ transformedStyles, transformedSvgs ] = useMemo( () => { const _styles = Object.values( styles ?? [] ); From 248188914dba663ef09a8e1350beb8b2cef65303 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 10 May 2024 10:50:03 +0900 Subject: [PATCH 44/52] Revert "Make global styles data available in block editor" This reverts commit 6d0d2078dcd59ae45cd76a8a31c7178fe8360d49. --- lib/block-editor-settings.php | 1 - .../class-wp-rest-block-editor-settings-controller.php | 2 +- .../editor/src/components/provider/use-block-editor-settings.js | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/block-editor-settings.php b/lib/block-editor-settings.php index ba726ffe2ad5b9..53668e114e04cb 100644 --- a/lib/block-editor-settings.php +++ b/lib/block-editor-settings.php @@ -78,7 +78,6 @@ function gutenberg_get_block_editor_settings( $settings ) { $settings['styles'] = array_merge( $global_styles, get_block_editor_theme_styles() ); - $settings['__experimentalStyles'] = gutenberg_get_global_styles(); $settings['__experimentalFeatures'] = gutenberg_get_global_settings(); // These settings may need to be updated based on data coming from theme.json sources. if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) { diff --git a/lib/experimental/class-wp-rest-block-editor-settings-controller.php b/lib/experimental/class-wp-rest-block-editor-settings-controller.php index 2d0537ca0891c9..2c4bf29bc21a73 100644 --- a/lib/experimental/class-wp-rest-block-editor-settings-controller.php +++ b/lib/experimental/class-wp-rest-block-editor-settings-controller.php @@ -155,7 +155,7 @@ public function get_item_schema() { '__experimentalStyles' => array( 'description' => __( 'Styles consolidated from core, theme, and user origins.', 'gutenberg' ), 'type' => 'object', - 'context' => array( 'post-editor', 'site-editor', 'widgets-editor', 'mobile' ), + 'context' => array( 'mobile' ), ), '__experimentalEnableQuoteBlockV2' => array( diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index bf0f6159f2ff96..2917c6905e3f0d 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -41,7 +41,6 @@ const BLOCK_EDITOR_SETTINGS = [ '__experimentalBlockDirectory', '__experimentalDiscussionSettings', '__experimentalFeatures', - '__experimentalStyles', '__experimentalGlobalStylesBaseStyles', '__unstableGalleryWithImageBlocks', 'alignWide', From c57996319a1da897c16e4dc6c9a3286da69272a4 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 10 May 2024 10:57:20 +0900 Subject: [PATCH 45/52] Switch variation hook to use new global styles editor setting --- .../block-editor/src/hooks/block-style-variation.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js index e7691b2aea63e3..1896c422d12375 100644 --- a/packages/block-editor/src/hooks/block-style-variation.js +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -15,6 +15,7 @@ import { } from '../components/global-styles'; import { useStyleOverride } from './utils'; import { store as blockEditorStore } from '../store'; +import { globalStylesDataKey } from '../store/private-keys'; function getVariationNameFromClass( className ) { const match = className?.match( /\bis-style-(?!default)(\S+)\b/ ); @@ -25,15 +26,12 @@ function useBlockSyleVariation( name, variation, clientId ) { // Prefer global styles data in GlobalStylesContext, which are available // if in the site editor. Otherwise fall back to whatever is in the // editor settings and available in the post editor. - // This can be updated once the global styles data is consistently - // available across the editors. const { merged: mergedConfig } = useContext( GlobalStylesContext ); const { globalSettings, globalStyles } = useSelect( ( select ) => { - const { __experimentalFeatures, __experimentalStyles } = - select( blockEditorStore ).getSettings(); + const settings = select( blockEditorStore ).getSettings(); return { - globalSettings: __experimentalFeatures, - globalStyles: __experimentalStyles, + globalSettings: settings.__experimentalFeatures, + globalStyles: settings[ globalStylesDataKey ], }; }, [] ); From 113c26929a31357cc91f3a71d5b0e28daee9912a Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 17 May 2024 16:33:02 +1000 Subject: [PATCH 46/52] Fix variations panel in site editor --- packages/edit-site/src/components/global-styles/screen-block.js | 2 +- .../src/components/global-styles/variations/variations-panel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/screen-block.js b/packages/edit-site/src/components/global-styles/screen-block.js index 9811f10b834dab..2368f7499acbf6 100644 --- a/packages/edit-site/src/components/global-styles/screen-block.js +++ b/packages/edit-site/src/components/global-styles/screen-block.js @@ -235,7 +235,7 @@ function ScreenBlock( { name, variation } ) { return ( <> { hasVariationsPanel && ( diff --git a/packages/edit-site/src/components/global-styles/variations/variations-panel.js b/packages/edit-site/src/components/global-styles/variations/variations-panel.js index 6c03821705619f..f98cc65e5c95b1 100644 --- a/packages/edit-site/src/components/global-styles/variations/variations-panel.js +++ b/packages/edit-site/src/components/global-styles/variations/variations-panel.js @@ -31,7 +31,7 @@ export function useBlockVariations( name ) { }, [ name ] ); - const [ variations ] = useGlobalStyle( 'variations', name, 'base' ); + const [ variations ] = useGlobalStyle( 'variations', name ); const variationNames = Object.keys( variations ?? {} ); return getFilteredBlockStyles( blockStyles, variationNames ); From e8ac4b2d98737b1c83a0fd7cb5f2c9ec6769e2e2 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 May 2024 13:01:58 +1000 Subject: [PATCH 47/52] Make block style variation retrieval more robust When old block style variation classes are left in a block's class string and they don't match a current variation, prevent them from blocking the first available variation. --- lib/block-supports/block-style-variations.php | 29 ++++++++++------ .../src/hooks/block-style-variation.js | 34 +++++++++++++++---- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index a717bac4a21bab..3a61cb6e3dd5aa 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -19,19 +19,19 @@ function gutenberg_get_block_style_variation_class_name( $block, $variation ) { } /** - * Determine a block style variation name from a CSS class string. + * Determines the block style variation names within a CSS class string. * * @param string $class_string CSS class string to look for a variation in. * - * @return string|null The block style variation name if found. + * @return array|null The block style variation name if found. */ function gutenberg_get_block_style_variation_name_from_class( $class_string ) { if ( ! is_string( $class_string ) ) { return null; } - preg_match( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches ); - return $matches ? $matches[1] : null; + preg_match_all( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches ); + return $matches[1] ?? null; } /** @@ -49,16 +49,25 @@ function gutenberg_get_block_style_variation_name_from_class( $class_string ) { * @return array The parsed block with block style variation classname added. */ function gutenberg_render_block_style_variation_support_styles( $parsed_block ) { - $classes = $parsed_block['attrs']['className'] ?? null; - $variation = gutenberg_get_block_style_variation_name_from_class( $classes ); + $classes = $parsed_block['attrs']['className'] ?? null; + $variations = gutenberg_get_block_style_variation_name_from_class( $classes ); - if ( ! $variation ) { + if ( ! $variations ) { return $parsed_block; } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $theme_json = $tree->get_raw_data(); - $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array(); + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $theme_json = $tree->get_raw_data(); + + // Only the first block style variation with data is supported. + $variation_data = array(); + foreach ( $variations as $variation ) { + $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array(); + + if ( ! empty( $variation_data ) ) { + break; + } + } if ( empty( $variation_data ) ) { return $parsed_block; diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js index 1896c422d12375..ee02b97d216704 100644 --- a/packages/block-editor/src/hooks/block-style-variation.js +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -17,9 +17,30 @@ import { useStyleOverride } from './utils'; import { store as blockEditorStore } from '../store'; import { globalStylesDataKey } from '../store/private-keys'; -function getVariationNameFromClass( className ) { - const match = className?.match( /\bis-style-(?!default)(\S+)\b/ ); - return match ? match[ 1 ] : null; +/** + * Get the first block style variation that has been registered from the class string. + * + * @param {string} className CSS class string for a block. + * @param {Array} registeredStyles Currently registered block styles. + * + * @return {string|null} The name of the first registered variation. + */ +function getVariationNameFromClass( className, registeredStyles = [] ) { + // The global flag affects how capturing groups work in JS. So the regex + // below will only return full CSS classes not just the variation name. + const matches = className?.match( /\bis-style-(?!default)(\S+)\b/g ); + + if ( ! matches ) { + return null; + } + + for ( const variationClass of matches ) { + const variation = variationClass.substring( 9 ); // Remove 'is-style-' prefix. + if ( registeredStyles.some( ( style ) => style.name === variation ) ) { + return variation; + } + } + return null; } function useBlockSyleVariation( name, variation, clientId ) { @@ -69,11 +90,12 @@ function useBlockSyleVariation( name, variation, clientId ) { // This is so that the variation style override's ID is predictable // when the order of applied style variations changes. function useBlockProps( { name, className, clientId } ) { - const variation = getVariationNameFromClass( className ); - const variationClass = `is-style-${ variation }-${ clientId }`; - const { getBlockStyles } = useSelect( blocksStore ); + const registeredStyles = getBlockStyles( name ); + const variation = getVariationNameFromClass( className, registeredStyles ); + const variationClass = `is-style-${ variation }-${ clientId }`; + const { settings, styles } = useBlockSyleVariation( name, variation, From e2d27d30a0f04c876ca3d40a8bc7c5b4c2c07e11 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 May 2024 13:28:04 +1000 Subject: [PATCH 48/52] Update test block theme json versions to latest --- .../styles/block-style-variation-a.json | 2 +- .../block-theme-child-with-block-style-variations/theme.json | 2 +- .../themedir1/block-theme/styles/block-style-variation-a.json | 2 +- .../themedir1/block-theme/styles/block-style-variation-b.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json index 660e4d9a684a8a..195321a33b3366 100644 --- a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "blockTypes": [ "core/group", "core/columns", "core/media-text" ], "styles": { "color": { diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json index 0da29ef16fd679..a471d8f326a4ab 100644 --- a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json @@ -1,4 +1,4 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2 + "version": 3 } diff --git a/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json index 03b3b0a93d809e..356bc4fc3de7d3 100644 --- a/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json +++ b/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "blockTypes": [ "core/group", "core/columns" ], "styles": { "color": { diff --git a/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json b/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json index 161a19bdf4d576..8b79948517255f 100644 --- a/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json +++ b/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "blockTypes": [ "core/group", "core/columns" ], "styles": { "color": { From e963725146974dd23e5840aa6c188dea865c26de Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 May 2024 14:57:24 +1000 Subject: [PATCH 49/52] Update missed theme json version numbers in tests --- phpunit/class-wp-theme-json-resolver-test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 23e2ebfcd673cd..9aa277469fc1eb 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -1094,7 +1094,7 @@ public function data_get_style_variations() { 'expected_variations' => array( array( 'blockTypes' => array( 'core/group', 'core/columns', 'core/media-text' ), - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'title' => 'block-style-variation-a', 'styles' => array( 'color' => array( @@ -1105,7 +1105,7 @@ public function data_get_style_variations() { ), array( 'blockTypes' => array( 'core/group', 'core/columns' ), - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'title' => 'block-style-variation-b', 'styles' => array( 'color' => array( From df4a51858d2821b41fe0702fd309321679a497b7 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 May 2024 20:43:34 +1000 Subject: [PATCH 50/52] Pass clientId directly through to block props component --- packages/block-editor/src/hooks/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 4beb5c9122f537..c14b6329cf2ec3 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -568,7 +568,7 @@ export function createBlockListBlockFilter( features ) { useBlockProps, } = feature; - const neededProps = { clientId: props.clientId }; + const neededProps = {}; for ( const key of attributeKeys ) { if ( props.attributes[ key ] ) { neededProps[ key ] = props.attributes[ key ]; @@ -595,6 +595,7 @@ export function createBlockListBlockFilter( features ) { // function reference. setAllWrapperProps={ setAllWrapperProps } name={ props.name } + clientId={ props.clientId } // This component is pure, so only pass needed // props!!! { ...neededProps } From 33830c4a628890b8d3f6233356aff64a77ebfc90 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 29 May 2024 10:52:11 +1000 Subject: [PATCH 51/52] Add @since comments --- lib/block-supports/block-style-variations.php | 22 +++++++++++++++++++ lib/class-wp-theme-json-gutenberg.php | 4 ++++ ...class-wp-theme-json-resolver-gutenberg.php | 6 +++-- .../block-style-variations-test.php | 2 ++ phpunit/class-wp-theme-json-resolver-test.php | 4 ++++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index 3a61cb6e3dd5aa..6fc89b01c6793d 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -9,6 +9,8 @@ /** * Get the class name for this application of this block's variation styles. * + * @since 6.6.0 + * * @param array $block Block object. * @param string $variation Slug for the block style variation. * @@ -21,6 +23,8 @@ function gutenberg_get_block_style_variation_class_name( $block, $variation ) { /** * Determines the block style variation names within a CSS class string. * + * @since 6.6.0 + * * @param string $class_string CSS class string to look for a variation in. * * @return array|null The block style variation name if found. @@ -44,6 +48,8 @@ function gutenberg_get_block_style_variation_name_from_class( $class_string ) { * in the DOM order. This is why the variation stylesheet generation is in a * different filter. * + * @since 6.6.0 + * * @param array $parsed_block The parsed block. * * @return array The parsed block with block style variation classname added. @@ -125,6 +131,8 @@ function gutenberg_render_block_style_variation_support_styles( $parsed_block ) * * @see gutenberg_render_block_style_variation_support_styles * + * @since 6.6.0 + * * @param string $block_content Rendered block content. * @param array $block Block object. * @@ -167,6 +175,8 @@ function gutenberg_render_block_style_variation_class_name( $block_content, $blo * been already. This registration is required for later sanitization of * theme.json data. * + * @since 6.6.0 + * * @param array $variations Shared block style variations. * * @return array Block variations data to be merged under styles.blocks @@ -230,6 +240,8 @@ function gutenberg_resolve_and_register_block_style_variations( $variations ) { * Merges variations data with existing theme.json data ensuring that the * current theme.json data values take precedence. * + * @since 6.6.0 + * * @param array $variations_data Block style variations data keyed by block type. * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. * @param string $origin Origin for the theme.json data. @@ -261,6 +273,8 @@ function gutenberg_merge_block_style_variations_data( $variations_data, $theme_j * custom user selections already made will take precedence over the shared * style variation value. * + * @since 6.6.0 + * * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. * * @return WP_Theme_JSON_Data_Gutenberg @@ -277,6 +291,8 @@ function gutenberg_resolve_block_style_variations_from_theme_style_variation( $t * Merges block style variation data sourced from standalone partial * theme.json files. * + * @since 6.6.0 + * * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. * * @return WP_Theme_JSON_Data_Gutenberg @@ -292,6 +308,8 @@ function gutenberg_resolve_block_style_variations_from_theme_json_partials( $the * Merges shared block style variations registered within the * `styles.blocks.variations` property of the primary theme.json file. * + * @since 6.6.0 + * * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. * * @return WP_Theme_JSON_Data_Gutenberg @@ -310,6 +328,8 @@ function gutenberg_resolve_block_style_variations_from_primary_theme_json( $them * Any variation values defined within the theme.json specific to a block type * will take precedence over these shared definitions. * + * @since 6.6.0 + * * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. * * @return WP_Theme_JSON_Data_Gutenberg @@ -333,6 +353,8 @@ function gutenberg_resolve_block_style_variations_from_styles_registry( $theme_j /** * Enqueues styles for block style variations. + * + * @since 6.6.0 */ function gutenberg_enqueue_block_style_variation_styles() { wp_enqueue_style( 'block-style-variation-styles' ); diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 99de056143dd69..04fe19027a4d64 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -817,6 +817,7 @@ protected static function do_opt_in_into_settings( &$context ) { * * @since 5.8.0 * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. + * @since 6.6.0 Extended schema definition to allow enhanced block style variations. * * @param array $input Structure to sanitize. * @param array $valid_block_names List of valid block names. @@ -1033,6 +1034,7 @@ protected static function prepend_to_selector( $selector, $to_prepend ) { * @since 5.8.0 * @since 5.9.0 Added `duotone` key with CSS selector. * @since 6.1.0 Added `features` key with block support feature level selectors. + * @since 6.6.0 Added non-core block style variations to generated metadata. * * @return array Block metadata. */ @@ -1220,6 +1222,7 @@ public function get_settings() { * * @since 5.8.0 * @since 5.9.0 Removed the `$type` parameter`, added the `$types` and `$origins` parameters. + * @since 6.6.0 Added option to skip root layout styles. * * @param array $types Types of styles to load. Will load all by default. It accepts: * - `variables`: only the CSS Custom Properties for presets & custom ones. @@ -3218,6 +3221,7 @@ protected static function filter_slugs( $node, $slugs ) { * Removes insecure data from theme.json. * * @since 5.9.0 + * @since 6.6.0 Added support for block style variation element styles. * * @param array $theme_json Structure to sanitize. * @return array Sanitized structure. diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index f13b1966b72de5..b21fb956ff8ff7 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -723,6 +723,8 @@ private static function recursively_iterate_json( $dir ) { * related property, e.g. `blockTypes`, it is assumed to be a theme style * variation. * + * @since 6.6.0 + * * @param array $variation Theme.json shaped style variation object. * @param string $scope Scope to check e.g. theme, block etc. * @@ -794,7 +796,7 @@ public static function get_style_variations( $scope = 'theme' ) { * * @since 6.6.0 * - * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. + * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. * @return array An array of resolved paths. */ public static function get_resolved_theme_uris( $theme_json ) { @@ -837,7 +839,7 @@ public static function get_resolved_theme_uris( $theme_json ) { * * @since 6.6.0 * - * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. + * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. * @return WP_Theme_JSON_Gutenberg Theme merged with resolved paths, if any found. */ public static function resolve_theme_file_uris( $theme_json ) { diff --git a/phpunit/block-supports/block-style-variations-test.php b/phpunit/block-supports/block-style-variations-test.php index 99d87d4736bd1a..b84267446330cc 100644 --- a/phpunit/block-supports/block-style-variations-test.php +++ b/phpunit/block-supports/block-style-variations-test.php @@ -3,6 +3,8 @@ /** * Test the block style variations block support. * + * @since 6.6.0 + * * @package Gutenberg */ diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 9aa277469fc1eb..50e1d9d846899e 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -991,6 +991,8 @@ public function data_get_merged_data_returns_origin() { * * @dataProvider data_get_style_variations * + * @since 6.6.0 Added tests for block style variations. + * * @param string $theme Name of the theme to use. * @param string $scope Scope to filter variations by e.g. theme vs block. * @param array $expected_variations Collection of expected variations. @@ -1010,6 +1012,8 @@ public function test_get_style_variations( $theme, $scope, $expected_variations /** * Data provider for test_get_style_variations * + * @since 6.6.0 Added data provider for testing theme and block style variations. + * * @return array */ public function data_get_style_variations() { From 168b5e42caa6f92efc21dbc44d6ffa1000cde1da Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 29 May 2024 13:05:28 +1000 Subject: [PATCH 52/52] Add backport change log --- backport-changelog/6.6/6662.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/6.6/6662.md diff --git a/backport-changelog/6.6/6662.md b/backport-changelog/6.6/6662.md new file mode 100644 index 00000000000000..2dfbc68dd23e03 --- /dev/null +++ b/backport-changelog/6.6/6662.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6662 + +* https://github.com/WordPress/gutenberg/pull/57908