diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index 2e5f509f6dc48..683e4f134d987 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` @@ -231,46 +230,9 @@ When the block declares support for `color.background`, the attributes definitio ### color.__experimentalDuotone -This property adds UI controls which allow to apply a duotone filter to a block or part of a block. +_**Note:** Deprecated since WordPress 6.3._ -The parent selector is automatically added much like nesting in Sass/SCSS (however, the `&` selector is not supported). - -```js -supports: { - color: { - // Apply the filter to the same selector in both edit and save. - __experimentalDuotone: '> .duotone-img, > .duotone-video', - - // Default values must be disabled if you don't want to use them with duotone. - background: false, - text: false - } -} -``` - -Duotone presets are sourced from `color.duotone` in [theme.json](/docs/how-to-guides/themes/theme-json.md). - -When the block declares support for `color.__experimentalDuotone`, the attributes definition is extended to include the attribute `style`: - -- `style`: attribute of `object` type with no default assigned. - - The block can apply a default duotone color by specifying its own attribute with a default e.g.: - - ```js - attributes: { - style: { - type: 'object', - default: { - color: { - duotone: [ - '#FFF', - '#000' - ] - } - } - } - } - ``` +This property has been replaced by [`filter.duotone`](#filter-duotone). ### color.gradients @@ -499,6 +461,60 @@ attributes: { } ``` +## filter +- Type: `Object` +- Default value: null +- Subproperties: + - `duotone`: type `boolean`, default value `false` + +This value signals that a block supports some of the properties related to filters. When it does, the block editor will show UI controls for the user to set their values. + +### filter.duotone + +This property adds UI controls which allow the user to apply a duotone filter to +a block or part of a block. + +```js +supports: { + filter: { + // Enable duotone support + duotone: true + } +}, +selectors: { + filter: { + // Apply the filter to img elements inside the image block + duotone: '.wp-block-image img' + } +} +``` + +The filter can be applied to an element inside the block by setting the `selectors.filter.duotone` selector. + +Duotone presets are sourced from `color.duotone` in [theme.json](/docs/how-to-guides/themes/theme-json.md). + +When the block declares support for `filter.duotone`, the attributes definition is extended to include the attribute `style`: + +- `style`: attribute of `object` type with no default assigned. + + The block can apply a default duotone color by specifying its own attribute with a default e.g.: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + duotone: [ + '#FFF', + '#000' + ] + } + } + } + } + ``` + ## html - Type: `boolean` diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index e5b524c685086..7d3b923488a0c 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 diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 1884283c3b150..e775bc9237697 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 ) { @@ -450,3 +452,4 @@ function gutenberg_render_duotone_support( $block_content, $block ) { add_action( 'wp_enqueue_scripts', array( 'WP_Duotone_Gutenberg', 'output_global_styles' ), 11 ); add_action( 'wp_footer', array( 'WP_Duotone_Gutenberg', 'output_footer_assets' ), 10 ); add_filter( 'block_editor_settings_all', array( 'WP_Duotone_Gutenberg', 'add_editor_settings' ), 10 ); +add_filter( 'block_type_metadata_settings', array( 'WP_Duotone_Gutenberg', 'migrate_experimental_duotone_support_flag' ), 10, 2 ); diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index ff963f46cb455..4d15b0b96381c 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -264,6 +264,42 @@ public static function output_global_styles() { } } + /** + * Get the CSS selector for a block type. + * + * @param string $block_name The block name. + * + * @return string The CSS selector or null if there is no support. + */ + private static function get_selector( $block_name ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); + + if ( $block_type && property_exists( $block_type, 'supports' ) ) { + // Backwards compatibility with `supports.color.__experimentalDuotone` + // is provided via the `block_type_metadata_settings` filter. If + // `supports.filter.duotone` has not been set and the experimental + // property has been, the experimental property value is copied into + // `supports.filter.duotone`. + $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); + if ( ! $duotone_support ) { + return null; + } + + // If the experimental duotone support was set, that value is to be + // treated as a selector and requires scoping. + $experimental_duotone = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); + if ( $experimental_duotone ) { + $root_selector = wp_get_block_css_selector( $block_type ); + return is_string( $experimental_duotone ) + ? WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $experimental_duotone ) + : $root_selector; + } + + // Regular filter.duotone support uses filter.duotone selectors with fallbacks. + return wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true ); + } + } + /** * Render out the duotone CSS styles and SVG. * @@ -272,14 +308,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 ) { - $duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); - $duotone_support = (bool) $duotone_selector; - } + $duotone_selector = self::get_selector( $block['blockName'] ); // The block should have a duotone attribute or have duotone defined in its theme.json to be processed. $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] ); @@ -287,7 +316,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; @@ -349,7 +378,17 @@ public static function render_duotone_support( $block_content, $block ) { $filter_id = gutenberg_get_duotone_filter_id( array( 'slug' => $slug ) ); // Build the CSS selectors to which the filter will be applied. - $selector = WP_Theme_JSON_Gutenberg::scope_selector( '.' . $filter_id, $duotone_selector ); + $selectors = explode( ',', $duotone_selector ); + + $selectors_scoped = array(); + foreach ( $selectors as $selector_part ) { + // Assuming the selector part is a subclass selector (not a tag name) + // so we can prepend the filter id class. If we want to support elements + // such as `img` or namespaces, we'll need to add a case for that here. + $selectors_scoped[] = '.' . $filter_id . trim( $selector_part ); + } + + $selector = implode( ', ', $selectors_scoped ); // We only want to add the selector if we have it in the output already, essentially skipping 'unset'. if ( array_key_exists( $slug, self::$output ) ) { @@ -386,4 +425,23 @@ public static function render_duotone_support( $block_content, $block ) { return $tags->get_updated_html(); } + + /** + * Migrate the old experimental duotone support flag to its stabilized location + * under `supports.filter.duotone` and sets. + * + * @param array $settings Current block type settings. + * @param array $metadata Block metadata as read in via block.json. + * + * @return array Filtered block type settings. + */ + public static function migrate_experimental_duotone_support_flag( $settings, $metadata ) { + $duotone_support = _wp_array_get( $metadata, array( 'supports', 'color', '__experimentalDuotone' ), null ); + + if ( ! isset( $settings['supports']['filter']['duotone'] ) && null !== $duotone_support ) { + _wp_array_set( $settings, array( 'supports', 'filter', 'duotone' ), (bool) $duotone_support ); + } + + return $settings; + } } diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index c7e1307f4a0bf..a6a7cc7d5de56 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -871,6 +871,17 @@ protected static function get_blocks_metadata() { // The block may or may not have a duotone selector. $duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); + + // Keep backwards compatibility for support.color.__experimentalDuotone. + if ( null === $duotone_selector ) { + $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), null ); + + if ( $duotone_support ) { + $root_selector = wp_get_block_css_selector( $block_type ); + $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); + } + } + if ( null !== $duotone_selector ) { static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector; } @@ -2389,8 +2400,7 @@ function( $pseudo_selector ) use ( $selector ) { // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { - $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] ); - $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); + $block_rules .= static::to_ruleset( $block_metadata['duotone'], $declarations_duotone ); } // 4. Generate Layout block gap styles. 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 c213e50d64ae7..bb489664e1eea 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,17 +25,6 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f $has_selectors = ! empty( $block_type->selectors ); - // Duotone (No fallback selectors for Duotone). - if ( 'filter.duotone' === $target || array( 'filter', 'duotone' ) === $target ) { - // If selectors API in use, only use it's value or null. - if ( $has_selectors ) { - return _wp_array_get( $block_type->selectors, array( 'filter', 'duotone' ), null ); - } - - // Selectors API, not available, check for old experimental selector. - return _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), null ); - } - // Root Selector. // Calculated before returning as it can be used as fallback for @@ -59,8 +48,8 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f return $root_selector; } - // If target is not `root` or `duotone` we have a feature or subfeature - // as the target. If the target is a string convert to an array. + // If target is not `root` we have a feature or subfeature as the target. + // If the target is a string convert to an array. if ( is_string( $target ) ) { $target = explode( '.', $target ); } @@ -95,25 +84,7 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f } // Scope the feature selector by the block's root selector. - $scopes = explode( ',', $root_selector ); - $selectors = explode( ',', $feature_selector ); - - $selectors_scoped = array(); - foreach ( $scopes as $outer ) { - foreach ( $selectors as $inner ) { - $outer = trim( $outer ); - $inner = trim( $inner ); - if ( ! empty( $outer ) && ! empty( $inner ) ) { - $selectors_scoped[] = $outer . ' ' . $inner; - } elseif ( empty( $outer ) ) { - $selectors_scoped[] = $inner; - } elseif ( empty( $inner ) ) { - $selectors_scoped[] = $outer; - } - } - } - - return implode( ', ', $selectors_scoped ); + return WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $feature_selector ); } // Subfeature selector 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 db58709fe79aa..79a736e1b6978 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,17 +34,6 @@ export function getBlockCSSSelector( const hasSelectors = ! isEmpty( selectors ); const path = Array.isArray( target ) ? target.join( '.' ) : target; - // Duotone ( no fallback selectors for Duotone ). - if ( path === 'filter.duotone' ) { - // If selectors API in use, only use its value or null. - if ( hasSelectors ) { - return get( selectors, path, null ); - } - - // Selectors API, not available, check for old experimental selector. - return get( supports, 'color.__experimentalDuotone', null ); - } - // Root selector. // Calculated before returning as it can be used as a fallback for feature 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 a826c56204478..329aa9f32868a 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', 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 2f68312b76c6b..17456a1ab85e0 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'; @@ -852,17 +853,16 @@ export const toStyles = ( delete styles.filter; } - // Process duotone styles (they use color.__experimentalDuotone selector). + // Process duotone styles. if ( duotoneSelector ) { const duotoneDeclarations = getStylesDeclarations( duotoneStyles ); if ( duotoneDeclarations.length > 0 ) { ruleset = ruleset + - `${ scopeSelector( - selector, - duotoneSelector - ) }{${ duotoneDeclarations.join( ';' ) };}`; + `${ duotoneSelector }{${ duotoneDeclarations.join( + ';' + ) };}`; } } @@ -1002,11 +1002,24 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const result = {}; blockTypes.forEach( ( blockType ) => { const name = blockType.name; - const selector = getBlockCSSSelector( blockType, 'root' ); - const duotoneSelector = getBlockCSSSelector( + const selector = getBlockCSSSelector( blockType ); + let duotoneSelector = getBlockCSSSelector( blockType, 'filter.duotone' ); + + // Keep backwards compatibility for support.color.__experimentalDuotone. + if ( ! duotoneSelector ) { + const rootSelector = getBlockCSSSelector( blockType ); + 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 a05e188d507e1..c3aac777a396b 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -32,6 +32,8 @@ import { __unstableDuotoneStylesheet as DuotoneStylesheet, __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 = []; @@ -166,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; } @@ -193,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( @@ -222,76 +229,71 @@ 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 DuotoneStyles( { + id: filterId, + selector: duotoneSelector, + attribute: duotoneAttr, +} ) { + const element = useContext( BlockList.__unstableElementContext ); -function BlockDuotoneStyles( { name, duotoneStyle, id } ) { 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 = - getBlockType( name ).selectors?.filter?.duotone || - getBlockSupport( name, 'color.__experimentalDuotone' ); + // 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() }`; + } ); - // 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 - ); + const selector = selectorsScoped.join( ', ' ); - return createPortal( - , - element + const isValidFilter = Array.isArray( colors ) || colors === 'unset'; + + return ( + element && + isValidFilter && + createPortal( + , + element + ) ); } @@ -304,16 +306,56 @@ function BlockDuotoneStyles( { name, duotoneStyle, id } ) { */ const withDuotoneStyles = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { - const duotoneSupport = getBlockSupport( - props.name, - 'color.__experimentalDuotone' - ); - - const id = `wp-duotone-${ useInstanceId( BlockListBlock ) }`; - const className = duotoneSupport - ? classnames( props?.className, id ) + const id = useInstanceId( BlockListBlock ); + + const selector = useMemo( () => { + const blockType = getBlockType( props.name ); + + if ( blockType ) { + // 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', + false + ); + if ( experimentalDuotone ) { + const rootSelector = getBlockCSSSelector( blockType ); + return typeof experimentalDuotone === 'string' + ? scopeSelector( rootSelector, experimentalDuotone ) + : rootSelector; + } + + // Regular filter.duotone support uses filter.duotone selectors with fallbacks. + return getBlockCSSSelector( blockType, 'filter.duotone', { + fallback: true, + } ); + } + }, [ props.name ] ); + + const attribute = props?.attributes?.style?.color?.duotone; + + const filterClass = `wp-duotone-${ id }`; + + const shouldRender = selector && attribute; + + 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 @@ -321,11 +363,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 b54358fc48d6a..9e0e749187aab 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, @@ -104,7 +106,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": [ diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 5a1383eadb17f..6fe04c07de1bf 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' ], diff --git a/phpunit/class-wp-duotone-test.php b/phpunit/class-wp-duotone-test.php index 49ef631e65f02..e9a6f52077fa2 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-get-block-css-selectors-test.php b/phpunit/class-wp-get-block-css-selectors-test.php index ba63f042a68dc..8f43d9df6d95e 100644 --- a/phpunit/class-wp-get-block-css-selectors-test.php +++ b/phpunit/class-wp-get-block-css-selectors-test.php @@ -76,45 +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_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( '.experimental-duotone', $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_get_feature_selector_via_selectors_api() { $block_type = self::register_test_block( 'test/feature-selector', diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index eeb5940f44cd3..6cde491d9d2b6 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, diff --git a/schemas/json/block.json b/schemas/json/block.json index 6023d829c5ecd..5b92a654fbc4a 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.",