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

Selectors API: Fix for global styles hook, style variations, and duotone #49393

Merged
merged 9 commits into from
Mar 29, 2023
35 changes: 0 additions & 35 deletions docs/reference-guides/block-api/block-metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ Starting in WordPress 5.8 release, we encourage using the `block.json` metadata
"my-plugin/message": "message"
},
"usesContext": [ "groupId" ],
"editorSelectors": {
"root": ".editor-styles-wrapper .wp-block-my-plugin-notice"
},
"selectors": {
"root": ".wp-block-my-plugin-notice"
},
Expand Down Expand Up @@ -385,38 +382,6 @@ See [the block context documentation](/docs/reference-guides/block-api/block-con
}
```

### Editor Selectors

- Type: `object`
- Optional
- Localized: No
- Property: `editorSelectors`
- Default: `{}`
- Since: `WordPress 6.3.0`

Any editor specific custom CSS selectors, keyed by `root`, feature, or
sub-feature, to be used when generating block styles for theme.json
(global styles) stylesheets in the editor.

Editor only selectors override those defined within the `selectors` property.

See the [the selectors documentation](/docs/reference-guides/block-api/block-selectors.md) for more details.

```json
{
"editorSelectors": {
"root": ".my-custom-block-selector",
"color": {
"text": ".my-custom-block-selector p"
},
"typography": {
"root": ".my-custom-block-selector > h2",
"text-decoration": ".my-custom-block-selector > h2 span"
}
}
}
```

### Selectors

- Type: `object`
Expand Down
33 changes: 2 additions & 31 deletions docs/reference-guides/block-api/block-selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Block Selectors is the API that allows blocks to customize the CSS selector used
when their styles are generated.

A block may customize its CSS selectors at three levels: root, feature, and
subfeature. Each may also be overridden with editor-only selectors.
subfeature.

## Root Selector

Expand Down Expand Up @@ -113,33 +113,4 @@ example. As the `color` feature also doesn't define a `root` selector,
selector, `.my-custom-block-selector`.

For a subfeature such as `typography.font-size`, it would fallback to its parent
feature's selector given that is present, i.e. `.my-custom-block-selector > h2`.

## Editor-only Selectors

There are scenarios in which a block might need different markup within the
editor compared to the frontend e.g. inline cropping of the Image block. Some
generated styles may then need to be applied to different, or multiple,
elements.

Continuing with the Image cropping example, the image border styles need to also
be applied to the cropping area. If the selector for the cropping area is added
to the normal `selectors` config for the block, it would be output unnecessarily
on the frontend.

To avoid this, and include the selector for the editor only, the selectors for the border feature can be
overridden via the `editorSelectors` config.

### Example
```json
{
...
"selectors": {
"root": ".wp-block-image",
"border": ".wp-block-image img"
},
"editorSelectors": {
"border": ".wp-block-image img, .wp-block-image .wp-block-image__crop-area"
},
}
```
feature's selector given that is present, i.e. `.my-custom-block-selector > h2`.
10 changes: 6 additions & 4 deletions lib/class-wp-duotone-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,11 @@ public static function output_global_styles() {
public static function render_duotone_support( $block_content, $block ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );

$duotone_support = false;
if ( $block_type && property_exists( $block_type, 'supports' ) ) {
$duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
$duotone_support = false;
$duotone_selector = null;
if ( $block_type ) {
$duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' );
$duotone_support = (bool) $duotone_selector;
}

// The block should have a duotone attribute or have duotone defined in its theme.json to be processed.
Expand Down Expand Up @@ -308,7 +310,7 @@ public static function render_duotone_support( $block_content, $block ) {
$filter_id = gutenberg_get_duotone_filter_id( array( 'slug' => $slug ) );

// Build the CSS selectors to which the filter will be applied.
$selector = WP_Theme_JSON_Gutenberg::scope_selector( '.' . $filter_id, $duotone_support );
$selector = WP_Theme_JSON_Gutenberg::scope_selector( '.' . $filter_id, $duotone_selector );

// We only want to add the selector if we have it in the output already, essentially skipping 'unset'.
if ( array_key_exists( $slug, self::$output ) ) {
Expand Down
112 changes: 41 additions & 71 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ protected static function get_blocks_metadata() {
}

// The block may or may not have a duotone selector.
$duotone_selector = wp_get_block_css_selector( $block_type, 'filters.duotone' );
$duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' );
if ( null !== $duotone_selector ) {
static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector;
}
Expand Down Expand Up @@ -2269,57 +2269,39 @@ public function get_styles_for_block( $block_metadata ) {
$selector = $block_metadata['selector'];
$settings = _wp_array_get( $this->theme_json, array( 'settings' ) );

$feature_declarations = static::get_feature_declarations_for_block( $block_metadata, $node, $settings, $this->theme_json );
$feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node, $settings, $this->theme_json );

// If there are style variations, generate the declarations for them, including any feature selectors the block may have.
$style_variation_declarations = array();
if ( ! empty( $block_metadata['variations'] ) ) {
foreach ( $block_metadata['variations'] as $style_variation ) {
$style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() );
$style_variation_selector = $style_variation['selector'];

// If the block has feature selectors, generate the declarations for them within the current style variation.
if ( ! empty( $block_metadata['features'] ) ) {
$clean_style_variation_selector = trim( $style_variation_selector );
foreach ( $block_metadata['features'] as $feature_name => $feature_selector ) {
if ( empty( $style_variation_node[ $feature_name ] ) ) {
continue;
}
// If feature selector includes block classname, remove it but leave the whitespace in.
$shortened_feature_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $feature_selector );
// Prepend the variation selector to the feature selector.
$split_feature_selectors = explode( ',', $shortened_feature_selector );
$feature_selectors = array_map(
static function( $split_feature_selector ) use ( $clean_style_variation_selector ) {
return $clean_style_variation_selector . $split_feature_selector;
},
$split_feature_selectors
);
$combined_feature_selectors = implode( ',', $feature_selectors );

// Compute declarations for the feature.
$new_feature_declarations = static::compute_style_properties( array( $feature_name => $style_variation_node[ $feature_name ] ), $settings, null, $this->theme_json );

/*
* Merge new declarations with any that already exist for
* the feature selector. This may occur when multiple block
* support features use the same custom selector.
*/
if ( isset( $style_variation_declarations[ $combined_feature_selectors ] ) ) {
$style_variation_declarations[ $combined_feature_selectors ] = array_merge( $style_variation_declarations[ $combined_feature_selectors ], $new_feature_declarations );
} else {
$style_variation_declarations[ $combined_feature_selectors ] = $new_feature_declarations;
}
$style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() );
$clean_style_variation_selector = trim( $style_variation['selector'] );

// Generate any feature/subfeature style declarations for the current style variation.
$variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node, $settings, $this->theme_json );

// Combine selectors with style variation's selector and add to overall style variation declarations.
foreach ( $variation_declarations as $current_selector => $new_declarations ) {
// If current selector includes block classname, remove it but leave the whitespace in.
$shortened_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $current_selector );

// Prepend the variation selector to the current selector.
$split_selectors = explode( ',', $shortened_selector );
$updated_selectors = array_map(
static function( $split_selector ) use ( $clean_style_variation_selector ) {
return $clean_style_variation_selector . $split_selector;
},
$split_selectors
);
$combined_selectors = implode( ',', $updated_selectors );

/*
* Remove the feature from the variation's node now the
* styles will be included under the feature level selector.
*/
unset( $style_variation_node[ $feature_name ] );
}
// Add the new declarations to the overall results under the modified selector.
$style_variation_declarations[ $combined_selectors ] = $new_declarations;
}

// Compute declarations for remaining styles not covered by feature level selectors.
$style_variation_declarations[ $style_variation_selector ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json );
$style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json );
}
}

Expand Down Expand Up @@ -3456,17 +3438,6 @@ protected static function get_root_block_selector( $block_type ) {
*/
protected static function get_block_selectors( $block_type, $root_selector ) {
if ( ! empty( $block_type->selectors ) ) {
$in_editor = false;

if ( function_exists( 'get_current_screen' ) ) {
$current_screen = get_current_screen();
$in_editor = $current_screen && $current_screen->is_block_editor;
}

if ( $in_editor && ! empty( $block_type->editor_selectors ) ) {
return array_merge( $block_type->selectors, $block_type->editor_selectors );
}

return $block_type->selectors;
}

Expand Down Expand Up @@ -3509,45 +3480,44 @@ protected static function get_block_element_selectors( $root_selector ) {
return $element_selectors;
}


/**
* Generates style declarations for the block's features e.g. color, border,
* typography etc, that have custom selectors in their block metadata.
* Generates style declarations for a node's features e.g. color, border,
* typography etc, that have custom selectors in their related block's
* metadata.
*
* @param object $block_metadata The block's metadata containing selectors for
* features.
* @param object $block_node The merged theme.json node for the block.
* @param object $metadata The related block metadata containing selectors.
* @param object $node A merged theme.json node for block or variation.
* @param object $settings The theme.json settings for the node.
* @param object $theme_json The current theme.json config.
*
* @return array The style declarations for the block's features with custom
* @return array The style declarations for the node's features with custom
* selectors.
*/
protected static function get_feature_declarations_for_block( $block_metadata, &$block_node, $settings, $theme_json ) {
protected static function get_feature_declarations_for_node( $metadata, &$node, $settings, $theme_json ) {
aaronrobertshaw marked this conversation as resolved.
Show resolved Hide resolved
$declarations = array();

if ( ! isset( $block_metadata['selectors'] ) ) {
if ( ! isset( $metadata['selectors'] ) ) {
return $declarations;
}

foreach ( $block_metadata['selectors'] as $feature => $feature_selectors ) {
foreach ( $metadata['selectors'] as $feature => $feature_selectors ) {
// Skip if this is the block's root selector or the block doesn't
// have any styles for the feature.
if ( 'root' === $feature || empty( $block_node[ $feature ] ) ) {
if ( 'root' === $feature || empty( $node[ $feature ] ) ) {
continue;
}

if ( is_array( $feature_selectors ) ) {
foreach ( $feature_selectors as $subfeature => $subfeature_selector ) {
if ( 'root' === $subfeature || empty( $block_node[ $feature ][ $subfeature ] ) ) {
if ( 'root' === $subfeature || empty( $node[ $feature ][ $subfeature ] ) ) {
continue;
}

// Create temporary node containing only the subfeature data
// to leverage existing `compute_style_properties` function.
$subfeature_node = array(
$feature => array(
$subfeature => $block_node[ $feature ][ $subfeature ],
$subfeature => $node[ $feature ][ $subfeature ],
),
);

Expand All @@ -3566,7 +3536,7 @@ protected static function get_feature_declarations_for_block( $block_metadata, &
// Remove the subfeature from the block's node now its
// styles will be included under its own selector not the
// block's.
unset( $block_node[ $feature ][ $subfeature ] );
unset( $node[ $feature ][ $subfeature ] );
}
}

Expand All @@ -3580,7 +3550,7 @@ protected static function get_feature_declarations_for_block( $block_metadata, &

// Create temporary node containing only the feature data
// to leverage existing `compute_style_properties` function.
$feature_node = array( $feature => $block_node[ $feature ] );
$feature_node = array( $feature => $node[ $feature ] );

// Generate the style declarations.
$new_declarations = static::compute_style_properties( $feature_node, $settings, null, $theme_json );
Expand All @@ -3598,7 +3568,7 @@ protected static function get_feature_declarations_for_block( $block_metadata, &

// Remove the feature from the block's node now its styles
// will be included under its own selector not the block's.
unset( $block_node[ $feature ] );
unset( $node[ $feature ] );
}
}

Expand Down
14 changes: 5 additions & 9 deletions lib/compat/wordpress-6.3/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
*/

/**
* Ensure the selectors, and editorSelectors properties, set via block.json
* metadata, are included within the block type's settings.
* Ensure the selectors, set via block.json metadata, are included within the
* block type's settings.
*
* Note: This should be removed when the minimum required WP version is >= 6.2.
* Note: This should be removed when the minimum required WP version is >= 6.3.
*
* @see https://github.com/WordPress/gutenberg/pull/46496
*
Expand All @@ -18,15 +18,11 @@
*
* @return array Filtered block type settings.
*/
function gutenberg_add_selectors_properties_to_block_type_settings( $settings, $metadata ) {
function gutenberg_add_selectors_property_to_block_type_settings( $settings, $metadata ) {
if ( ! isset( $settings['selectors'] ) && isset( $metadata['selectors'] ) ) {
$settings['selectors'] = $metadata['selectors'];
}

if ( ! isset( $settings['editor_selectors'] ) && isset( $metadata['editorSelectors'] ) ) {
$settings['editor_selectors'] = $metadata['editorSelectors'];
}

return $settings;
}
add_filter( 'block_type_metadata_settings', 'gutenberg_add_selectors_properties_to_block_type_settings', 10, 2 );
add_filter( 'block_type_metadata_settings', 'gutenberg_add_selectors_property_to_block_type_settings', 10, 2 );
Loading