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 and ryanwelcher committed Apr 5, 2023
1 parent 3180818 commit fa6e2a6
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

0 comments on commit fa6e2a6

Please sign in to comment.