Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theme.json: Extend block style variations support #56540

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a65a9f5
Allow block style variations to be registered across multiple block t…
aaronrobertshaw Dec 14, 2023
8da9d1f
Extend theme.json schema for extended block style variations
aaronrobertshaw Dec 14, 2023
01d1843
Update PHP processing of block style variations in theme.json
aaronrobertshaw Dec 14, 2023
883c467
Update JS processing of block style variations
aaronrobertshaw Dec 14, 2023
5f17722
Allow non-core block style variations to be configured within Global …
aaronrobertshaw Dec 14, 2023
a4d54d6
Remove no longer required option to skip root layout styles
aaronrobertshaw Dec 14, 2023
e5b5196
Remove unnecessary comments
aaronrobertshaw Dec 14, 2023
b66a1cb
Fix typo and remove stray comment
aaronrobertshaw Jan 2, 2024
4744dc3
Ensure test block style variation is unregistered before assertions
aaronrobertshaw Jan 3, 2024
1130551
Pay the price for my codespell ceasing to function
aaronrobertshaw Jan 3, 2024
d49e029
Prevent variation elements and pseudo selectors from being removed
aaronrobertshaw Jan 4, 2024
7582c5f
Fix block style variation schemas and sanitization
aaronrobertshaw Jan 4, 2024
7426a3d
Try reducing the main block style variation selector specificity
aaronrobertshaw Jan 9, 2024
f48dd57
Bump block instance elements specificity
aaronrobertshaw Jan 9, 2024
c47aa41
Update theme.json and element unit test for reduction in element spec…
aaronrobertshaw Jan 10, 2024
9aca87e
Reinstate processing of variation > block > elements in block node ge…
aaronrobertshaw Jan 10, 2024
a003477
Bump element styles specificity again for overcoming variation styles
aaronrobertshaw Jan 10, 2024
562a9fa
Add element styles for inner blocks within a variation to site editor
aaronrobertshaw Jan 11, 2024
f09833f
Revert "Bump element styles specificity again for overcoming variatio…
aaronrobertshaw Jan 15, 2024
29180f7
Remove elements and incorrect blocks properties from variations schema
aaronrobertshaw Jan 15, 2024
6413ecd
Remove variation > block > elements from theme.json data
aaronrobertshaw Jan 15, 2024
4f56178
Revert "Add element styles for inner blocks within a variation to sit…
aaronrobertshaw Jan 15, 2024
f092ad7
Revert "Reinstate processing of variation > block > elements in block…
aaronrobertshaw Jan 15, 2024
15270b6
Fix block style selectors when root block selector is an element
aaronrobertshaw Jan 15, 2024
6fb1cfb
Revert "Fix block style selectors when root block selector is an elem…
aaronrobertshaw Jan 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions lib/block-supports/elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,20 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) {
}

$class_name = wp_get_elements_class_name( $block );
$class_name = ".$class_name.$class_name";

