From 90b3140598be567686d96ca472d8b7bc25c17e11 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Tue, 10 May 2022 16:03:49 +1000 Subject: [PATCH 1/7] initial commit Migrate get_block_classes to 6.1 Add prettify option Testing rule generation for get_styles_for_block Let the style engine take care of margin: 0 on the body --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 15 +- .../style-engine/class-wp-style-engine.php | 5 +- .../phpunit/class-wp-style-engine-test.php | 26 ++ test/emptytheme/theme.json | 352 +++++++++++++++++- 4 files changed, 392 insertions(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 189e3694dd87f6..af414e507e804c 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -522,7 +522,20 @@ function( $pseudo_selector ) use ( $selector ) { } // 2. Generate and append the rules that use the general selector. - $block_rules .= static::to_ruleset( $selector, $declarations ); + //$block_rules .= static::to_ruleset( $selector, $declarations ); + // @TODO check duotone + + $styles = gutenberg_style_engine_generate( + $node, + array( + 'selector' => $selector, + 'prettify' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, + ) + ); + + if ( isset( $styles['css'] ) ) { + $block_rules .= $styles['css']; + } // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 15c803832fcbff..810363b1a7b57a 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -423,8 +423,9 @@ public function generate( $block_styles, $options ) { } // Build CSS rules output. - $css_selector = isset( $options['selector'] ) ? $options['selector'] : null; - $style_rules = new WP_Style_Engine_CSS_Declarations( $css_declarations ); + $css_selector = isset( $options['selector'] ) ? $options['selector'] : null; + $should_prettify = isset( $options['prettify'] ) ? $options['prettify'] : null; + $style_rules = new WP_Style_Engine_CSS_Declarations( $css_declarations ); // The return object. $styles_output = array(); diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index 3038364a5fc386..fcd518f049f514 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -157,6 +157,32 @@ public function data_generate_styles_fixtures() { ), ), + 'style_block_with_selector_and_pretty_css' => array( + 'block_styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '42px', + 'left' => '2%', + 'bottom' => '44px', + 'right' => '5rem', + ), + ), + ), + 'options' => array( + 'selector' => '.wp-selector > p', + 'prettify' => true, + ), + 'expected_output' => array( + 'css' => '.wp-selector > p { + padding-top: 42px; + padding-left: 2%; + padding-bottom: 44px; + padding-right: 5rem; +} +', + ), + ), + 'elements_with_css_var_value' => array( 'block_styles' => array( 'color' => array( diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json index ed2d7b8d0946aa..873cce498ff996 100644 --- a/test/emptytheme/theme.json +++ b/test/emptytheme/theme.json @@ -1,11 +1,357 @@ { "version": 2, + "customTemplates": [ + { + "name": "blank", + "title": "Blank", + "postTypes": [ "page", "post" ] + }, + { + "name": "page-large-header", + "title": "Page (Large Header)", + "postTypes": [ "page" ] + }, + { + "name": "single-no-separators", + "title": "Single Post (No Separators)", + "postTypes": [ "post" ] + }, + { + "name": "page-no-separators", + "title": "Page (No Separators)", + "postTypes": [ "page" ] + } + ], "settings": { "appearanceTools": true, + "color": { + "duotone": [ + { + "colors": [ "#000000", "#ffffff" ], + "slug": "foreground-and-background", + "name": "Foreground and background" + }, + { + "colors": [ "#000000", "#ffe2c7" ], + "slug": "foreground-and-secondary", + "name": "Foreground and secondary" + }, + { + "colors": [ "#000000", "#f6f6f6" ], + "slug": "foreground-and-tertiary", + "name": "Foreground and tertiary" + }, + { + "colors": [ "#1a4548", "#ffffff" ], + "slug": "primary-and-background", + "name": "Primary and background" + }, + { + "colors": [ "#1a4548", "#ffe2c7" ], + "slug": "primary-and-secondary", + "name": "Primary and secondary" + }, + { + "colors": [ "#1a4548", "#f6f6f6" ], + "slug": "primary-and-tertiary", + "name": "Primary and tertiary" + } + ], + "gradients": [ + { + "slug": "vertical-secondary-to-tertiary", + "gradient": "linear-gradient(to bottom,var(--wp--preset--color--secondary) 0%,var(--wp--preset--color--tertiary) 100%)", + "name": "Vertical secondary to tertiary" + }, + { + "slug": "vertical-secondary-to-background", + "gradient": "linear-gradient(to bottom,var(--wp--preset--color--secondary) 0%,var(--wp--preset--color--background) 100%)", + "name": "Vertical secondary to background" + }, + { + "slug": "vertical-tertiary-to-background", + "gradient": "linear-gradient(to bottom,var(--wp--preset--color--tertiary) 0%,var(--wp--preset--color--background) 100%)", + "name": "Vertical tertiary to background" + }, + { + "slug": "diagonal-primary-to-foreground", + "gradient": "linear-gradient(to bottom right,var(--wp--preset--color--primary) 0%,var(--wp--preset--color--foreground) 100%)", + "name": "Diagonal primary to foreground" + }, + { + "slug": "diagonal-secondary-to-background", + "gradient": "linear-gradient(to bottom right,var(--wp--preset--color--secondary) 50%,var(--wp--preset--color--background) 50%)", + "name": "Diagonal secondary to background" + }, + { + "slug": "diagonal-background-to-secondary", + "gradient": "linear-gradient(to bottom right,var(--wp--preset--color--background) 50%,var(--wp--preset--color--secondary) 50%)", + "name": "Diagonal background to secondary" + }, + { + "slug": "diagonal-tertiary-to-background", + "gradient": "linear-gradient(to bottom right,var(--wp--preset--color--tertiary) 50%,var(--wp--preset--color--background) 50%)", + "name": "Diagonal tertiary to background" + }, + { + "slug": "diagonal-background-to-tertiary", + "gradient": "linear-gradient(to bottom right,var(--wp--preset--color--background) 50%,var(--wp--preset--color--tertiary) 50%)", + "name": "Diagonal background to tertiary" + } + ], + "palette": [ + { + "slug": "foreground", + "color": "#000000", + "name": "Foreground" + }, + { + "slug": "background", + "color": "#ffffff", + "name": "Background" + }, + { + "slug": "primary", + "color": "#1a4548", + "name": "Primary" + }, + { + "slug": "secondary", + "color": "#ffe2c7", + "name": "Secondary" + }, + { + "slug": "tertiary", + "color": "#F6F6F6", + "name": "Tertiary" + } + ], + "customDuotone": true + }, + "custom": { + "spacing": { + "small": "max(1.25rem, 5vw)", + "medium": "clamp(2rem, 8vw, calc(4 * var(--wp--style--block-gap)))", + "large": "clamp(4rem, 10vw, 8rem)", + "outer": "var(--wp--custom--spacing--small, 1.25rem)" + }, + "typography": { + "font-size": { + "huge": "clamp(2.25rem, 4vw, 2.75rem)", + "gigantic": "clamp(2.75rem, 6vw, 3.25rem)", + "colossal": "clamp(3.25rem, 8vw, 6.25rem)" + }, + "line-height": { + "tiny": 1.15, + "small": 1.2, + "medium": 1.4, + "normal": 1.6 + } + } + }, + "spacing": { + "units": [ "%", "px", "em", "rem", "vh", "vw" ] + }, + "typography": { + "dropCap": false, + "fontFamilies": [ + { + "fontFamily": "-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif", + "name": "System Font", + "slug": "system-font" + }, + { + "fontFamily": "\"Source Serif Pro\", serif", + "name": "Source Serif Pro", + "slug": "source-serif-pro" + } + ], + "fontSizes": [ + { + "size": "1rem", + "slug": "small" + }, + { + "size": "1.125rem", + "slug": "medium" + }, + { + "size": "1.75rem", + "slug": "large" + }, + { + "size": "clamp(1.75rem, 3vw, 2.25rem)", + "slug": "x-large" + } + ] + }, "layout": { - "contentSize": "840px", - "wideSize": "1100px" + "contentSize": "650px", + "wideSize": "1000px" + } + }, + "styles": { + "blocks": { + "core/button": { + "border": { + "radius": "0" + }, + "color": { + "background": "var(--wp--preset--color--primary)", + "text": "var(--wp--preset--color--background)" + }, + "typography": { + "fontSize": "var(--wp--preset--font-size--medium)" + } + }, + "core/post-title": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--source-serif-pro)", + "fontWeight": "300", + "lineHeight": "var(--wp--custom--typography--line-height--tiny)", + "fontSize": "var(--wp--custom--typography--font-size--gigantic)" + } + }, + "core/post-comments": { + "spacing": { + "padding": { + "top": "var(--wp--custom--spacing--small)" + } + } + }, + "core/pullquote": { + "border": { + "width": "1px 0" + } + }, + "core/query-title": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--source-serif-pro)", + "fontWeight": "300", + "lineHeight": "var(--wp--custom--typography--line-height--small)", + "fontSize": "var(--wp--custom--typography--font-size--gigantic)" + } + }, + "core/quote": { + "border": { + "width": "1px" + } + }, + "core/site-title": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--system-font)", + "lineHeight": "var(--wp--custom--typography--line-height--normal)", + "fontSize": "var(--wp--preset--font-size--medium)", + "fontWeight": "normal" + } + }, + "core/image": { + "color": { + "duotone": "#ffffff" + } + }, + "core/group": { + "spacing": { + "margin": { + "top": "40px" + } + }, + "color": { + "background": "var(--wp--preset--color--primary)", + "text": "var(--wp--preset--color--background)" + } + } + }, + "color": { + "background": "var(--wp--preset--color--background)", + "text": "var(--wp--preset--color--foreground)" + }, + "elements": { + "h1": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--source-serif-pro)", + "fontWeight": "300", + "lineHeight": "var(--wp--custom--typography--line-height--tiny)", + "fontSize": "var(--wp--custom--typography--font-size--colossal)" + } + }, + "h2": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--source-serif-pro)", + "fontWeight": "300", + "lineHeight": "var(--wp--custom--typography--line-height--small)", + "fontSize": "var(--wp--custom--typography--font-size--gigantic)" + } + }, + "h3": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--source-serif-pro)", + "fontWeight": "300", + "lineHeight": "var(--wp--custom--typography--line-height--tiny)", + "fontSize": "var(--wp--custom--typography--font-size--huge)" + } + }, + "h4": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--source-serif-pro)", + "fontWeight": "300", + "lineHeight": "var(--wp--custom--typography--line-height--tiny)", + "fontSize": "var(--wp--preset--font-size--x-large)" + } + }, + "h5": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--system-font)", + "fontWeight": "700", + "textTransform": "uppercase", + "lineHeight": "var(--wp--custom--typography--line-height--normal)", + "fontSize": "var(--wp--preset--font-size--medium)" + } + }, + "h6": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--system-font)", + "fontWeight": "400", + "textTransform": "uppercase", + "lineHeight": "var(--wp--custom--typography--line-height--normal)", + "fontSize": "var(--wp--preset--font-size--medium)" + } + }, + "link": { + "color": { + "text": "var(--wp--preset--color--foreground)" + } + } + }, + "spacing": { + "blockGap": "1.5rem" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--system-font)", + "lineHeight": "var(--wp--custom--typography--line-height--normal)", + "fontSize": "var(--wp--preset--font-size--medium)" } }, - "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] + "templateParts": [ + { + "name": "header", + "title": "Header", + "area": "header" + }, + { + "name": "header-large-dark", + "title": "Header (Dark, large)", + "area": "header" + }, + { + "name": "header-small-dark", + "title": "Header (Dark, small)", + "area": "header" + }, + { + "name": "footer", + "title": "Footer", + "area": "footer" + } + ] } From 5d128ce6008e838a5a604db1425d4cd6c5d9aaea Mon Sep 17 00:00:00 2001 From: ramonjd Date: Wed, 22 Jun 2022 13:53:09 +1000 Subject: [PATCH 2/7] Supporting blockGap and background color vars --- lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php | 2 ++ packages/style-engine/class-wp-style-engine.php | 3 +++ 2 files changed, 5 insertions(+) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index af414e507e804c..0ec03e77d4c55f 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -529,11 +529,13 @@ function( $pseudo_selector ) use ( $selector ) { $node, array( 'selector' => $selector, + 'css_vars' => true, 'prettify' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, ) ); if ( isset( $styles['css'] ) ) { + $block_rules .= $styles['css']; } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 810363b1a7b57a..aaa7293ab477d8 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -63,6 +63,9 @@ class WP_Style_Engine { 'default' => 'background-color', ), 'path' => array( 'color', 'background' ), + 'css_vars' => array( + 'color' => '--wp--preset--color--$slug', + ), 'classnames' => array( 'has-background' => true, 'has-$slug-background-color' => 'color', From 3a11a2c119abd0459684a1fd2487f0cf125119b4 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 1 Jul 2022 12:54:11 +1000 Subject: [PATCH 3/7] Adding tests for block gap. This exposes the need for a workaround for css custom properties. --- .../style-engine/class-wp-style-engine.php | 79 +++++++++++++++---- .../phpunit/class-wp-style-engine-test.php | 20 ++++- 2 files changed, 79 insertions(+), 20 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index aaa7293ab477d8..fcbabe36ab0d4f 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -30,6 +30,14 @@ class WP_Style_Engine { */ private static $instance = null; + /** + * A white list of CSS custom properties. + * Used to bypass safecss_filter_attr(). + */ + const VALID_CUSTOM_PROPERTIES = array( + '--wp--style--block-gap' => array( 'spacing', 'blockGap' ), + ); + /** * Style definitions that contain the instructions to * parse/output valid Gutenberg styles from a block's attributes. @@ -145,7 +153,7 @@ class WP_Style_Engine { ), ), 'spacing' => array( - 'padding' => array( + 'padding' => array( 'property_keys' => array( 'default' => 'padding', 'individual' => 'padding-%s', @@ -155,7 +163,7 @@ class WP_Style_Engine { 'spacing' => '--wp--preset--spacing--$slug', ), ), - 'margin' => array( + 'margin' => array( 'property_keys' => array( 'default' => 'margin', 'individual' => 'margin-%s', @@ -165,6 +173,15 @@ class WP_Style_Engine { 'spacing' => '--wp--preset--spacing--$slug', ), ), + 'blockGap' => array( + 'property_keys' => array( + // @TODO 'grid-gap' has been deprecated in favor of 'gap'. + // See: https://developer.mozilla.org/en-US/docs/Web/CSS/gap. + // Update the white list in safecss_filter_attr (kses.php). + 'default' => 'grid-gap', + ), + 'path' => array( 'spacing', 'blockGap' ), + ), ), 'typography' => array( 'fontSize' => array( @@ -296,6 +313,25 @@ protected static function is_valid_style_value( $style_value ) { return true; } + /** + * Merges single style definitions with incoming custom style definitions. + * + * @param array $style_definition The internal style definition metadata. + * @param array $custom_definition The custom style definition metadata to be merged. + * + * @return array The merged definition metadata. + */ + public static function merge_custom_style_definitions_metadata( $style_definition, $custom_definition = array() ) { + $valid_keys = array( 'property_keys', 'classnames' ); + foreach ( $valid_keys as $key ) { + if ( isset( $custom_definition[ $key ] ) && is_array( $custom_definition[ $key ] ) ) { + $style_definition[ $key ] = array_merge( $style_definition[ $key ], $custom_definition[ $key ] ); + } + } + + return $style_definition; + } + /** * Returns classnames, and generates classname(s) from a CSS preset property pattern, e.g., 'var:preset|color|heavenly-blue'. * @@ -337,20 +373,21 @@ protected static function get_classnames( $style_value, $style_definition ) { * * @param array $style_value A single raw style value from the generate() $block_styles array. * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. - * @param boolean $should_skip_css_vars Whether to skip compiling CSS var values. + * @param array $options Options passed to generate(). * * @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). */ - protected static function get_css_declarations( $style_value, $style_definition, $should_skip_css_vars = false ) { + protected static function get_css_declarations( $style_value, $style_definition, $options ) { if ( isset( $style_definition['value_func'] ) && is_callable( $style_definition['value_func'] ) ) { - return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $should_skip_css_vars ); + return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $options ); } - $css_declarations = array(); - $style_property_keys = $style_definition['property_keys']; + $css_declarations = array(); + $style_property_keys = $style_definition['property_keys']; + $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; // Build CSS var values from var:? values, e.g, `var(--wp--css--rule-slug )` // Check if the value is a CSS preset and there's a corresponding css_var pattern in the style definition. @@ -369,6 +406,9 @@ protected static function get_css_declarations( $style_value, $style_definition, // for styles such as margins and padding. if ( is_array( $style_value ) ) { foreach ( $style_value as $key => $value ) { + if ( ! isset( $style_property_keys['individual'] ) ) { + return $css_declarations; + } if ( is_string( $value ) && strpos( $value, 'var:' ) !== false && ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) { $value = static::get_css_var_value( $value, $style_definition['css_vars'] ); } @@ -377,7 +417,7 @@ protected static function get_css_declarations( $style_value, $style_definition, $css_declarations[ $individual_property ] = $value; } } - } else { + } elseif ( isset( $style_property_keys['default'] ) ) { $css_declarations[ $style_property_keys['default'] ] = $style_value; } @@ -404,16 +444,20 @@ public function generate( $block_styles, $options ) { return null; } - $css_declarations = array(); - $classnames = array(); - $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; + $css_declarations = array(); + $classnames = array(); + $custom_metadata = isset( $options['custom_metadata'] ) && is_array( $options['custom_metadata'] ) ? $options['custom_metadata'] : null; // Collect CSS and classnames. foreach ( static::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group_key => $definition_group_style ) { if ( empty( $block_styles[ $definition_group_key ] ) ) { continue; } - foreach ( $definition_group_style as $style_definition ) { + foreach ( $definition_group_style as $style_key => $style_definition ) { + // Merge incoming custom metadata. + if ( isset( $custom_metadata[ "$definition_group_key.$style_key" ] ) ) { + $style_definition = static::merge_custom_style_definitions_metadata( $style_definition, $custom_metadata[ "$definition_group_key.$style_key" ] ); + } $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); if ( ! static::is_valid_style_value( $style_value ) ) { @@ -421,7 +465,7 @@ public function generate( $block_styles, $options ) { } $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); - $css_declarations = array_merge( $css_declarations, static::get_css_declarations( $style_value, $style_definition, $should_skip_css_vars ) ); + $css_declarations = array_merge( $css_declarations, static::get_css_declarations( $style_value, $style_definition, $options ) ); } } @@ -458,19 +502,20 @@ public function generate( $block_styles, $options ) { * "border-{top|right|bottom|left}-{color|width|style}: {value};" or, * "border-image-{outset|source|width|repeat|slice}: {value};" * - * @param array $style_value A single raw Gutenberg style attributes value for a CSS property. - * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. - * @param boolean $should_skip_css_vars Whether to skip compiling CSS var values. + * @param array $style_value A single raw Gutenberg style attributes value for a CSS property. + * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. + * @param array $options Options passed to generate(). * * @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). */ - protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $should_skip_css_vars ) { + protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $options ) { $css_declarations = array(); if ( ! is_array( $style_value ) || empty( $style_value ) || empty( $individual_property_definition['path'] ) ) { return $css_declarations; } + $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; // The first item in $individual_property_definition['path'] array tells us the style property, e.g., "border". // We use this to get a corresponding CSS style definition such as "color" or "width" from the same group. // The second item in $individual_property_definition['path'] array refers to the individual property marker, e.g., "top". diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index fcd518f049f514..a22b012fd33fb3 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -231,13 +231,14 @@ public function data_generate_styles_fixtures() { 'valid_classnames_and_css_vars' => array( 'block_styles' => array( 'color' => array( - 'text' => 'var:preset|color|teal-independents', + 'text' => 'var:preset|color|teal-independents', + 'background' => 'var:preset|color|blue-ribbon', ), ), 'options' => array(), 'expected_output' => array( - 'css' => 'color: var(--wp--preset--color--teal-independents);', - 'classnames' => 'has-text-color has-teal-independents-color', + 'css' => 'color: var(--wp--preset--color--teal-independents); background-color: var(--wp--preset--color--blue-ribbon);', + 'classnames' => 'has-text-color has-teal-independents-color has-background has-blue-ribbon-background-color', ), ), @@ -399,6 +400,19 @@ public function data_generate_styles_fixtures() { 'css' => 'border-bottom-color: var(--wp--preset--color--terrible-lizard);', ), ), + + 'valid_css_custom_property' => array( + 'block_styles' => array( + 'spacing' => array( + //'--wp--style--block-gap' => '.0001rem', + 'blockGap' => '10000rem', + ), + ), + 'options' => array(), + 'expected_output' => array( + 'css' => '--wp--style--block-gap: 10000rem;', + ), + ), ); } } From 707412b99521a1bca932443fa9485ac77785aa23 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 1 Jul 2022 15:59:35 +1000 Subject: [PATCH 4/7] Testing passing a layer in options to discern between --wp--style--block-gap and gap. Not a great way to tell the style engine to convert a key to a css custom property.. but... --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 45 ++++++++++--------- .../style-engine/class-wp-style-engine.php | 8 ++-- .../phpunit/class-wp-style-engine-test.php | 14 +++--- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 0ec03e77d4c55f..8ac04f7a883709 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -491,23 +491,25 @@ function( $pseudo_selector ) use ( $selector ) { // If the current selector is a pseudo selector that's defined in the allow list for the current // element then compute the style properties for it. // Otherwise just compute the styles for the default selector as normal. - if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ) { - $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json ); - } else { - $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json ); - } + +// if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ) { +// $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json ); +// } else { +// $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json ); +// } $block_rules = ''; + // @TODO migrate duotone to style engine // 1. Separate the ones who use the general selector // and the ones who use the duotone selector. - $declarations_duotone = array(); - foreach ( $declarations as $index => $declaration ) { - if ( 'filter' === $declaration['name'] ) { - unset( $declarations[ $index ] ); - $declarations_duotone[] = $declaration; - } - } +// $declarations_duotone = array(); +// foreach ( $declarations as $index => $declaration ) { +// if ( 'filter' === $declaration['name'] ) { +// unset( $declarations[ $index ] ); +// $declarations_duotone[] = $declaration; +// } +// } /* * Reset default browser margin on the root body element. @@ -523,27 +525,30 @@ function( $pseudo_selector ) use ( $selector ) { // 2. Generate and append the rules that use the general selector. //$block_rules .= static::to_ruleset( $selector, $declarations ); - // @TODO check duotone + + + $block_styles = $pseudo_selector && isset( $node[ $pseudo_selector ] ) && isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ? $node[ $pseudo_selector ] : $node; $styles = gutenberg_style_engine_generate( - $node, + $block_styles, array( 'selector' => $selector, - 'css_vars' => true, + 'layer' => 0, // Or 'global'. + 'css_vars' => true, // @TODO this doesn't make sense in this context. Refactor. 'prettify' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, ) ); if ( isset( $styles['css'] ) ) { - $block_rules .= $styles['css']; } // 3. Generate and append the rules that use the duotone selector. - if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { - $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] ); - $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); - } + // @TODO migrate duotone to style engine +// if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { +// $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] ); +// $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); +// } if ( static::ROOT_BLOCK_SELECTOR === $selector ) { $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index fcbabe36ab0d4f..6801fd51c995e8 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -371,9 +371,9 @@ protected static function get_classnames( $style_value, $style_definition ) { /** * Returns an array of CSS declarations based on valid block style values. * - * @param array $style_value A single raw style value from the generate() $block_styles array. - * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. - * @param array $options Options passed to generate(). + * @param array $style_value A single raw style value from the generate() $block_styles array. + * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. + * @param array $options Options passed to generate(). * * @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). */ @@ -394,7 +394,7 @@ protected static function get_css_declarations( $style_value, $style_definition, if ( is_string( $style_value ) && strpos( $style_value, 'var:' ) !== false ) { if ( ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) { $css_var = static::get_css_var_value( $style_value, $style_definition['css_vars'] ); - if ( $css_var ) { + if ( $css_var && isset( $style_property_keys['default'] ) ) { $css_declarations[ $style_property_keys['default'] ] = $css_var; } } diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index a22b012fd33fb3..ffc35ce1982484 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -74,8 +74,9 @@ public function data_generate_styles_fixtures() { 'text' => 'var:preset|color|texas-flood', ), 'spacing' => array( - 'margin' => '111px', - 'padding' => '0', + 'margin' => '111px', + 'padding' => '0', + //'blockGap' => '100px', ), 'border' => array( 'color' => 'var:preset|color|cool-caramel', @@ -85,7 +86,7 @@ public function data_generate_styles_fixtures() { ), 'options' => array( 'convert_vars_to_classnames' => true ), 'expected_output' => array( - 'css' => 'border-style: dotted; border-width: 2rem; padding: 0; margin: 111px;', + 'css' => 'border-style: dotted; border-width: 2rem; padding: 0; margin: 111px;/* gap: 100px;*/', 'classnames' => 'has-text-color has-texas-flood-color has-border-color has-cool-caramel-border-color', ), ), @@ -404,11 +405,12 @@ public function data_generate_styles_fixtures() { 'valid_css_custom_property' => array( 'block_styles' => array( 'spacing' => array( - //'--wp--style--block-gap' => '.0001rem', - 'blockGap' => '10000rem', + 'blockGap' => '10000rem', ), ), - 'options' => array(), + 'options' => array( + 'layer' => 0, + ), 'expected_output' => array( 'css' => '--wp--style--block-gap: 10000rem;', ), From a394cecfbb5ca812f7b7c0ddf9b696dfc8dd35b5 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Sun, 3 Jul 2022 15:14:48 +1000 Subject: [PATCH 5/7] Allow for merging of style definition properties to overwrite default property keys. Tests Formatting --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 14 +++++-- .../style-engine/class-wp-style-engine.php | 42 +++++++++---------- .../phpunit/class-wp-style-engine-test.php | 16 ++++--- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 8ac04f7a883709..1913d1b369e874 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -532,10 +532,16 @@ function( $pseudo_selector ) use ( $selector ) { $styles = gutenberg_style_engine_generate( $block_styles, array( - 'selector' => $selector, - 'layer' => 0, // Or 'global'. - 'css_vars' => true, // @TODO this doesn't make sense in this context. Refactor. - 'prettify' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, + 'selector' => $selector, + 'custom_metadata' => array( + 'spacing.blockGap' => array( + 'property_keys' => array( + 'default' => '--wp--style--block-gap', + ), + ), + ), + 'css_vars' => true, // @TODO this doesn't make sense in this context. Refactor. + 'prettify' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, ) ); diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 6801fd51c995e8..59771a5dda29c0 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -256,6 +256,25 @@ public static function get_instance() { return self::$instance; } + /** + * Merges single style definitions with incoming custom style definitions. + * + * @param array $style_definition The internal style definition metadata. + * @param array $custom_definition The custom style definition metadata to be merged. + * + * @return array The merged definition metadata. + */ + public static function merge_custom_style_definitions_metadata( $style_definition, $custom_definition = array() ) { + $valid_keys = array( 'property_keys', 'classnames' ); + foreach ( $valid_keys as $key ) { + if ( isset( $custom_definition[ $key ] ) && is_array( $custom_definition[ $key ] ) ) { + $style_definition[ $key ] = array_merge( $style_definition[ $key ], $custom_definition[ $key ] ); + } + } + + return $style_definition; + } + /** * Extracts the slug in kebab case from a preset string, e.g., "heavenly-blue" from 'var:preset|color|heavenlyBlue'. * @@ -313,25 +332,6 @@ protected static function is_valid_style_value( $style_value ) { return true; } - /** - * Merges single style definitions with incoming custom style definitions. - * - * @param array $style_definition The internal style definition metadata. - * @param array $custom_definition The custom style definition metadata to be merged. - * - * @return array The merged definition metadata. - */ - public static function merge_custom_style_definitions_metadata( $style_definition, $custom_definition = array() ) { - $valid_keys = array( 'property_keys', 'classnames' ); - foreach ( $valid_keys as $key ) { - if ( isset( $custom_definition[ $key ] ) && is_array( $custom_definition[ $key ] ) ) { - $style_definition[ $key ] = array_merge( $style_definition[ $key ], $custom_definition[ $key ] ); - } - } - - return $style_definition; - } - /** * Returns classnames, and generates classname(s) from a CSS preset property pattern, e.g., 'var:preset|color|heavenly-blue'. * @@ -502,8 +502,8 @@ public function generate( $block_styles, $options ) { * "border-{top|right|bottom|left}-{color|width|style}: {value};" or, * "border-image-{outset|source|width|repeat|slice}: {value};" * - * @param array $style_value A single raw Gutenberg style attributes value for a CSS property. - * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. + * @param array $style_value A single raw Gutenberg style attributes value for a CSS property. + * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. * @param array $options Options passed to generate(). * * @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index ffc35ce1982484..71f713810c53d1 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -18,7 +18,7 @@ class WP_Style_Engine_Test extends WP_UnitTestCase { * * @dataProvider data_generate_styles_fixtures */ - function test_generate_styles( $block_styles, $options, $expected_output ) { + public function test_generate_styles( $block_styles, $options, $expected_output ) { $generated_styles = wp_style_engine_generate( $block_styles, $options ); $this->assertSame( $expected_output, $generated_styles ); } @@ -76,7 +76,7 @@ public function data_generate_styles_fixtures() { 'spacing' => array( 'margin' => '111px', 'padding' => '0', - //'blockGap' => '100px', + 'blockGap' => '100px', ), 'border' => array( 'color' => 'var:preset|color|cool-caramel', @@ -86,7 +86,7 @@ public function data_generate_styles_fixtures() { ), 'options' => array( 'convert_vars_to_classnames' => true ), 'expected_output' => array( - 'css' => 'border-style: dotted; border-width: 2rem; padding: 0; margin: 111px;/* gap: 100px;*/', + 'css' => 'border-style: dotted; border-width: 2rem; padding: 0; margin: 111px; grid-gap: 100px;', 'classnames' => 'has-text-color has-texas-flood-color has-border-color has-cool-caramel-border-color', ), ), @@ -327,7 +327,7 @@ public function data_generate_styles_fixtures() { ), ), - 'invalid_classnames_options' => array( + 'invalid_property_array_values' => array( 'block_styles' => array( 'typography' => array( 'fontSize' => array( @@ -409,7 +409,13 @@ public function data_generate_styles_fixtures() { ), ), 'options' => array( - 'layer' => 0, + 'custom_metadata' => array( + 'spacing.blockGap' => array( + 'property_keys' => array( + 'default' => '--wp--style--block-gap', + ), + ), + ), ), 'expected_output' => array( 'css' => '--wp--style--block-gap: 10000rem;', From 0918a1e2eea9511a41508b82ef847dbf445852eb Mon Sep 17 00:00:00 2001 From: ramonjd Date: Mon, 4 Jul 2022 15:55:17 +1000 Subject: [PATCH 6/7] Testing with duotone --- lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php | 5 ++++- packages/style-engine/class-wp-style-engine.php | 8 ++++++++ test/emptytheme/theme.json | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 1913d1b369e874..638ebecc625a92 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -526,9 +526,12 @@ function( $pseudo_selector ) use ( $selector ) { // 2. Generate and append the rules that use the general selector. //$block_rules .= static::to_ruleset( $selector, $declarations ); - $block_styles = $pseudo_selector && isset( $node[ $pseudo_selector ] ) && isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ? $node[ $pseudo_selector ] : $node; + if ( isset( $block_styles['filter']['duotone'] ) && ! empty( $block_styles['filter']['duotone'] ) ) { + $selector = static::scope_selector( $selector, $block_metadata['duotone'] ); + } + $styles = gutenberg_style_engine_generate( $block_styles, array( diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 59771a5dda29c0..cc01b137b02bbb 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -152,6 +152,14 @@ class WP_Style_Engine { ), ), ), + 'filter' => array( + 'duotone' => array( + 'property_keys' => array( + 'default' => 'filter', + ), + 'path' => array( 'filter', 'duotone' ), + ), + ), 'spacing' => array( 'padding' => array( 'property_keys' => array( diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json index 873cce498ff996..b22c80cb4ebe31 100644 --- a/test/emptytheme/theme.json +++ b/test/emptytheme/theme.json @@ -246,8 +246,8 @@ } }, "core/image": { - "color": { - "duotone": "#ffffff" + "filter": { + "duotone": "var(--wp--preset--duotone--primary-and-tertiary)" } }, "core/group": { From 365a6ddaf2f45a4650a97f56c887afe50d8c732f Mon Sep 17 00:00:00 2001 From: ramonjd Date: Tue, 5 Jul 2022 10:38:26 +1000 Subject: [PATCH 7/7] Rebasedw --- ...class-wp-style-engine-css-declarations.php | 19 ++++++++++++++++--- .../style-engine/class-wp-style-engine.php | 15 +++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-declarations.php b/packages/style-engine/class-wp-style-engine-css-declarations.php index cde6b51355195e..3c7ddc1083552b 100644 --- a/packages/style-engine/class-wp-style-engine-css-declarations.php +++ b/packages/style-engine/class-wp-style-engine-css-declarations.php @@ -26,7 +26,13 @@ class WP_Style_Engine_CSS_Declarations { protected $styles = array(); /** - * Contructor for this object. + * A white list of CSS custom properties. + * Used to bypass safecss_filter_attr(). + */ + const VALID_CUSTOM_PROPERTIES = array( '--wp--style--block-gap' => 'blockGap' ); + + /** + * Constructor for this object. * * If a `$styles` array is passed, it will be used to populate * the initial $styles prop of the object by calling add_declarations(). @@ -92,15 +98,22 @@ public function get_styles() { /** * Get the CSS styles. * + * @param boolean $should_prettify Whether to format the output with indents. + * * @return string The CSS styles. */ - public function get_styles_string() { + public function get_styles_string( $should_prettify = false ) { $styles_array = $this->get_styles(); $styles = ''; + $indent = $should_prettify ? "\t" : ''; + $newline = $should_prettify ? "\n" : ' '; foreach ( $styles_array as $property => $value ) { $css = esc_html( safecss_filter_attr( "{$property}: {$value}" ) ); + // @TODO The safecss_filter_attr_allow_css filter in safecss_filter_attr (kses.php) does not let through CSS custom variables. + // So we have to be strict about them here. + $css = isset( static::VALID_CUSTOM_PROPERTIES[ $property ] ) ? esc_html( "{$property}: {$value}" ) : esc_html( safecss_filter_attr( "{$property}: {$value}" ) ); if ( $css ) { - $styles .= $css . '; '; + $styles .= "$indent$css;$newline"; } } return rtrim( $styles ); diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index cc01b137b02bbb..1c9bb9ec2eae9d 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -30,14 +30,6 @@ class WP_Style_Engine { */ private static $instance = null; - /** - * A white list of CSS custom properties. - * Used to bypass safecss_filter_attr(). - */ - const VALID_CUSTOM_PROPERTIES = array( - '--wp--style--block-gap' => array( 'spacing', 'blockGap' ), - ); - /** * Style definitions that contain the instructions to * parse/output valid Gutenberg styles from a block's attributes. @@ -484,14 +476,17 @@ public function generate( $block_styles, $options ) { // The return object. $styles_output = array(); - $css = $style_rules->get_styles_string(); + $css = $style_rules->get_styles_string( $should_prettify ); // Return css, if any. if ( ! empty( $css ) ) { $styles_output['css'] = $css; // Return an entire rule if there is a selector. if ( $css_selector ) { - $styles_output['css'] = $css_selector . ' { ' . $css . ' }'; + $new_line = $should_prettify ? "\n" : ''; + $spacer = $should_prettify ? '' : ' '; + // @TODO this is just weird. Simplify? + $styles_output['css'] = "$css_selector {{$spacer}{$new_line}{$css}{$new_line}{$spacer}}$new_line"; } }