diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index d1f0df50ed148..f2465e4df1082 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -183,6 +183,7 @@ The settings section has the following structure and default values: "color": { "custom": true, /* false to opt-out, as in add_theme_support('disable-custom-colors') */ "customGradient": true, /* false to opt-out, as in add_theme_support('disable-custom-gradients') */ + "duotone": [ ... ], /* duotone presets, a list of { "colors": [ "#000", "#FFF" ], "slug": "black-and-white", "name": "Black and White" } */ "gradients": [ ... ], /* gradient presets, as in add_theme_support('editor-gradient-presets', ... ) */ "link": false, /* true to opt-in, as in add_theme_support('experimental-link-color') */ "palette": [ ... ], /* color presets, as in add_theme_support('editor-color-palette', ... ) */ diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index 565b1f0bbbd8e..ff3f50cad8d85 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -86,12 +86,11 @@ supports: { - Default value: null - Subproperties: - `background`: type `boolean`, default value `true` + - `duotone`: type `string`, default value undefined - `gradients`: type `boolean`, default value `false` - `text`: type `boolean`, default value `true` -This value signals that a block supports some of the CSS style properties related to color. When it does, the block editor will show UI controls for the user to set their values. - -The controls for background and text will source their colors from the `editor-color-palette` [theme support](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes), while the gradient's from `editor-gradient-presets` [theme support](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets). +This value signals that a block supports some of the properties related to color. When it does, the block editor will show UI controls for the user to set their values. Note that the `text` and `background` keys have a default value of `true`, so if the `color` property is present they'll also be considered enabled: @@ -115,58 +114,227 @@ supports: { } ``` -When the block has support for a specific color property, the attributes definition is extended to include some attributes. +### color.background + +This property adds UI controls which allow the user to apply a solid background color to a block. -- `style`: attribute of `object` type with no default assigned. This is added when any of support color properties are declared. It stores the custom values set by the user. The block can apply a default style by specifying its own `style` attribute with a default e.g.: +When color support is declared, this property is enabled by default (along with text), so simply setting color will enable background color. ```js -attributes: { - style: { - type: 'object', - default: { - color: { - background: 'value', - gradient: 'value', - text: 'value' - } - } +supports: { + color: true // Enable both background and text +} +``` + +To disable background support while keeping other color supports enabled, set to `false`. + +```js +supports: { + color: { + // Disable background support. Text color support is still enabled. + background: false } } ``` -- When `background` support is declared: it'll be added a new `backgroundColor` attribute of type `string` with no default assigned. It stores the preset values set by the user. The block can apply a default background color by specifying its own attribute with a default e.g.: +When the block declares support for `color.background`, the attributes definition is extended to include two new attributes: `backgroundColor` and `style`: + +- `backgroundColor`: attribute of `string` type with no default assigned. + + When a user chooses from the list of preset background colors, the preset slug is stored in the `backgroundColor` attribute. + + Background color presets are sourced from the `editor-color-palette` [theme support](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes). + + The block can apply a default preset background color by specifying its own attribute with a default e.g.: + + ```js + attributes: { + backgroundColor: { + type: 'string', + default: 'some-preset-background-slug', + } + } + ``` + +- `style`: attribute of `object` type with no default assigned. + + When a custom background color is selected (i.e. using the custom color picker), the custom color value is stored in the `style.color.background` attribute. + + The block can apply a default custom background color by specifying its own attribute with a default e.g.: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + background: '#aabbcc', + } + } + } + } + ``` + +### 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 -attributes: { - backgroundColor: { - type: 'string', - default: 'some-value', +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 } } ``` -- When `gradients` support is declared: it'll be added a new `gradient` attribute of type `string` with no default assigned. It stores the preset values set by the user. The block can apply a default text color by specifying its own attribute with a default e.g.: +Duotone presets are sourced from `color.duotone` in [theme.json](https://developer.wordpress.org/block-editor/developers/themes/theme-json/). + +When the block declares support for `color.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 + ] + } + } + } + } + ``` + +### color.gradients + +This property adds UI controls which allow the user to apply a gradient background to a block. ```js -attributes: { - gradient: { - type: 'string', - default: 'some-value', +supports: { + color: { + gradient: true, + + // Default values must be disabled if you don't want to use them with gradient. + background: false, + text: false } } ``` -- When `text` support is declared: it'll be added a new `textColor` attribute of type `string` with no default assigned. It stores the preset values set by the user. The block can apply a default text color by specifying its own attribute with a default e.g.: +Gradient presets are sourced from `editor-gradient-presets` [theme support](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets). + + +When the block declares support for `color.gradient`, the attributes definition is extended to include two new attributes: `gradient` and `style`: + +- `gradient`: attribute of `string` type with no default assigned. + + When a user chooses from the list of preset gradients, the preset slug is stored in the `gradient` attribute. + + The block can apply a default preset gradient by specifying its own attribute with a default e.g.: + + ```js + attributes: { + gradient: { + type: 'string', + default: 'some-preset-gradient-slug', + } + } + ``` + +- `style`: attribute of `object` type with no default assigned. + + When a custom gradient is selected (i.e. using the custom gradient picker), the custom gradient value is stored in the `style.color.gradient` attribute. + + The block can apply a default custom gradient by specifying its own attribute with a default e.g.: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + background: 'linear-gradient(135deg,rgb(170,187,204) 0%,rgb(17,34,51) 100%)', + } + } + } + } + ``` + +### color.text + +This property adds block controls which allow the user to set text color in a block. + +When color support is declared, this property is enabled by default (along with background), so simply setting color will enable text color. ```js -attributes: { - textColor: { - type: 'string', - default: 'some-value', +supports: { + color: true // Enable both text and background +} +``` + +To disable text color support while keeping other color supports enabled, set to `false`. + +```js +supports: { + color: { + // Disable text color support. Background support is still enabled. + text: false } } ``` +Text color presets are sourced from the `editor-color-palette` [theme support](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes). + + +When the block declares support for `color.text`, the attributes definition is extended to include two new attributes: `textColor` and `style`: + +- `textColor`: attribute of `string` type with no default assigned. + + When a user chooses from the list of preset text colors, the preset slug is stored in the `textColor` attribute. + + The block can apply a default preset text color by specifying its own attribute with a default e.g.: + + ```js + attributes: { + textColor: { + type: 'string', + default: 'some-preset-text-color-slug', + } + } + ``` + +- `style`: attribute of `object` type with no default assigned. + + When a custom text color is selected (i.e. using the custom color picker), the custom color value is stored in the `style.color.text` attribute. + + The block can apply a default custom text color by specifying its own attribute with a default e.g.: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + text: '#aabbcc', + } + } + } + } + ``` + ## customClassName - Type: `boolean` diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php new file mode 100644 index 0000000000000..f3b4743832f0a --- /dev/null +++ b/lib/block-supports/duotone.php @@ -0,0 +1,364 @@ + gutenberg_tinycolor_bound01( $rgb_color['r'], 255 ) * 255, + 'g' => gutenberg_tinycolor_bound01( $rgb_color['g'], 255 ) * 255, + 'b' => gutenberg_tinycolor_bound01( $rgb_color['b'], 255 ) * 255, + ); +} + +/** + * Helper function for hsl to rgb conversion. + * + * @see https://github.com/bgrins/TinyColor + * + * @param float $p first component. + * @param float $q second component. + * @param float $t third component. + * @return float R, G, or B component. + */ +function gutenberg_tinycolor_hue_to_rgb( $p, $q, $t ) { + if ( $t < 0 ) { + $t += 1; + } + if ( $t > 1 ) { + $t -= 1; + } + if ( $t < 1 / 6 ) { + return $p + ( $q - $p ) * 6 * $t; + } + if ( $t < 1 / 2 ) { + return $q; + } + if ( $t < 2 / 3 ) { + return $p + ( $q - $p ) * ( 2 / 3 - $t ) * 6; + } + return $p; +} + +/** + * Convert an HSL object to an RGB object with converted and rounded values. + * + * @see https://github.com/bgrins/TinyColor + * + * @param array $hsl_color HSL object. + * @return array Rounded and converted RGB object. + */ +function gutenberg_tinycolor_hsl_to_rgb( $hsl_color ) { + $h = gutenberg_tinycolor_bound01( $hsl_color['h'], 360 ); + $s = gutenberg_tinycolor_bound01( $hsl_color['s'], 100 ); + $l = gutenberg_tinycolor_bound01( $hsl_color['l'], 100 ); + + if ( 0 === $s ) { + // Achromatic. + $r = $l; + $g = $l; + $b = $l; + } else { + $q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s; + $p = 2 * $l - $q; + $r = gutenberg_tinycolor_hue_to_rgb( $p, $q, $h + 1 / 3 ); + $g = gutenberg_tinycolor_hue_to_rgb( $p, $q, $h ); + $b = gutenberg_tinycolor_hue_to_rgb( $p, $q, $h - 1 / 3 ); + } + + return array( + 'r' => $r * 255, + 'g' => $g * 255, + 'b' => $b * 255, + ); +} + +/** + * Parses hex, hsl, and rgb CSS strings using the same regex as tinycolor v1.4.2 + * used in the JavaScript. Only colors output from react-color are implemented + * and the alpha value is ignored as it is not used in duotone. + * + * @see https://github.com/bgrins/TinyColor + * @see https://github.com/casesandberg/react-color/ + * + * @param string $color_str CSS color string. + * @return array RGB object. + */ +function gutenberg_tinycolor_string_to_rgb( $color_str ) { + $color_str = strtolower( trim( $color_str ) ); + + $css_integer = '[-\\+]?\\d+%?'; + $css_number = '[-\\+]?\\d*\\.\\d+%?'; + + $css_unit = '(?:' . $css_number . ')|(?:' . $css_integer . ')'; + + $permissive_match3 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?'; + $permissive_match4 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?'; + + $rgb_regexp = '/^rgb' . $permissive_match3 . '$/'; + if ( preg_match( $rgb_regexp, $color_str, $match ) ) { + return gutenberg_tinycolor_rgb_to_rgb( + array( + 'r' => $match[1], + 'g' => $match[2], + 'b' => $match[3], + ) + ); + } + + $rgba_regexp = '/^rgba' . $permissive_match4 . '$/'; + if ( preg_match( $rgba_regexp, $color_str, $match ) ) { + return gutenberg_tinycolor_rgb_to_rgb( + array( + 'r' => $match[1], + 'g' => $match[2], + 'b' => $match[3], + ) + ); + } + + $hsl_regexp = '/^hsl' . $permissive_match3 . '$/'; + if ( preg_match( $hsl_regexp, $color_str, $match ) ) { + return gutenberg_tinycolor_hsl_to_rgb( + array( + 'h' => $match[1], + 's' => $match[2], + 'l' => $match[3], + ) + ); + } + + $hsla_regexp = '/^hsla' . $permissive_match4 . '$/'; + if ( preg_match( $hsla_regexp, $color_str, $match ) ) { + return gutenberg_tinycolor_hsl_to_rgb( + array( + 'h' => $match[1], + 's' => $match[2], + 'l' => $match[3], + ) + ); + } + + $hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/'; + if ( preg_match( $hex8_regexp, $color_str, $match ) ) { + return gutenberg_tinycolor_rgb_to_rgb( + array( + 'r' => base_convert( $match[1], 16, 10 ), + 'g' => base_convert( $match[2], 16, 10 ), + 'b' => base_convert( $match[3], 16, 10 ), + ) + ); + } + + $hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/'; + if ( preg_match( $hex6_regexp, $color_str, $match ) ) { + return gutenberg_tinycolor_rgb_to_rgb( + array( + 'r' => base_convert( $match[1], 16, 10 ), + 'g' => base_convert( $match[2], 16, 10 ), + 'b' => base_convert( $match[3], 16, 10 ), + ) + ); + } + + $hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/'; + if ( preg_match( $hex4_regexp, $color_str, $match ) ) { + return gutenberg_tinycolor_rgb_to_rgb( + array( + 'r' => base_convert( $match[1] . $match[1], 16, 10 ), + 'g' => base_convert( $match[2] . $match[2], 16, 10 ), + 'b' => base_convert( $match[3] . $match[3], 16, 10 ), + ) + ); + } + + $hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/'; + if ( preg_match( $hex3_regexp, $color_str, $match ) ) { + return gutenberg_tinycolor_rgb_to_rgb( + array( + 'r' => base_convert( $match[1] . $match[1], 16, 10 ), + 'g' => base_convert( $match[2] . $match[2], 16, 10 ), + 'b' => base_convert( $match[3] . $match[3], 16, 10 ), + ) + ); + } +} + + +/** + * Registers the style and colors block attributes for block types that support it. + * + * @param WP_Block_Type $block_type Block Type. + */ +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 ); + } + + if ( $has_duotone_support ) { + if ( ! $block_type->attributes ) { + $block_type->attributes = array(); + } + + if ( ! array_key_exists( 'style', $block_type->attributes ) ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } + } +} + +/** + * Render out the duotone stylesheet and SVG. + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function gutenberg_render_duotone_support( $block_content, $block ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + + $duotone_support = false; + if ( $block_type && property_exists( $block_type, 'supports' ) ) { + $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); + } + + $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] ); + + if ( + ! $duotone_support || + ! $has_duotone_attribute + ) { + return $block_content; + } + + $duotone_colors = $block['attrs']['style']['color']['duotone']; + + $duotone_values = array( + 'r' => array(), + 'g' => array(), + 'b' => array(), + ); + foreach ( $duotone_colors as $color_str ) { + $color = gutenberg_tinycolor_string_to_rgb( $color_str ); + + $duotone_values['r'][] = $color['r'] / 255; + $duotone_values['g'][] = $color['g'] / 255; + $duotone_values['b'][] = $color['b'] / 255; + } + + $duotone_id = 'wp-duotone-filter-' . uniqid(); + + $selectors = explode( ',', $duotone_support ); + $selectors_scoped = array_map( + function ( $selector ) use ( $duotone_id ) { + return '.' . $duotone_id . ' ' . trim( $selector ); + }, + $selectors + ); + $selectors_group = implode( ', ', $selectors_scoped ); + + ob_start(); + + ?> + + + + + + + + values=".299 .587 .114 0 0 + .299 .587 .114 0 0 + .299 .587 .114 0 0 + 0 0 0 1 0" + + /> + + + + + + + + + + register( + 'duotone', + array( + 'register_attribute' => 'gutenberg_register_duotone_support', + ) +); +add_filter( 'render_block', 'gutenberg_render_duotone_support', 10, 2 ); diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php index e81472dbd23b1..76e26cd1d4fff 100644 --- a/lib/class-wp-theme-json.php +++ b/lib/class-wp-theme-json.php @@ -108,6 +108,7 @@ class WP_Theme_JSON { 'gradients' => null, 'link' => null, 'palette' => null, + 'duotone' => null, ), 'spacing' => array( 'customPadding' => null, diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json index 5eebd221e8a2c..447a768dcd5bd 100644 --- a/lib/experimental-default-theme.json +++ b/lib/experimental-default-theme.json @@ -126,6 +126,48 @@ "slug": "midnight" } ], + "duotone": [ + { + "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" + }, + { + "name": "Blue and red" , + "colors": [ "#000097", "#ff4747" ], + "slug": "blue-red" + }, + { + "name": "Midnight" , + "colors": [ "#000000", "#00a5ff" ], + "slug": "midnight" + }, + { + "name": "Magenta and yellow" , + "colors": [ "#c7005a", "#fff278" ], + "slug": "magenta-yellow" + }, + { + "name": "Purple and green" , + "colors": [ "#a60072", "#67ff66" ], + "slug": "purple-green" + }, + { + "name": "Blue and orange" , + "colors": [ "#1900d8", "#ffa96b" ], + "slug": "blue-orange" + } + ], "custom": true, "link": false, "customGradient": true diff --git a/lib/experimental-i18n-theme.json b/lib/experimental-i18n-theme.json index 94889f733a331..a9dc4c429ea47 100644 --- a/lib/experimental-i18n-theme.json +++ b/lib/experimental-i18n-theme.json @@ -43,6 +43,11 @@ { "name": "Gradient name" } + ], + "duotone": [ + { + "name": "Duotone name" + } ] } } diff --git a/lib/load.php b/lib/load.php index 2deda54b6646b..dcf9539a1e526 100644 --- a/lib/load.php +++ b/lib/load.php @@ -126,3 +126,4 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/border.php'; require __DIR__ . '/block-supports/layout.php'; require __DIR__ . '/block-supports/padding.php'; +require __DIR__ . '/block-supports/duotone.php'; diff --git a/packages/block-editor/src/components/duotone-control/duotone-picker-popover.js b/packages/block-editor/src/components/duotone-control/duotone-picker-popover.js new file mode 100644 index 0000000000000..7926cd5f977a6 --- /dev/null +++ b/packages/block-editor/src/components/duotone-control/duotone-picker-popover.js @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { Popover, MenuGroup, DuotonePicker } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function DuotonePickerPopover( { + value, + onChange, + onToggle, + duotonePalette, + colorPalette, +} ) { + return ( + + + + + + ); +} + +export default DuotonePickerPopover; diff --git a/packages/block-editor/src/components/duotone-control/index.js b/packages/block-editor/src/components/duotone-control/index.js new file mode 100644 index 0000000000000..c7f597c561237 --- /dev/null +++ b/packages/block-editor/src/components/duotone-control/index.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +import { ToolbarButton, DuotoneSwatch } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { DOWN } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import DuotonePickerPopover from './duotone-picker-popover'; + +function DuotoneControl( { colorPalette, duotonePalette, value, onChange } ) { + const [ isOpen, setIsOpen ] = useState( false ); + const onToggle = () => { + setIsOpen( ( prev ) => ! prev ); + }; + const openOnArrowDown = ( event ) => { + if ( ! isOpen && event.keyCode === DOWN ) { + event.preventDefault(); + event.stopPropagation(); + onToggle(); + } + }; + return ( + <> + } + /> + { isOpen && ( + + ) } + + ); +} + +export default DuotoneControl; diff --git a/packages/block-editor/src/components/duotone-control/style.scss b/packages/block-editor/src/components/duotone-control/style.scss new file mode 100644 index 0000000000000..133f0e0b8a748 --- /dev/null +++ b/packages/block-editor/src/components/duotone-control/style.scss @@ -0,0 +1,26 @@ +.block-editor-duotone-control__popover { + .components-popover__content { + border: $border-width solid $gray-900; + min-width: 214px; + } + + .components-circular-option-picker { + padding: $grid-unit-15; + } + + .components-menu-group__label { + padding: $grid-unit-15 $grid-unit-15 0 $grid-unit-15; + width: 100%; + } +} + +.block-editor-duotone-control__popover > .components-popover__content { + // Matches 8 swatches in width. + width: 334px; +} + +// Better align the popover under the swatch. +// @todo: when the positioning for popovers gets refactored, this can presumably be removed. +.block-editor-duotone-control__popover:not([data-y-axis="middle"][data-x-axis="right"]) > .components-popover__content { + margin-left: -14px; +} diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index ac7471317acbe..ad27f85c085d8 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -36,6 +36,7 @@ export { default as ButtonBlockerAppender } from './button-block-appender'; export { default as ColorPalette } from './color-palette'; export { default as ColorPaletteControl } from './color-palette/control'; export { default as ContrastChecker } from './contrast-checker'; +export { default as __experimentalDuotoneControl } from './duotone-control'; export { default as __experimentalGradientPicker } from './gradient-picker'; export { default as __experimentalGradientPickerControl } from './gradient-picker/control'; export { default as __experimentalGradientPickerPanel } from './gradient-picker/panel'; diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js new file mode 100644 index 0000000000000..dd2d27a784d7b --- /dev/null +++ b/packages/block-editor/src/hooks/duotone.js @@ -0,0 +1,255 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import tinycolor from 'tinycolor2'; + +/** + * WordPress dependencies + */ +import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; +import { SVG } from '@wordpress/components'; +import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; +import { addFilter } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import { + BlockControls, + __experimentalDuotoneControl as DuotoneControl, + __experimentalUseEditorFeature as useEditorFeature, +} from '../components'; + +/** + * Convert a list of colors to an object of R, G, and B values. + * + * @param {string[]} colors Array of RBG color strings. + * + * @return {Object} R, G, and B values. + */ +export function getValuesFromColors( colors = [] ) { + const values = { r: [], g: [], b: [] }; + + colors.forEach( ( color ) => { + // Access values directly to skip extra rounding that tinycolor.toRgb() does. + const tcolor = tinycolor( color ); + values.r.push( tcolor._r / 255 ); + values.g.push( tcolor._g / 255 ); + values.b.push( tcolor._b / 255 ); + } ); + + return values; +} + +/** + * Values for the SVG `feComponentTransfer`. + * + * @typedef Values {Object} + * @property {number[]} r Red values. + * @property {number[]} g Green values. + * @property {number[]} b Blue values. + */ + +/** + * SVG and stylesheet needed for rendering the duotone filter. + * + * @param {Object} props Duotone props. + * @param {string} props.selector Selector to apply the filter to. + * @param {string} props.id Unique id for this duotone filter. + * @param {Values} props.values R, G, and B values to filter with. + * @return {WPElement} Duotone element. + */ +function DuotoneFilter( { selector, id, values } ) { + const stylesheet = ` +${ selector } { + filter: url( #${ id } ); +} +`; + + return ( + <> + + + + + + + + + + + + +