From 77359805a50bcb3c2fb9e3d712261aa8ba82ee73 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 29 Mar 2023 17:14:34 +1000 Subject: [PATCH 01/33] Make duotone selectors fallback and be scoped --- .../get-global-styles-and-settings.php | 84 +++++++++++-------- .../global-styles/get-block-css-selector.js | 42 ++++++---- .../global-styles/use-global-styles-output.js | 9 +- packages/block-editor/src/hooks/duotone.js | 8 +- .../class-wp-get-block-css-selectors-test.php | 24 +++++- 5 files changed, 104 insertions(+), 63 deletions(-) diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index c213e50d64ae7b..5effd6718e64cd 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -25,21 +25,7 @@ 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 - // feature selectors later on. + // Root Selector ( can be used as a fallback ). $root_selector = null; if ( $has_selectors && isset( $block_type->selectors['root'] ) ) { @@ -59,16 +45,58 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f return $root_selector; } + $fallback_selector = $fallback ? $root_selector : null; + + // Helper to scope old experimental selectors. + $scope_selector = function( $scope, $selector ) { + $scopes = explode( ',', $scope ); + $selectors = explode( ',', $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 ); + }; + + // Duotone ( may fallback to root selector ). + if ( 'filter.duotone' === $target || array( 'filter', 'duotone' ) === $target ) { + // If selectors API in use, only use it's value, fallback, or null. + if ( $has_selectors ) { + return _wp_array_get( $block_type->selectors, array( 'filter', 'duotone' ), $fallback_selector ); + } + + // Selectors API, not available, check for old experimental selector. + $duotone_selector = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), null ); + + // Nothing to work with, provide fallback or null. + if ( null === $duotone_selector ) { + return $fallback_selector; + } + + // Scope the duotone selector by the block's root selector. + return $scope_selector( $root_selector, $duotone_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 ( is_string( $target ) ) { $target = explode( '.', $target ); } - // Feature Selectors ( May fallback to root selector ). + // Feature Selectors ( may fallback to root selector ). if ( 1 === count( $target ) ) { - $fallback_selector = $fallback ? $root_selector : null; - // Prefer the selectors API if available. if ( $has_selectors ) { // Look for selector under `feature.root`. @@ -95,25 +123,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 $scope_selector( $root_selector, $feature_selector ); } // Subfeature selector diff --git a/packages/block-editor/src/components/global-styles/get-block-css-selector.js b/packages/block-editor/src/components/global-styles/get-block-css-selector.js index db58709fe79aae..76a56f88fc34a4 100644 --- a/packages/block-editor/src/components/global-styles/get-block-css-selector.js +++ b/packages/block-editor/src/components/global-styles/get-block-css-selector.js @@ -34,21 +34,7 @@ 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 - // selectors later on. + // Root selector ( can be used as fallback ). let rootSelector = null; if ( hasSelectors && selectors.root ) { @@ -68,14 +54,36 @@ export function getBlockCSSSelector( return rootSelector; } + const fallbackSelector = fallback ? rootSelector : null; + + // Duotone ( may fallback to root selector ). + if ( path === 'filter.duotone' ) { + // If selectors API in use, only use its value, fallback, or null. + if ( hasSelectors ) { + return get( selectors, path, fallbackSelector ); + } + + // Selectors API, not available, check for old experimental selector. + const duotoneSelector = get( + supports, + 'color.__experimentalDuotone', + null + ); + + // If nothing to work with, provide fallback selector if available. + if ( ! duotoneSelector ) { + return fallbackSelector; + } + + return scopeSelector( rootSelector, duotoneSelector ); + } + // 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. const pathArray = Array.isArray( target ) ? target : target.split( '.' ); // Feature selectors ( may fallback to root selector ); if ( pathArray.length === 1 ) { - const fallbackSelector = fallback ? rootSelector : null; - // Prefer the selectors API if available. if ( hasSelectors ) { // Get selector from either `feature.root` or shorthand path. 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 2f68312b76c6b3..e5a52534981b37 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 @@ -19,7 +19,7 @@ import { getCSSRules } from '@wordpress/style-engine'; /** * Internal dependencies */ -import { PRESET_METADATA, ROOT_BLOCK_SELECTOR, scopeSelector } from './utils'; +import { PRESET_METADATA, ROOT_BLOCK_SELECTOR } from './utils'; import { getBlockCSSSelector } from './get-block-css-selector'; import { getTypographyFontSizeValue } from './typography-utils'; import { GlobalStylesContext } from './context'; @@ -859,10 +859,9 @@ export const toStyles = ( if ( duotoneDeclarations.length > 0 ) { ruleset = ruleset + - `${ scopeSelector( - selector, - duotoneSelector - ) }{${ duotoneDeclarations.join( ';' ) };}`; + `${ duotoneSelector }{${ duotoneDeclarations.join( + ';' + ) };}`; } } diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index a05e188d507e1a..fb7a749d82f7e6 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -32,6 +32,7 @@ import { __unstableDuotoneStylesheet as DuotoneStylesheet, __unstableDuotoneUnsetStylesheet as DuotoneUnsetStylesheet, } from '../components/duotone'; +import { getBlockCSSSelector } from '../components/global-styles/get-block-css-selector'; import { store as blockEditorStore } from '../store'; const EMPTY_ARRAY = []; @@ -273,9 +274,10 @@ function BlockDuotoneStyles( { name, duotoneStyle, id } ) { colors = getColorsFromDuotonePreset( colors, duotonePalette ); } - const duotoneSupportSelectors = - getBlockType( name ).selectors?.filter?.duotone || - getBlockSupport( name, 'color.__experimentalDuotone' ); + const duotoneSupportSelectors = getBlockCSSSelector( + getBlockType( name ), + 'filter.duotone' + ); // Extra .editor-styles-wrapper specificity is needed in the editor // since we're not using inline styles to apply the filter. We need to diff --git a/phpunit/class-wp-get-block-css-selectors-test.php b/phpunit/class-wp-get-block-css-selectors-test.php index ba63f042a68dc0..7d3c9889e8a3a4 100644 --- a/phpunit/class-wp-get-block-css-selectors-test.php +++ b/phpunit/class-wp-get-block-css-selectors-test.php @@ -101,7 +101,7 @@ public function test_get_duotone_selector_via_experimental_property() { ); $selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); - $this->assertEquals( '.experimental-duotone', $selector ); + $this->assertEquals( '.wp-block-test-experimental-duotone-selector .experimental-duotone', $selector ); } public function test_no_duotone_selector_set() { @@ -115,6 +115,28 @@ public function test_no_duotone_selector_set() { $this->assertEquals( null, $selector ); } + public function test_fallback_duotone_selector() { + $block_type = self::register_test_block( + 'test/fallback-duotone-selector', + array( 'root' => '.fallback-root-selector' ), + null + ); + + $selector = wp_get_block_css_selector( $block_type, 'filter.duotone', true ); + $this->assertEquals( '.fallback-root-selector', $selector ); + } + + public function test_fallback_duotone_selector_to_generated_class() { + $block_type = self::register_test_block( + 'test/fallback-duotone-selector', + array(), + null + ); + + $selector = wp_get_block_css_selector( $block_type, 'filter.duotone', true ); + $this->assertEquals( '.wp-block-test-fallback-duotone-selector', $selector ); + } + public function test_get_feature_selector_via_selectors_api() { $block_type = self::register_test_block( 'test/feature-selector', From 0c52d64abb21eb8ccd5eefa45434c146e6ab3d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 29 Mar 2023 13:34:15 +0200 Subject: [PATCH 02/33] Use whole selectors for duotone as well --- packages/block-library/src/image/block.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index b54358fc48d6a4..75a49d35d829a8 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -104,7 +104,7 @@ "selectors": { "border": ".wp-block-image img, .wp-block-image .wp-block-image__crop-area", "filter": { - "duotone": "img, .components-placeholder" + "duotone": ".wp-block-image img, .wp-block-image .components-placeholder" } }, "styles": [ From 02bfc17c2dfd13f02ab89de25a1e39c44c1cd4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:18:51 +0200 Subject: [PATCH 03/33] Update the block-level duotone selector Before: .wp-duotone .wp-block-name After: .wp-duotone.wp-blockname --- lib/class-wp-duotone-gutenberg.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index ff963f46cb4555..8260fd799b3d42 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -349,7 +349,26 @@ 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 ); + $scopes = explode( ',', '.' . $filter_id ); + $selectors = explode( ',', $duotone_selector ); + + $selectors_scoped = array(); + foreach ( $scopes as $outer ) { + foreach ( $selectors as $inner ) { + $outer = trim( $outer ); + $inner = trim( $inner ); + if ( ! empty( $outer ) && ! empty( $inner ) ) { + // unlike WP_Theme_JSON_Gutenberg::scope_selector + // this concatenates the selectors without a space. + $selectors_scoped[] = $outer . '' . $inner; + } elseif ( empty( $outer ) ) { + $selectors_scoped[] = $inner; + } elseif ( empty( $inner ) ) { + $selectors_scoped[] = $outer; + } + } + } + $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 ) ) { From e5ed22799731d1710b5bed7d7aa82b8f8e3ee495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 29 Mar 2023 16:50:34 +0200 Subject: [PATCH 04/33] Use existing function --- .../get-global-styles-and-settings.php | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index 5effd6718e64cd..1bdf1372075b7e 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -47,29 +47,6 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f $fallback_selector = $fallback ? $root_selector : null; - // Helper to scope old experimental selectors. - $scope_selector = function( $scope, $selector ) { - $scopes = explode( ',', $scope ); - $selectors = explode( ',', $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 ); - }; - // Duotone ( may fallback to root selector ). if ( 'filter.duotone' === $target || array( 'filter', 'duotone' ) === $target ) { // If selectors API in use, only use it's value, fallback, or null. @@ -86,7 +63,7 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f } // Scope the duotone selector by the block's root selector. - return $scope_selector( $root_selector, $duotone_selector ); + return WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_selector ); } // If target is not `root` or `duotone` we have a feature or subfeature @@ -123,7 +100,7 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f } // Scope the feature selector by the block's root selector. - return $scope_selector( $root_selector, $feature_selector ); + return WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $feature_selector ); } // Subfeature selector From e60c4b7a397b342f27765af2cba90036fb55da72 Mon Sep 17 00:00:00 2001 From: scruffian Date: Wed, 29 Mar 2023 16:33:45 +0100 Subject: [PATCH 05/33] scope the selectors in the post editor --- packages/block-editor/src/hooks/duotone.js | 58 +++++++++------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index fb7a749d82f7e6..647391067b6541 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -223,37 +223,6 @@ const withDuotoneControls = createHigherOrderComponent( 'withDuotoneControls' ); -/** - * Function that scopes a selector with another one. This works a bit like - * SCSS nesting except the `&` operator isn't supported. - * - * @example - * ```js - * const scope = '.a, .b .c'; - * const selector = '> .x, .y'; - * const merged = scopeSelector( scope, selector ); - * // merged is '.a > .x, .a .y, .b .c > .x, .b .c .y' - * ``` - * - * @param {string} scope Selector to scope to. - * @param {string} selector Original selector. - * - * @return {string} Scoped selector. - */ -function scopeSelector( scope, selector ) { - const scopes = scope.split( ',' ); - const selectors = selector.split( ',' ); - - const selectorsScoped = []; - scopes.forEach( ( outer ) => { - selectors.forEach( ( inner ) => { - selectorsScoped.push( `${ outer.trim() } ${ inner.trim() }` ); - } ); - } ); - - return selectorsScoped.join( ', ' ); -} - function BlockDuotoneStyles( { name, duotoneStyle, id } ) { const duotonePalette = useMultiOriginPresets( { presetSetting: 'color.duotone', @@ -282,10 +251,29 @@ function BlockDuotoneStyles( { name, duotoneStyle, id } ) { // Extra .editor-styles-wrapper specificity is needed in the editor // since we're not using inline styles to apply the filter. We need to // override duotone applied by global styles and theme.json. - const selectorsGroup = scopeSelector( - `.editor-styles-wrapper .${ id }`, - duotoneSupportSelectors - ); + + // This is a JavaScript implementation of the PHP function in lib/class-wp-duotone-gutenberg.php + const scopes = ( '.' + id ).split( ',' ); + const selectors = duotoneSupportSelectors.split( ',' ); + + const selectorsScoped = []; + scopes.forEach( ( outer ) => { + selectors.forEach( ( inner ) => { + outer = outer.trim(); + inner = inner.trim(); + if ( outer && inner ) { + // unlike scopeSelector this concatenates the selectors without a space. + selectorsScoped.push( + '.editor-styles-wrapper ' + outer + '' + inner + ); + } else if ( outer ) { + selectorsScoped.push( '.editor-styles-wrapper ' + inner ); + } else if ( inner ) { + selectorsScoped.push( '.editor-styles-wrapper ' + outer ); + } + } ); + } ); + const selectorsGroup = selectorsScoped.join( ',' ); return createPortal( Date: Wed, 29 Mar 2023 17:50:19 +0200 Subject: [PATCH 06/33] Duotone: do not scope twice --- lib/class-wp-theme-json-gutenberg.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index c7e1307f4a0bf0..82ad6e3d4b7ab8 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2389,8 +2389,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. From 64a505a8e11565f80e271cc233ffbeafe7618ef3 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 10:53:01 -0500 Subject: [PATCH 07/33] Simplify __experimentalDuotone backwards compatibility --- .../get-global-styles-and-settings.php | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index 1bdf1372075b7e..5b4aebcaee34c5 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -47,31 +47,18 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f $fallback_selector = $fallback ? $root_selector : null; - // Duotone ( may fallback to root selector ). - if ( 'filter.duotone' === $target || array( 'filter', 'duotone' ) === $target ) { - // If selectors API in use, only use it's value, fallback, or null. - if ( $has_selectors ) { - return _wp_array_get( $block_type->selectors, array( 'filter', 'duotone' ), $fallback_selector ); - } - - // Selectors API, not available, check for old experimental selector. - $duotone_selector = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), null ); - - // Nothing to work with, provide fallback or null. - if ( null === $duotone_selector ) { - return $fallback_selector; - } - - // Scope the duotone selector by the block's root selector. - return WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_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 ); } + // Backwards compatibility for supports.__experimentalDuotone selectors when filter.duotone isn't set. + if ( array( 'filter', 'duotone' ) === $target && null === _wp_array_get( $block_type->selectors, $target ) ) { + $duotone_selector = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ) ); + return null === $duotone_selector ? null : WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_selector ); + } + // Feature Selectors ( may fallback to root selector ). if ( 1 === count( $target ) ) { // Prefer the selectors API if available. From 3cd86f0c49c1dca17ddfd84d1fae832d76c83947 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 14:28:56 -0500 Subject: [PATCH 08/33] Update the __experimentalDuotoen back compat to explicitly handle each case --- .../get-global-styles-and-settings.php | 16 +++++++++-- .../global-styles/get-block-css-selector.js | 28 ++++++++----------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index 5b4aebcaee34c5..f2308b355fdc4f 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -53,10 +53,22 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f $target = explode( '.', $target ); } - // Backwards compatibility for supports.__experimentalDuotone selectors when filter.duotone isn't set. + // Backwards compatibility for supports.__experimentalDuotone selectors. if ( array( 'filter', 'duotone' ) === $target && null === _wp_array_get( $block_type->selectors, $target ) ) { $duotone_selector = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ) ); - return null === $duotone_selector ? null : WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_selector ); + + // String color.__experimentalDuotone values need to be scoped to the root selector. + if ( is_string( $duotone_selector ) ) { + return WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_selector ); + } + + // When color.__experimentalDuotone is true, the root selector should be used. + if ( true === $duotone_selector ) { + return $root_selector; + } + + // Experimental duotone support is not enabled. + return null; } // Feature Selectors ( may fallback to root selector ). diff --git a/packages/block-editor/src/components/global-styles/get-block-css-selector.js b/packages/block-editor/src/components/global-styles/get-block-css-selector.js index 76a56f88fc34a4..0672b844451524 100644 --- a/packages/block-editor/src/components/global-styles/get-block-css-selector.js +++ b/packages/block-editor/src/components/global-styles/get-block-css-selector.js @@ -56,26 +56,22 @@ export function getBlockCSSSelector( const fallbackSelector = fallback ? rootSelector : null; - // Duotone ( may fallback to root selector ). - if ( path === 'filter.duotone' ) { - // If selectors API in use, only use its value, fallback, or null. - if ( hasSelectors ) { - return get( selectors, path, fallbackSelector ); - } + // Backwards compatibility for supports.__experimentalDuotone selectors. + if ( path === 'filter.duotone' && ! get( selectors, path ) ) { + const duotoneSelector = get( supports, 'color.__experimentalDuotone' ); - // Selectors API, not available, check for old experimental selector. - const duotoneSelector = get( - supports, - 'color.__experimentalDuotone', - null - ); + // String color.__experimentalDuotone values need to be scoped to the root selector. + if ( typeof duotoneSelector === 'string' ) { + return scopeSelector( rootSelector, duotoneSelector ); + } - // If nothing to work with, provide fallback selector if available. - if ( ! duotoneSelector ) { - return fallbackSelector; + // When color.__experimentalDuotone is true, the root selector should be used. + if ( duotoneSelector === true ) { + return rootSelector; } - return scopeSelector( rootSelector, duotoneSelector ); + // Experimental duotone support is not enabled. + return null; } // If target is not `root` or `duotone` we have a feature or subfeature From 7d370c6a8f5cc16ab71b6c189e98b1cc65dbf177 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 15:52:57 -0500 Subject: [PATCH 09/33] Simplify adding the filter class to the selectors --- lib/class-wp-duotone-gutenberg.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 8260fd799b3d42..111afd3e816b63 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -349,25 +349,14 @@ 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. - $scopes = explode( ',', '.' . $filter_id ); $selectors = explode( ',', $duotone_selector ); $selectors_scoped = array(); - foreach ( $scopes as $outer ) { - foreach ( $selectors as $inner ) { - $outer = trim( $outer ); - $inner = trim( $inner ); - if ( ! empty( $outer ) && ! empty( $inner ) ) { - // unlike WP_Theme_JSON_Gutenberg::scope_selector - // this concatenates the selectors without a space. - $selectors_scoped[] = $outer . '' . $inner; - } elseif ( empty( $outer ) ) { - $selectors_scoped[] = $inner; - } elseif ( empty( $inner ) ) { - $selectors_scoped[] = $outer; - } - } + foreach ( $selectors as $selector_part ) { + // The selector part should always begin with the block's class, so we can safely just prepend the filter id class. + $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'. From 521db543b18df46d63bb519636d7976e89a1772a Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 19:43:57 -0500 Subject: [PATCH 10/33] Refactor to handle __experimentalDuotone where it was previously handled --- lib/class-wp-duotone-gutenberg.php | 15 +- lib/class-wp-theme-json-gutenberg.php | 8 + .../get-global-styles-and-settings.php | 29 +--- .../global-styles/use-global-styles-output.js | 18 +- packages/block-editor/src/hooks/duotone.js | 163 +++++++++++------- packages/block-library/src/image/block.json | 4 +- 6 files changed, 147 insertions(+), 90 deletions(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 111afd3e816b63..aaf19c91eed30f 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -276,9 +276,17 @@ public static function render_duotone_support( $block_content, $block ) { $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; + + if ( $block_type && property_exists( $block_type, 'supports' ) ) { + $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); + $duotone_selector = wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true ); + + // Keep backwards compatibility for support.color.__experimentalDuotone. + if ( ! $duotone_support || ! $duotone_selector ) { + $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); + $root_selector = wp_get_block_css_selector( $block_type, 'root' ); + $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); + } } // The block should have a duotone attribute or have duotone defined in its theme.json to be processed. @@ -288,6 +296,7 @@ public static function render_duotone_support( $block_content, $block ) { if ( empty( $block_content ) || ! $duotone_support || + ! $duotone_selector || ( ! $has_duotone_attribute && ! $has_global_styles_duotone ) ) { return $block_content; diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 82ad6e3d4b7ab8..6ca004a204d9d3 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -871,6 +871,14 @@ 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' ), false ); + $root_selector = wp_get_block_css_selector( $block_type, 'root' ); + $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); + } + if ( null !== $duotone_selector ) { static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector; } diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index f2308b355fdc4f..840903887c87f3 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -25,7 +25,10 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f $has_selectors = ! empty( $block_type->selectors ); - // Root Selector ( can be used as a fallback ). + // Root Selector ( May be used as a fallback ). + + // Calculated before returning as it can be used as fallback for + // feature selectors later on. $root_selector = null; if ( $has_selectors && isset( $block_type->selectors['root'] ) ) { @@ -45,34 +48,16 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f return $root_selector; } - $fallback_selector = $fallback ? $root_selector : null; - // 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 ); } - // Backwards compatibility for supports.__experimentalDuotone selectors. - if ( array( 'filter', 'duotone' ) === $target && null === _wp_array_get( $block_type->selectors, $target ) ) { - $duotone_selector = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ) ); - - // String color.__experimentalDuotone values need to be scoped to the root selector. - if ( is_string( $duotone_selector ) ) { - return WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_selector ); - } - - // When color.__experimentalDuotone is true, the root selector should be used. - if ( true === $duotone_selector ) { - return $root_selector; - } - - // Experimental duotone support is not enabled. - return null; - } - - // Feature Selectors ( may fallback to root selector ). + // Feature Selectors ( May fallback to root selector ). if ( 1 === count( $target ) ) { + $fallback_selector = $fallback ? $root_selector : null; + // Prefer the selectors API if available. if ( $has_selectors ) { // Look for selector under `feature.root`. 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 e5a52534981b37..d563372fff6ffa 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 @@ -9,6 +9,7 @@ import { get, isEmpty, kebabCase, set } from 'lodash'; import { __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY, __EXPERIMENTAL_ELEMENTS as ELEMENTS, + getBlockSupport, getBlockTypes, store as blocksStore, } from '@wordpress/blocks'; @@ -19,7 +20,7 @@ import { getCSSRules } from '@wordpress/style-engine'; /** * Internal dependencies */ -import { PRESET_METADATA, ROOT_BLOCK_SELECTOR } from './utils'; +import { PRESET_METADATA, ROOT_BLOCK_SELECTOR, scopeSelector } from './utils'; import { getBlockCSSSelector } from './get-block-css-selector'; import { getTypographyFontSizeValue } from './typography-utils'; import { GlobalStylesContext } from './context'; @@ -1002,10 +1003,23 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { blockTypes.forEach( ( blockType ) => { const name = blockType.name; const selector = getBlockCSSSelector( blockType, 'root' ); - const duotoneSelector = getBlockCSSSelector( + let duotoneSelector = getBlockCSSSelector( blockType, 'filter.duotone' ); + + // Keep backwards compatibility for support.color.__experimentalDuotone. + if ( ! duotoneSelector ) { + const rootSelector = getBlockCSSSelector( blockType, 'root' ); + const duotoneSupport = getBlockSupport( + blockType, + 'color.__experimentalDuotone', + false + ); + duotoneSelector = + duotoneSupport && scopeSelector( rootSelector, duotoneSupport ); + } + const hasLayoutSupport = !! blockType?.supports?.__experimentalLayout; const fallbackGapValue = blockType?.supports?.spacing?.blockGap?.__experimentalDefault; diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 647391067b6541..88af912b739229 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -33,6 +33,7 @@ import { __unstableDuotoneUnsetStylesheet as DuotoneUnsetStylesheet, } from '../components/duotone'; import { getBlockCSSSelector } from '../components/global-styles/get-block-css-selector'; +import { scopeSelector } from '../components/global-styles/utils'; import { store as blockEditorStore } from '../store'; const EMPTY_ARRAY = []; @@ -223,65 +224,67 @@ const withDuotoneControls = createHigherOrderComponent( 'withDuotoneControls' ); -function BlockDuotoneStyles( { name, duotoneStyle, id } ) { +function DuotoneStyles( { + id: filterId, + selector: duotoneSelector, + attribute: duotoneAttr, +} ) { + const element = useContext( BlockList.__unstableElementContext ); + const duotonePalette = useMultiOriginPresets( { presetSetting: 'color.duotone', defaultSetting: 'color.defaultDuotone', } ); - const element = useContext( BlockList.__unstableElementContext ); - - // Portals cannot exist without a container. - // Guard against empty Duotone styles. - if ( ! element || ! duotoneStyle ) { - return null; + // Possible values for duotone attribute: + // 1. Array of colors - e.g. ['#000000', '#ffffff']. + // 2. Variable for an existing Duotone preset - e.g. 'var:preset|duotone|green-blue' or 'var(--wp--preset--duotone--green-blue)'' + // 3. A CSS string - e.g. 'unset' to remove globally applied duotone. + const isCustom = Array.isArray( duotoneAttr ); + const duotonePreset = isCustom + ? undefined + : getColorsFromDuotonePreset( duotoneAttr, duotonePalette ); + const isPreset = typeof duotoneAttr === 'string' && duotonePreset; + const isCSS = typeof duotoneAttr === 'string' && ! isPreset; + + // Match the structure of WP_Duotone_Gutenberg::render_duotone_support() in PHP. + let colors = null; + if ( isPreset ) { + // Array of colors. + colors = duotonePreset; + } else if ( isCSS ) { + // CSS filter property string (e.g. 'unset'). + colors = duotoneAttr; + } else if ( isCustom ) { + // Array of colors. + colors = duotoneAttr; } - let colors = duotoneStyle; + // Build the CSS selectors to which the filter will be applied. + const selectors = duotoneSelector.split( ',' ); - if ( ! Array.isArray( colors ) && colors !== 'unset' ) { - colors = getColorsFromDuotonePreset( colors, duotonePalette ); - } + const selectorsScoped = selectors.map( ( selectorPart ) => { + // Extra .editor-styles-wrapper specificity is needed in the editor + // since we're not using inline styles to apply the filter. We need to + // override duotone applied by global styles and theme.json. - const duotoneSupportSelectors = getBlockCSSSelector( - getBlockType( name ), - 'filter.duotone' - ); - - // Extra .editor-styles-wrapper specificity is needed in the editor - // since we're not using inline styles to apply the filter. We need to - // override duotone applied by global styles and theme.json. - - // This is a JavaScript implementation of the PHP function in lib/class-wp-duotone-gutenberg.php - const scopes = ( '.' + id ).split( ',' ); - const selectors = duotoneSupportSelectors.split( ',' ); - - const selectorsScoped = []; - scopes.forEach( ( outer ) => { - selectors.forEach( ( inner ) => { - outer = outer.trim(); - inner = inner.trim(); - if ( outer && inner ) { - // unlike scopeSelector this concatenates the selectors without a space. - selectorsScoped.push( - '.editor-styles-wrapper ' + outer + '' + inner - ); - } else if ( outer ) { - selectorsScoped.push( '.editor-styles-wrapper ' + inner ); - } else if ( inner ) { - selectorsScoped.push( '.editor-styles-wrapper ' + outer ); - } - } ); + // The selector part should always begin with the block's class, so we + // can safely just prepend the filter id class. + return `.editor-styles-wrapper .${ filterId }${ selectorPart.trim() }`; } ); - const selectorsGroup = selectorsScoped.join( ',' ); - - return createPortal( - , - element + + const selector = selectorsScoped.join( ', ' ); + + return ( + element && + createPortal( + , + element + ) ); } @@ -294,16 +297,52 @@ function BlockDuotoneStyles( { name, duotoneStyle, id } ) { */ const withDuotoneStyles = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { - const duotoneSupport = getBlockSupport( - props.name, - 'color.__experimentalDuotone' - ); + const id = useInstanceId( BlockListBlock ); + + const { duotoneSelector, duotoneSupport } = useMemo( () => { + const blockType = getBlockType( props.name ); + let duotoneSupport = false; + let duotoneSelector = null; + + if ( blockType ) { + duotoneSupport = getBlockSupport( + blockType, + 'filter.duotone', + false + ); + duotoneSelector = getBlockCSSSelector( + blockType, + 'filter.duotone', + { fallback: true } + ); + if ( ! duotoneSelector || ! duotoneSupport ) { + const rootSelector = getBlockCSSSelector( + blockType, + 'root' + ); + duotoneSupport = getBlockSupport( + blockType, + 'color.__experimentalDuotone', + false + ); + duotoneSelector = + duotoneSupport && + scopeSelector( rootSelector, duotoneSupport ); + } + } + return { duotoneSelector, duotoneSupport }; + }, [ props.name ] ); + + const duotoneAttribute = props?.attributes?.style?.color?.duotone; + + const filterClass = `wp-duotone-${ id }`; + + const shouldRender = + duotoneSupport && duotoneSelector && duotoneAttribute; - const id = `wp-duotone-${ useInstanceId( BlockListBlock ) }`; - const className = duotoneSupport - ? classnames( props?.className, id ) + const className = shouldRender + ? classnames( props?.className, filterClass ) : props?.className; - const duotoneStyle = props?.attributes?.style?.color?.duotone; // CAUTION: code added before this line will be executed // for all blocks, not just those that support duotone. Code added @@ -311,11 +350,11 @@ const withDuotoneStyles = createHigherOrderComponent( // performance. return ( <> - { duotoneSupport && duotoneStyle && ( - ) } diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 75a49d35d829a8..9e0e749187aab2 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -85,10 +85,12 @@ "supports": { "anchor": true, "color": { - "__experimentalDuotone": true, "text": false, "background": false }, + "filter": { + "duotone": true + }, "__experimentalBorder": { "color": true, "radius": true, From fc2f0c0d7188c3d89165d9df9415812c8fbfa469 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 19:48:55 -0500 Subject: [PATCH 11/33] Remove some dead code that I missed --- .../global-styles/get-block-css-selector.js | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/get-block-css-selector.js b/packages/block-editor/src/components/global-styles/get-block-css-selector.js index 0672b844451524..7d33b136b1e336 100644 --- a/packages/block-editor/src/components/global-styles/get-block-css-selector.js +++ b/packages/block-editor/src/components/global-styles/get-block-css-selector.js @@ -34,7 +34,9 @@ export function getBlockCSSSelector( const hasSelectors = ! isEmpty( selectors ); const path = Array.isArray( target ) ? target.join( '.' ) : target; - // Root selector ( can be used as fallback ). + // Root selector ( may be used as fallback ). + // Calculated before returning as it can be used as a fallback for feature + // selectors later on. let rootSelector = null; if ( hasSelectors && selectors.root ) { @@ -54,32 +56,14 @@ export function getBlockCSSSelector( return rootSelector; } - const fallbackSelector = fallback ? rootSelector : null; - - // Backwards compatibility for supports.__experimentalDuotone selectors. - if ( path === 'filter.duotone' && ! get( selectors, path ) ) { - const duotoneSelector = get( supports, 'color.__experimentalDuotone' ); - - // String color.__experimentalDuotone values need to be scoped to the root selector. - if ( typeof duotoneSelector === 'string' ) { - return scopeSelector( rootSelector, duotoneSelector ); - } - - // When color.__experimentalDuotone is true, the root selector should be used. - if ( duotoneSelector === true ) { - return rootSelector; - } - - // Experimental duotone support is not enabled. - return null; - } - // 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. const pathArray = Array.isArray( target ) ? target : target.split( '.' ); // Feature selectors ( may fallback to root selector ); if ( pathArray.length === 1 ) { + const fallbackSelector = fallback ? rootSelector : null; + // Prefer the selectors API if available. if ( hasSelectors ) { // Get selector from either `feature.root` or shorthand path. From 53410e53d5bc999cf003d6ead7f991ad9677d467 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 20:01:05 -0500 Subject: [PATCH 12/33] Fix lint --- lib/class-wp-duotone-gutenberg.php | 6 +++--- lib/class-wp-theme-json-gutenberg.php | 4 ++-- .../get-global-styles-and-settings.php | 2 +- .../global-styles/get-block-css-selector.js | 3 ++- packages/block-editor/src/hooks/duotone.js | 13 ++++++------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index aaf19c91eed30f..f620bb195ed554 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -278,13 +278,13 @@ public static function render_duotone_support( $block_content, $block ) { $duotone_selector = null; if ( $block_type && property_exists( $block_type, 'supports' ) ) { - $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); + $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); $duotone_selector = wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true ); // Keep backwards compatibility for support.color.__experimentalDuotone. if ( ! $duotone_support || ! $duotone_selector ) { - $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); - $root_selector = wp_get_block_css_selector( $block_type, 'root' ); + $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); + $root_selector = wp_get_block_css_selector( $block_type, 'root' ); $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); } } diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 6ca004a204d9d3..9d814a0cc0e557 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -874,8 +874,8 @@ protected static function get_blocks_metadata() { // Keep backwards compatibility for support.color.__experimentalDuotone. if ( null === $duotone_selector ) { - $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); - $root_selector = wp_get_block_css_selector( $block_type, 'root' ); + $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); + $root_selector = wp_get_block_css_selector( $block_type, 'root' ); $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); } diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index 840903887c87f3..bb489664e1eea9 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -25,7 +25,7 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f $has_selectors = ! empty( $block_type->selectors ); - // Root Selector ( May be used as a fallback ). + // Root Selector. // Calculated before returning as it can be used as fallback for // feature selectors later on. diff --git a/packages/block-editor/src/components/global-styles/get-block-css-selector.js b/packages/block-editor/src/components/global-styles/get-block-css-selector.js index 7d33b136b1e336..79a736e1b6978c 100644 --- a/packages/block-editor/src/components/global-styles/get-block-css-selector.js +++ b/packages/block-editor/src/components/global-styles/get-block-css-selector.js @@ -34,7 +34,8 @@ export function getBlockCSSSelector( const hasSelectors = ! isEmpty( selectors ); const path = Array.isArray( target ) ? target.join( '.' ) : target; - // Root selector ( may be used as fallback ). + // Root selector. + // Calculated before returning as it can be used as a fallback for feature // selectors later on. let rootSelector = null; diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 88af912b739229..33e0820f338df9 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -299,7 +299,7 @@ const withDuotoneStyles = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { const id = useInstanceId( BlockListBlock ); - const { duotoneSelector, duotoneSupport } = useMemo( () => { + const { selector, support } = useMemo( () => { const blockType = getBlockType( props.name ); let duotoneSupport = false; let duotoneSelector = null; @@ -330,15 +330,14 @@ const withDuotoneStyles = createHigherOrderComponent( scopeSelector( rootSelector, duotoneSupport ); } } - return { duotoneSelector, duotoneSupport }; + return { selector: duotoneSelector, support: duotoneSupport }; }, [ props.name ] ); - const duotoneAttribute = props?.attributes?.style?.color?.duotone; + const attribute = props?.attributes?.style?.color?.duotone; const filterClass = `wp-duotone-${ id }`; - const shouldRender = - duotoneSupport && duotoneSelector && duotoneAttribute; + const shouldRender = support && selector && attribute; const className = shouldRender ? classnames( props?.className, filterClass ) @@ -353,8 +352,8 @@ const withDuotoneStyles = createHigherOrderComponent( { shouldRender && ( ) } From 42e1b3fac73b4e1c4b8d15c4355b4e87bbf8ff37 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 20:26:30 -0500 Subject: [PATCH 13/33] Remove __experimentalDuotone test since it doesn't handle that anymore --- phpunit/class-wp-get-block-css-selectors-test.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/phpunit/class-wp-get-block-css-selectors-test.php b/phpunit/class-wp-get-block-css-selectors-test.php index 7d3c9889e8a3a4..51819be4ffc204 100644 --- a/phpunit/class-wp-get-block-css-selectors-test.php +++ b/phpunit/class-wp-get-block-css-selectors-test.php @@ -89,21 +89,6 @@ public function test_get_duotone_selector_via_selectors_api() { $this->assertEquals( '.duotone-selector', $selector ); } - public function test_get_duotone_selector_via_experimental_property() { - $block_type = self::register_test_block( - 'test/experimental-duotone-selector', - null, - array( - 'color' => array( - '__experimentalDuotone' => '.experimental-duotone', - ), - ) - ); - - $selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); - $this->assertEquals( '.wp-block-test-experimental-duotone-selector .experimental-duotone', $selector ); - } - public function test_no_duotone_selector_set() { $block_type = self::register_test_block( 'test/null-duotone-selector', From 50997ca8b43b6e1a66141b554cc41a3d4dd3978c Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 20:28:38 -0500 Subject: [PATCH 14/33] Remove remainder of duotone tests because it is no longer handled differently --- .../class-wp-get-block-css-selectors-test.php | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/phpunit/class-wp-get-block-css-selectors-test.php b/phpunit/class-wp-get-block-css-selectors-test.php index 51819be4ffc204..8f43d9df6d95e3 100644 --- a/phpunit/class-wp-get-block-css-selectors-test.php +++ b/phpunit/class-wp-get-block-css-selectors-test.php @@ -76,52 +76,6 @@ public function test_default_root_selector_generation() { $this->assertEquals( '.wp-block-test-without-selectors-or-supports', $selector ); } - public function test_get_duotone_selector_via_selectors_api() { - $block_type = self::register_test_block( - 'test/duotone-selector', - array( - 'filter' => array( 'duotone' => '.duotone-selector' ), - ), - null - ); - - $selector = wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ) ); - $this->assertEquals( '.duotone-selector', $selector ); - } - - public function test_no_duotone_selector_set() { - $block_type = self::register_test_block( - 'test/null-duotone-selector', - null, - null - ); - - $selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); - $this->assertEquals( null, $selector ); - } - - public function test_fallback_duotone_selector() { - $block_type = self::register_test_block( - 'test/fallback-duotone-selector', - array( 'root' => '.fallback-root-selector' ), - null - ); - - $selector = wp_get_block_css_selector( $block_type, 'filter.duotone', true ); - $this->assertEquals( '.fallback-root-selector', $selector ); - } - - public function test_fallback_duotone_selector_to_generated_class() { - $block_type = self::register_test_block( - 'test/fallback-duotone-selector', - array(), - null - ); - - $selector = wp_get_block_css_selector( $block_type, 'filter.duotone', true ); - $this->assertEquals( '.wp-block-test-fallback-duotone-selector', $selector ); - } - public function test_get_feature_selector_via_selectors_api() { $block_type = self::register_test_block( 'test/feature-selector', From b9d16ea603bd2abe6ac77213d2cc88e470372be6 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 20:32:56 -0500 Subject: [PATCH 15/33] Fix getBlockSelectors test because __experimentalDuotone now always returns the full selector --- .../components/global-styles/test/use-global-styles-output.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index a826c562044789..329aa9f32868aa 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -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', From 31aa06efe3c98a278bb75b8469fdee6797935ca1 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 29 Mar 2023 20:55:46 -0500 Subject: [PATCH 16/33] Fix rendering when preset doesn't exist --- packages/block-editor/src/hooks/duotone.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 33e0820f338df9..fb251d5f5104ae 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -277,6 +277,7 @@ function DuotoneStyles( { return ( element && + Array.isArray( colors ) && createPortal( Date: Wed, 29 Mar 2023 20:58:01 -0500 Subject: [PATCH 17/33] Fix unset filters --- packages/block-editor/src/hooks/duotone.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index fb251d5f5104ae..0753871b4163c6 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -275,9 +275,11 @@ function DuotoneStyles( { const selector = selectorsScoped.join( ', ' ); + const isValidFilter = Array.isArray( colors ) || colors === 'unset'; + return ( element && - Array.isArray( colors ) && + isValidFilter && createPortal( Date: Thu, 30 Mar 2023 14:00:07 +1000 Subject: [PATCH 18/33] Fix typos --- phpunit/class-wp-duotone-test.php | 2 +- phpunit/class-wp-theme-json-test.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpunit/class-wp-duotone-test.php b/phpunit/class-wp-duotone-test.php index 49ef631e65f022..e9a6f52077fa28 100644 --- a/phpunit/class-wp-duotone-test.php +++ b/phpunit/class-wp-duotone-test.php @@ -8,7 +8,7 @@ class WP_Duotone_Gutenberg_Test extends WP_UnitTestCase { /** - * Cleans up CSS added to block-supports from duotone styles. We neeed to do this + * Cleans up CSS added to block-supports from duotone styles. We need to do this * in order to avoid impacting other tests. */ public static function wpTearDownAfterClass() { diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index eeb5940f44cd35..6cde491d9d2b6b 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -1927,7 +1927,7 @@ public function test_update_separator_declarations() { $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); $this->assertEquals( $expected, $stylesheet ); - // If background, text, and border-color are defined, include everything, CSS specifity will decide which to apply. + // If background, text, and border-color are defined, include everything, CSS specificity will decide which to apply. $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, @@ -1951,7 +1951,7 @@ public function test_update_separator_declarations() { $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); $this->assertEquals( $expected, $stylesheet ); - // If background and border color are defined, include everything, CSS specifity will decide which to apply. + // If background and border color are defined, include everything, CSS specificity will decide which to apply. $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, From 6a750e352fc3f31864ff9b2bc08ec3a37ef147bb Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:26:09 +1000 Subject: [PATCH 19/33] Update docs --- docs/reference-guides/core-blocks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index e5b524c6850860..7d3b923488a0c8 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -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 From 2236ce569253983dafc7424f02f23ffa4d0c287d Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:26:52 +1000 Subject: [PATCH 20/33] Tweak incorrect comment --- .../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 d563372fff6ffa..08a11ba0c2b9b9 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 @@ -853,7 +853,7 @@ export const toStyles = ( delete styles.filter; } - // Process duotone styles (they use color.__experimentalDuotone selector). + // Process duotone styles. if ( duotoneSelector ) { const duotoneDeclarations = getStylesDeclarations( duotoneStyles ); From 972dca7156552d667bc01060d874dbf8f9011c9d Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 Mar 2023 16:36:31 +1000 Subject: [PATCH 21/33] Fix duotone support flag relocation --- lib/block-supports/duotone.php | 24 ++++++++++++++++++- lib/class-wp-duotone-gutenberg.php | 8 ++++--- lib/class-wp-theme-json-gutenberg.php | 9 ++++--- .../global-styles/use-global-styles-output.js | 4 ++-- packages/block-editor/src/hooks/duotone.js | 14 ++++++----- packages/blocks/src/api/constants.js | 2 +- 6 files changed, 45 insertions(+), 16 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 1884283c3b150a..f5571bbd6224d5 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -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 ) { @@ -434,6 +436,25 @@ function gutenberg_render_duotone_support( $block_content, $block ) { return WP_Duotone_Gutenberg::render_duotone_support( $block_content, $block ); } +/** + * 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. + */ +function gutenberg_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; +} + // Register the block support. WP_Block_Supports::get_instance()->register( 'duotone', @@ -450,3 +471,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', 'gutenberg_migrate_experimental_duotone_support_flag', 10, 2 ); diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index f620bb195ed554..22f5636b5646e0 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -278,13 +278,15 @@ public static function render_duotone_support( $block_content, $block ) { $duotone_selector = null; if ( $block_type && property_exists( $block_type, 'supports' ) ) { + // Support flag `filter.duotone` will be populated from the previous + // `color.__experimentalDuotone` support via block_type_metadata_settings filter. $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); - $duotone_selector = wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true ); + $duotone_selector = wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ) ); // Keep backwards compatibility for support.color.__experimentalDuotone. - if ( ! $duotone_support || ! $duotone_selector ) { + if ( $duotone_support && ! $duotone_selector ) { $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); - $root_selector = wp_get_block_css_selector( $block_type, 'root' ); + $root_selector = wp_get_block_css_selector( $block_type ); $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); } } diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 9d814a0cc0e557..a6a7cc7d5de56b 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -874,9 +874,12 @@ protected static function get_blocks_metadata() { // Keep backwards compatibility for support.color.__experimentalDuotone. if ( null === $duotone_selector ) { - $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); - $root_selector = wp_get_block_css_selector( $block_type, 'root' ); - $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); + $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 ) { 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 08a11ba0c2b9b9..17456a1ab85e00 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 @@ -1002,7 +1002,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const result = {}; blockTypes.forEach( ( blockType ) => { const name = blockType.name; - const selector = getBlockCSSSelector( blockType, 'root' ); + const selector = getBlockCSSSelector( blockType ); let duotoneSelector = getBlockCSSSelector( blockType, 'filter.duotone' @@ -1010,7 +1010,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { // Keep backwards compatibility for support.color.__experimentalDuotone. if ( ! duotoneSelector ) { - const rootSelector = getBlockCSSSelector( blockType, 'root' ); + const rootSelector = getBlockCSSSelector( blockType ); const duotoneSupport = getBlockSupport( blockType, 'color.__experimentalDuotone', diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 0753871b4163c6..da8003c864ebaa 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -168,7 +168,9 @@ function DuotonePanel( { attributes, setAttributes } ) { * @return {Object} Filtered block settings. */ function addDuotoneAttributes( settings ) { - if ( ! hasBlockSupport( settings, 'color.__experimentalDuotone' ) ) { + // Previous `color.__experimentalDuotone` support flag is migrated via + // block_type_metadata_settings filter in `lib/block-supports/duotone.php`. + if ( ! hasBlockSupport( settings, 'filter.duotone' ) ) { return settings; } @@ -195,10 +197,13 @@ function addDuotoneAttributes( settings ) { */ const withDuotoneControls = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { + // Previous `color.__experimentalDuotone` support flag is migrated via + // block_type_metadata_settings filter in `lib/block-supports/duotone.php`. const hasDuotoneSupport = hasBlockSupport( props.name, - 'color.__experimentalDuotone' + 'filter.duotone' ); + const isContentLocked = useSelect( ( select ) => { return select( @@ -319,10 +324,7 @@ const withDuotoneStyles = createHigherOrderComponent( { fallback: true } ); if ( ! duotoneSelector || ! duotoneSupport ) { - const rootSelector = getBlockCSSSelector( - blockType, - 'root' - ); + const rootSelector = getBlockCSSSelector( blockType ); duotoneSupport = getBlockSupport( blockType, 'color.__experimentalDuotone', diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 5a1383eadb17f3..6fe04c07de1bfb 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -130,7 +130,7 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, filter: { value: [ 'filter', 'duotone' ], - support: [ 'color', '__experimentalDuotone' ], + support: [ 'filter', 'duotone' ], }, linkColor: { value: [ 'elements', 'link', 'color', 'text' ], From b401c1279d78a837345a1f9dc751843f7bbbfc96 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 Mar 2023 16:41:46 +1000 Subject: [PATCH 22/33] Add filter.duotone to block-supports docs --- .../block-api/block-supports.md | 79 +++++++++---------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index 2e5f509f6dc48e..4a9f861a125f64 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -229,48 +229,6 @@ 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. - -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' - ] - } - } - } - } - ``` ### color.gradients @@ -499,6 +457,43 @@ 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. + +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` From 59b135e6ad08d39d84c4dc19dca8646a6aad0b75 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 Mar 2023 16:46:14 +1000 Subject: [PATCH 23/33] Add filter.duotone to block.json schema --- schemas/json/block.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/schemas/json/block.json b/schemas/json/block.json index 6023d829c5ecd0..5b92a654fbc4a5 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -315,6 +315,17 @@ } } }, + "filter": { + "type": "object", + "description": "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 if the theme declares support.\n\nWhen the block declares support for a specific filter property, its attributes definition is extended to include the style attribute.", + "properties": { + "duotone": { + "type": "boolean", + "description": "Allow blocks to define a duotone filter.", + "default": false + } + } + }, "html": { "type": "boolean", "description": "By default, a block’s markup can be edited individually. To disable this behavior, set html to false.", From 6367140dd516d448c33bb61c5a0a8e053b049212 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 10:32:43 -0500 Subject: [PATCH 24/33] Be more precise in filterId selector comment --- lib/class-wp-duotone-gutenberg.php | 4 +++- packages/block-editor/src/hooks/duotone.js | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 22f5636b5646e0..37e0a0a2bbb1ff 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -364,7 +364,9 @@ public static function render_duotone_support( $block_content, $block ) { $selectors_scoped = array(); foreach ( $selectors as $selector_part ) { - // The selector part should always begin with the block's class, so we can safely just prepend the filter id class. + // 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 ); } diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index da8003c864ebaa..09598c3f622976 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -273,8 +273,9 @@ function DuotoneStyles( { // since we're not using inline styles to apply the filter. We need to // override duotone applied by global styles and theme.json. - // The selector part should always begin with the block's class, so we - // can safely just prepend the filter id class. + // 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. return `.editor-styles-wrapper .${ filterId }${ selectorPart.trim() }`; } ); From f1506d058afe41fc31a9bf274cd8b88b119e67db Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 13:01:18 -0500 Subject: [PATCH 25/33] Fix filters in editor applying to cover block text --- lib/class-wp-duotone-gutenberg.php | 50 +++++++++++-------- packages/block-editor/src/hooks/duotone.js | 57 ++++++++++++++-------- 2 files changed, 69 insertions(+), 38 deletions(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 37e0a0a2bbb1ff..2411d3f1ceeb8d 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -264,6 +264,36 @@ public static function output_global_styles() { } } + 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 for color.__experimentalDuotone. This will + // have priority over filter.duotone support. Unfortunately we can't + // prefer filter.duotone because it gets set when __experimentalDuotone + // is set via a block_type_metadata_settings hook. It shouldn't be too + // much of a problem because I would expect consumers to not use both + // at the same time. + $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; + } + + // Support flag `filter.duotone` will be populated from the previous + // `color.__experimentalDuotone` support via block_type_metadata_settings filter. + $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); + if ( ! $duotone_support ) { + return null; + } + + // 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. * @@ -272,24 +302,7 @@ 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 && property_exists( $block_type, 'supports' ) ) { - // Support flag `filter.duotone` will be populated from the previous - // `color.__experimentalDuotone` support via block_type_metadata_settings filter. - $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); - $duotone_selector = wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ) ); - - // Keep backwards compatibility for support.color.__experimentalDuotone. - if ( $duotone_support && ! $duotone_selector ) { - $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); - $root_selector = wp_get_block_css_selector( $block_type ); - $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); - } - } + $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'] ); @@ -297,7 +310,6 @@ public static function render_duotone_support( $block_content, $block ) { if ( empty( $block_content ) || - ! $duotone_support || ! $duotone_selector || ( ! $has_duotone_attribute && ! $has_global_styles_duotone ) ) { diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 09598c3f622976..130c4cc7f1fc6a 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -308,42 +308,61 @@ const withDuotoneStyles = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { const id = useInstanceId( BlockListBlock ); - const { selector, support } = useMemo( () => { + /** + * experimental true + * experimental string + * + * no selector + * filter selector + * filter.root selector + * filter.duotone selector + */ + + const selector = useMemo( () => { const blockType = getBlockType( props.name ); - let duotoneSupport = false; - let duotoneSelector = null; if ( blockType ) { - duotoneSupport = getBlockSupport( + // Backwards compatibility for color.__experimentalDuotone. This will + // have priority over filter.duotone support. Unfortunately we can't + // prefer filter.duotone because it gets set when __experimentalDuotone + // is set via a block_type_metadata_settings hook. It shouldn't be too + // much of a problem because I would expect consumers to not use both + // at the same time. + const experimentalDuotone = getBlockSupport( blockType, - 'filter.duotone', + 'color.__experimentalDuotone', false ); - duotoneSelector = getBlockCSSSelector( + if ( experimentalDuotone ) { + const rootSelector = getBlockCSSSelector( blockType ); + return typeof experimentalDuotone === 'string' + ? scopeSelector( rootSelector, experimentalDuotone ) + : rootSelector; + } + + // Support flag `filter.duotone` will be populated from the previous + // `color.__experimentalDuotone` support via block_type_metadata_settings filter. + const duotoneSupport = getBlockSupport( blockType, 'filter.duotone', - { fallback: true } + false ); - if ( ! duotoneSelector || ! duotoneSupport ) { - const rootSelector = getBlockCSSSelector( blockType ); - duotoneSupport = getBlockSupport( - blockType, - 'color.__experimentalDuotone', - false - ); - duotoneSelector = - duotoneSupport && - scopeSelector( rootSelector, duotoneSupport ); + if ( ! duotoneSupport ) { + return null; } + + // Regular filter.duotone support uses filter.duotone selectors with fallbacks. + return getBlockCSSSelector( blockType, 'filter.duotone', { + fallback: true, + } ); } - return { selector: duotoneSelector, support: duotoneSupport }; }, [ props.name ] ); const attribute = props?.attributes?.style?.color?.duotone; const filterClass = `wp-duotone-${ id }`; - const shouldRender = support && selector && attribute; + const shouldRender = selector && attribute; const className = shouldRender ? classnames( props?.className, filterClass ) From 502f8b4bd9bb9bf869fb955b67803dd8bf247411 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 13:02:34 -0500 Subject: [PATCH 26/33] Remove comment I accidentally committed --- packages/block-editor/src/hooks/duotone.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 130c4cc7f1fc6a..7c4ce086367774 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -308,16 +308,6 @@ const withDuotoneStyles = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { const id = useInstanceId( BlockListBlock ); - /** - * experimental true - * experimental string - * - * no selector - * filter selector - * filter.root selector - * filter.duotone selector - */ - const selector = useMemo( () => { const blockType = getBlockType( props.name ); From 571681e88783ac1cb46d684b48a31fbe31f38d96 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 13:11:26 -0500 Subject: [PATCH 27/33] Remove __experimentalDuotone from color supports docs --- docs/reference-guides/block-api/block-supports.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index 4a9f861a125f64..c0b998834c57ae 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -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` From 5f16e9ca2ae07bec50f83e4fc74386a366fc3f42 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 13:17:22 -0500 Subject: [PATCH 28/33] Add example to the filter.duotone docs --- .../block-api/block-supports.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index c0b998834c57ae..bcaa4948feffda 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -469,6 +469,23 @@ This value signals that a block supports some of the properties related to filte 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`: From bd02cb2b6086583a861cac8b830ad71bcd78daba Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 13:21:48 -0500 Subject: [PATCH 29/33] Move migration into duotone class --- lib/block-supports/duotone.php | 21 +-------------------- lib/class-wp-duotone-gutenberg.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index f5571bbd6224d5..e775bc92376973 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -436,25 +436,6 @@ function gutenberg_render_duotone_support( $block_content, $block ) { return WP_Duotone_Gutenberg::render_duotone_support( $block_content, $block ); } -/** - * 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. - */ -function gutenberg_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; -} - // Register the block support. WP_Block_Supports::get_instance()->register( 'duotone', @@ -471,4 +452,4 @@ function gutenberg_migrate_experimental_duotone_support_flag( $settings, $metada 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', 'gutenberg_migrate_experimental_duotone_support_flag', 10, 2 ); +add_filter( 'block_type_metadata_settings', array( 'WP_Duotone_Gutenberg', 'migrate_experimental_duotone_support_flag' ), 10, 2 ); diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 2411d3f1ceeb8d..56eb4d6b47aaa8 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -419,4 +419,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; + } } From b20c08e0b889354c27f63ec64458d20cddd7977b Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 13:28:17 -0500 Subject: [PATCH 30/33] Add deprecation note to experimentalDuotone docs --- docs/reference-guides/block-api/block-supports.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index bcaa4948feffda..683e4f134d9877 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -228,6 +228,11 @@ When the block declares support for `color.background`, the attributes definitio } ``` +### color.__experimentalDuotone + +_**Note:** Deprecated since WordPress 6.3._ + +This property has been replaced by [`filter.duotone`](#filter-duotone). ### color.gradients From a7337a73305d81fe6f2d4c98fcf6596ad7e4875f Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 13:33:06 -0500 Subject: [PATCH 31/33] Fix PHP lint --- lib/class-wp-duotone-gutenberg.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 56eb4d6b47aaa8..eb6cee046e3abd 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -264,6 +264,13 @@ 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 ); @@ -276,7 +283,7 @@ private static function get_selector( $block_name ) { // at the same time. $experimental_duotone = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); if ( $experimental_duotone ) { - $root_selector = wp_get_block_css_selector( $block_type ); + $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; From 7e7bf1c57015724343343aa1f22775f33adf3a64 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 30 Mar 2023 13:35:15 -0500 Subject: [PATCH 32/33] Fix PHP lint --- lib/class-wp-duotone-gutenberg.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index eb6cee046e3abd..22d25e485d5097 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -266,9 +266,9 @@ 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 ) { From 3346b9aaf4fd250de1158076820bb857bb74fe15 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:18:09 +1000 Subject: [PATCH 33/33] Correct experimental BC comments and tweak flow --- lib/class-wp-duotone-gutenberg.php | 25 ++++++++-------- packages/block-editor/src/hooks/duotone.js | 33 +++++++++++----------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 22d25e485d5097..4d15b0b96381c5 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -275,12 +275,18 @@ 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 for color.__experimentalDuotone. This will - // have priority over filter.duotone support. Unfortunately we can't - // prefer filter.duotone because it gets set when __experimentalDuotone - // is set via a block_type_metadata_settings hook. It shouldn't be too - // much of a problem because I would expect consumers to not use both - // at the same time. + // 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 ); @@ -289,13 +295,6 @@ private static function get_selector( $block_name ) { : $root_selector; } - // Support flag `filter.duotone` will be populated from the previous - // `color.__experimentalDuotone` support via block_type_metadata_settings filter. - $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); - if ( ! $duotone_support ) { - return null; - } - // Regular filter.duotone support uses filter.duotone selectors with fallbacks. return wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true ); } diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 7c4ce086367774..c3aac777a396b5 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -312,12 +312,22 @@ const withDuotoneStyles = createHigherOrderComponent( const blockType = getBlockType( props.name ); if ( blockType ) { - // Backwards compatibility for color.__experimentalDuotone. This will - // have priority over filter.duotone support. Unfortunately we can't - // prefer filter.duotone because it gets set when __experimentalDuotone - // is set via a block_type_metadata_settings hook. It shouldn't be too - // much of a problem because I would expect consumers to not use both - // at the same time. + // Backwards compatibility for `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`. + const duotoneSupport = getBlockSupport( + blockType, + 'filter.duotone', + false + ); + if ( ! duotoneSupport ) { + return null; + } + + // If the experimental duotone support was set, that value is + // to be treated as a selector and requires scoping. const experimentalDuotone = getBlockSupport( blockType, 'color.__experimentalDuotone', @@ -330,17 +340,6 @@ const withDuotoneStyles = createHigherOrderComponent( : rootSelector; } - // Support flag `filter.duotone` will be populated from the previous - // `color.__experimentalDuotone` support via block_type_metadata_settings filter. - const duotoneSupport = getBlockSupport( - blockType, - 'filter.duotone', - false - ); - if ( ! duotoneSupport ) { - return null; - } - // Regular filter.duotone support uses filter.duotone selectors with fallbacks. return getBlockCSSSelector( blockType, 'filter.duotone', { fallback: true,