diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index ca9f614a263491..e0dea9e97cc2bb 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -311,9 +311,6 @@ function gutenberg_get_duotone_filter_id( $preset ) { * @return string Duotone CSS filter property url value. */ function gutenberg_get_duotone_filter_property( $preset ) { - if ( isset( $preset['colors'] ) && 'unset' === $preset['colors'] ) { - return 'none'; - } $filter_id = gutenberg_get_duotone_filter_id( $preset ); return "url('#" . $filter_id . "')"; } @@ -445,15 +442,45 @@ function gutenberg_render_duotone_support( $block_content, $block ) { return $block_content; } - $colors = $block['attrs']['style']['color']['duotone']; - $filter_key = is_array( $colors ) ? implode( '-', $colors ) : $colors; - $filter_preset = array( - 'slug' => wp_unique_id( sanitize_key( $filter_key . '-' ) ), - 'colors' => $colors, - ); - $filter_property = gutenberg_get_duotone_filter_property( $filter_preset ); - $filter_id = gutenberg_get_duotone_filter_id( $filter_preset ); + // Possible values for duotone attribute: + // 1. Array of colors - e.g. array('#000000', '#ffffff'). + // 2. Slug of an existing Duotone preset - e.g. 'green-blue'. + // 3. The string 'unset' - indicates explicitly "no Duotone".. + $duotone_attr = $block['attrs']['style']['color']['duotone']; + + $is_duotone_colors_array = is_array( $duotone_attr ); + $is_duotone_unset = 'unset' === $duotone_attr; + $is_duotone_preset = ! $is_duotone_colors_array && ! $is_duotone_unset; + + if ( $is_duotone_preset ) { + $filter_preset = array( + 'slug' => $duotone_attr, + ); + + // Utilise existing CSS custom property. + $filter_property = "var(--wp--preset--duotone--$duotone_attr)"; + } else { + // Handle when Duotone is either: + // - "unset" + // - an array of colors. + + // Build a unique slug for the filter based on the array of colors. + $filter_key = $is_duotone_colors_array ? implode( '-', $duotone_attr ) : $duotone_attr; + $filter_preset = array( + 'slug' => wp_unique_id( sanitize_key( $filter_key . '-' ) ), + 'colors' => $duotone_attr, // required for building the SVG with gutenberg_get_duotone_filter_svg. + ); + + // Build a customised CSS filter property for unique slug. + $filter_property = $is_duotone_unset ? 'none' : gutenberg_get_duotone_filter_property( $filter_preset ); + } + + // - Applied as a class attribute to the block wrapper. + // - Used as a selector to apply the filter to the block. + $filter_id = gutenberg_get_duotone_filter_id( $filter_preset ); + // Build the CSS selectors to which the filter will be applied. + // Todo - encapsulate this in a function. $scope = '.' . $filter_id; $selectors = explode( ',', $duotone_support ); $scoped = array(); @@ -483,8 +510,11 @@ function gutenberg_render_duotone_support( $block_content, $block ) { ) ); - if ( 'unset' !== $colors ) { + // For *non*-presets then generate an SVG for the filter. + // Note: duotone presets are already pre-generated so no need to do this again. + if ( $is_duotone_colors_array ) { $filter_svg = gutenberg_get_duotone_filter_svg( $filter_preset ); + add_action( 'wp_footer', static function () use ( $filter_svg, $selector ) { diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index a33ad059cb6653..fef0f8de32cddc 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -75,9 +75,34 @@ function useMultiOriginPresets( { presetSetting, defaultSetting } ) { ); } +export function getColorsFromDuotonePreset( duotone, duotonePalette ) { + if ( ! duotone ) { + return; + } + const preset = duotonePalette?.find( ( { slug } ) => { + return slug === duotone; + } ); + + return preset ? preset.colors : undefined; +} + +export function getDuotonePresetFromColors( colors, duotonePalette ) { + if ( ! colors || ! Array.isArray( colors ) ) { + return; + } + + const preset = duotonePalette?.find( ( duotonePreset ) => { + return duotonePreset?.colors?.every( + ( val, index ) => val === colors[ index ] + ); + } ); + + return preset ? preset.slug : undefined; +} + function DuotonePanel( { attributes, setAttributes } ) { const style = attributes?.style; - const duotone = style?.color?.duotone; + const duotoneStyle = style?.color?.duotone; const duotonePalette = useMultiOriginPresets( { presetSetting: 'color.duotone', @@ -96,6 +121,10 @@ function DuotonePanel( { attributes, setAttributes } ) { return null; } + const duotonePresetOrColors = ! Array.isArray( duotoneStyle ) + ? getColorsFromDuotonePreset( duotoneStyle, duotonePalette ) + : duotoneStyle; + return ( { + const maybePreset = getDuotonePresetFromColors( + newDuotone, + duotonePalette + ); + const newStyle = { ...style, color: { ...style?.color, - duotone: newDuotone, + duotone: maybePreset ?? newDuotone, // use preset or fallback to custom colors. }, }; setAttributes( { style: newStyle } ); @@ -224,14 +258,27 @@ const withDuotoneStyles = createHigherOrderComponent( props.name, 'color.__experimentalDuotone' ); - const colors = props?.attributes?.style?.color?.duotone; + const duotonePalette = useMultiOriginPresets( { + presetSetting: 'color.duotone', + defaultSetting: 'color.defaultDuotone', + } ); + + const id = `wp-duotone-${ useInstanceId( BlockListBlock ) }`; + + let colors = props?.attributes?.style?.color?.duotone; + + if ( ! Array.isArray( colors ) ) { + const duotone = duotonePalette.find( ( dt ) => dt.slug === colors ); + + if ( duotone ) { + colors = duotone.colors; + } + } if ( ! duotoneSupport || ! colors ) { return ; } - const id = `wp-duotone-${ useInstanceId( BlockListBlock ) }`; - // 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. diff --git a/packages/block-editor/src/hooks/test/duotone.js b/packages/block-editor/src/hooks/test/duotone.js new file mode 100644 index 00000000000000..b97c967d212f07 --- /dev/null +++ b/packages/block-editor/src/hooks/test/duotone.js @@ -0,0 +1,99 @@ +/** + * Internal dependencies + */ +import { + getColorsFromDuotonePreset, + getDuotonePresetFromColors, +} from '../duotone'; + +describe( 'Duotone utilities', () => { + const duotonePalette = [ + { + name: 'Dark grayscale', + colors: [ '#000000', '#7f7f7f' ], + slug: 'dark-grayscale', + }, + { + name: 'Grayscale', + colors: [ '#000000', '#ffffff' ], + slug: 'grayscale', + }, + { + name: 'Purple and yellow', + colors: [ '#8c00b7', '#fcff41' ], + slug: 'purple-yellow', + }, + ]; + describe( 'getColorsFromDuotonePreset', () => { + it( 'should return undefined if no arguments are provided', () => { + expect( getColorsFromDuotonePreset() ).toBeUndefined(); + } ); + + it( 'should return undefined if no duotone preset is provided', () => { + expect( + getColorsFromDuotonePreset( undefined, duotonePalette ) + ).toBeUndefined(); + } ); + + it( 'should return undefined if a non-existent preset is provided', () => { + expect( + getColorsFromDuotonePreset( 'does-not-exist', duotonePalette ) + ).toBeUndefined(); + } ); + + it( 'should return the colors from the preset if found', () => { + expect( + getColorsFromDuotonePreset( + duotonePalette[ 2 ].slug, + duotonePalette + ) + ).toEqual( duotonePalette[ 2 ].colors ); + } ); + } ); + + describe( 'getDuotonePresetFromColors', () => { + it( 'should return undefined if no arguments are provided', () => { + expect( getDuotonePresetFromColors() ).toBeUndefined(); + } ); + + it( 'should return undefined if no colors are provided', () => { + expect( + getDuotonePresetFromColors( undefined, duotonePalette ) + ).toBeUndefined(); + } ); + + it( 'should return undefined if provided colors is not of valid type', () => { + const notAnArrayOfColorStrings = 'purple-yellow'; + expect( + getDuotonePresetFromColors( + notAnArrayOfColorStrings, + duotonePalette + ) + ).toBeUndefined(); + } ); + + it( 'should return undefined if no duotone palette is provided', () => { + expect( + getDuotonePresetFromColors( [ '#8c00b7', '#fcff41' ] ) + ).toBeUndefined(); + } ); + + it( 'should return undefined if the provided colors do not match any preset', () => { + expect( + getDuotonePresetFromColors( + [ '#000000', '#000000' ], + duotonePalette + ) + ).toBeUndefined(); + } ); + + it( 'should return the slug of the preset if found', () => { + expect( + getDuotonePresetFromColors( + duotonePalette[ 2 ].colors, + duotonePalette + ) + ).toEqual( duotonePalette[ 2 ].slug ); + } ); + } ); +} );