$element_types = array(
'button' => array(
'selector' => ".$class_name .wp-element-button, .$class_name .wp-block-button__link",
'selector' => "$class_name .wp-element-button, $class_name .wp-block-button__link",
'skip' => $skip_button_color_serialization,
),
'link' => array(
'selector' => ".$class_name a",
'hover_selector' => ".$class_name a:hover",
'selector' => "$class_name a",
'hover_selector' => "$class_name a:hover",
'skip' => $skip_link_color_serialization,
),
'heading' => array(
'selector' => ".$class_name h1, .$class_name h2, .$class_name h3, .$class_name h4, .$class_name h5, .$class_name h6",
'selector' => "$class_name h1, $class_name h2, $class_name h3, $class_name h4, $class_name h5, $class_name h6",
'skip' => $skip_heading_color_serialization,
'elements' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ),
),
Expand Down Expand Up @@ -190,7 +191,7 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) {
gutenberg_style_engine_get_styles(
$element_style_object,
array(
'selector' => ".$class_name $element",
'selector' => "$class_name $element",
'context' => 'block-supports',
)
);
Expand Down
241 changes: 208 additions & 33 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -841,9 +841,30 @@ 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 can contain styles for inner `blocks`.
// - Variations definitions cannot be nested.
// - Variations inner block styles cannot contain `elements`.
//
// As each variation needs a `blocks` schema but without `elements` and
// inner `blocks`, the overall schema will be generated in multiple
// passes.
foreach ( $valid_block_names as $block ) {
$schema_settings_blocks[ $block ] = static::VALID_SETTINGS;
$schema_styles_blocks[ $block ] = $styles_non_top_level;
}

// Generate block style variations schema including nested block styles
// schema as generated above.
$block_style_variation_styles = static::VALID_STYLES;
$block_style_variation_styles['blocks'] = $schema_styles_blocks;
$block_style_variation_styles['elements'] = $schema_styles_elements;

foreach ( $valid_block_names as $block ) {
// Build the schema for each block style variation.
$style_variation_names = array();

if (
! empty( $input['styles']['blocks'][ $block ]['variations'] ) &&
is_array( $input['styles']['blocks'][ $block ]['variations'] ) &&
Expand All @@ -857,13 +878,14 @@ 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;

// The element styles schema can now be added for this block to the
// styles.blocks.$block schema.
$schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements;
}

$schema['styles'] = static::VALID_STYLES;
Expand Down Expand Up @@ -980,12 +1002,34 @@ protected static function prepend_to_selector( $selector, $to_prepend ) {
*/
protected static function get_blocks_metadata() {
// NOTE: the compat/6.1 version of this method in Gutenberg did not have these changes.
$registry = WP_Block_Type_Registry::get_instance();
$blocks = $registry->get_all_registered();
$registry = WP_Block_Type_Registry::get_instance();
$blocks = $registry->get_all_registered();
$style_registry = WP_Block_Styles_Registry::get_instance();

// Is there metadata for all currently registered blocks?
$blocks = array_diff_key( $blocks, static::$blocks_metadata );

if ( empty( $blocks ) ) {
// New block styles may have been registered within WP_Block_Styles_Registry.
// Update block metadata for any new block style variations.
$registered_styles = $style_registry->get_all_registered();
foreach ( static::$blocks_metadata as $block_name => $block_metadata ) {
if ( ! empty( $registered_styles[ $block_name ] ) ) {
$style_selectors = $block_metadata['styleVariations'] ?? array();

foreach ( $registered_styles[ $block_name ] as $block_style ) {
if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) {
$style_selectors[ $block_style['name'] ] = static::append_to_selector(
'.is-style-' . $block_style['name'],
$block_metadata['selector']
);
}
}

static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors;
}
}

return static::$blocks_metadata;
}

Expand All @@ -1009,7 +1053,7 @@ protected static function get_blocks_metadata() {

if ( $duotone_support ) {
$root_selector = wp_get_block_css_selector( $block_type );
$duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support );
$duotone_selector = static::scope_selector( $root_selector, $duotone_support );
}
}

Expand All @@ -1018,11 +1062,20 @@ protected static function get_blocks_metadata() {
}

// If the block has style variations, append their selectors to the block metadata.
$style_selectors = array();
if ( ! empty( $block_type->styles ) ) {
$style_selectors = array();
foreach ( $block_type->styles as $style ) {
$style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
}
}

// Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json.
$registered_styles = $style_registry->get_registered_styles_for_block( $block_name );
foreach ( $registered_styles as $style ) {
$style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
}

if ( ! empty( $style_selectors ) ) {
static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors;
}
}
Expand Down Expand Up @@ -1748,6 +1801,10 @@ protected static function compute_preset_classes( $settings, $selector, $origins
* @return string Scoped selector.
*/
public static function scope_selector( $scope, $selector ) {
if ( ! $selector || ! $scope ) {
return $selector;
}
Comment on lines +1804 to +1806
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tweak just allowed more flexible use of the scope_selector util function, simplifying code elsewhere.


$scopes = explode( ',', $scope );
$selectors = explode( ',', $selector );

Expand Down Expand Up @@ -2380,38 +2437,80 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) {
}

foreach ( $theme_json['styles']['blocks'] as $name => $node ) {
$selector = null;
if ( isset( $selectors[ $name ]['selector'] ) ) {
$selector = $selectors[ $name ]['selector'];
}
$selector = $selectors[ $name ]['selector'] ?? null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid the null coalescing operator isn't officially supported in WP yet. There's a ticket for it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for flagging that. I had a suspicion that was the case although I recalled some recent performance PRs swapping out _wp_array_get for it. It's used a fair bit now already within this class.

Do you think it is that much of a nuisance when it comes to backporting?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeahhh those perf improvements were changed before landing in core - see the discussion here. I'd avoid it if you don't want the backport PR to turn into an argument 😂

$duotone_selector = $selectors[ $name ]['duotone'] ?? null;
$feature_selectors = $selectors[ $name ]['selectors'] ?? null;
$variations = $node['variations'] ?? array();
$variation_selectors = array();
$variation_nodes = array();

$duotone_selector = null;
if ( isset( $selectors[ $name ]['duotone'] ) ) {
$duotone_selector = $selectors[ $name ]['duotone'];
}
foreach ( $variations as $variation => $variation_node ) {
$variation_selector = $selectors[ $name ]['styleVariations'][ $variation ];
$variation_selectors[] = array(
'path' => array( 'styles', 'blocks', $name, 'variations', $variation ),
'selector' => $variation_selector,
);

$feature_selectors = null;
if ( isset( $selectors[ $name ]['selectors'] ) ) {
$feature_selectors = $selectors[ $name ]['selectors'];
}
$variation_blocks = $variation_node['blocks'] ?? array();
$variation_elements = $variation_node['elements'] ?? array();

$variation_selectors = array();
if ( isset( $node['variations'] ) ) {
foreach ( $node['variations'] as $variation => $node ) {
$variation_selectors[] = array(
'path' => array( 'styles', 'blocks', $name, 'variations', $variation ),
'selector' => $selectors[ $name ]['styleVariations'][ $variation ],
foreach ( $variation_blocks as $variation_block => $variation_block_node ) {
$variation_block_selector = static::scope_selector( $variation_selector, $selectors[ $variation_block ]['selector'] ?? null );
$variation_duotone_selector = static::scope_selector( $variation_selector, $selectors[ $variation_block ]['duotone'] ?? null );
$variation_feature_selectors = $selectors[ $variation_block ]['selectors'] ?? null;

if ( $variation_feature_selectors ) {
foreach ( $variation_feature_selectors as $feature => $feature_selector ) {
if ( is_string( $feature_selector ) ) {
$variation_feature_selectors[ $feature ] = static::scope_selector( $variation_selector, $feature_selector );
}

if ( is_array( $feature_selector ) ) {
foreach ( $feature_selector as $subfeature => $subfeature_selector ) {
$variation_feature_selectors[ $feature ][ $subfeature ] = static::scope_selector( $variation_selector, $subfeature_selector );
}
}
}
}

$variation_nodes[] = array(
'name' => $variation_block,
'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'blocks', $variation_block ),
'selector' => $variation_block_selector,
'selectors' => $variation_feature_selectors,
'duotone' => $variation_duotone_selector,
);
}

foreach ( $variation_elements as $variation_element => $variation_element_node ) {
$nodes[] = array(
'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'elements', $variation_element ),
'selector' => static::scope_selector( $variation_selector, static::ELEMENTS[ $variation_element ] ),
);

// Handle any pseudo selectors for the element.
if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $variation_element ] ) ) {
foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $variation_element ] as $pseudo_selector ) {
if ( isset( $variation_element_node[ $pseudo_selector ] ) ) {
$pseudo_element_selector = static::append_to_selector( static::ELEMENTS[ $variation_element ], $pseudo_selector );
$nodes[] = array(
'path' => array( 'styles', 'blocks', $name, 'variations', $variation, 'elements', $variation_element ),
'selector' => static::scope_selector( $variation_selector, $pseudo_element_selector ),
);
}
}
}
}
}

$nodes[] = array(
'name' => $name,
'path' => array( 'styles', 'blocks', $name ),
'selector' => $selector,
'selectors' => $feature_selectors,
'duotone' => $duotone_selector,
'variations' => $variation_selectors,
'name' => $name,
'path' => array( 'styles', 'blocks', $name ),
'selector' => $selector,
'selectors' => $feature_selectors,
'duotone' => $duotone_selector,
'variations' => $variation_selectors,
'variation_nodes' => $variation_nodes,
);

if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
Expand Down Expand Up @@ -2490,6 +2589,25 @@ static function ( $split_selector ) use ( $clean_style_variation_selector ) {
}
}

