From bbdd7abb1e2a3276060c969edf1ca04fd938bafc Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 8 Sep 2021 01:25:32 -0400 Subject: [PATCH 01/22] Add duotone theme.json styles support --- lib/block-supports/duotone.php | 103 ++++++++++++------- lib/class-wp-theme-json-gutenberg.php | 109 ++++++++++++++++++++- lib/global-styles.php | 22 +++++ packages/block-editor/src/hooks/duotone.js | 41 +++++++- 4 files changed, 230 insertions(+), 45 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 5869d521b2d38..e5b9148c05472 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -251,31 +251,47 @@ function gutenberg_register_duotone_support( $block_type ) { } /** - * Render out the duotone stylesheet and SVG. + * Get the duotone stylesheet. * - * @param string $block_content Rendered block content. - * @param array $block Block object. - * @return string Filtered block content. + * @param string $duotone_id Unique id for the duotone filter. + * @param string $duotone_selector Duotone selector as declared in block support. + * + * @return string Duotone stylesheet. */ -function gutenberg_render_duotone_support( $block_content, $block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); +function gutenberg_get_duotone_stylesheet( $duotone_id, $duotone_selector ) { + $selectors = explode( ',', $duotone_selector ); + $selectors_scoped = array_map( + function ( $selector ) use ( $duotone_id ) { + return '.' . $duotone_id . ' ' . trim( $selector ); + }, + $selectors + ); + $selectors_group = implode( ', ', $selectors_scoped ); - $duotone_support = false; - if ( $block_type && property_exists( $block_type, 'supports' ) ) { - $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); - } + ob_start(); - $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']; + array(), 'g' => array(), @@ -289,27 +305,10 @@ function gutenberg_render_duotone_support( $block_content, $block ) { $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(); ?> - - 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_id = 'wp-duotone-filter-' . uniqid(); + + $duotone = gutenberg_get_duotone_stylesheet( $duotone_id, $duotone_support ); + $duotone .= gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors ); // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper. $content = preg_replace( diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 29cca882ffa13..c900844df3076 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -57,6 +57,7 @@ class WP_Theme_JSON_Gutenberg { ), 'color' => array( 'background' => null, + 'duotone' => null, 'gradient' => null, 'text' => null, ), @@ -243,6 +244,16 @@ class WP_Theme_JSON_Gutenberg { 'text-transform' => array( 'typography', 'textTransform' ), ); + /** + * Metadata for style properties that need to use the duotone selector. + * + * Each element is a direct mapping from the CSS property name to the + * path to the value in theme.json & block attributes. + */ + const DUOTONE_PROPERTIES_METADATA = array( + 'filter' => array( 'color', 'duotone' ), + ); + const ELEMENTS = array( 'link' => 'a', 'h1' => 'h1', @@ -360,9 +371,13 @@ private static function sanitize( $input, $valid_block_names, $valid_element_nam * }, * 'core/heading': { * 'selector': 'h1' - * } + * }, * 'core/group': { * 'selector': '.wp-block-group' + * }, + * 'core/cover': { + * 'selector': '.wp-block-cover', + * 'duotone': '> .wp-block-cover__image-background, > .wp-block-cover__video-background' * } * } * @@ -387,6 +402,13 @@ private static function get_blocks_metadata() { self::$blocks_metadata[ $block_name ]['selector'] = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ); } + if ( + isset( $block_type->supports['color']['__experimentalDuotone'] ) && + is_string( $block_type->supports['color']['__experimentalDuotone'] ) + ) { + self::$blocks_metadata[ $block_name ]['duotone'] = $block_type->supports['color']['__experimentalDuotone']; + } + // Assign defaults, then overwrite those that the block sets by itself. // If the block selector is compounded, will append the element to each // individual block selector. @@ -540,16 +562,17 @@ private static function get_property_value( $styles, $path ) { * ``` * * @param array $styles Styles to process. + * @param array $properties Properties metadata. * * @return array Returns the modified $declarations. */ - private static function compute_style_properties( $styles ) { + private static function compute_style_properties( $styles, $properties = self::PROPERTIES_METADATA ) { $declarations = array(); if ( empty( $styles ) ) { return $declarations; } - foreach ( self::PROPERTIES_METADATA as $css_property => $value_path ) { + foreach ( $properties as $css_property => $value_path ) { $value = self::get_property_value( $styles, $value_path ); // Skip if empty and not "0" or value represents array of longhand values. @@ -589,6 +612,36 @@ private static function append_to_selector( $selector, $to_append ) { return implode( ',', $new_selectors ); } + /** + * Function that scopes a selector with another one. This works a bit like + * SCSS nesting except the `&` operator isn't supported. + * + * + * $scope = '.a, .b .c'; + * $selector = '> .x, .y'; + * $merged = scope_selector( $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. + */ + private static function scope_selector( $scope, $selector ) { + $scopes = explode( ',', $scope ); + $selectors = explode( ',', $selector ); + + $selectors_scoped = array(); + foreach ( $scopes as $outer ) { + foreach ( $selectors as $inner ) { + $selectors_scoped[] = trim( $outer ) . ' ' . trim( $inner ); + } + } + + return implode( ', ', $selectors_scoped ); + } + /** * Function that given an array of presets keyed by origin * and the value key of the preset returns an array where each key is @@ -826,11 +879,40 @@ private function get_block_classes( $style_nodes ) { $block_rules .= '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }'; } } + + if ( isset( $metadata['duotone'] ) ) { + $selector = self::scope_selector( $metadata['selector'], $metadata['duotone'] ); + $declarations = self::compute_style_properties( $node, self::DUOTONE_PROPERTIES_METADATA ); + $block_rules .= self::to_ruleset( $selector, $declarations ); + } } return $block_rules; } + /** + * Gets the SVGs for duotone filter support. + * + * @param array $settings Settings per block. + * + * @return string The SVGs containing the duotone filters. + */ + private function get_svg_filters( $settings ) { + if ( ! isset( $settings['color']['duotone'] ) ) { + return; + } + + $block_svgs = ''; + + foreach ( $settings['color']['duotone'] as $swatch ) { + $duotone_id = 'wp-duotone-filter-' . $swatch['slug']; + $duotone_colors = $swatch['colors']; + $block_svgs .= gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors ); + } + + return $block_svgs; + } + /** * Creates new rulesets as classes for each preset value such as: * @@ -951,11 +1033,13 @@ public function get_template_parts() { * [ * [ * 'path' => [ 'path', 'to', 'some', 'node' ], - * 'selector' => 'CSS selector for some node' + * 'selector' => 'CSS selector for some node', + * 'duotone' => 'CSS selector for duotone for some node' * ], * [ * 'path' => ['path', 'to', 'other', 'node' ], - * 'selector' => 'CSS selector for other node' + * 'selector' => 'CSS selector for other node', + * 'duotone' => null * ], * ] * @@ -996,9 +1080,15 @@ private static function get_style_nodes( $theme_json, $selectors = array() ) { $selector = $selectors[ $name ]['selector']; } + $duotone_selector = null; + if ( isset( $selectors[ $name ]['duotone'] ) ) { + $duotone_selector = $selectors[ $name ]['duotone']; + } + $nodes[] = array( 'path' => array( 'styles', 'blocks', $name ), 'selector' => $selector, + 'duotone' => $duotone_selector, ); if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { @@ -1093,6 +1183,15 @@ public function get_stylesheet( $type = 'all' ) { } } + /** + * Returns the SVGs for filters used by the stylesheets. + * + * @return string SVGs. + */ + public function get_svgs() { + return $this->get_svg_filters( $this->get_settings() ); + } + /** * Merge new incoming data. * diff --git a/lib/global-styles.php b/lib/global-styles.php index 59e392830517b..ab7f9843fe585 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -69,6 +69,25 @@ function gutenberg_experimental_global_styles_enqueue_assets() { } } +/** + * Fetches the preferences for each origin (core, theme, user) + * and enqueues the resulting SVGs for media filter support. + * + * TODO: Is this the right way to render the SVGs? Maybe I should render them as + * CSS variables so they can be included in the stylesheet? + */ +function gutenberg_experimental_global_styles_enqueue_svg_filters() { + if ( ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { + return; + } + + $settings = gutenberg_get_default_block_editor_settings(); + $all = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings ); + + // TODO: Should this be cached like the stylesheet? + echo $all->get_svgs(); +} + /** * Adds the necessary settings for the Global Styles client UI. * @@ -336,6 +355,9 @@ function gutenberg_global_styles_include_support_for_wp_variables( $allow_css, $ add_action( 'init', 'gutenberg_experimental_global_styles_register_user_cpt' ); add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' ); +// SVG defs need to be within a container that is displayed (not inside a display: none or in the header). +add_action( is_admin() ? 'admin_footer' : 'wp_footer', 'gutenberg_experimental_global_styles_enqueue_svg_filters' ); + // kses actions&filters. add_action( 'init', 'gutenberg_global_styles_kses_init' ); add_action( 'set_current_user', 'gutenberg_global_styles_kses_init' ); diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 0bde4cdbe2c60..f89fec145aa3a 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -213,6 +213,37 @@ 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( ', ' ); +} + /** * Override the default block element to include duotone styles. * @@ -234,11 +265,13 @@ const withDuotoneStyles = createHigherOrderComponent( const id = `wp-duotone-filter-${ useInstanceId( BlockListBlock ) }`; - const selectors = duotoneSupport.split( ',' ); - const selectorsScoped = selectors.map( - ( selector ) => `.${ id } ${ selector.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 }`, + duotoneSupport ); - const selectorsGroup = selectorsScoped.join( ', ' ); const className = classnames( props?.className, id ); From 5ec2efee1fffd6e6664cc45676e0960cee8e4d84 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 8 Sep 2021 19:28:43 -0400 Subject: [PATCH 02/22] Add support for generating duotone CSS variables --- lib/block-supports/duotone.php | 105 ++++++++++---------------- lib/class-wp-theme-json-gutenberg.php | 44 +++-------- lib/global-styles.php | 22 ------ 3 files changed, 50 insertions(+), 121 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index e5b9148c05472..b0f3c64b47ebe 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -251,47 +251,14 @@ function gutenberg_register_duotone_support( $block_type ) { } /** - * Get the duotone stylesheet. - * - * @param string $duotone_id Unique id for the duotone filter. - * @param string $duotone_selector Duotone selector as declared in block support. - * - * @return string Duotone stylesheet. - */ -function gutenberg_get_duotone_stylesheet( $duotone_id, $duotone_selector ) { - $selectors = explode( ',', $duotone_selector ); - $selectors_scoped = array_map( - function ( $selector ) use ( $duotone_id ) { - return '.' . $duotone_id . ' ' . trim( $selector ); - }, - $selectors - ); - $selectors_group = implode( ', ', $selectors_scoped ); - - ob_start(); - - ?> - - - - array(), 'g' => array(), @@ -309,25 +276,17 @@ function gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors ) { ?> - + - values=".299 .587 .114 0 0 - .299 .587 .114 0 0 - .299 .587 .114 0 0 - 0 0 0 1 0" - + values=" + .299 .587 .114 0 0 + .299 .587 .114 0 0 + .299 .587 .114 0 0 + 0 0 0 1 0 + " /> @@ -340,7 +299,17 @@ function gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors ) { <', $svg ); + $svg = trim( $svg ); + + $data_uri = 'data:image/svg+xml,' . $svg . '#' . $duotone_id; + + // All the variables are already escaped above, so we're not calling esc_url() here. + return "url('" . $data_uri . "')"; } /** @@ -367,30 +336,32 @@ function gutenberg_render_duotone_support( $block_content, $block ) { return $block_content; } - $duotone_colors = $block['attrs']['style']['color']['duotone']; + $duotone_id = 'wp-duotone-filter-' . uniqid(); + $duotone_colors = $block['attrs']['style']['color']['duotone']; + $filter_property = gutenberg_get_duotone_filter_property( $duotone_id, $duotone_colors ); - $duotone_id = 'wp-duotone-filter-' . uniqid(); + $selectors = explode( ',', $duotone_support ); + $scoped = array(); + foreach ( $selectors as $sel ) { + $scoped[] = '.' . $duotone_id . ' ' . trim( $sel ); + } + $selector = implode( ', ', $scoped ); + + $filter_style = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG + ? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n" + : $selector . '{filter:' . $filter_property . ' !important;}'; - $duotone = gutenberg_get_duotone_stylesheet( $duotone_id, $duotone_support ); - $duotone .= gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors ); + wp_register_style( $duotone_id, false, array(), true, true ); + wp_add_inline_style( $duotone_id, $filter_style ); + wp_enqueue_style( $duotone_id ); // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper. - $content = preg_replace( + return preg_replace( '/' . preg_quote( 'class="', '/' ) . '/', 'class="' . $duotone_id . ' ', $block_content, 1 ); - - add_action( - // Ideally we should use wp_head, but SVG defs can't be put in there. - 'wp_footer', - function () use ( $duotone ) { - echo $duotone; - } - ); - - return $content; } // Register the block support. diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index c900844df3076..b20e709c3d1e1 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -736,6 +736,18 @@ private static function compute_preset_vars( $settings ) { } } + // Duotone filters are a special case and need to be handled differently. + $duotone_values = _wp_array_get( $settings, array( 'color', 'duotone' ), array() ); + foreach ( $duotone_values as $duotone ) { + $slug = gutenberg_experimental_to_kebab_case( $duotone['slug'] ); + $duotone_id = 'wp-preset-duotone-filter-' . $slug; + $duotone_colors = $duotone['colors']; + $declarations[] = array( + 'name' => '--wp--preset--duotone-filter--' . $slug, + 'value' => gutenberg_get_duotone_filter_property( $duotone_id, $duotone_colors ), + ); + } + return $declarations; } @@ -890,29 +902,6 @@ private function get_block_classes( $style_nodes ) { return $block_rules; } - /** - * Gets the SVGs for duotone filter support. - * - * @param array $settings Settings per block. - * - * @return string The SVGs containing the duotone filters. - */ - private function get_svg_filters( $settings ) { - if ( ! isset( $settings['color']['duotone'] ) ) { - return; - } - - $block_svgs = ''; - - foreach ( $settings['color']['duotone'] as $swatch ) { - $duotone_id = 'wp-duotone-filter-' . $swatch['slug']; - $duotone_colors = $swatch['colors']; - $block_svgs .= gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors ); - } - - return $block_svgs; - } - /** * Creates new rulesets as classes for each preset value such as: * @@ -1183,15 +1172,6 @@ public function get_stylesheet( $type = 'all' ) { } } - /** - * Returns the SVGs for filters used by the stylesheets. - * - * @return string SVGs. - */ - public function get_svgs() { - return $this->get_svg_filters( $this->get_settings() ); - } - /** * Merge new incoming data. * diff --git a/lib/global-styles.php b/lib/global-styles.php index ab7f9843fe585..59e392830517b 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -69,25 +69,6 @@ function gutenberg_experimental_global_styles_enqueue_assets() { } } -/** - * Fetches the preferences for each origin (core, theme, user) - * and enqueues the resulting SVGs for media filter support. - * - * TODO: Is this the right way to render the SVGs? Maybe I should render them as - * CSS variables so they can be included in the stylesheet? - */ -function gutenberg_experimental_global_styles_enqueue_svg_filters() { - if ( ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { - return; - } - - $settings = gutenberg_get_default_block_editor_settings(); - $all = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings ); - - // TODO: Should this be cached like the stylesheet? - echo $all->get_svgs(); -} - /** * Adds the necessary settings for the Global Styles client UI. * @@ -355,9 +336,6 @@ function gutenberg_global_styles_include_support_for_wp_variables( $allow_css, $ add_action( 'init', 'gutenberg_experimental_global_styles_register_user_cpt' ); add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' ); -// SVG defs need to be within a container that is displayed (not inside a display: none or in the header). -add_action( is_admin() ? 'admin_footer' : 'wp_footer', 'gutenberg_experimental_global_styles_enqueue_svg_filters' ); - // kses actions&filters. add_action( 'init', 'gutenberg_global_styles_kses_init' ); add_action( 'set_current_user', 'gutenberg_global_styles_kses_init' ); From 3c0a97e6012b42cff836504386018a50c9b93cc6 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 8 Sep 2021 21:47:59 -0400 Subject: [PATCH 03/22] Add value_func and value_args metadata to compute duotone --- lib/class-wp-theme-json-gutenberg.php | 133 +++++++++++++++++++------- 1 file changed, 98 insertions(+), 35 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index b20e709c3d1e1..b3b9e6523e945 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -144,6 +144,10 @@ class WP_Theme_JSON_Gutenberg { * * - value_key => the key that represents the value * + * - value_func => a function to generate the value + * + * - value_args => array of keys that will be mapped to values passed to value_func + * * - css_var_infix => infix to use in generating the CSS Custom Property. Example: * --wp--preset----: * @@ -186,6 +190,13 @@ class WP_Theme_JSON_Gutenberg { ), ), ), + array( + 'path' => array( 'color', 'duotone' ), + 'value_func' => 'gutenberg_get_duotone_filter_property', + 'value_args' => array( 'slug', 'colors' ), + 'css_var_infix' => 'duotone-filter', + 'classes' => array(), + ), array( 'path' => array( 'typography', 'fontSizes' ), 'value_key' => 'size', @@ -643,27 +654,93 @@ private static function scope_selector( $scope, $selector ) { } /** - * Function that given an array of presets keyed by origin - * and the value key of the preset returns an array where each key is - * the a preset slug and each value is the preset value. + * Gets preset values keyed by slugs based on settings and metadata. * - * @param array $preset_per_origin Array of presets keyed by origin. - * @param string $value_key The property of the preset that contains its value. + * + * $settings = array( + * 'color' => array( + * 'palette' => array( + * array( + * 'slug' => 'sansSerif', + * 'fontFamily' => '"Helvetica Neue", sans-serif', + * ), + * array( + * 'slug' => 'serif', + * 'colors' => 'Georgia, serif', + * ) + * ), + * ), + * ); + * $meta = array( + * 'path' => array( 'typography', 'fontFamilies' ), + * 'value_key' => 'fontFamily', + * ); + * $values_by_slug = get_settings_values_by_slug(); + * // $values_by_slug === array( + * // 'sans-serif' => '"Helvetica Neue", sans-serif', + * // 'serif' => 'Georgia, serif', + * // ); + * + * + * @param array $settings Settings to process. + * @param array $meta One of the PRESETS_METADATA values. * * @return array Array of presets where each key is a slug and each value is the preset value. */ - private static function get_merged_preset_by_slug( $preset_per_origin, $value_key ) { + private static function get_settings_values_by_slug( $settings, $meta ) { + $preset_per_origin = _wp_array_get( $settings, $meta['path'], array() ); + $result = array(); foreach ( self::VALID_ORIGINS as $origin ) { if ( ! isset( $preset_per_origin[ $origin ] ) ) { continue; } foreach ( $preset_per_origin[ $origin ] as $preset ) { - // We don't want to use kebabCase here, - // see https://github.com/WordPress/gutenberg/issues/32347 - // However, we need to make sure the generated class or css variable - // doesn't contain spaces. - $result[ preg_replace( '/\s+/', '-', $preset['slug'] ) ] = $preset[ $value_key ]; + $slug = gutenberg_experimental_to_kebab_case( $preset['slug'] ); + + $value = ''; + if ( isset( $meta['value_key'] ) ) { + $value_key = $meta['value_key']; + $value = $preset[ $value_key ]; + } elseif ( is_callable( $meta['value_func'] ) ) { + $value_func = $meta['value_func']; + $value_args = array(); + foreach ( $meta['value_args'] as $meta_arg ) { + $value_args[] = $preset[ $meta_arg ]; + } + $value = call_user_func_array( $value_func, $value_args ); + } else { + // If we don't have a value, then don't add it to the result. + continue; + } + + $result[ $slug ] = $value; + } + } + return $result; + } + + /** + * Similar to get_settings_values_by_slug, but doesn't compute the value. + * + * @param array $settings Settings to process. + * @param array $meta One of the PRESETS_METADATA values. + * + * @return array Array of presets where the key and value are both the slug. + */ + private static function get_settings_slugs( $settings, $meta ) { + $preset_per_origin = _wp_array_get( $settings, $meta['path'], array() ); + + $result = array(); + foreach ( self::VALID_ORIGINS as $origin ) { + if ( ! isset( $preset_per_origin[ $origin ] ) ) { + continue; + } + foreach ( $preset_per_origin[ $origin ] as $preset ) { + $slug = gutenberg_experimental_to_kebab_case( $preset['slug'] ); + + // Use the array as a set so we don't get duplicates. + $result[ $slug ] = $slug; } } return $result; @@ -686,17 +763,16 @@ private static function compute_preset_classes( $settings, $selector ) { } $stylesheet = ''; - foreach ( self::PRESETS_METADATA as $preset ) { - $preset_per_origin = _wp_array_get( $settings, $preset['path'], array() ); - $preset_by_slug = self::get_merged_preset_by_slug( $preset_per_origin, $preset['value_key'] ); - foreach ( $preset['classes'] as $class ) { - foreach ( $preset_by_slug as $slug => $value ) { + foreach ( self::PRESETS_METADATA as $meta ) { + $slugs = self::get_settings_slugs( $settings, $meta ); + foreach ( $meta['classes'] as $class ) { + foreach ( $slugs as $slug ) { $stylesheet .= self::to_ruleset( - self::append_to_selector( $selector, '.has-' . gutenberg_experimental_to_kebab_case( $slug ) . '-' . $class['class_suffix'] ), + self::append_to_selector( $selector, '.has-' . $slug . '-' . $class['class_suffix'] ), array( array( 'name' => $class['property_name'], - 'value' => 'var(--wp--preset--' . $preset['css_var_infix'] . '--' . gutenberg_experimental_to_kebab_case( $slug ) . ') !important', + 'value' => 'var(--wp--preset--' . $meta['css_var_infix'] . '--' . $slug . ') !important', ), ) ); @@ -725,29 +801,16 @@ private static function compute_preset_classes( $settings, $selector ) { */ private static function compute_preset_vars( $settings ) { $declarations = array(); - foreach ( self::PRESETS_METADATA as $preset ) { - $preset_per_origin = _wp_array_get( $settings, $preset['path'], array() ); - $preset_by_slug = self::get_merged_preset_by_slug( $preset_per_origin, $preset['value_key'] ); - foreach ( $preset_by_slug as $slug => $value ) { + foreach ( self::PRESETS_METADATA as $meta ) { + $values_by_slug = self::get_settings_values_by_slug( $settings, $meta ); + foreach ( $values_by_slug as $slug => $value ) { $declarations[] = array( - 'name' => '--wp--preset--' . $preset['css_var_infix'] . '--' . gutenberg_experimental_to_kebab_case( $slug ), + 'name' => '--wp--preset--' . $meta['css_var_infix'] . '--' . $slug, 'value' => $value, ); } } - // Duotone filters are a special case and need to be handled differently. - $duotone_values = _wp_array_get( $settings, array( 'color', 'duotone' ), array() ); - foreach ( $duotone_values as $duotone ) { - $slug = gutenberg_experimental_to_kebab_case( $duotone['slug'] ); - $duotone_id = 'wp-preset-duotone-filter-' . $slug; - $duotone_colors = $duotone['colors']; - $declarations[] = array( - 'name' => '--wp--preset--duotone-filter--' . $slug, - 'value' => gutenberg_get_duotone_filter_property( $duotone_id, $duotone_colors ), - ); - } - return $declarations; } From ec86c0b80fa7d174362342dcb347b8f259513bca Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 8 Sep 2021 22:14:22 -0400 Subject: [PATCH 04/22] Update phpunit tests --- phpunit/class-wp-theme-json-test.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index d88adc7f384a3..86ffeb0c0f75a 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -784,7 +784,9 @@ public function test_merge_incoming_data_empty_presets() { 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( - 'duotone' => array(), + 'duotone' => array( + 'theme' => array(), + ), 'gradients' => array( 'theme' => array(), ), @@ -872,9 +874,11 @@ public function test_merge_incoming_data_null_presets() { 'color' => array( 'custom' => false, 'duotone' => array( - array( - 'slug' => 'value', - 'colors' => array( 'red', 'green' ), + 'theme' => array( + array( + 'slug' => 'value', + 'colors' => array( 'red', 'green' ), + ), ), ), 'gradients' => array( From 358c456035d1c94676e1de26144b6ad304eeb9bd Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 8 Sep 2021 22:37:31 -0400 Subject: [PATCH 05/22] Fix duotone filter id on css variables --- lib/block-supports/duotone.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index b0f3c64b47ebe..96c63762d4a13 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -259,6 +259,8 @@ function gutenberg_register_duotone_support( $block_type ) { * @return string Duotone CSS filter property. */ function gutenberg_get_duotone_filter_property( $duotone_id, $duotone_colors ) { + $filter_id = 'wp-duotone-filter-' . $duotone_id; + $duotone_values = array( 'r' => array(), 'g' => array(), @@ -278,7 +280,7 @@ function gutenberg_get_duotone_filter_property( $duotone_id, $duotone_colors ) { - + Date: Thu, 9 Sep 2021 10:33:04 -0400 Subject: [PATCH 07/22] Fix get_settings_values_by_slug example --- lib/class-wp-theme-json-gutenberg.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index b3b9e6523e945..016621137e5c9 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -658,8 +658,8 @@ private static function scope_selector( $scope, $selector ) { * * * $settings = array( - * 'color' => array( - * 'palette' => array( + * 'typography' => array( + * 'fontFamilies' => array( * array( * 'slug' => 'sansSerif', * 'fontFamily' => '"Helvetica Neue", sans-serif', From cebab758cbcb033b6e14c98b5c2edfdc6dc55ae7 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 9 Sep 2021 13:22:25 -0400 Subject: [PATCH 08/22] Add color.duotone to PATHS_WITH_MERGE --- packages/blocks/src/api/constants.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 2ca489dd609f5..674caea6af4c9 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -129,6 +129,7 @@ export const __EXPERIMENTAL_ELEMENTS = { }; export const __EXPERIMENTAL_PATHS_WITH_MERGE = { + 'color.duotone': true, 'color.gradients': true, 'color.palette': true, 'typography.fontFamilies': true, From 5956f187e61027edec7add408ea86b7cafbbdd9a Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 9 Sep 2021 13:25:03 -0400 Subject: [PATCH 09/22] Rename $meta to $preset_meta to be more descriptive --- lib/class-wp-theme-json-gutenberg.php | 42 +++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 016621137e5c9..fa114ee743ef3 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -301,8 +301,8 @@ public function __construct( $theme_json = array(), $origin = 'theme' ) { // Internally, presets are keyed by origin. $nodes = self::get_setting_nodes( $this->theme_json ); foreach ( $nodes as $node ) { - foreach ( self::PRESETS_METADATA as $preset ) { - $path = array_merge( $node['path'], $preset['path'] ); + foreach ( self::PRESETS_METADATA as $preset_meta ) { + $path = array_merge( $node['path'], $preset_meta['path'] ); $preset = _wp_array_get( $this->theme_json, $path, null ); if ( null !== $preset ) { gutenberg_experimental_set( $this->theme_json, $path, array( $origin => $preset ) ); @@ -683,12 +683,12 @@ private static function scope_selector( $scope, $selector ) { * * * @param array $settings Settings to process. - * @param array $meta One of the PRESETS_METADATA values. + * @param array $preset_meta One of the PRESETS_METADATA values. * * @return array Array of presets where each key is a slug and each value is the preset value. */ - private static function get_settings_values_by_slug( $settings, $meta ) { - $preset_per_origin = _wp_array_get( $settings, $meta['path'], array() ); + private static function get_settings_values_by_slug( $settings, $preset_meta ) { + $preset_per_origin = _wp_array_get( $settings, $preset_meta['path'], array() ); $result = array(); foreach ( self::VALID_ORIGINS as $origin ) { @@ -699,14 +699,14 @@ private static function get_settings_values_by_slug( $settings, $meta ) { $slug = gutenberg_experimental_to_kebab_case( $preset['slug'] ); $value = ''; - if ( isset( $meta['value_key'] ) ) { - $value_key = $meta['value_key']; + if ( isset( $preset_meta['value_key'] ) ) { + $value_key = $preset_meta['value_key']; $value = $preset[ $value_key ]; - } elseif ( is_callable( $meta['value_func'] ) ) { - $value_func = $meta['value_func']; + } elseif ( is_callable( $preset_meta['value_func'] ) ) { + $value_func = $preset_meta['value_func']; $value_args = array(); - foreach ( $meta['value_args'] as $meta_arg ) { - $value_args[] = $preset[ $meta_arg ]; + foreach ( $preset_meta['value_args'] as $preset_meta_arg ) { + $value_args[] = $preset[ $preset_meta_arg ]; } $value = call_user_func_array( $value_func, $value_args ); } else { @@ -724,12 +724,12 @@ private static function get_settings_values_by_slug( $settings, $meta ) { * Similar to get_settings_values_by_slug, but doesn't compute the value. * * @param array $settings Settings to process. - * @param array $meta One of the PRESETS_METADATA values. + * @param array $preset_meta One of the PRESETS_METADATA values. * * @return array Array of presets where the key and value are both the slug. */ - private static function get_settings_slugs( $settings, $meta ) { - $preset_per_origin = _wp_array_get( $settings, $meta['path'], array() ); + private static function get_settings_slugs( $settings, $preset_meta ) { + $preset_per_origin = _wp_array_get( $settings, $preset_meta['path'], array() ); $result = array(); foreach ( self::VALID_ORIGINS as $origin ) { @@ -763,16 +763,16 @@ private static function compute_preset_classes( $settings, $selector ) { } $stylesheet = ''; - foreach ( self::PRESETS_METADATA as $meta ) { - $slugs = self::get_settings_slugs( $settings, $meta ); - foreach ( $meta['classes'] as $class ) { + foreach ( self::PRESETS_METADATA as $preset_meta ) { + $slugs = self::get_settings_slugs( $settings, $preset_meta ); + foreach ( $preset_meta['classes'] as $class ) { foreach ( $slugs as $slug ) { $stylesheet .= self::to_ruleset( self::append_to_selector( $selector, '.has-' . $slug . '-' . $class['class_suffix'] ), array( array( 'name' => $class['property_name'], - 'value' => 'var(--wp--preset--' . $meta['css_var_infix'] . '--' . $slug . ') !important', + 'value' => 'var(--wp--preset--' . $preset_meta['css_var_infix'] . '--' . $slug . ') !important', ), ) ); @@ -801,11 +801,11 @@ private static function compute_preset_classes( $settings, $selector ) { */ private static function compute_preset_vars( $settings ) { $declarations = array(); - foreach ( self::PRESETS_METADATA as $meta ) { - $values_by_slug = self::get_settings_values_by_slug( $settings, $meta ); + foreach ( self::PRESETS_METADATA as $preset_meta ) { + $values_by_slug = self::get_settings_values_by_slug( $settings, $preset_meta ); foreach ( $values_by_slug as $slug => $value ) { $declarations[] = array( - 'name' => '--wp--preset--' . $meta['css_var_infix'] . '--' . $slug, + 'name' => '--wp--preset--' . $preset_meta['css_var_infix'] . '--' . $slug, 'value' => $value, ); } From c15c1cf866edecff22ead060ae7c334356cdf91e Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 14 Sep 2021 16:43:28 -0400 Subject: [PATCH 10/22] Render duotone styles when settings are disabled --- lib/class-wp-theme-json-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index fa114ee743ef3..cff5d24dcc4c8 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1249,8 +1249,8 @@ public function merge( $incoming ) { // In those cases, we want to replace the existing with the incoming value, if it exists. $to_replace = array(); $to_replace[] = array( 'spacing', 'units' ); - $to_replace[] = array( 'color', 'duotone' ); foreach ( self::VALID_ORIGINS as $origin ) { + $to_replace[] = array( 'color', 'duotone', $origin ); $to_replace[] = array( 'color', 'palette', $origin ); $to_replace[] = array( 'color', 'gradients', $origin ); $to_replace[] = array( 'typography', 'fontSizes', $origin ); From 3c2c1666793f85b5da30f1424b2e4467a6b6727c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 21 Sep 2021 17:46:54 +0200 Subject: [PATCH 11/22] Improve mechanism to declare a selector for the property --- lib/class-wp-theme-json-gutenberg.php | 39 ++++++++++++--------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index cff5d24dcc4c8..ea1ef7ad350c6 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -234,6 +234,7 @@ class WP_Theme_JSON_Gutenberg { 'border-width' => array( 'border', 'width' ), 'border-style' => array( 'border', 'style' ), 'color' => array( 'color', 'text' ), + 'filter' => array( 'color', 'duotone' ), 'font-family' => array( 'typography', 'fontFamily' ), 'font-size' => array( 'typography', 'fontSize' ), 'font-style' => array( 'typography', 'fontStyle' ), @@ -255,16 +256,6 @@ class WP_Theme_JSON_Gutenberg { 'text-transform' => array( 'typography', 'textTransform' ), ); - /** - * Metadata for style properties that need to use the duotone selector. - * - * Each element is a direct mapping from the CSS property name to the - * path to the value in theme.json & block attributes. - */ - const DUOTONE_PROPERTIES_METADATA = array( - 'filter' => array( 'color', 'duotone' ), - ); - const ELEMENTS = array( 'link' => 'a', 'h1' => 'h1', @@ -388,7 +379,7 @@ private static function sanitize( $input, $valid_block_names, $valid_element_nam * }, * 'core/cover': { * 'selector': '.wp-block-cover', - * 'duotone': '> .wp-block-cover__image-background, > .wp-block-cover__video-background' + * 'filter': '> .wp-block-cover__image-background, > .wp-block-cover__video-background' * } * } * @@ -417,7 +408,7 @@ private static function get_blocks_metadata() { isset( $block_type->supports['color']['__experimentalDuotone'] ) && is_string( $block_type->supports['color']['__experimentalDuotone'] ) ) { - self::$blocks_metadata[ $block_name ]['duotone'] = $block_type->supports['color']['__experimentalDuotone']; + self::$blocks_metadata[ $block_name ]['filter'] = $block_type->supports['color']['__experimentalDuotone']; } // Assign defaults, then overwrite those that the block sets by itself. @@ -573,17 +564,16 @@ private static function get_property_value( $styles, $path ) { * ``` * * @param array $styles Styles to process. - * @param array $properties Properties metadata. * * @return array Returns the modified $declarations. */ - private static function compute_style_properties( $styles, $properties = self::PROPERTIES_METADATA ) { + private static function compute_style_properties( $styles ) { $declarations = array(); if ( empty( $styles ) ) { return $declarations; } - foreach ( $properties as $css_property => $value_path ) { + foreach ( self::PROPERTIES_METADATA as $css_property => $value_path ) { $value = self::get_property_value( $styles, $value_path ); // Skip if empty and not "0" or value represents array of longhand values. @@ -946,6 +936,16 @@ private function get_block_classes( $style_nodes ) { $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); $selector = $metadata['selector']; $declarations = self::compute_style_properties( $node ); + + // Some declarations may have its own registered selector. + foreach ( $declarations as $name => $value ) { + if ( isset( $metadata[ $name ] ) ) { + $property_selector = self::scope_selector( $metadata['selector'], $metadata[ $name ] ); + $block_rules .= self::to_ruleset( $property_selector, array( array( $name => $value ) ) ); + unset( $declarations[ $name ] ); + } + } + // The rest use the general selector. $block_rules .= self::to_ruleset( $selector, $declarations ); if ( self::ROOT_BLOCK_SELECTOR === $selector ) { @@ -955,11 +955,6 @@ private function get_block_classes( $style_nodes ) { } } - if ( isset( $metadata['duotone'] ) ) { - $selector = self::scope_selector( $metadata['selector'], $metadata['duotone'] ); - $declarations = self::compute_style_properties( $node, self::DUOTONE_PROPERTIES_METADATA ); - $block_rules .= self::to_ruleset( $selector, $declarations ); - } } return $block_rules; @@ -1086,12 +1081,12 @@ public function get_template_parts() { * [ * 'path' => [ 'path', 'to', 'some', 'node' ], * 'selector' => 'CSS selector for some node', - * 'duotone' => 'CSS selector for duotone for some node' + * 'filter' => 'CSS selector for duotone for some node' * ], * [ * 'path' => ['path', 'to', 'other', 'node' ], * 'selector' => 'CSS selector for other node', - * 'duotone' => null + * 'filter' => null * ], * ] * From 220d86d64a9483a5b717e02ff9d225c3c4856510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 22 Sep 2021 10:47:53 +0200 Subject: [PATCH 12/22] Revert "Improve mechanism to declare a selector for the property" This reverts commit 3c2c1666793f85b5da30f1424b2e4467a6b6727c. --- lib/class-wp-theme-json-gutenberg.php | 39 +++++++++++++++------------ 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index ea1ef7ad350c6..cff5d24dcc4c8 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -234,7 +234,6 @@ class WP_Theme_JSON_Gutenberg { 'border-width' => array( 'border', 'width' ), 'border-style' => array( 'border', 'style' ), 'color' => array( 'color', 'text' ), - 'filter' => array( 'color', 'duotone' ), 'font-family' => array( 'typography', 'fontFamily' ), 'font-size' => array( 'typography', 'fontSize' ), 'font-style' => array( 'typography', 'fontStyle' ), @@ -256,6 +255,16 @@ class WP_Theme_JSON_Gutenberg { 'text-transform' => array( 'typography', 'textTransform' ), ); + /** + * Metadata for style properties that need to use the duotone selector. + * + * Each element is a direct mapping from the CSS property name to the + * path to the value in theme.json & block attributes. + */ + const DUOTONE_PROPERTIES_METADATA = array( + 'filter' => array( 'color', 'duotone' ), + ); + const ELEMENTS = array( 'link' => 'a', 'h1' => 'h1', @@ -379,7 +388,7 @@ private static function sanitize( $input, $valid_block_names, $valid_element_nam * }, * 'core/cover': { * 'selector': '.wp-block-cover', - * 'filter': '> .wp-block-cover__image-background, > .wp-block-cover__video-background' + * 'duotone': '> .wp-block-cover__image-background, > .wp-block-cover__video-background' * } * } * @@ -408,7 +417,7 @@ private static function get_blocks_metadata() { isset( $block_type->supports['color']['__experimentalDuotone'] ) && is_string( $block_type->supports['color']['__experimentalDuotone'] ) ) { - self::$blocks_metadata[ $block_name ]['filter'] = $block_type->supports['color']['__experimentalDuotone']; + self::$blocks_metadata[ $block_name ]['duotone'] = $block_type->supports['color']['__experimentalDuotone']; } // Assign defaults, then overwrite those that the block sets by itself. @@ -564,16 +573,17 @@ private static function get_property_value( $styles, $path ) { * ``` * * @param array $styles Styles to process. + * @param array $properties Properties metadata. * * @return array Returns the modified $declarations. */ - private static function compute_style_properties( $styles ) { + private static function compute_style_properties( $styles, $properties = self::PROPERTIES_METADATA ) { $declarations = array(); if ( empty( $styles ) ) { return $declarations; } - foreach ( self::PROPERTIES_METADATA as $css_property => $value_path ) { + foreach ( $properties as $css_property => $value_path ) { $value = self::get_property_value( $styles, $value_path ); // Skip if empty and not "0" or value represents array of longhand values. @@ -936,16 +946,6 @@ private function get_block_classes( $style_nodes ) { $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); $selector = $metadata['selector']; $declarations = self::compute_style_properties( $node ); - - // Some declarations may have its own registered selector. - foreach ( $declarations as $name => $value ) { - if ( isset( $metadata[ $name ] ) ) { - $property_selector = self::scope_selector( $metadata['selector'], $metadata[ $name ] ); - $block_rules .= self::to_ruleset( $property_selector, array( array( $name => $value ) ) ); - unset( $declarations[ $name ] ); - } - } - // The rest use the general selector. $block_rules .= self::to_ruleset( $selector, $declarations ); if ( self::ROOT_BLOCK_SELECTOR === $selector ) { @@ -955,6 +955,11 @@ private function get_block_classes( $style_nodes ) { } } + if ( isset( $metadata['duotone'] ) ) { + $selector = self::scope_selector( $metadata['selector'], $metadata['duotone'] ); + $declarations = self::compute_style_properties( $node, self::DUOTONE_PROPERTIES_METADATA ); + $block_rules .= self::to_ruleset( $selector, $declarations ); + } } return $block_rules; @@ -1081,12 +1086,12 @@ public function get_template_parts() { * [ * 'path' => [ 'path', 'to', 'some', 'node' ], * 'selector' => 'CSS selector for some node', - * 'filter' => 'CSS selector for duotone for some node' + * 'duotone' => 'CSS selector for duotone for some node' * ], * [ * 'path' => ['path', 'to', 'other', 'node' ], * 'selector' => 'CSS selector for other node', - * 'filter' => null + * 'duotone' => null * ], * ] * From 537d60895494606d60319c001ca84afbdc64e33b Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 23 Sep 2021 17:22:29 -0400 Subject: [PATCH 13/22] duotone-filter to just duotone css var infix --- lib/class-wp-theme-json-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 435323df1d017..6cb3ee002316f 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -193,7 +193,7 @@ class WP_Theme_JSON_Gutenberg { 'path' => array( 'color', 'duotone' ), 'value_func' => 'gutenberg_get_duotone_filter_property', 'value_args' => array( 'slug', 'colors' ), - 'css_var_infix' => 'duotone-filter', + 'css_var_infix' => 'duotone', 'classes' => array(), ), array( From 775717e6c91976461efe98249661cf97e0d68327 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Mon, 27 Sep 2021 18:30:12 -0400 Subject: [PATCH 14/22] Revert duotone SVG to be inside the footer --- lib/block-supports/duotone.php | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 9f70ba501a8b3..916ac6a678a6c 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -278,7 +278,15 @@ function gutenberg_get_duotone_filter_property( $duotone_id, $duotone_colors ) { ?> - + <', $svg ); - $svg = trim( $svg ); + if ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) { + // Clean up the whitespace. + $svg = preg_replace( "/(\r|\n|\t)+/", ' ', $svg ); + $svg = preg_replace( '/> <', $svg ); + $svg = trim( $svg ); + } - $data_uri = 'data:image/svg+xml,' . $svg . '#' . $filter_id; + add_action( + // Safari doesn't render SVG filters defined in data URIs, + // and SVG filters won't render in the head of a document, + // so the next best place to put the SVG is in the footer. + is_admin() ? 'admin_footer' : 'wp_footer', + function () use ( $svg ) { + echo $svg; + } + ); - // All the variables are already escaped above, so we're not calling esc_url() here. - return "url('" . $data_uri . "')"; + return "url('#" . $filter_id . "')"; } /** From 2a6a7e827e74ee5eea77f3b07c4fa7a9a5a7d871 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Mon, 27 Sep 2021 18:48:25 -0400 Subject: [PATCH 15/22] Simplify value_func args --- lib/block-supports/duotone.php | 17 +++++++++++------ lib/class-wp-theme-json-gutenberg.php | 14 ++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 916ac6a678a6c..1baf14a613cd0 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -253,14 +253,14 @@ function gutenberg_register_duotone_support( $block_type ) { /** * Get the duotone filter property. * - * @param string $duotone_id Unique id for the duotone filter. - * @param array $duotone_colors Array of CSS color strings that can be parsed by tinycolor. + * @param array $preset Duotone preset value as seen. * * @return string Duotone CSS filter property. */ -function gutenberg_get_duotone_filter_property( $duotone_id, $duotone_colors ) { - $filter_id = 'wp-duotone-filter-' . $duotone_id; - +function gutenberg_render_duotone_filter( $preset ) { + $duotone_id = $preset['slug']; + $duotone_colors = $preset['colors']; + $filter_id = 'wp-duotone-filter-' . $duotone_id; $duotone_values = array( 'r' => array(), 'g' => array(), @@ -357,7 +357,12 @@ function gutenberg_render_duotone_support( $block_content, $block ) { $duotone_id = uniqid(); $duotone_colors = $block['attrs']['style']['color']['duotone']; - $filter_property = gutenberg_get_duotone_filter_property( $duotone_id, $duotone_colors ); + $filter_property = gutenberg_render_duotone_filter( + array( + 'slug' => $duotone_id, + 'colors' => $duotone_colors, + ) + ); $filter_id = 'wp-duotone-filter-' . $duotone_id; diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 6cb3ee002316f..7a5d8fde60c19 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -143,9 +143,8 @@ class WP_Theme_JSON_Gutenberg { * * - value_key => the key that represents the value * - * - value_func => a function to generate the value - * - * - value_args => array of keys that will be mapped to values passed to value_func + * - value_func => optionally, instead of value_key, a function to generate + * the value that takes a preset as an argument * * - css_var_infix => infix to use in generating the CSS Custom Property. Example: * --wp--preset----: @@ -191,8 +190,7 @@ class WP_Theme_JSON_Gutenberg { ), array( 'path' => array( 'color', 'duotone' ), - 'value_func' => 'gutenberg_get_duotone_filter_property', - 'value_args' => array( 'slug', 'colors' ), + 'value_func' => 'gutenberg_render_duotone_filter', 'css_var_infix' => 'duotone', 'classes' => array(), ), @@ -704,11 +702,7 @@ private static function get_settings_values_by_slug( $settings, $preset_meta, $o $value = $preset[ $value_key ]; } elseif ( is_callable( $preset_meta['value_func'] ) ) { $value_func = $preset_meta['value_func']; - $value_args = array(); - foreach ( $preset_meta['value_args'] as $preset_meta_arg ) { - $value_args[] = $preset[ $preset_meta_arg ]; - } - $value = call_user_func_array( $value_func, $value_args ); + $value = call_user_func( $value_func, $preset ); } else { // If we don't have a value, then don't add it to the result. continue; From c147b04bb11715c9ebacf593be25387f2138293e Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Mon, 27 Sep 2021 19:06:10 -0400 Subject: [PATCH 16/22] Additionally strip multiple spaces in SVG --- lib/block-supports/duotone.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 1baf14a613cd0..0302de72d7024 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -313,7 +313,7 @@ function gutenberg_render_duotone_filter( $preset ) { if ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) { // Clean up the whitespace. - $svg = preg_replace( "/(\r|\n|\t)+/", ' ', $svg ); + $svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg ); $svg = preg_replace( '/> <', $svg ); $svg = trim( $svg ); } From e31fa8e09c48c06c88d43098e60c2423a7c305e9 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Mon, 27 Sep 2021 19:06:45 -0400 Subject: [PATCH 17/22] Improve code quality and inline docs --- lib/block-supports/duotone.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 0302de72d7024..94aefbeca16bc 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -253,11 +253,11 @@ function gutenberg_register_duotone_support( $block_type ) { /** * Get the duotone filter property. * - * @param array $preset Duotone preset value as seen. + * @param array $preset Duotone preset value as seen in theme.json. * * @return string Duotone CSS filter property. */ -function gutenberg_render_duotone_filter( $preset ) { +function gutenberg_render_duotone_filter_preset( $preset ) { $duotone_id = $preset['slug']; $duotone_colors = $preset['colors']; $filter_id = 'wp-duotone-filter-' . $duotone_id; @@ -355,16 +355,12 @@ function gutenberg_render_duotone_support( $block_content, $block ) { return $block_content; } - $duotone_id = uniqid(); - $duotone_colors = $block['attrs']['style']['color']['duotone']; - $filter_property = gutenberg_render_duotone_filter( - array( - 'slug' => $duotone_id, - 'colors' => $duotone_colors, - ) + $filter_preset = array( + 'slug' => uniqid(), + 'colors' => $block['attrs']['style']['color']['duotone'], ); - - $filter_id = 'wp-duotone-filter-' . $duotone_id; + $filter_property = gutenberg_render_duotone_filter_preset( $filter_preset ); + $filter_id = 'wp-duotone-filter-' . $filter_preset['slug']; $scope = '.' . $filter_id; $selectors = explode( ',', $duotone_support ); From 8aaa12f74df25c9dd6d842c0741e9978dc2ce8ac Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Mon, 27 Sep 2021 19:11:07 -0400 Subject: [PATCH 18/22] Fix value_func after rename --- lib/class-wp-theme-json-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 7a5d8fde60c19..daa29652373c4 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -190,7 +190,7 @@ class WP_Theme_JSON_Gutenberg { ), array( 'path' => array( 'color', 'duotone' ), - 'value_func' => 'gutenberg_render_duotone_filter', + 'value_func' => 'gutenberg_render_duotone_filter_preset', 'css_var_infix' => 'duotone', 'classes' => array(), ), From c793a69adb295a18ee50937a9e916b8c31da6c7a Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Mon, 27 Sep 2021 19:27:43 -0400 Subject: [PATCH 19/22] Improve inline docs --- lib/block-supports/duotone.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 94aefbeca16bc..9c55f28984179 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -251,7 +251,8 @@ function gutenberg_register_duotone_support( $block_type ) { } /** - * Get the duotone filter property. + * Renders the duotone filter SVG and returns the CSS filter property to + * reference the rendered SVG. * * @param array $preset Duotone preset value as seen in theme.json. * From df50ef00cadd2bd16525a0161ece3448acd52148 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 28 Sep 2021 09:58:58 -0400 Subject: [PATCH 20/22] Update duotone-filter -> duotone for generated ids --- lib/block-supports/duotone.php | 4 ++-- packages/block-editor/src/hooks/duotone.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 9c55f28984179..5d507ea051ee0 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -261,7 +261,7 @@ function gutenberg_register_duotone_support( $block_type ) { function gutenberg_render_duotone_filter_preset( $preset ) { $duotone_id = $preset['slug']; $duotone_colors = $preset['colors']; - $filter_id = 'wp-duotone-filter-' . $duotone_id; + $filter_id = 'wp-duotone-' . $duotone_id; $duotone_values = array( 'r' => array(), 'g' => array(), @@ -361,7 +361,7 @@ function gutenberg_render_duotone_support( $block_content, $block ) { 'colors' => $block['attrs']['style']['color']['duotone'], ); $filter_property = gutenberg_render_duotone_filter_preset( $filter_preset ); - $filter_id = 'wp-duotone-filter-' . $filter_preset['slug']; + $filter_id = 'wp-duotone-' . $filter_preset['slug']; $scope = '.' . $filter_id; $selectors = explode( ',', $duotone_support ); diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 011ff8c3155b8..031914a8a8865 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -265,7 +265,7 @@ const withDuotoneStyles = createHigherOrderComponent( return ; } - const id = `wp-duotone-filter-${ useInstanceId( BlockListBlock ) }`; + 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 From 53b154b47bb07c6d1854ef075f0b028992b9f1ae Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 28 Sep 2021 10:49:23 -0400 Subject: [PATCH 21/22] Rename preset_meta -> preset_metadata --- lib/class-wp-theme-json-gutenberg.php | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index daa29652373c4..583a2660613de 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -298,8 +298,8 @@ public function __construct( $theme_json = array(), $origin = 'theme' ) { // Internally, presets are keyed by origin. $nodes = self::get_setting_nodes( $this->theme_json ); foreach ( $nodes as $node ) { - foreach ( self::PRESETS_METADATA as $preset_meta ) { - $path = array_merge( $node['path'], $preset_meta['path'] ); + foreach ( self::PRESETS_METADATA as $preset_metadata ) { + $path = array_merge( $node['path'], $preset_metadata['path'] ); $preset = _wp_array_get( $this->theme_json, $path, null ); if ( null !== $preset ) { gutenberg_experimental_set( $this->theme_json, $path, array( $origin => $preset ) ); @@ -680,13 +680,13 @@ private static function scope_selector( $scope, $selector ) { * * * @param array $settings Settings to process. - * @param array $preset_meta One of the PRESETS_METADATA values. + * @param array $preset_metadata One of the PRESETS_METADATA values. * @param array $origins List of origins to process. * * @return array Array of presets where each key is a slug and each value is the preset value. */ - private static function get_settings_values_by_slug( $settings, $preset_meta, $origins ) { - $preset_per_origin = _wp_array_get( $settings, $preset_meta['path'], array() ); + private static function get_settings_values_by_slug( $settings, $preset_metadata, $origins ) { + $preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() ); $result = array(); foreach ( $origins as $origin ) { @@ -697,11 +697,11 @@ private static function get_settings_values_by_slug( $settings, $preset_meta, $o $slug = gutenberg_experimental_to_kebab_case( $preset['slug'] ); $value = ''; - if ( isset( $preset_meta['value_key'] ) ) { - $value_key = $preset_meta['value_key']; + if ( isset( $preset_metadata['value_key'] ) ) { + $value_key = $preset_metadata['value_key']; $value = $preset[ $value_key ]; - } elseif ( is_callable( $preset_meta['value_func'] ) ) { - $value_func = $preset_meta['value_func']; + } elseif ( is_callable( $preset_metadata['value_func'] ) ) { + $value_func = $preset_metadata['value_func']; $value = call_user_func( $value_func, $preset ); } else { // If we don't have a value, then don't add it to the result. @@ -718,12 +718,12 @@ private static function get_settings_values_by_slug( $settings, $preset_meta, $o * Similar to get_settings_values_by_slug, but doesn't compute the value. * * @param array $settings Settings to process. - * @param array $preset_meta One of the PRESETS_METADATA values. + * @param array $preset_metadata One of the PRESETS_METADATA values. * * @return array Array of presets where the key and value are both the slug. */ - private static function get_settings_slugs( $settings, $preset_meta ) { - $preset_per_origin = _wp_array_get( $settings, $preset_meta['path'], array() ); + private static function get_settings_slugs( $settings, $preset_metadata ) { + $preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() ); $result = array(); foreach ( self::VALID_ORIGINS as $origin ) { @@ -758,16 +758,16 @@ private static function compute_preset_classes( $settings, $selector, $origins ) } $stylesheet = ''; - foreach ( self::PRESETS_METADATA as $preset_meta ) { - $slugs = self::get_settings_slugs( $settings, $preset_meta, $origins ); - foreach ( $preset_meta['classes'] as $class ) { + foreach ( self::PRESETS_METADATA as $preset_metadata ) { + $slugs = self::get_settings_slugs( $settings, $preset_metadata, $origins ); + foreach ( $preset_metadata['classes'] as $class ) { foreach ( $slugs as $slug ) { $stylesheet .= self::to_ruleset( self::append_to_selector( $selector, '.has-' . $slug . '-' . $class['class_suffix'] ), array( array( 'name' => $class['property_name'], - 'value' => 'var(--wp--preset--' . $preset_meta['css_var_infix'] . '--' . $slug . ') !important', + 'value' => 'var(--wp--preset--' . $preset_metadata['css_var_infix'] . '--' . $slug . ') !important', ), ) ); @@ -797,11 +797,11 @@ private static function compute_preset_classes( $settings, $selector, $origins ) */ private static function compute_preset_vars( $settings, $origins ) { $declarations = array(); - foreach ( self::PRESETS_METADATA as $preset_meta ) { - $values_by_slug = self::get_settings_values_by_slug( $settings, $preset_meta, $origins ); + foreach ( self::PRESETS_METADATA as $preset_metadata ) { + $values_by_slug = self::get_settings_values_by_slug( $settings, $preset_metadata, $origins ); foreach ( $values_by_slug as $slug => $value ) { $declarations[] = array( - 'name' => '--wp--preset--' . $preset_meta['css_var_infix'] . '--' . $slug, + 'name' => '--wp--preset--' . $preset_metadata['css_var_infix'] . '--' . $slug, 'value' => $value, ); } From 008c5d558be5873353a387b2caa49f3d301a03e9 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 28 Sep 2021 12:20:12 -0400 Subject: [PATCH 22/22] Move duotone styles from color -> filter --- lib/class-wp-theme-json-gutenberg.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 583a2660613de..3923516efffe9 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -57,10 +57,12 @@ class WP_Theme_JSON_Gutenberg { ), 'color' => array( 'background' => null, - 'duotone' => null, 'gradient' => null, 'text' => null, ), + 'filter' => array( + 'duotone' => null, + ), 'spacing' => array( 'margin' => null, 'padding' => null, @@ -259,7 +261,7 @@ class WP_Theme_JSON_Gutenberg { * path to the value in theme.json & block attributes. */ const DUOTONE_PROPERTIES_METADATA = array( - 'filter' => array( 'color', 'duotone' ), + 'filter' => array( 'filter', 'duotone' ), ); const ELEMENTS = array(