Skip to content

Commit

Permalink
Selectors API: Make duotone selectors fallback and be scoped (#49423)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronrobertshaw authored Mar 31, 2023
1 parent adb2cbb commit c2b4b1e
Show file tree
Hide file tree
Showing 16 changed files with 300 additions and 224 deletions.
96 changes: 56 additions & 40 deletions docs/reference-guides/block-api/block-supports.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ supports: {
- Default value: null
- Subproperties:
- `background`: type `boolean`, default value `true`
- `__experimentalDuotone`: type `string`, default value undefined
- `gradients`: type `boolean`, default value `false`
- `link`: type `boolean`, default value `false`
- `text`: type `boolean`, default value `true`
Expand Down Expand Up @@ -231,46 +230,9 @@ When the block declares support for `color.background`, the attributes definitio

### color.__experimentalDuotone

This property adds UI controls which allow to apply a duotone filter to a block or part of a block.
_**Note:** Deprecated since WordPress 6.3._

The parent selector is automatically added much like nesting in Sass/SCSS (however, the `&` selector is not supported).

```js
supports: {
color: {
// Apply the filter to the same selector in both edit and save.
__experimentalDuotone: '> .duotone-img, > .duotone-video',

// Default values must be disabled if you don't want to use them with duotone.
background: false,
text: false
}
}
```

Duotone presets are sourced from `color.duotone` in [theme.json](/docs/how-to-guides/themes/theme-json.md).

When the block declares support for `color.__experimentalDuotone`, the attributes definition is extended to include the attribute `style`:

- `style`: attribute of `object` type with no default assigned.

The block can apply a default duotone color by specifying its own attribute with a default e.g.:

```js
attributes: {
style: {
type: 'object',
default: {
color: {
duotone: [
'#FFF',
'#000'
]
}
}
}
}
```
This property has been replaced by [`filter.duotone`](#filter-duotone).

### color.gradients

Expand Down Expand Up @@ -499,6 +461,60 @@ attributes: {
}
```

## filter
- Type: `Object`
- Default value: null
- Subproperties:
- `duotone`: type `boolean`, default value `false`

This value signals that a block supports some of the properties related to filters. When it does, the block editor will show UI controls for the user to set their values.

### filter.duotone

This property adds UI controls which allow the user to apply a duotone filter to
a block or part of a block.

```js
supports: {
filter: {
// Enable duotone support
duotone: true
}
},
selectors: {
filter: {
// Apply the filter to img elements inside the image block
duotone: '.wp-block-image img'
}
}
```

The filter can be applied to an element inside the block by setting the `selectors.filter.duotone` selector.

Duotone presets are sourced from `color.duotone` in [theme.json](/docs/how-to-guides/themes/theme-json.md).

When the block declares support for `filter.duotone`, the attributes definition is extended to include the attribute `style`:

- `style`: attribute of `object` type with no default assigned.

The block can apply a default duotone color by specifying its own attribute with a default e.g.:

```js
attributes: {
style: {
type: 'object',
default: {
color: {
duotone: [
'#FFF',
'#000'
]
}
}
}
}
```

## html

- Type: `boolean`
Expand Down
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ Insert an image to make a visual statement. ([Source](https://github.com/WordPre

- **Name:** core/image
- **Category:** media
- **Supports:** anchor, color (~~background~~, ~~text~~)
- **Supports:** anchor, color (~~background~~, ~~text~~), filter (duotone)
- **Attributes:** align, alt, caption, height, href, id, linkClass, linkDestination, linkTarget, rel, sizeSlug, title, url, width

## Latest Comments
Expand Down
5 changes: 4 additions & 1 deletion lib/block-supports/duotone.php
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,9 @@ function gutenberg_get_duotone_filter_svg( $preset ) {
function gutenberg_register_duotone_support( $block_type ) {
$has_duotone_support = false;
if ( property_exists( $block_type, 'supports' ) ) {
$has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
// Previous `color.__experimentalDuotone` support flag is migrated
// to `filter.duotone` via `block_type_metadata_settings` filter.
$has_duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), null );
}

if ( $has_duotone_support ) {
Expand Down Expand Up @@ -450,3 +452,4 @@ function gutenberg_render_duotone_support( $block_content, $block ) {
add_action( 'wp_enqueue_scripts', array( 'WP_Duotone_Gutenberg', 'output_global_styles' ), 11 );
add_action( 'wp_footer', array( 'WP_Duotone_Gutenberg', 'output_footer_assets' ), 10 );
add_filter( 'block_editor_settings_all', array( 'WP_Duotone_Gutenberg', 'add_editor_settings' ), 10 );
add_filter( 'block_type_metadata_settings', array( 'WP_Duotone_Gutenberg', 'migrate_experimental_duotone_support_flag' ), 10, 2 );
78 changes: 68 additions & 10 deletions lib/class-wp-duotone-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,42 @@ public static function output_global_styles() {
}
}

/**
* Get the CSS selector for a block type.
*
* @param string $block_name The block name.
*
* @return string The CSS selector or null if there is no support.
*/
private static function get_selector( $block_name ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );

if ( $block_type && property_exists( $block_type, 'supports' ) ) {
// Backwards compatibility with `supports.color.__experimentalDuotone`
// is provided via the `block_type_metadata_settings` filter. If
// `supports.filter.duotone` has not been set and the experimental
// property has been, the experimental property value is copied into
// `supports.filter.duotone`.
$duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false );
if ( ! $duotone_support ) {
return null;
}

// If the experimental duotone support was set, that value is to be
// treated as a selector and requires scoping.
$experimental_duotone = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
if ( $experimental_duotone ) {
$root_selector = wp_get_block_css_selector( $block_type );
return is_string( $experimental_duotone )
? WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $experimental_duotone )
: $root_selector;
}

// Regular filter.duotone support uses filter.duotone selectors with fallbacks.
return wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true );
}
}

/**
* Render out the duotone CSS styles and SVG.
*
Expand All @@ -272,22 +308,15 @@ public static function output_global_styles() {
* @return string Filtered block content.
*/
public static function render_duotone_support( $block_content, $block ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );

$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;
}
$duotone_selector = self::get_selector( $block['blockName'] );

// The block should have a duotone attribute or have duotone defined in its theme.json to be processed.
$has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );
$has_global_styles_duotone = array_key_exists( $block['blockName'], self::$global_styles_block_names );

if (
empty( $block_content ) ||
! $duotone_support ||
! $duotone_selector ||
( ! $has_duotone_attribute && ! $has_global_styles_duotone )
) {
return $block_content;
Expand Down Expand Up @@ -349,7 +378,17 @@ 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_selector );
$selectors = explode( ',', $duotone_selector );

$selectors_scoped = array();
foreach ( $selectors as $selector_part ) {
// Assuming the selector part is a subclass selector (not a tag name)
// so we can prepend the filter id class. If we want to support elements
// such as `img` or namespaces, we'll need to add a case for that here.
$selectors_scoped[] = '.' . $filter_id . trim( $selector_part );
}

$selector = implode( ', ', $selectors_scoped );

// 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 Expand Up @@ -386,4 +425,23 @@ public static function render_duotone_support( $block_content, $block ) {

return $tags->get_updated_html();
}

/**
* Migrate the old experimental duotone support flag to its stabilized location
* under `supports.filter.duotone` and sets.
*
* @param array $settings Current block type settings.
* @param array $metadata Block metadata as read in via block.json.
*
* @return array Filtered block type settings.
*/
public static function migrate_experimental_duotone_support_flag( $settings, $metadata ) {
$duotone_support = _wp_array_get( $metadata, array( 'supports', 'color', '__experimentalDuotone' ), null );

if ( ! isset( $settings['supports']['filter']['duotone'] ) && null !== $duotone_support ) {
_wp_array_set( $settings, array( 'supports', 'filter', 'duotone' ), (bool) $duotone_support );
}

return $settings;
}
}
14 changes: 12 additions & 2 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,17 @@ 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, 'filter.duotone' );

// Keep backwards compatibility for support.color.__experimentalDuotone.
if ( null === $duotone_selector ) {
$duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), null );

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

if ( null !== $duotone_selector ) {
static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector;
}
Expand Down Expand Up @@ -2389,8 +2400,7 @@ function( $pseudo_selector ) use ( $selector ) {

// 3. Generate and append the rules that use the duotone selector.
if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) {
$selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] );
$block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone );
$block_rules .= static::to_ruleset( $block_metadata['duotone'], $declarations_duotone );
}

// 4. Generate Layout block gap styles.
Expand Down
35 changes: 3 additions & 32 deletions lib/compat/wordpress-6.3/get-global-styles-and-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,6 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f

$has_selectors = ! empty( $block_type->selectors );

// Duotone (No fallback selectors for Duotone).
if ( 'filter.duotone' === $target || array( 'filter', 'duotone' ) === $target ) {
// If selectors API in use, only use it's value or null.
if ( $has_selectors ) {
return _wp_array_get( $block_type->selectors, array( 'filter', 'duotone' ), null );
}

// Selectors API, not available, check for old experimental selector.
return _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), null );
}

// Root Selector.

// Calculated before returning as it can be used as fallback for
Expand All @@ -59,8 +48,8 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f
return $root_selector;
}

// If target is not `root` or `duotone` we have a feature or subfeature
// as the target. If the target is a string convert to an array.
// If target is not `root` we have a feature or subfeature as the target.
// If the target is a string convert to an array.
if ( is_string( $target ) ) {
$target = explode( '.', $target );
}
Expand Down Expand Up @@ -95,25 +84,7 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f
}

// Scope the feature selector by the block's root selector.
$scopes = explode( ',', $root_selector );
$selectors = explode( ',', $feature_selector );

$selectors_scoped = array();
foreach ( $scopes as $outer ) {
foreach ( $selectors as $inner ) {
$outer = trim( $outer );
$inner = trim( $inner );
if ( ! empty( $outer ) && ! empty( $inner ) ) {
$selectors_scoped[] = $outer . ' ' . $inner;
} elseif ( empty( $outer ) ) {
$selectors_scoped[] = $inner;
} elseif ( empty( $inner ) ) {
$selectors_scoped[] = $outer;
}
}
}

return implode( ', ', $selectors_scoped );
return WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $feature_selector );
}

// Subfeature selector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,6 @@ export function getBlockCSSSelector(
const hasSelectors = ! isEmpty( selectors );
const path = Array.isArray( target ) ? target.join( '.' ) : target;

// Duotone ( no fallback selectors for Duotone ).
if ( path === 'filter.duotone' ) {
// If selectors API in use, only use its value or null.
if ( hasSelectors ) {
return get( selectors, path, null );
}

// Selectors API, not available, check for old experimental selector.
return get( supports, 'color.__experimentalDuotone', null );
}

// Root selector.

// Calculated before returning as it can be used as a fallback for feature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ describe( 'global styles renderer', () => {
'core/image': {
name: imageBlock.name,
selector: imageSupports.__experimentalSelector,
duotoneSelector: imageSupports.color.__experimentalDuotone,
duotoneSelector: '.my-image img',
fallbackGapValue: undefined,
featureSelectors: {
root: '.my-image',
Expand Down
Loading

1 comment on commit c2b4b1e

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in c2b4b1e.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4570919779
📝 Reported issues:

Please sign in to comment.