From f5d4eba19288a484f1d6aeca80fab2f09050c3ab Mon Sep 17 00:00:00 2001 From: ramonjd Date: Thu, 2 Jun 2022 15:15:01 +1000 Subject: [PATCH] Migrate get_block_classes to 6.1 Add prettify option --- .../wordpress-6.0/class-wp-theme-json-6-0.php | 8 +- .../wordpress-6.1/class-wp-theme-json-6-1.php | 86 ++++++++++++ .../style-engine/class-wp-style-engine.php | 130 ++++++++++-------- .../phpunit/class-wp-style-engine-test.php | 62 ++++++++- 4 files changed, 216 insertions(+), 70 deletions(-) diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php b/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php index c0609dd10f1ec..77d731b9d4544 100644 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php +++ b/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php @@ -330,8 +330,6 @@ protected function get_block_classes( $style_nodes ) { $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); $declarations = static::compute_style_properties( $node, $settings ); - - // 1. Separate the ones who use the general selector // and the ones who use the duotone selector. $declarations_duotone = array(); @@ -355,11 +353,7 @@ protected function get_block_classes( $style_nodes ) { } // 2. Generate the rules that use the general selector. - $styles = gutenberg_style_engine_generate( $node, array( 'selector' => $selector ) ); - if ( isset( $styles['css'] ) ) { - $block_rules .= $styles['css']; - } - + $block_rules .= static::to_ruleset( $selector, $declarations ); // 3. Generate the rules that use the duotone selector. if ( isset( $metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { 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 0331616fb1b9e..5203263639368 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 @@ -229,4 +229,90 @@ public function get_styles_for_block( $block_metadata ) { $block_rules = static::to_ruleset( $selector, $declarations ); return $block_rules; } + + /** + * Converts each style section into a list of rulesets + * containing the block styles to be appended to the stylesheet. + * + * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax + * + * For each section this creates a new ruleset such as: + * + * block-selector { + * style-property-one: value; + * } + * + * @param array $style_nodes Nodes with styles. + * @return string The new stylesheet. + */ + protected function get_block_classes( $style_nodes ) { + $block_rules = ''; + + foreach ( $style_nodes as $metadata ) { + if ( null === $metadata['selector'] ) { + continue; + } + + $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + $selector = $metadata['selector']; + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); + $declarations = static::compute_style_properties( $node, $settings ); + + // 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; + } + } + + /* + * Reset default browser margin on the root body element. + * This is set on the root selector **before** generating the ruleset + * from the `theme.json`. This is to ensure that if the `theme.json` declares + * `margin` in its `spacing` declaration for the `body` element then these + * user-generated values take precedence in the CSS cascade. + * @link https://github.com/WordPress/gutenberg/issues/36147. + */ + if ( static::ROOT_BLOCK_SELECTOR === $selector ) { + $block_rules .= "body { margin: 0; }\n"; + } + + // 2. Generate the rules that use the general selector. + //$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 the rules that use the duotone selector. + if ( isset( $metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { + $selector_duotone = static::scope_selector( $metadata['selector'], $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; }'; + $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; + $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; + + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; + if ( $has_block_gap_support ) { + $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; + $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; + } + } + } + + return $block_rules; + } } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 613e5db43ebb2..43d331c02c5fe 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -137,8 +137,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. */ @@ -190,40 +190,66 @@ 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. + $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']; + + // Here we could build CSS var values from `var:preset|?` style values, e.g, `var(--wp--css--rule-slug )` + // For now, just skip such values. if ( is_string( $style_value ) && strpos( $style_value, 'var:' ) !== false ) { - return array(); + 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; } - // 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 $rules; } /** * Returns an CSS ruleset. * Styles are bundled based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. * - * @param array $styles An array of Gutenberg styles from a block's attributes or theme JSON. - * @param array $options An array of options to determine the output. + * @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. + * 'prettify' => (boolean) If `true` adds space formatting to CSS selector + rules. + * );. * * @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. * ); */ - public function generate( $styles, $options ) { - if ( empty( $styles ) || ! is_array( $styles ) ) { + public function generate( $block_styles, $options ) { + if ( empty( $block_styles ) || ! is_array( $block_styles ) ) { return null; } - $css_rules = array(); - $classnames = array(); - $styles_output = array(); + $css_rules = array(); + $classnames = array(); // Collect CSS and classnames. foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { + if ( ! $definition_group ) { + continue; + } + foreach ( $definition_group as $style_definition ) { - $style_value = _wp_array_get( $styles, $style_definition['path'], null ); + $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); if ( empty( $style_value ) ) { continue; @@ -235,71 +261,51 @@ public function generate( $styles, $options ) { } // Build CSS rules output. - $selector = isset( $options['selector'] ) ? $options['selector'] : null; - $css_output = array(); + $selector = isset( $options['selector'] ) ? $options['selector'] : null; + $should_prettify = isset( $options['prettify'] ) ? $options['prettify'] : 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 . ';'; + if ( $should_prettify ) { + $css[] = "\t$filtered_css;\n"; + } else { + $css[] = $filtered_css . ';'; + } } } } - if ( ! empty( $css_output ) ) { + // Return css, if any. + if ( ! empty( $css ) ) { + // Return an entire rule if there is a selector. 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"; + if ( $should_prettify ) { + $style_block = "$selector {\n"; + $style_block .= implode( '', $css ); + $style_block .= "}\n"; + } else { + $style_block = "$selector { "; + $style_block .= implode( ' ', $css ); + $style_block .= ' }'; + } $styles_output['css'] = $style_block; } 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 ) ); } return $styles_output; } - - /** - * 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 - * - * @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. - * - * @return array The class name for the added style. - */ - protected static function get_css_rules( $style_value, $style_property ) { - $rules = array(); - - if ( ! $style_value ) { - return $rules; - } - - // We assume box model-like properties. - if ( is_array( $style_value ) ) { - foreach ( $style_value as $key => $value ) { - $rules[ "$style_property-$key" ] = $value; - } - } else { - $rules[ $style_property ] = $style_value; - } - - return $rules; - } } /** @@ -308,7 +314,9 @@ protected static function get_css_rules( $style_value, $style_property ) { * Returns an CSS ruleset. * Styles are bundled based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. * - * @param array $styles An array of Gutenberg styles from a block's attributes or theme JSON. + * @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. * * @return array|null array( @@ -316,10 +324,10 @@ protected static function get_css_rules( $style_value, $style_property ) { * 'classnames' => (string) Classnames separated by a space. * ); */ -function wp_style_engine_generate( $styles, $options = array() ) { +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( $styles, $options ); + 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 f9830e173e11a..15cff7f1de101 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 ); + 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 ); } @@ -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' => array(), '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,55 @@ 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 > p' ), + 'expected_output' => array( + 'css' => '.wp-selector > p { padding-top: 42px; padding-left: 2%; padding-bottom: 44px; padding-right: 5rem; }', + ), + ), + + '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; +} +', + ), + ), + 'valid_classnames_deduped' => array( 'block_styles' => array( 'color' => array( @@ -128,6 +180,7 @@ public function data_generate_styles_fixtures() { 'fontFamily' => 'var:preset|font-family|totally-awesome', ), ), + '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', ), @@ -139,11 +192,13 @@ public function data_generate_styles_fixtures() { 'background' => null, ), ), + 'options' => array(), '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' => array(), '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' => array(), 'expected_output' => array(), ), );