$style_variation_block_declarations = array();
if ( ! empty( $block_metadata['variation_nodes'] ) ) {
foreach ( $block_metadata['variation_nodes'] as $variation_node ) {
$style_variation_block_node = _wp_array_get( $this->theme_json, $variation_node['path'], array() );
$variation_block_declarations = static::get_feature_declarations_for_node( $variation_node, $style_variation_block_node );

foreach ( $variation_block_declarations as $current_selector => $new_declarations ) {
$style_variation_block_declarations[ $current_selector ] = $new_declarations;
}

$style_variation_block_declarations[ $variation_node['selector'] ] = static::compute_style_properties(
$style_variation_block_node,
$settings,
null,
$this->theme_json
);
}
}

/*
* Get a reference to element name from path.
* $block_metadata['path'] = array( 'styles','elements','link' );
Expand Down Expand Up @@ -2591,6 +2709,11 @@ static function ( $pseudo_selector ) use ( $selector ) {
$block_rules .= static::to_ruleset( $style_variation_selector, $individual_style_variation_declarations );
}

// 7. Generate and append the block style variations for inner blocks and elements.
foreach ( $style_variation_block_declarations as $style_variation_block_selector => $individual_style_variation_block_declaration ) {
$block_rules .= static::to_ruleset( $style_variation_block_selector, $individual_style_variation_block_declaration );
}

return $block_rules;
}

Expand Down Expand Up @@ -3050,6 +3173,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.
aaronrobertshaw marked this conversation as resolved.
Show resolved Hide resolved
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 );
}
Expand Down Expand Up @@ -3229,6 +3375,35 @@ public function get_raw_data() {
return $this->theme_json;
}

/**
* Converts block styles registered through the `WP_Block_Styles_Registry`
* with a style object, into theme.json format.
*
* @since 6.5.0
*
* @return array Styles configuration adhering to the theme.json schema.
*/
public static function get_from_block_styles_registry() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block style variations will also now support registering them through the style registry with a style object. This function collects those style objects and returns them all as theme json data that can be merged into theme.json objects from other objects. See the changes to WP_Theme_JSON_Resolver_Gutenberg for more details.

$variations_data = array();
$registry = WP_Block_Styles_Registry::get_instance();
$styles = $registry->get_all_registered();

foreach ( $styles as $block_name => $variations ) {
foreach ( $variations as $variation_name => $variation ) {
if ( ! empty( $variation['style_data'] ) ) {
$variations_data[ $block_name ]['variations'][ $variation_name ] = $variation['style_data'];
}
}
}

return array(
'version' => static::LATEST_SCHEMA,
'styles' => array(
'blocks' => $variations_data,
),
);
}

/**
* Transforms the given editor settings according the
* add_theme_support format to the theme.json format.
Expand Down
Loading
Loading