From 37e082ccbf4f34545abaa57c119ffddaa6a01497 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Wed, 11 May 2022 15:53:54 +1000 Subject: [PATCH 01/10] Initial commit - not working Trying to come up with a model for elements --- lib/block-supports/elements.php | 34 +++----- .../style-engine/class-wp-style-engine.php | 86 +++++++++++++++---- .../phpunit/class-wp-style-engine-test.php | 62 ++++++++++++- 3 files changed, 139 insertions(+), 43 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 62671c5f81d01d..59233c2cacfa3b 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -87,16 +87,7 @@ function gutenberg_render_elements_support( $block_content, $block ) { * @return null */ function gutenberg_render_elements_support_styles( $pre_render, $block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $skip_link_color_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'color', 'link' ); - if ( $skip_link_color_serialization ) { - return null; - } - - $link_color = null; - if ( ! empty( $block['attrs'] ) ) { - $link_color = _wp_array_get( $block['attrs'], array( 'style', 'elements', 'link', 'color', 'text' ), null ); - } + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); /* * For now we only care about link color. @@ -104,23 +95,22 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { * should take advantage of WP_Theme_JSON_Gutenberg::compute_style_properties * and work for any element and style. */ - if ( null === $link_color ) { + $skip_link_color_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'color', 'link' ); + + if ( $skip_link_color_serialization ) { return null; } - $class_name = gutenberg_get_elements_class_name( $block ); + $block_styles = isset( $block['attrs']['style'] ) ? $block['attrs']['style'] : null; + $class_name = gutenberg_get_elements_class_name( $block ); + $styles = gutenberg_style_engine_generate( + $block_styles, + array( 'selector' => ".$class_name a", 'element' => 'link' ) + ); - if ( strpos( $link_color, 'var:preset|color|' ) !== false ) { - // Get the name from the string and add proper styles. - $index_to_splice = strrpos( $link_color, '|' ) + 1; - $link_color_name = substr( $link_color, $index_to_splice ); - $link_color = "var(--wp--preset--color--$link_color_name)"; + if ( ! empty( $styles['css'] ) ) { + gutenberg_enqueue_block_support_styles( $styles['css'] ); } - $link_color_declaration = esc_html( safecss_filter_attr( "color: $link_color" ) ); - - $style = ".$class_name a{" . $link_color_declaration . ';}'; - - gutenberg_enqueue_block_support_styles( $style ); return null; } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 5687a2f422186e..d4c146137e743d 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -67,6 +67,19 @@ class WP_Style_Engine { ), ), ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => array( + 'property_key' => 'color', + 'path' => array( 'elements', 'link', 'color', 'text' ), + 'css_vars' => array( + '--wp--preset--color--$slug' => 'color', + ), + ), + ), + ), + ), 'spacing' => array( 'padding' => array( 'property_key' => 'padding', @@ -190,13 +203,8 @@ protected static function get_classnames( $style_value, $style_definition ) { * @return array An array of CSS rules. */ protected static function get_css( $style_value, $style_definition ) { - // Low-specificity check to see if the value is a CSS preset. - if ( is_string( $style_value ) && strpos( $style_value, 'var:' ) !== false ) { - return array(); - } - // If required in the future, style definitions could define a callable `value_func` to generate custom CSS rules. - return static::get_css_rules( $style_value, $style_definition['property_key'] ); + return static::get_css_rules( $style_value, $style_definition ); } /** @@ -204,13 +212,14 @@ protected static function get_css( $style_value, $style_definition ) { * Styles are bundled based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. * * @param array $block_styles An array of styles from a block's attributes. + * @param array $options An array of options to determine the output. * * @return array|null array( - * 'styles' => (string) A CSS ruleset formatted to be placed in an HTML `style` attribute or tag. + * 'styles' => (string) A CSS ruleset formatted to be placed in an HTML `style` attribute or tag. Default is a string of inline styles. * 'classnames' => (string) Classnames separated by a space. * ); */ - public function generate( $block_styles ) { + public function generate( $block_styles, $options ) { if ( empty( $block_styles ) || ! is_array( $block_styles ) ) { return null; } @@ -218,9 +227,15 @@ public function generate( $block_styles ) { $css_rules = array(); $classnames = array(); $styles_output = array(); + $element = isset( $options['element'] ) ? $options['element'] : null; + $block_definitions = self::BLOCK_STYLE_DEFINITIONS_METADATA; + + if ( ! empty( $element ) && isset( self::BLOCK_STYLE_DEFINITIONS_METADATA['elements'][ $element ] ) ) { + $block_definitions = self::BLOCK_STYLE_DEFINITIONS_METADATA['elements'][ $element ]; + } // Collect CSS and classnames. - foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { + foreach ( $block_definitions as $definition_group ) { foreach ( $definition_group as $style_definition ) { $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); @@ -234,21 +249,34 @@ public function generate( $block_styles ) { } // Build CSS rules output. - $css_output = ''; + $selector = isset( $options['selector'] ) ? $options['selector'] : null; + $css_output = array(); + if ( ! empty( $css_rules ) ) { // Generate inline style rules. - // In the future there might be a flag in the option to output - // inline CSS rules (for HTML style attributes) vs selectors + rules for style tags. foreach ( $css_rules as $rule => $value ) { $filtered_css = esc_html( safecss_filter_attr( "{$rule}: {$value}" ) ); if ( ! empty( $filtered_css ) ) { - $css_output .= $filtered_css . '; '; + $css_output[] = $filtered_css . ';'; } } } if ( ! empty( $css_output ) ) { - $styles_output['css'] = trim( $css_output ); + if ( $selector ) { + $style_block = "$selector {\n"; + $css_output = array_map( + function ( $value ) { + return "\t$value\n"; + }, + $css_output + ); + $style_block .= implode( '', $css_output ); + $style_block .= "}\n"; + $styles_output['css'] = $style_block; + } else { + $styles_output['css'] = implode( ' ', $css_output ); + } } if ( ! empty( $classnames ) ) { @@ -263,18 +291,37 @@ public function generate( $block_styles ) { * If the input contains an array, it will be treated like a box model * for styles such as margins and padding * - * @param string|array $style_value A single raw Gutenberg style attributes value for a CSS property. - * @param string $style_property The CSS property for which we're creating a rule. + * @param string|array $style_value A single raw Gutenberg style attributes value for a CSS property. + * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. * * @return array The class name for the added style. */ - protected static function get_css_rules( $style_value, $style_property ) { + protected static function get_css_rules( $style_value, $style_definition ) { $rules = array(); if ( ! $style_value ) { return $rules; } + $style_property = $style_definition[ 'property_key' ]; + + // Check if the value is a CSS preset and there's a corresponding css_var pattern in the style definition. + if ( is_string( $style_value ) && strpos( $style_value, 'var:' ) !== false ) { + if ( $style_definition['css_vars'] ) { + foreach ( $style_definition['css_vars'] as $css_var_pattern => $property_key ) { + $slug = static::get_slug_from_preset_value( $style_value, $property_key ); + if ( $slug ) { + $css_var = strtr( + $css_var_pattern, + array( '$slug' => $slug ) + ); + $rules[ $style_property ] = "var($css_var)"; + } + } + } + return $rules; + } + // We assume box model-like properties. if ( is_array( $style_value ) ) { foreach ( $style_value as $key => $value ) { @@ -295,16 +342,17 @@ protected static function get_css_rules( $style_value, $style_property ) { * Styles are bundled based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. * * @param array $block_styles An array of styles from a block's attributes. + * @param array $options An array of options to determine the output. * * @return array|null array( * 'styles' => (string) A CSS ruleset formatted to be placed in an HTML `style` attribute or tag. * 'classnames' => (string) Classnames separated by a space. * ); */ -function wp_style_engine_generate( $block_styles ) { +function wp_style_engine_generate( $block_styles, $options = array() ) { if ( class_exists( 'WP_Style_Engine' ) ) { $style_engine = WP_Style_Engine::get_instance(); - return $style_engine->generate( $block_styles ); + return $style_engine->generate( $block_styles, $options ); } return null; } 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 f9830e173e11a7..18320e950d034f 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -17,8 +17,8 @@ class WP_Style_Engine_Test extends WP_UnitTestCase { * * @dataProvider data_generate_styles_fixtures */ - function test_generate_styles( $block_styles, $expected_output ) { - $generated_styles = wp_style_engine_generate( $block_styles ); + function test_generate_styles( $block_styles, $options, $expected_output ) { + $generated_styles = wp_style_engine_generate( $block_styles, $options ); $this->assertSame( $expected_output, $generated_styles ); } @@ -31,11 +31,13 @@ public function data_generate_styles_fixtures() { return array( 'default_return_value' => array( 'block_styles' => array(), + 'options' => null, 'expected_output' => null, ), 'inline_invalid_block_styles_empty' => array( 'block_styles' => 'hello world!', + 'options' => null, 'expected_output' => null, ), @@ -43,6 +45,7 @@ public function data_generate_styles_fixtures() { 'block_styles' => array( 'pageBreakAfter' => 'verso', ), + 'options' => null, 'expected_output' => array(), ), @@ -50,6 +53,7 @@ public function data_generate_styles_fixtures() { 'block_styles' => array( 'pageBreakAfter' => 'verso', ), + 'options' => null, 'expected_output' => array(), ), @@ -59,6 +63,7 @@ public function data_generate_styles_fixtures() { 'gap' => '1000vw', ), ), + 'options' => null, 'expected_output' => array(), ), @@ -71,6 +76,7 @@ public function data_generate_styles_fixtures() { 'margin' => '111px', ), ), + 'options' => null, 'expected_output' => array( 'css' => 'margin: 111px;', 'classnames' => 'has-text-color has-texas-flood-color', @@ -94,6 +100,7 @@ public function data_generate_styles_fixtures() { ), ), ), + 'options' => null, 'expected_output' => array( 'css' => 'padding-top: 42px; padding-left: 2%; padding-bottom: 44px; padding-right: 5rem; margin-top: 12rem; margin-left: 2vh; margin-bottom: 2px; margin-right: 10em;', ), @@ -112,10 +119,54 @@ public function data_generate_styles_fixtures() { 'letterSpacing' => '2', ), ), + 'options' => null, 'expected_output' => array( 'css' => 'font-family: Roboto,Oxygen-Sans,Ubuntu,sans-serif; font-style: italic; font-weight: 800; line-height: 1.3; text-decoration: underline; text-transform: uppercase; letter-spacing: 2;', ), ), + + 'style_block_with_selector' => array( + 'block_styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '42px', + 'left' => '2%', + 'bottom' => '44px', + 'right' => '5rem', + ), + ), + ), + 'options' => array( 'selector' => '.wp-selector' ), + 'expected_output' => array( + 'css' => '.wp-selector { + padding-top: 42px; + padding-left: 2%; + padding-bottom: 44px; + padding-right: 5rem; +} +', + ), + ), + + 'elements_with_css_var_value' => array( + 'block_styles' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'var:preset|color|my-little-pony', + ), + ), + ), + ), + 'options' => array( 'selector' => '.wp-selector', 'element' => 'link' ), + 'expected_output' => array( + 'css' => '.wp-selector { + color: var(--wp--preset--color--my-little-pony); +} +', + ), + ), + 'valid_classnames_deduped' => array( 'block_styles' => array( 'color' => array( @@ -128,10 +179,12 @@ public function data_generate_styles_fixtures() { 'fontFamily' => 'var:preset|font-family|totally-awesome', ), ), + 'options' => null, 'expected_output' => array( 'classnames' => 'has-text-color has-copper-socks-color has-background has-splendid-carrot-background-color has-like-wow-dude-gradient-background has-fantastic-font-size has-totally-awesome-font-family', ), ), + 'valid_classnames_with_null_style_values' => array( 'block_styles' => array( 'color' => array( @@ -139,11 +192,13 @@ public function data_generate_styles_fixtures() { 'background' => null, ), ), + 'options' => null, 'expected_output' => array( 'css' => 'color: #fff;', 'classnames' => 'has-text-color', ), ), + 'invalid_classnames_preset_value' => array( 'block_styles' => array( 'color' => array( @@ -155,10 +210,12 @@ public function data_generate_styles_fixtures() { 'padding' => 'var:preset|spacing|padding', ), ), + 'options' => null, 'expected_output' => array( 'classnames' => 'has-text-color has-background', ), ), + 'invalid_classnames_options' => array( 'block_styles' => array( 'typography' => array( @@ -170,6 +227,7 @@ public function data_generate_styles_fixtures() { ), ), ), + 'options' => null, 'expected_output' => array(), ), ); From e3a9beba4200ba1a46b63225bd74f751dd83e53b Mon Sep 17 00:00:00 2001 From: ramonjd Date: Wed, 11 May 2022 18:41:58 +1000 Subject: [PATCH 02/10] Some more faffing about. This time, let's pass the block_style-like object from the style.elements array to the style engine since that's what it expects anyway --- lib/block-supports/elements.php | 21 +++++++----- .../style-engine/class-wp-style-engine.php | 32 ++++++++++++------- .../phpunit/class-wp-style-engine-test.php | 18 +++++++---- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 59233c2cacfa3b..e2037aa4d1fced 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -87,7 +87,8 @@ function gutenberg_render_elements_support( $block_content, $block ) { * @return null */ function gutenberg_render_elements_support_styles( $pre_render, $block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $element_block_styles = isset( $block['attrs']['style']['elements'] ) ? $block['attrs']['style']['elements'] : null; /* * For now we only care about link color. @@ -100,15 +101,19 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { if ( $skip_link_color_serialization ) { return null; } + $class_name = gutenberg_get_elements_class_name( $block ); + $styles = array(); + $link_block_styles = isset( $element_block_styles['link'] ) ? $element_block_styles['link'] : null; + + if ( $link_block_styles ) { + $styles = gutenberg_style_engine_generate( + $link_block_styles, + array( 'selector' => ".$class_name a", 'element' => 'link' ) + ); + } - $block_styles = isset( $block['attrs']['style'] ) ? $block['attrs']['style'] : null; - $class_name = gutenberg_get_elements_class_name( $block ); - $styles = gutenberg_style_engine_generate( - $block_styles, - array( 'selector' => ".$class_name a", 'element' => 'link' ) - ); - if ( ! empty( $styles['css'] ) ) { + if ( ! $styles ) { gutenberg_enqueue_block_support_styles( $styles['css'] ); } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index d4c146137e743d..f11101c84ed4d2 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -69,15 +69,7 @@ class WP_Style_Engine { ), 'elements' => array( 'link' => array( - 'color' => array( - 'text' => array( - 'property_key' => 'color', - 'path' => array( 'elements', 'link', 'color', 'text' ), - 'css_vars' => array( - '--wp--preset--color--$slug' => 'color', - ), - ), - ), + 'path' => array( 'spacing', 'padding' ), ), ), 'spacing' => array( @@ -132,6 +124,20 @@ class WP_Style_Engine { ), ); + const ELEMENTS_STYLES_DEFINITIONS_METADATA = array( + 'link' => array( + 'color' => array( + 'text' => array( + 'property_key' => 'color', + 'path' => array( 'color', 'text' ), + 'css_vars' => array( + '--wp--preset--color--$slug' => 'color', + ), + ), + ), + ) + ); + /** * Utility method to retrieve the main instance of the class. * @@ -230,12 +236,16 @@ public function generate( $block_styles, $options ) { $element = isset( $options['element'] ) ? $options['element'] : null; $block_definitions = self::BLOCK_STYLE_DEFINITIONS_METADATA; - if ( ! empty( $element ) && isset( self::BLOCK_STYLE_DEFINITIONS_METADATA['elements'][ $element ] ) ) { - $block_definitions = self::BLOCK_STYLE_DEFINITIONS_METADATA['elements'][ $element ]; + if ( $element ) { + $block_definitions = isset( self::ELEMENTS_STYLES_DEFINITIONS_METADATA[ $element ] ) ? self::ELEMENTS_STYLES_DEFINITIONS_METADATA[ $element ] : array(); } // Collect CSS and classnames. foreach ( $block_definitions as $definition_group ) { + if ( ! $definition_group ) { + continue; + } + foreach ( $definition_group as $style_definition ) { $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); 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 18320e950d034f..3d5dc50f08d1c3 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -150,12 +150,8 @@ public function data_generate_styles_fixtures() { 'elements_with_css_var_value' => array( 'block_styles' => array( - 'elements' => array( - 'link' => array( - 'color' => array( - 'text' => 'var:preset|color|my-little-pony', - ), - ), + 'color' => array( + 'text' => 'var:preset|color|my-little-pony', ), ), 'options' => array( 'selector' => '.wp-selector', 'element' => 'link' ), @@ -167,6 +163,16 @@ public function data_generate_styles_fixtures() { ), ), + 'elements_with_invalid_element' => array( + 'block_styles' => array( + 'color' => array( + 'text' => 'var:preset|color|my-little-pony', + ), + ), + 'options' => array( 'selector' => '.wp-selector', 'element' => 'marquee' ), + 'expected_output' => array(), + ), + 'valid_classnames_deduped' => array( 'block_styles' => array( 'color' => array( From 160925f6cd5c28707b431d6013df70fed5fad410 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Thu, 12 May 2022 12:45:25 +1000 Subject: [PATCH 03/10] Here's an attempt at making classnames optional. Where a consumer specifies that they want to return classnames from preset values, the style engine will do that instead of building css var style values. --- lib/block-supports/colors.php | 2 +- lib/block-supports/elements.php | 4 +- lib/block-supports/typography.php | 2 +- .../style-engine/class-wp-style-engine.php | 162 +++++++++--------- .../phpunit/class-wp-style-engine-test.php | 33 ++-- 5 files changed, 98 insertions(+), 105 deletions(-) diff --git a/lib/block-supports/colors.php b/lib/block-supports/colors.php index fe51ffede8d49a..ab1aa3dd86d177 100644 --- a/lib/block-supports/colors.php +++ b/lib/block-supports/colors.php @@ -102,7 +102,7 @@ function gutenberg_apply_colors_support( $block_type, $block_attributes ) { } $attributes = array(); - $styles = gutenberg_style_engine_generate( array( 'color' => $color_block_styles ) ); + $styles = gutenberg_style_engine_generate( array( 'color' => $color_block_styles ), array( 'classnames' => true ) ); if ( ! empty( $styles['classnames'] ) ) { $attributes['class'] = $styles['classnames']; diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index e2037aa4d1fced..b498a4a9eb1588 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -108,12 +108,12 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { if ( $link_block_styles ) { $styles = gutenberg_style_engine_generate( $link_block_styles, - array( 'selector' => ".$class_name a", 'element' => 'link' ) + array( 'selector' => ".$class_name a" ) ); } - if ( ! $styles ) { + if ( ! empty( $styles ) ) { gutenberg_enqueue_block_support_styles( $styles['css'] ); } diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 35912e59e3a6bf..9312e7f7c5f59e 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -141,7 +141,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { } $attributes = array(); - $styles = gutenberg_style_engine_generate( array( 'typography' => $typography_block_styles ) ); + $styles = gutenberg_style_engine_generate( array( 'typography' => $typography_block_styles ), array( 'classnames' => true ) ); if ( ! empty( $styles['classnames'] ) ) { $attributes['class'] = $styles['classnames']; diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index f11101c84ed4d2..b87e2cd21dc281 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -45,6 +45,9 @@ class WP_Style_Engine { 'text' => array( 'property_key' => 'color', 'path' => array( 'color', 'text' ), + 'css_vars' => array( + '--wp--preset--color--$slug' => 'color', + ), 'classnames' => array( 'has-text-color' => true, 'has-%s-color' => 'color', @@ -124,20 +127,6 @@ class WP_Style_Engine { ), ); - const ELEMENTS_STYLES_DEFINITIONS_METADATA = array( - 'link' => array( - 'color' => array( - 'text' => array( - 'property_key' => 'color', - 'path' => array( 'color', 'text' ), - 'css_vars' => array( - '--wp--preset--color--$slug' => 'color', - ), - ), - ), - ) - ); - /** * Utility method to retrieve the main instance of the class. * @@ -205,12 +194,51 @@ 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_return_css_vars Whether to try build and return CSS var values. * * @return array An array of CSS rules. */ - protected static function get_css( $style_value, $style_definition ) { - // If required in the future, style definitions could define a callable `value_func` to generate custom CSS rules. - return static::get_css_rules( $style_value, $style_definition ); + protected static function get_css( $style_value, $style_definition, $should_return_css_vars ) { + $rules = array(); + + if ( ! $style_value ) { + return $rules; + } + + // Before default processing, style definitions could define a callable `value_func` to generate custom CSS rules at this point. + + $style_property = $style_definition[ 'property_key' ]; + + // 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. + if ( is_string( $style_value ) && strpos( $style_value, 'var:' ) !== false ) { + if ( $should_return_css_vars && $style_definition['css_vars'] ) { + foreach ( $style_definition['css_vars'] as $css_var_pattern => $property_key ) { + $slug = static::get_slug_from_preset_value( $style_value, $property_key ); + if ( $slug ) { + $css_var = strtr( + $css_var_pattern, + array( '$slug' => $slug ) + ); + $rules[ $style_property ] = "var($css_var)"; + } + } + } + return $rules; + } + + // Default rule builder. + // If the input contains an array, ee assume box model-like properties + // for styles such as margins and padding + if ( is_array( $style_value ) ) { + foreach ( $style_value as $key => $value ) { + $rules[ "$style_property-$key" ] = $value; + } + } else { + $rules[ $style_property ] = $style_value; + } + + return $rules; } /** @@ -218,10 +246,13 @@ protected static function get_css( $style_value, $style_definition ) { * Styles are bundled based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. * * @param array $block_styles An array of styles from a block's attributes. - * @param array $options An array of options to determine the output. + * @param array $options array( + * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. + * 'classnames' => (boolean) Whether to return classnames. If `true` var:? values will be parsed to return classnames instead of CSS vars. Default is `false`. + * ); * * @return array|null array( - * 'styles' => (string) A CSS ruleset formatted to be placed in an HTML `style` attribute or tag. Default is a string of inline styles. + * 'css' => (string) A CSS ruleset formatted to be placed in an HTML `style` attribute or tag. Default is a string of inline styles. * 'classnames' => (string) Classnames separated by a space. * ); */ @@ -230,18 +261,13 @@ public function generate( $block_styles, $options ) { return null; } - $css_rules = array(); - $classnames = array(); - $styles_output = array(); - $element = isset( $options['element'] ) ? $options['element'] : null; - $block_definitions = self::BLOCK_STYLE_DEFINITIONS_METADATA; - - if ( $element ) { - $block_definitions = isset( self::ELEMENTS_STYLES_DEFINITIONS_METADATA[ $element ] ) ? self::ELEMENTS_STYLES_DEFINITIONS_METADATA[ $element ] : array(); - } + $css_rules = array(); + $classnames = array(); + $styles_output = array(); + $should_generate_classnames_from_presets = isset( $options['classnames'] ) ? true === $options['classnames'] : false; // Collect CSS and classnames. - foreach ( $block_definitions as $definition_group ) { + foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { if ( ! $definition_group ) { continue; } @@ -253,8 +279,12 @@ public function generate( $block_styles, $options ) { continue; } - $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); - $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition ) ); + // Generate classnames from var:? values. + if ( $should_generate_classnames_from_presets ) { + $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); + } + + $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, ! $should_generate_classnames_from_presets ) ); } } @@ -274,15 +304,9 @@ public function generate( $block_styles, $options ) { if ( ! empty( $css_output ) ) { if ( $selector ) { - $style_block = "$selector {\n"; - $css_output = array_map( - function ( $value ) { - return "\t$value\n"; - }, - $css_output - ); - $style_block .= implode( '', $css_output ); - $style_block .= "}\n"; + $style_block = "$selector { "; + $style_block .= implode( ' ', $css_output ); + $style_block .= " }"; $styles_output['css'] = $style_block; } else { $styles_output['css'] = implode( ' ', $css_output ); @@ -297,51 +321,29 @@ function ( $value ) { } /** - * Default style value parser that returns a CSS ruleset. - * If the input contains an array, it will be treated like a box model - * for styles such as margins and padding + * This function takes care of adding inline styles + * in the proper place, depending on the theme in use. + * + * For block themes, it's loaded in the head. + * For classic ones, it's loaded in the body + * because the wp_head action happens before + * the render_block. * - * @param string|array $style_value A single raw Gutenberg style attributes value for a CSS property. - * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. + * @link https://core.trac.wordpress.org/ticket/53494. * - * @return array The class name for the added style. + * @param string $style String containing the CSS styles to be added. */ - protected static function get_css_rules( $style_value, $style_definition ) { - $rules = array(); - - if ( ! $style_value ) { - return $rules; - } - - $style_property = $style_definition[ 'property_key' ]; - - // Check if the value is a CSS preset and there's a corresponding css_var pattern in the style definition. - if ( is_string( $style_value ) && strpos( $style_value, 'var:' ) !== false ) { - if ( $style_definition['css_vars'] ) { - foreach ( $style_definition['css_vars'] as $css_var_pattern => $property_key ) { - $slug = static::get_slug_from_preset_value( $style_value, $property_key ); - if ( $slug ) { - $css_var = strtr( - $css_var_pattern, - array( '$slug' => $slug ) - ); - $rules[ $style_property ] = "var($css_var)"; - } - } - } - return $rules; + private function enqueue_block_support_styles( $styles ) { + $action_hook_name = 'wp_footer'; + if ( wp_is_block_theme() ) { + $action_hook_name = 'wp_head'; } - - // We assume box model-like properties. - if ( is_array( $style_value ) ) { - foreach ( $style_value as $key => $value ) { - $rules[ "$style_property-$key" ] = $value; + add_action( + $action_hook_name, + static function () use ( $styles ) { + echo "\n"; } - } else { - $rules[ $style_property ] = $style_value; - } - - return $rules; + ); } } 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 3d5dc50f08d1c3..f6171ccbf71d5f 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -76,7 +76,7 @@ public function data_generate_styles_fixtures() { 'margin' => '111px', ), ), - 'options' => null, + 'options' => array( 'classnames' => true ), 'expected_output' => array( 'css' => 'margin: 111px;', 'classnames' => 'has-text-color has-texas-flood-color', @@ -136,15 +136,9 @@ public function data_generate_styles_fixtures() { ), ), ), - 'options' => array( 'selector' => '.wp-selector' ), + 'options' => array( 'selector' => '.wp-selector > p' ), 'expected_output' => array( - 'css' => '.wp-selector { - padding-top: 42px; - padding-left: 2%; - padding-bottom: 44px; - padding-right: 5rem; -} -', + 'css' => '.wp-selector > p { padding-top: 42px; padding-left: 2%; padding-bottom: 44px; padding-right: 5rem; }', ), ), @@ -154,22 +148,19 @@ public function data_generate_styles_fixtures() { 'text' => 'var:preset|color|my-little-pony', ), ), - 'options' => array( 'selector' => '.wp-selector', 'element' => 'link' ), + 'options' => array( 'selector' => '.wp-selector' ), 'expected_output' => array( - 'css' => '.wp-selector { - color: var(--wp--preset--color--my-little-pony); -} -', + 'css' => '.wp-selector { color: var(--wp--preset--color--my-little-pony); }', ), ), - 'elements_with_invalid_element' => array( + 'elements_with_invalid_preset_style_property' => array( 'block_styles' => array( 'color' => array( - 'text' => 'var:preset|color|my-little-pony', + 'text' => 'var:preset|invalid_property|my-little-pony', ), ), - 'options' => array( 'selector' => '.wp-selector', 'element' => 'marquee' ), + 'options' => array( 'selector' => '.wp-selector' ), 'expected_output' => array(), ), @@ -185,7 +176,7 @@ public function data_generate_styles_fixtures() { 'fontFamily' => 'var:preset|font-family|totally-awesome', ), ), - 'options' => null, + 'options' => array( 'classnames' => true ), 'expected_output' => array( 'classnames' => 'has-text-color has-copper-socks-color has-background has-splendid-carrot-background-color has-like-wow-dude-gradient-background has-fantastic-font-size has-totally-awesome-font-family', ), @@ -198,7 +189,7 @@ public function data_generate_styles_fixtures() { 'background' => null, ), ), - 'options' => null, + 'options' => array( 'classnames' => true ), 'expected_output' => array( 'css' => 'color: #fff;', 'classnames' => 'has-text-color', @@ -216,7 +207,7 @@ public function data_generate_styles_fixtures() { 'padding' => 'var:preset|spacing|padding', ), ), - 'options' => null, + 'options' => array( 'classnames' => true ), 'expected_output' => array( 'classnames' => 'has-text-color has-background', ), @@ -233,7 +224,7 @@ public function data_generate_styles_fixtures() { ), ), ), - 'options' => null, + 'options' => array( 'classnames' => true ), 'expected_output' => array(), ), ); From c0bf52030bcfd5c4f0271740ce11ed8001ea4306 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Thu, 12 May 2022 14:34:47 +1000 Subject: [PATCH 04/10] This commit registers block support styles so we can print them out in a single style tag. The idea is to extend this to layout as well. --- lib/block-supports/elements.php | 10 +-- .../style-engine/class-wp-style-engine.php | 85 ++++++++++++++----- 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index b498a4a9eb1588..056275b54a41c8 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -102,21 +102,15 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { return null; } $class_name = gutenberg_get_elements_class_name( $block ); - $styles = array(); $link_block_styles = isset( $element_block_styles['link'] ) ? $element_block_styles['link'] : null; if ( $link_block_styles ) { - $styles = gutenberg_style_engine_generate( + gutenberg_style_engine_generate( $link_block_styles, - array( 'selector' => ".$class_name a" ) + array( 'selector' => ".$class_name a", 'enqueue_block_support_styles' => true ) ); } - - if ( ! empty( $styles ) ) { - gutenberg_enqueue_block_support_styles( $styles['css'] ); - } - return null; } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index b87e2cd21dc281..378529c76ee89a 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -30,6 +30,11 @@ class WP_Style_Engine { */ private static $instance = null; + /** + * Registered block support styles. + */ + private $registered_block_support_styles = array(); + /** * Style definitions that contain the instructions to * parse/output valid Gutenberg styles from a block's attributes. @@ -70,11 +75,6 @@ class WP_Style_Engine { ), ), ), - 'elements' => array( - 'link' => array( - 'path' => array( 'spacing', 'padding' ), - ), - ), 'spacing' => array( 'padding' => array( 'property_key' => 'padding', @@ -127,6 +127,13 @@ class WP_Style_Engine { ), ); + /** + * Register action for outputting styles when the class is constructed. + */ + public function __construct() { + $this->enqueue_block_support_styles(); + } + /** * Utility method to retrieve the main instance of the class. * @@ -206,7 +213,6 @@ protected static function get_css( $style_value, $style_definition, $should_retu } // Before default processing, style definitions could define a callable `value_func` to generate custom CSS rules at this point. - $style_property = $style_definition[ 'property_key' ]; // Build CSS var values from var:? values, e..g, `var(--wp--css--rule-slug )` @@ -247,8 +253,9 @@ protected static function get_css( $style_value, $style_definition, $should_retu * * @param array $block_styles An array of styles from a block's attributes. * @param array $options array( - * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. - * 'classnames' => (boolean) Whether to return classnames. If `true` var:? values will be parsed to return classnames instead of CSS vars. Default is `false`. + * 'enqueue_block_support_styles' => (boolean) Whether to register generated styles and output them together in a style block. A `selector` is required. + * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. + * 'classnames' => (boolean) Whether to return classnames. If `true` var:? values will be parsed to return classnames instead of CSS vars. Default is `false`. * ); * * @return array|null array( @@ -263,8 +270,8 @@ public function generate( $block_styles, $options ) { $css_rules = array(); $classnames = array(); - $styles_output = array(); - $should_generate_classnames_from_presets = isset( $options['classnames'] ) ? true === $options['classnames'] : false; + $should_generate_classnames_from_presets = isset( $options['classnames'] ) && true === $options['classnames']; + $should_enqueue_block_support_styles = isset( $options['enqueue_block_support_styles'] ) && true === $options['enqueue_block_support_styles']; // Collect CSS and classnames. foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { @@ -289,30 +296,42 @@ public function generate( $block_styles, $options ) { } // Build CSS rules output. - $selector = isset( $options['selector'] ) ? $options['selector'] : null; - $css_output = array(); + $selector = isset( $options['selector'] ) ? $options['selector'] : null; + $css = array(); + $styles_output = array(); if ( ! empty( $css_rules ) ) { // Generate inline style rules. foreach ( $css_rules as $rule => $value ) { $filtered_css = esc_html( safecss_filter_attr( "{$rule}: {$value}" ) ); if ( ! empty( $filtered_css ) ) { - $css_output[] = $filtered_css . ';'; + $css[] = $filtered_css . ';'; } } } - if ( ! empty( $css_output ) ) { + // Return css, if any. + if ( ! empty( $css ) ) { if ( $selector ) { $style_block = "$selector { "; - $style_block .= implode( ' ', $css_output ); + $style_block .= implode( ' ', $css ); $style_block .= " }"; $styles_output['css'] = $style_block; + + // Enqueue block support styles where there is a selector. + if ( $should_enqueue_block_support_styles ) { + if ( isset( $this->registered_block_support_styles[ $selector ] ) ) { + $css = array_unique( array_merge( $this->registered_block_support_styles[ $selector ], $css ) ); + } + $this->registered_block_support_styles[ $selector ] = $css; + } + } else { - $styles_output['css'] = implode( ' ', $css_output ); + $styles_output['css'] = implode( ' ', $css ); } } + // Return classnames, if any. if ( ! empty( $classnames ) ) { $styles_output['classnames'] = implode( ' ', array_unique( $classnames ) ); } @@ -321,6 +340,29 @@ public function generate( $block_styles, $options ) { } /** + * Prints registered styles in the page head or footer. + * + * @see $this->enqueue_block_support_styles + */ + public function output_registered_block_support_styles() { + if ( empty( $this->registered_block_support_styles ) ) { + return; + } + + $output = ''; + + foreach ( $this->registered_block_support_styles as $selector => $rules ) { + $output .= "\t$selector { "; + $output .= implode( ' ', $rules ); + $output .= " }\n"; + } + + echo "\n"; + } + + /** + * Taken from gutenberg_enqueue_block_support_styles() + * * This function takes care of adding inline styles * in the proper place, depending on the theme in use. * @@ -329,20 +371,17 @@ public function generate( $block_styles, $options ) { * because the wp_head action happens before * the render_block. * - * @link https://core.trac.wordpress.org/ticket/53494. + * @see gutenberg_enqueue_block_support_styles() * - * @param string $style String containing the CSS styles to be added. */ - private function enqueue_block_support_styles( $styles ) { + private function enqueue_block_support_styles() { $action_hook_name = 'wp_footer'; if ( wp_is_block_theme() ) { $action_hook_name = 'wp_head'; } add_action( $action_hook_name, - static function () use ( $styles ) { - echo "\n"; - } + array( $this, 'output_registered_block_support_styles' ) ); } } @@ -353,6 +392,8 @@ static function () use ( $styles ) { * Returns an CSS ruleset. * Styles are bundled based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. * + * @access public + * * @param array $block_styles An array of styles from a block's attributes. * @param array $options An array of options to determine the output. * From 52e3345822b91bc68c7119272790adfac3f6b0e5 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Thu, 12 May 2022 15:16:49 +1000 Subject: [PATCH 05/10] LINT! --- lib/block-supports/elements.php | 5 ++- .../style-engine/class-wp-style-engine.php | 16 ++++---- .../phpunit/class-wp-style-engine-test.php | 40 +++++++++---------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 056275b54a41c8..636534fe4928d1 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -107,7 +107,10 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { if ( $link_block_styles ) { gutenberg_style_engine_generate( $link_block_styles, - array( 'selector' => ".$class_name a", 'enqueue_block_support_styles' => true ) + array( + 'selector' => ".$class_name a", + 'enqueue_block_support_styles' => true, + ) ); } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 378529c76ee89a..a1a807ec03ff5e 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -32,6 +32,8 @@ class WP_Style_Engine { /** * Registered block support styles. + * + * @var WP_Style_Engine|null */ private $registered_block_support_styles = array(); @@ -213,7 +215,7 @@ protected static function get_css( $style_value, $style_definition, $should_retu } // Before default processing, style definitions could define a callable `value_func` to generate custom CSS rules at this point. - $style_property = $style_definition[ 'property_key' ]; + $style_property = $style_definition['property_key']; // 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. @@ -222,7 +224,7 @@ protected static function get_css( $style_value, $style_definition, $should_retu foreach ( $style_definition['css_vars'] as $css_var_pattern => $property_key ) { $slug = static::get_slug_from_preset_value( $style_value, $property_key ); if ( $slug ) { - $css_var = strtr( + $css_var = strtr( $css_var_pattern, array( '$slug' => $slug ) ); @@ -235,7 +237,7 @@ protected static function get_css( $style_value, $style_definition, $should_retu // Default rule builder. // If the input contains an array, ee assume box model-like properties - // for styles such as margins and padding + // for styles such as margins and padding. if ( is_array( $style_value ) ) { foreach ( $style_value as $key => $value ) { $rules[ "$style_property-$key" ] = $value; @@ -256,7 +258,7 @@ protected static function get_css( $style_value, $style_definition, $should_retu * 'enqueue_block_support_styles' => (boolean) Whether to register generated styles and output them together in a style block. A `selector` is required. * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. * 'classnames' => (boolean) Whether to return classnames. If `true` var:? values will be parsed to return classnames instead of CSS vars. Default is `false`. - * ); + * );. * * @return array|null array( * 'css' => (string) A CSS ruleset formatted to be placed in an HTML `style` attribute or tag. Default is a string of inline styles. @@ -291,7 +293,7 @@ public function generate( $block_styles, $options ) { $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); } - $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, ! $should_generate_classnames_from_presets ) ); + $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, ! $should_generate_classnames_from_presets ) ); } } @@ -315,7 +317,7 @@ public function generate( $block_styles, $options ) { if ( $selector ) { $style_block = "$selector { "; $style_block .= implode( ' ', $css ); - $style_block .= " }"; + $style_block .= ' }'; $styles_output['css'] = $style_block; // Enqueue block support styles where there is a selector. @@ -325,7 +327,6 @@ public function generate( $block_styles, $options ) { } $this->registered_block_support_styles[ $selector ] = $css; } - } else { $styles_output['css'] = implode( ' ', $css ); } @@ -372,7 +373,6 @@ public function output_registered_block_support_styles() { * the render_block. * * @see gutenberg_enqueue_block_support_styles() - * */ private function enqueue_block_support_styles() { $action_hook_name = 'wp_footer'; 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 f6171ccbf71d5f..306066823cd758 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -31,13 +31,13 @@ public function data_generate_styles_fixtures() { return array( 'default_return_value' => array( 'block_styles' => array(), - 'options' => null, + 'options' => null, 'expected_output' => null, ), 'inline_invalid_block_styles_empty' => array( 'block_styles' => 'hello world!', - 'options' => null, + 'options' => null, 'expected_output' => null, ), @@ -45,7 +45,7 @@ public function data_generate_styles_fixtures() { 'block_styles' => array( 'pageBreakAfter' => 'verso', ), - 'options' => null, + 'options' => null, 'expected_output' => array(), ), @@ -53,7 +53,7 @@ public function data_generate_styles_fixtures() { 'block_styles' => array( 'pageBreakAfter' => 'verso', ), - 'options' => null, + 'options' => null, 'expected_output' => array(), ), @@ -63,7 +63,7 @@ public function data_generate_styles_fixtures() { 'gap' => '1000vw', ), ), - 'options' => null, + 'options' => null, 'expected_output' => array(), ), @@ -76,7 +76,7 @@ public function data_generate_styles_fixtures() { 'margin' => '111px', ), ), - 'options' => array( 'classnames' => true ), + 'options' => array( 'classnames' => true ), 'expected_output' => array( 'css' => 'margin: 111px;', 'classnames' => 'has-text-color has-texas-flood-color', @@ -100,7 +100,7 @@ public function data_generate_styles_fixtures() { ), ), ), - 'options' => null, + 'options' => null, 'expected_output' => array( 'css' => 'padding-top: 42px; padding-left: 2%; padding-bottom: 44px; padding-right: 5rem; margin-top: 12rem; margin-left: 2vh; margin-bottom: 2px; margin-right: 10em;', ), @@ -119,13 +119,13 @@ public function data_generate_styles_fixtures() { 'letterSpacing' => '2', ), ), - 'options' => null, + 'options' => null, 'expected_output' => array( 'css' => 'font-family: Roboto,Oxygen-Sans,Ubuntu,sans-serif; font-style: italic; font-weight: 800; line-height: 1.3; text-decoration: underline; text-transform: uppercase; letter-spacing: 2;', ), ), - 'style_block_with_selector' => array( + 'style_block_with_selector' => array( 'block_styles' => array( 'spacing' => array( 'padding' => array( @@ -136,31 +136,31 @@ public function data_generate_styles_fixtures() { ), ), ), - 'options' => array( 'selector' => '.wp-selector > p' ), + 'options' => array( 'selector' => '.wp-selector > p' ), '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( + 'elements_with_css_var_value' => array( 'block_styles' => array( - 'color' => array( + 'color' => array( 'text' => 'var:preset|color|my-little-pony', ), ), - 'options' => array( 'selector' => '.wp-selector' ), + 'options' => array( 'selector' => '.wp-selector' ), 'expected_output' => array( 'css' => '.wp-selector { color: var(--wp--preset--color--my-little-pony); }', ), ), - 'elements_with_invalid_preset_style_property' => array( + 'elements_with_invalid_preset_style_property' => array( 'block_styles' => array( - 'color' => array( + 'color' => array( 'text' => 'var:preset|invalid_property|my-little-pony', ), ), - 'options' => array( 'selector' => '.wp-selector' ), + 'options' => array( 'selector' => '.wp-selector' ), 'expected_output' => array(), ), @@ -176,7 +176,7 @@ public function data_generate_styles_fixtures() { 'fontFamily' => 'var:preset|font-family|totally-awesome', ), ), - 'options' => array( 'classnames' => true ), + 'options' => array( 'classnames' => true ), 'expected_output' => array( 'classnames' => 'has-text-color has-copper-socks-color has-background has-splendid-carrot-background-color has-like-wow-dude-gradient-background has-fantastic-font-size has-totally-awesome-font-family', ), @@ -189,7 +189,7 @@ public function data_generate_styles_fixtures() { 'background' => null, ), ), - 'options' => array( 'classnames' => true ), + 'options' => array( 'classnames' => true ), 'expected_output' => array( 'css' => 'color: #fff;', 'classnames' => 'has-text-color', @@ -207,7 +207,7 @@ public function data_generate_styles_fixtures() { 'padding' => 'var:preset|spacing|padding', ), ), - 'options' => array( 'classnames' => true ), + 'options' => array( 'classnames' => true ), 'expected_output' => array( 'classnames' => 'has-text-color has-background', ), @@ -224,7 +224,7 @@ public function data_generate_styles_fixtures() { ), ), ), - 'options' => array( 'classnames' => true ), + 'options' => array( 'classnames' => true ), 'expected_output' => array(), ), ); From 6e2b6f0c62c05f2c549080b51349c46b38ea6553 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Tue, 24 May 2022 15:47:15 +1000 Subject: [PATCH 06/10] Favouring a `css_vars` options flag over a `classnames` options flag. This means the style engine will always attempt to output classes by default using the incoming `var:preset|*` values. The consumer is free to ignore them. The style engine will skip over `var:preset|*` values when building CSS rules. To return CSS vars in the CSS rules however, the consumer will need to specify a `css_vars` flag. The style engine will try to return `var(--preset--*)` values from `var:preset|*` values when building CSS rules. This gives us a way to return both the classname and CSS var if we wish, rather than a CSS var OR a classname. --- lib/block-supports/colors.php | 2 +- lib/block-supports/elements.php | 1 + lib/block-supports/typography.php | 2 +- .../style-engine/class-wp-style-engine.php | 22 +++++------- .../phpunit/class-wp-style-engine-test.php | 35 ++++++++++++++----- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/lib/block-supports/colors.php b/lib/block-supports/colors.php index ab1aa3dd86d177..fe51ffede8d49a 100644 --- a/lib/block-supports/colors.php +++ b/lib/block-supports/colors.php @@ -102,7 +102,7 @@ function gutenberg_apply_colors_support( $block_type, $block_attributes ) { } $attributes = array(); - $styles = gutenberg_style_engine_generate( array( 'color' => $color_block_styles ), array( 'classnames' => true ) ); + $styles = gutenberg_style_engine_generate( array( 'color' => $color_block_styles ) ); if ( ! empty( $styles['classnames'] ) ) { $attributes['class'] = $styles['classnames']; diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 636534fe4928d1..f7c7240ed2b560 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -109,6 +109,7 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { $link_block_styles, array( 'selector' => ".$class_name a", + 'css_vars' => true, 'enqueue_block_support_styles' => true, ) ); diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 9312e7f7c5f59e..35912e59e3a6bf 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -141,7 +141,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { } $attributes = array(); - $styles = gutenberg_style_engine_generate( array( 'typography' => $typography_block_styles ), array( 'classnames' => true ) ); + $styles = gutenberg_style_engine_generate( array( 'typography' => $typography_block_styles ) ); if ( ! empty( $styles['classnames'] ) ) { $attributes['class'] = $styles['classnames']; diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index a1a807ec03ff5e..a51afb3c20288c 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -154,8 +154,8 @@ public static function get_instance() { /** * Extracts the slug in kebab case from a preset string, e.g., "heavenly-blue" from 'var:preset|color|heavenlyBlue'. * - * @param string $style_value A single css preset value. - * @param string $property_key The CSS property that is the second element of the preset string. Used for matching. + * @param string? $style_value A single css preset value. + * @param string $property_key The CSS property that is the second element of the preset string. Used for matching. * * @return string|null The slug, or null if not found. */ @@ -257,7 +257,7 @@ protected static function get_css( $style_value, $style_definition, $should_retu * @param array $options array( * 'enqueue_block_support_styles' => (boolean) Whether to register generated styles and output them together in a style block. A `selector` is required. * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. - * 'classnames' => (boolean) Whether to return classnames. If `true` var:? values will be parsed to return classnames instead of CSS vars. Default is `false`. + * 'css_vars' => (boolean) Whether to covert CSS values to var() values. If `true` the style engine will try to parse var:? values and output var( --wp--preset--* ) rules. Default is `false`. * );. * * @return array|null array( @@ -270,10 +270,10 @@ public function generate( $block_styles, $options ) { return null; } - $css_rules = array(); - $classnames = array(); - $should_generate_classnames_from_presets = isset( $options['classnames'] ) && true === $options['classnames']; - $should_enqueue_block_support_styles = isset( $options['enqueue_block_support_styles'] ) && true === $options['enqueue_block_support_styles']; + $css_rules = array(); + $classnames = array(); + $should_css_vars_from_presets = isset( $options['css_vars'] ) && true === $options['css_vars']; + $should_enqueue_block_support_styles = isset( $options['enqueue_block_support_styles'] ) && true === $options['enqueue_block_support_styles']; // Collect CSS and classnames. foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { @@ -288,12 +288,8 @@ public function generate( $block_styles, $options ) { continue; } - // Generate classnames from var:? values. - if ( $should_generate_classnames_from_presets ) { - $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); - } - - $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, ! $should_generate_classnames_from_presets ) ); + $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); + $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, $should_css_vars_from_presets ) ); } } 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 306066823cd758..d6c83f85394023 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -76,7 +76,7 @@ public function data_generate_styles_fixtures() { 'margin' => '111px', ), ), - 'options' => array( 'classnames' => true ), + 'options' => array(), 'expected_output' => array( 'css' => 'margin: 111px;', 'classnames' => 'has-text-color has-texas-flood-color', @@ -148,9 +148,13 @@ public function data_generate_styles_fixtures() { 'text' => 'var:preset|color|my-little-pony', ), ), - 'options' => array( 'selector' => '.wp-selector' ), + 'options' => array( + 'selector' => '.wp-selector', + 'css_vars' => true, + ), 'expected_output' => array( - 'css' => '.wp-selector { color: var(--wp--preset--color--my-little-pony); }', + 'css' => '.wp-selector { color: var(--wp--preset--color--my-little-pony); }', + 'classnames' => 'has-text-color has-my-little-pony-color', ), ), @@ -161,7 +165,9 @@ public function data_generate_styles_fixtures() { ), ), 'options' => array( 'selector' => '.wp-selector' ), - 'expected_output' => array(), + 'expected_output' => array( + 'classnames' => 'has-text-color', + ), ), 'valid_classnames_deduped' => array( @@ -176,12 +182,25 @@ public function data_generate_styles_fixtures() { 'fontFamily' => 'var:preset|font-family|totally-awesome', ), ), - 'options' => array( 'classnames' => true ), + 'options' => array(), 'expected_output' => array( 'classnames' => 'has-text-color has-copper-socks-color has-background has-splendid-carrot-background-color has-like-wow-dude-gradient-background has-fantastic-font-size has-totally-awesome-font-family', ), ), + 'valid_classnames_and_css_vars' => array( + 'block_styles' => array( + 'color' => array( + 'text' => 'var:preset|color|teal-independents', + ), + ), + 'options' => array( 'css_vars' => true ), + 'expected_output' => array( + 'css' => 'color: var(--wp--preset--color--teal-independents);', + 'classnames' => 'has-text-color has-teal-independents-color', + ), + ), + 'valid_classnames_with_null_style_values' => array( 'block_styles' => array( 'color' => array( @@ -189,7 +208,7 @@ public function data_generate_styles_fixtures() { 'background' => null, ), ), - 'options' => array( 'classnames' => true ), + 'options' => array(), 'expected_output' => array( 'css' => 'color: #fff;', 'classnames' => 'has-text-color', @@ -207,7 +226,7 @@ public function data_generate_styles_fixtures() { 'padding' => 'var:preset|spacing|padding', ), ), - 'options' => array( 'classnames' => true ), + 'options' => array(), 'expected_output' => array( 'classnames' => 'has-text-color has-background', ), @@ -224,7 +243,7 @@ public function data_generate_styles_fixtures() { ), ), ), - 'options' => array( 'classnames' => true ), + 'options' => array(), 'expected_output' => array(), ), ); From 93492ac4d776c7e3a68afc6fecce1434ffb665ae Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 27 May 2022 15:38:14 +1000 Subject: [PATCH 07/10] Removing registration, bundling and rendering of block styles. Will separate that out into another PR> --- lib/block-supports/elements.php | 11 +-- .../style-engine/class-wp-style-engine.php | 69 +------------------ 2 files changed, 8 insertions(+), 72 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index f7c7240ed2b560..9230fd48b1c102 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -105,14 +105,17 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { $link_block_styles = isset( $element_block_styles['link'] ) ? $element_block_styles['link'] : null; if ( $link_block_styles ) { - gutenberg_style_engine_generate( + $styles = gutenberg_style_engine_generate( $link_block_styles, array( - 'selector' => ".$class_name a", - 'css_vars' => true, - 'enqueue_block_support_styles' => true, + 'selector' => ".$class_name a", + 'css_vars' => true, ) ); + + if ( ! empty( $styles['css'] ) ) { + gutenberg_enqueue_block_support_styles( $styles['css'] ); + } } return null; diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index a51afb3c20288c..c0919f43fd714d 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -30,13 +30,6 @@ class WP_Style_Engine { */ private static $instance = null; - /** - * Registered block support styles. - * - * @var WP_Style_Engine|null - */ - private $registered_block_support_styles = array(); - /** * Style definitions that contain the instructions to * parse/output valid Gutenberg styles from a block's attributes. @@ -129,13 +122,6 @@ class WP_Style_Engine { ), ); - /** - * Register action for outputting styles when the class is constructed. - */ - public function __construct() { - $this->enqueue_block_support_styles(); - } - /** * Utility method to retrieve the main instance of the class. * @@ -255,7 +241,6 @@ protected static function get_css( $style_value, $style_definition, $should_retu * * @param array $block_styles An array of styles from a block's attributes. * @param array $options array( - * 'enqueue_block_support_styles' => (boolean) Whether to register generated styles and output them together in a style block. A `selector` is required. * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. * 'css_vars' => (boolean) Whether to covert CSS values to var() values. If `true` the style engine will try to parse var:? values and output var( --wp--preset--* ) rules. Default is `false`. * );. @@ -310,19 +295,12 @@ public function generate( $block_styles, $options ) { // Return css, if any. if ( ! empty( $css ) ) { + // Return an entire rule if there is a selector. if ( $selector ) { $style_block = "$selector { "; $style_block .= implode( ' ', $css ); $style_block .= ' }'; $styles_output['css'] = $style_block; - - // Enqueue block support styles where there is a selector. - if ( $should_enqueue_block_support_styles ) { - if ( isset( $this->registered_block_support_styles[ $selector ] ) ) { - $css = array_unique( array_merge( $this->registered_block_support_styles[ $selector ], $css ) ); - } - $this->registered_block_support_styles[ $selector ] = $css; - } } else { $styles_output['css'] = implode( ' ', $css ); } @@ -335,51 +313,6 @@ public function generate( $block_styles, $options ) { return $styles_output; } - - /** - * Prints registered styles in the page head or footer. - * - * @see $this->enqueue_block_support_styles - */ - public function output_registered_block_support_styles() { - if ( empty( $this->registered_block_support_styles ) ) { - return; - } - - $output = ''; - - foreach ( $this->registered_block_support_styles as $selector => $rules ) { - $output .= "\t$selector { "; - $output .= implode( ' ', $rules ); - $output .= " }\n"; - } - - echo "\n"; - } - - /** - * Taken from gutenberg_enqueue_block_support_styles() - * - * This function takes care of adding inline styles - * in the proper place, depending on the theme in use. - * - * For block themes, it's loaded in the head. - * For classic ones, it's loaded in the body - * because the wp_head action happens before - * the render_block. - * - * @see gutenberg_enqueue_block_support_styles() - */ - private function enqueue_block_support_styles() { - $action_hook_name = 'wp_footer'; - if ( wp_is_block_theme() ) { - $action_hook_name = 'wp_head'; - } - add_action( - $action_hook_name, - array( $this, 'output_registered_block_support_styles' ) - ); - } } /** From e59101ffe5bb40e4cee6c99f8ea1eab7f6d92ec9 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 27 May 2022 15:52:04 +1000 Subject: [PATCH 08/10] Remove unused var --- packages/style-engine/class-wp-style-engine.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index c0919f43fd714d..9fea7fbb8eb7f6 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -255,10 +255,9 @@ public function generate( $block_styles, $options ) { return null; } - $css_rules = array(); - $classnames = array(); - $should_css_vars_from_presets = isset( $options['css_vars'] ) && true === $options['css_vars']; - $should_enqueue_block_support_styles = isset( $options['enqueue_block_support_styles'] ) && true === $options['enqueue_block_support_styles']; + $css_rules = array(); + $classnames = array(); + $should_css_vars_from_presets = isset( $options['css_vars'] ) && true === $options['css_vars']; // Collect CSS and classnames. foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { From 7a1947696b7ab5698a443c34fa6aa1fbd641762f Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 2 Jun 2022 15:10:20 +1000 Subject: [PATCH 09/10] Typo in comments --- packages/style-engine/class-wp-style-engine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 9fea7fbb8eb7f6..d43b26d7d5ff87 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -203,7 +203,7 @@ protected static function get_css( $style_value, $style_definition, $should_retu // Before default processing, style definitions could define a callable `value_func` to generate custom CSS rules at this point. $style_property = $style_definition['property_key']; - // Build CSS var values from var:? values, e..g, `var(--wp--css--rule-slug )` + // 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. if ( is_string( $style_value ) && strpos( $style_value, 'var:' ) !== false ) { if ( $should_return_css_vars && $style_definition['css_vars'] ) { From c33a76565117b062bc70459fde96930c2fbfc596 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 3 Jun 2022 11:48:19 +1000 Subject: [PATCH 10/10] Updating comments Fixing typos Renaming variables for clarity Props @andrewserong --- .../style-engine/class-wp-style-engine.php | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index d43b26d7d5ff87..c14b0e731e8646 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -35,8 +35,10 @@ class WP_Style_Engine { * parse/output valid Gutenberg styles from a block's attributes. * For every style definition, the follow properties are valid: * - classnames => an array of classnames to be returned for block styles. The key is a classname or pattern. - * A value of `true` means the classname should be applied always. Otherwise a valid CSS property - * to match the incoming value, e.g., "color" to match var:preset|color|somePresetName. + * A value of `true` means the classname should be applied always. Otherwise, a valid CSS property (string) + * to match the incoming value, e.g., "color" to match var:preset|color|somePresetSlug. + * - css_vars => an array of key value pairs used to generate CSS var values. The key is a CSS var pattern, whose `$slug` fragment will be replaced with a preset slug. + * The value should be a valid CSS property (string) to match the incoming value, e.g., "color" to match var:preset|color|somePresetSlug. * - property_key => the key that represents a valid CSS property, e.g., "margin" or "border". * - path => a path that accesses the corresponding style value in the block style object. */ @@ -189,7 +191,7 @@ 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_return_css_vars Whether to try build and return CSS var values. + * @param boolean $should_return_css_vars Whether to try to build and return CSS var values. * * @return array An array of CSS rules. */ @@ -200,13 +202,12 @@ protected static function get_css( $style_value, $style_definition, $should_retu return $rules; } - // Before default processing, style definitions could define a callable `value_func` to generate custom CSS rules at this point. $style_property = $style_definition['property_key']; // 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. if ( is_string( $style_value ) && strpos( $style_value, 'var:' ) !== false ) { - if ( $should_return_css_vars && $style_definition['css_vars'] ) { + if ( $should_return_css_vars && ! empty( $style_definition['css_vars'] ) ) { foreach ( $style_definition['css_vars'] as $css_var_pattern => $property_key ) { $slug = static::get_slug_from_preset_value( $style_value, $property_key ); if ( $slug ) { @@ -222,7 +223,7 @@ protected static function get_css( $style_value, $style_definition, $should_retu } // Default rule builder. - // If the input contains an array, ee assume box model-like properties + // If the input contains an array, assume box model-like properties // for styles such as margins and padding. if ( is_array( $style_value ) ) { foreach ( $style_value as $key => $value ) { @@ -241,8 +242,8 @@ protected static function get_css( $style_value, $style_definition, $should_retu * * @param array $block_styles An array of styles from a block's attributes. * @param array $options array( - * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. - * 'css_vars' => (boolean) Whether to covert CSS values to var() values. If `true` the style engine will try to parse var:? values and output var( --wp--preset--* ) rules. Default is `false`. + * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. + * 'css_vars' => (boolean) Whether to covert CSS values to var() values. If `true` the style engine will try to parse var:? values and output var( --wp--preset--* ) rules. Default is `false`. * );. * * @return array|null array( @@ -255,9 +256,9 @@ public function generate( $block_styles, $options ) { return null; } - $css_rules = array(); - $classnames = array(); - $should_css_vars_from_presets = isset( $options['css_vars'] ) && true === $options['css_vars']; + $css_rules = array(); + $classnames = array(); + $should_return_css_vars = isset( $options['css_vars'] ) && true === $options['css_vars']; // Collect CSS and classnames. foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { @@ -273,7 +274,7 @@ public function generate( $block_styles, $options ) { } $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); - $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, $should_css_vars_from_presets ) ); + $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, $should_return_css_vars ) ); } }