diff --git a/lib/block-supports/colors.php b/lib/block-supports/colors.php index 266c6716e4817..fe51ffede8d49 100644 --- a/lib/block-supports/colors.php +++ b/lib/block-supports/colors.php @@ -76,66 +76,40 @@ function gutenberg_apply_colors_support( $block_type, $block_attributes ) { $has_text_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'text' ), true ) ); $has_background_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'background' ), true ) ); $has_gradients_support = _wp_array_get( $color_support, array( 'gradients' ), false ); - $classes = array(); - $styles = array(); + $color_block_styles = array(); // Text colors. // Check support for text colors. if ( $has_text_colors_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'color', 'text' ) ) { - $has_named_text_color = array_key_exists( 'textColor', $block_attributes ); - $has_custom_text_color = isset( $block_attributes['style']['color']['text'] ); - - // Apply required generic class. - if ( $has_custom_text_color || $has_named_text_color ) { - $classes[] = 'has-text-color'; - } - // Apply color class or inline style. - if ( $has_named_text_color ) { - $classes[] = sprintf( 'has-%s-color', _wp_to_kebab_case( $block_attributes['textColor'] ) ); - } elseif ( $has_custom_text_color ) { - $styles[] = sprintf( 'color: %s;', $block_attributes['style']['color']['text'] ); - } + $preset_text_color = array_key_exists( 'textColor', $block_attributes ) ? "var:preset|color|{$block_attributes['textColor']}" : null; + $custom_text_color = _wp_array_get( $block_attributes, array( 'style', 'color', 'text' ), null ); + $color_block_styles['text'] = $preset_text_color ? $preset_text_color : $custom_text_color; } // Background colors. if ( $has_background_colors_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'color', 'background' ) ) { - $has_named_background_color = array_key_exists( 'backgroundColor', $block_attributes ); - $has_custom_background_color = isset( $block_attributes['style']['color']['background'] ); - - // Apply required background class. - if ( $has_custom_background_color || $has_named_background_color ) { - $classes[] = 'has-background'; - } - // Apply background color classes or styles. - if ( $has_named_background_color ) { - $classes[] = sprintf( 'has-%s-background-color', _wp_to_kebab_case( $block_attributes['backgroundColor'] ) ); - } elseif ( $has_custom_background_color ) { - $styles[] = sprintf( 'background-color: %s;', $block_attributes['style']['color']['background'] ); - } + $preset_background_color = array_key_exists( 'backgroundColor', $block_attributes ) ? "var:preset|color|{$block_attributes['backgroundColor']}" : null; + $custom_background_color = _wp_array_get( $block_attributes, array( 'style', 'color', 'background' ), null ); + $color_block_styles['background'] = $preset_background_color ? $preset_background_color : $custom_background_color; } // Gradients. + if ( $has_gradients_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'color', 'gradients' ) ) { - $has_named_gradient = array_key_exists( 'gradient', $block_attributes ); - $has_custom_gradient = isset( $block_attributes['style']['color']['gradient'] ); - - if ( $has_named_gradient || $has_custom_gradient ) { - $classes[] = 'has-background'; - } - // Apply required background class. - if ( $has_named_gradient ) { - $classes[] = sprintf( 'has-%s-gradient-background', _wp_to_kebab_case( $block_attributes['gradient'] ) ); - } elseif ( $has_custom_gradient ) { - $styles[] = sprintf( 'background: %s;', $block_attributes['style']['color']['gradient'] ); - } + $preset_gradient_color = array_key_exists( 'gradient', $block_attributes ) ? "var:preset|gradient|{$block_attributes['gradient']}" : null; + $custom_gradient_color = _wp_array_get( $block_attributes, array( 'style', 'color', 'gradient' ), null ); + $color_block_styles['gradient'] = $preset_gradient_color ? $preset_gradient_color : $custom_gradient_color; } $attributes = array(); - if ( ! empty( $classes ) ) { - $attributes['class'] = implode( ' ', $classes ); + $styles = gutenberg_style_engine_generate( array( 'color' => $color_block_styles ) ); + + if ( ! empty( $styles['classnames'] ) ) { + $attributes['class'] = $styles['classnames']; } - if ( ! empty( $styles ) ) { - $attributes['style'] = implode( ' ', $styles ); + + if ( ! empty( $styles['css'] ) ) { + $attributes['style'] = $styles['css']; } return $attributes; diff --git a/lib/block-supports/spacing.php b/lib/block-supports/spacing.php index 86bb96598af44..b20c7d4406f4f 100644 --- a/lib/block-supports/spacing.php +++ b/lib/block-supports/spacing.php @@ -52,21 +52,17 @@ function gutenberg_apply_spacing_support( $block_type, $block_attributes ) { return $attributes; } - $style_engine = WP_Style_Engine_Gutenberg::get_instance(); $skip_padding = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'padding' ); $skip_margin = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'margin' ); $spacing_block_styles = array(); $spacing_block_styles['padding'] = $has_padding_support && ! $skip_padding ? _wp_array_get( $block_styles, array( 'spacing', 'padding' ), null ) : null; $spacing_block_styles['margin'] = $has_margin_support && ! $skip_margin ? _wp_array_get( $block_styles, array( 'spacing', 'margin' ), null ) : null; - $inline_styles = $style_engine->generate( - array( 'spacing' => $spacing_block_styles ), - array( - 'inline' => true, - ) + $styles = gutenberg_style_engine_generate( + array( 'spacing' => $spacing_block_styles ) ); - if ( ! empty( $inline_styles ) ) { - $attributes['style'] = $inline_styles; + if ( ! empty( $styles['css'] ) ) { + $attributes['style'] = $styles['css']; } return $attributes; diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 9661277c1c7b9..35912e59e3a6b 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -79,10 +79,6 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { return array(); } - $attributes = array(); - $classes = array(); - $styles = array(); - $has_font_family_support = _wp_array_get( $typography_supports, array( '__experimentalFontFamily' ), false ); $has_font_size_support = _wp_array_get( $typography_supports, array( 'fontSize' ), false ); $has_font_style_support = _wp_array_get( $typography_supports, array( '__experimentalFontStyle' ), false ); @@ -92,89 +88,107 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { $has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false ); $has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false ); - if ( $has_font_size_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'fontSize' ) ) { - $has_named_font_size = array_key_exists( 'fontSize', $block_attributes ); - $has_custom_font_size = isset( $block_attributes['style']['typography']['fontSize'] ); + // Whether to skip individual block support features. + $should_skip_font_size = gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'fontSize' ); + $should_skip_font_family = gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'fontFamily' ); + $should_skip_font_style = gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'fontStyle' ); + $should_skip_font_weight = gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'fontWeight' ); + $should_skip_line_height = gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'lineHeight' ); + $should_skip_text_decoration = gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' ); + $should_skip_text_transform = gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' ); + $should_skip_letter_spacing = gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' ); - if ( $has_named_font_size ) { - $classes[] = sprintf( 'has-%s-font-size', _wp_to_kebab_case( $block_attributes['fontSize'] ) ); - } elseif ( $has_custom_font_size ) { - $styles[] = sprintf( 'font-size: %s;', $block_attributes['style']['typography']['fontSize'] ); - } + $typography_block_styles = array(); + if ( $has_font_size_support && ! $should_skip_font_size ) { + $preset_font_size = array_key_exists( 'fontSize', $block_attributes ) ? "var:preset|font-size|{$block_attributes['fontSize']}" : null; + $custom_font_size = isset( $block_attributes['style']['typography']['fontSize'] ) ? $block_attributes['style']['typography']['fontSize'] : null; + $typography_block_styles['fontSize'] = $preset_font_size ? $preset_font_size : $custom_font_size; } - if ( $has_font_family_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'fontFamily' ) ) { - $has_named_font_family = array_key_exists( 'fontFamily', $block_attributes ); - $has_custom_font_family = isset( $block_attributes['style']['typography']['fontFamily'] ); - - if ( $has_named_font_family ) { - $classes[] = sprintf( 'has-%s-font-family', _wp_to_kebab_case( $block_attributes['fontFamily'] ) ); - } elseif ( $has_custom_font_family ) { - // Before using classes, the value was serialized as a CSS Custom Property. - // We don't need this code path when it lands in core. - $font_family_custom = $block_attributes['style']['typography']['fontFamily']; - if ( strpos( $font_family_custom, 'var:preset|font-family' ) !== false ) { - $index_to_splice = strrpos( $font_family_custom, '|' ) + 1; - $font_family_slug = _wp_to_kebab_case( substr( $font_family_custom, $index_to_splice ) ); - $font_family_custom = sprintf( 'var(--wp--preset--font-family--%s)', $font_family_slug ); - } - $styles[] = sprintf( 'font-family: %s;', $font_family_custom ); - } + if ( $has_font_family_support && ! $should_skip_font_family ) { + $preset_font_family = array_key_exists( 'fontFamily', $block_attributes ) ? "var:preset|font-family|{$block_attributes['fontFamily']}" : null; + $custom_font_family = isset( $block_attributes['style']['typography']['fontFamily'] ) ? gutenberg_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['fontFamily'], 'font-family' ) : null; + $typography_block_styles['fontFamily'] = $preset_font_family ? $preset_font_family : $custom_font_family; } - if ( $has_font_style_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'fontStyle' ) ) { - $font_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontStyle', 'font-style' ); - if ( $font_style ) { - $styles[] = $font_style; - } + if ( $has_font_style_support && ! $should_skip_font_style && isset( $block_attributes['style']['typography']['fontStyle'] ) ) { + $typography_block_styles['fontStyle'] = + gutenberg_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['fontStyle'], 'font-style' ); } - if ( $has_font_weight_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'fontWeight' ) ) { - $font_weight = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontWeight', 'font-weight' ); - if ( $font_weight ) { - $styles[] = $font_weight; - } + if ( $has_font_weight_support && ! $should_skip_font_weight && isset( $block_attributes['style']['typography']['fontWeight'] ) ) { + $typography_block_styles['fontWeight'] = + gutenberg_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['fontWeight'], 'font-weight' ); } - if ( $has_line_height_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'lineHeight' ) ) { - $has_line_height = isset( $block_attributes['style']['typography']['lineHeight'] ); - if ( $has_line_height ) { - $styles[] = sprintf( 'line-height: %s;', $block_attributes['style']['typography']['lineHeight'] ); - } + if ( $has_line_height_support && ! $should_skip_line_height ) { + $typography_block_styles['lineHeight'] = _wp_array_get( $block_attributes, array( 'style', 'typography', 'lineHeight' ), null ); } - if ( $has_text_decoration_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' ) ) { - $text_decoration_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'textDecoration', 'text-decoration' ); - if ( $text_decoration_style ) { - $styles[] = $text_decoration_style; - } + if ( $has_text_decoration_support && ! $should_skip_text_decoration && isset( $block_attributes['style']['typography']['textDecoration'] ) ) { + $typography_block_styles['textDecoration'] = + gutenberg_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['textDecoration'], 'text-decoration' ); } - if ( $has_text_transform_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' ) ) { - $text_transform_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'textTransform', 'text-transform' ); - if ( $text_transform_style ) { - $styles[] = $text_transform_style; - } + if ( $has_text_transform_support && ! $should_skip_text_transform && isset( $block_attributes['style']['typography']['textTransform'] ) ) { + $typography_block_styles['textTransform'] = + gutenberg_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['textTransform'], 'text-transform' ); } - if ( $has_letter_spacing_support && ! gutenberg_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' ) ) { - $letter_spacing_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'letterSpacing', 'letter-spacing' ); - if ( $letter_spacing_style ) { - $styles[] = $letter_spacing_style; - } + if ( $has_letter_spacing_support && ! $should_skip_letter_spacing && isset( $block_attributes['style']['typography']['letterSpacing'] ) ) { + $typography_block_styles['letterSpacing'] = + gutenberg_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['letterSpacing'], 'letter-spacing' ); } - if ( ! empty( $classes ) ) { - $attributes['class'] = implode( ' ', $classes ); + $attributes = array(); + $styles = gutenberg_style_engine_generate( array( 'typography' => $typography_block_styles ) ); + + if ( ! empty( $styles['classnames'] ) ) { + $attributes['class'] = $styles['classnames']; } - if ( ! empty( $styles ) ) { - $attributes['style'] = implode( ' ', $styles ); + + if ( ! empty( $styles['css'] ) ) { + $attributes['style'] = $styles['css']; } return $attributes; } /** + * Note: this method is for backwards compatibility. + * It mostly replaces `gutenberg_typography_get_css_variable_inline_style()`. + * + * Generates an inline style value for a typography feature e.g. text decoration, + * text transform, and font style. + * + * @param string $style_value A raw style value for a single typography feature from a block's style attribute. + * @param string $css_property Slug for the CSS property the inline style sets. + * + * @return string? A CSS inline style value. + */ +function gutenberg_typography_get_preset_inline_style_value( $style_value, $css_property ) { + // If the style value is not a preset CSS variable go no further. + if ( empty( $style_value ) || strpos( $style_value, "var:preset|{$css_property}|" ) === false ) { + return $style_value; + } + + // For backwards compatibility. + // Presets were removed in https://github.com/WordPress/gutenberg/pull/27555. + // We have a preset CSS variable as the style. + // Get the style value from the string and return CSS style. + $index_to_splice = strrpos( $style_value, '|' ) + 1; + $slug = _wp_to_kebab_case( substr( $style_value, $index_to_splice ) ); + + // Return the actual CSS inline style value e.g. `var(--wp--preset--text-decoration--underline);`. + return sprintf( 'var(--wp--preset--%s--%s);', $css_property, $slug ); +} + +/** + * Deprecated. + * This method is no longer used and will have to be deprecated in Core. + * + * It can be deleted once migrated to the next WordPress version. + * * Generates an inline style for a typography feature e.g. text decoration, * text transform, and font style. * @@ -213,5 +227,3 @@ function gutenberg_typography_get_css_variable_inline_style( $attributes, $featu 'apply' => 'gutenberg_apply_typography_support', ) ); - - diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index c07075184c41b..5687a2f422186 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -16,6 +16,11 @@ * * Consolidates rendering block styles to reduce duplication and streamline * CSS styles generation. + * + * This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes). + * This is a low-level API that may need to do breaking changes. Please, use gutenberg_style_engine_get_styles instead. + * + * @access private */ class WP_Style_Engine { /** @@ -29,23 +34,87 @@ class WP_Style_Engine { * Style definitions that contain the instructions to * 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. * - 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. - * - value_func => a function to generate an array of valid CSS rules for a particular style object. - * For example, `'padding' => 'array( 'top' => '1em' )` will return `array( 'padding-top' => '1em' )` */ const BLOCK_STYLE_DEFINITIONS_METADATA = array( - 'spacing' => array( + 'color' => array( + 'text' => array( + 'property_key' => 'color', + 'path' => array( 'color', 'text' ), + 'classnames' => array( + 'has-text-color' => true, + 'has-%s-color' => 'color', + ), + ), + 'background' => array( + 'property_key' => 'background-color', + 'path' => array( 'color', 'background' ), + 'classnames' => array( + 'has-background' => true, + 'has-%s-background-color' => 'color', + ), + ), + 'gradient' => array( + 'property_key' => 'background', + 'path' => array( 'color', 'gradient' ), + 'classnames' => array( + 'has-background' => true, + 'has-%s-gradient-background' => 'gradient', + ), + ), + ), + 'spacing' => array( 'padding' => array( 'property_key' => 'padding', 'path' => array( 'spacing', 'padding' ), - 'value_func' => 'static::get_css_box_rules', ), 'margin' => array( 'property_key' => 'margin', 'path' => array( 'spacing', 'margin' ), - 'value_func' => 'static::get_css_box_rules', + ), + ), + 'typography' => array( + 'fontSize' => array( + 'property_key' => 'font-size', + 'path' => array( 'typography', 'fontSize' ), + 'classnames' => array( + 'has-%s-font-size' => 'font-size', + ), + ), + 'fontFamily' => array( + 'property_key' => 'font-family', + 'path' => array( 'typography', 'fontFamily' ), + 'classnames' => array( + 'has-%s-font-family' => 'font-family', + ), + ), + 'fontStyle' => array( + 'property_key' => 'font-style', + 'path' => array( 'typography', 'fontStyle' ), + ), + 'fontWeight' => array( + 'property_key' => 'font-weight', + 'path' => array( 'typography', 'fontWeight' ), + ), + 'lineHeight' => array( + 'property_key' => 'line-height', + 'path' => array( 'typography', 'lineHeight' ), + ), + 'textDecoration' => array( + 'property_key' => 'text-decoration', + 'path' => array( 'typography', 'textDecoration' ), + ), + 'textTransform' => array( + 'property_key' => 'text-transform', + 'path' => array( 'typography', 'textTransform' ), + ), + 'letterSpacing' => array( + 'property_key' => 'letter-spacing', + 'path' => array( 'typography', 'letterSpacing' ), ), ), ); @@ -66,26 +135,68 @@ public static function get_instance() { } /** - * Returns a CSS ruleset based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. + * 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. + * + * @return string|null The slug, or null if not found. + */ + protected static function get_slug_from_preset_value( $style_value, $property_key ) { + if ( is_string( $style_value ) && strpos( $style_value, "var:preset|{$property_key}|" ) !== false ) { + $index_to_splice = strrpos( $style_value, '|' ) + 1; + return _wp_to_kebab_case( substr( $style_value, $index_to_splice ) ); + } + return null; + } + + /** + * Returns classnames, and generates classname(s) from a CSS preset property pattern, e.g., 'var:preset|color|heavenly-blue'. * - * @param string|array $style_value A single raw Gutenberg style attributes value for a CSS property. - * @param array $path An array of strings representing a path to the style value. + * @param array $style_value A single raw style value or css preset property from the generate() $block_styles array. + * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. * - * @return array A CSS ruleset compatible with generate(). + * @return array An array of CSS classnames. */ - protected function get_block_style_css_rules( $style_value, $path ) { - $style_definition = _wp_array_get( static::BLOCK_STYLE_DEFINITIONS_METADATA, $path, null ); - - if ( ! empty( $style_definition ) ) { - if ( - isset( $style_definition['value_func'] ) && - is_callable( $style_definition['value_func'] ) - ) { - return call_user_func( $style_definition['value_func'], $style_value, $style_definition['property_key'] ); + protected static function get_classnames( $style_value, $style_definition ) { + $classnames = array(); + if ( ! empty( $style_definition['classnames'] ) ) { + foreach ( $style_definition['classnames'] as $classname => $property_key ) { + if ( true === $property_key ) { + $classnames[] = $classname; + } + + $slug = static::get_slug_from_preset_value( $style_value, $property_key ); + + if ( $slug ) { + // Right now we expect a classname pattern to be stored in BLOCK_STYLE_DEFINITIONS_METADATA. + // One day, if there are no stored schemata, we could allow custom patterns or + // generate classnames based on other properties + // such as a path or a value or a prefix passed in options. + $classnames[] = sprintf( $classname, $slug ); + } } } - return array(); + return $classnames; + } + + /** + * Returns CSS rules 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. + * + * @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'] ); } /** @@ -93,72 +204,78 @@ protected function get_block_style_css_rules( $style_value, $path ) { * 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 = array( - * 'inline' => (boolean) Whether to return inline CSS rules destined to be inserted in an HTML `style` attribute. - * 'path' => (array) Specify a block style to generate, otherwise it'll try all in BLOCK_STYLE_DEFINITIONS_METADATA. - * );. * - * @return string A CSS ruleset formatted to be placed in an HTML `style` attribute. + * @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. + * ); */ - public function generate( $block_styles, $options = array() ) { - $output = ''; - - if ( empty( $block_styles ) ) { - return $output; + public function generate( $block_styles ) { + if ( empty( $block_styles ) || ! is_array( $block_styles ) ) { + return null; } - $rules = array(); + $css_rules = array(); + $classnames = array(); + $styles_output = array(); - // If a path to a specific block style is defined, only return rules for that style. - if ( isset( $options['path'] ) && is_array( $options['path'] ) ) { - $style_value = _wp_array_get( $block_styles, $options['path'], null ); - if ( empty( $style_value ) ) { - return $output; - } - $rules = array_merge( $rules, $this->get_block_style_css_rules( $style_value, $options['path'] ) ); - } else { - // Otherwise build them all. - foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { - foreach ( $definition_group as $style_definition ) { - $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); - if ( empty( $style_value ) ) { - continue; - } - $rules = array_merge( $rules, $this->get_block_style_css_rules( $style_value, $style_definition['path'] ) ); + // Collect CSS and classnames. + foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { + foreach ( $definition_group as $style_definition ) { + $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); + + if ( empty( $style_value ) ) { + 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 ) ); } } - if ( ! empty( $rules ) ) { + // Build CSS rules output. + $css_output = ''; + if ( ! empty( $css_rules ) ) { // Generate inline style rules. - if ( isset( $options['inline'] ) && true === $options['inline'] ) { - foreach ( $rules as $rule => $value ) { - $filtered_css = esc_html( safecss_filter_attr( "{$rule}:{$value}" ) ); - if ( ! empty( $filtered_css ) ) { - $output .= $filtered_css . ';'; - } + // 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 . '; '; } } } - return $output; + if ( ! empty( $css_output ) ) { + $styles_output['css'] = trim( $css_output ); + } + + if ( ! empty( $classnames ) ) { + $styles_output['classnames'] = implode( ' ', array_unique( $classnames ) ); + } + + return $styles_output; } /** - * Returns a CSS ruleset for box model styles such as margins, padding, and borders. + * 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. */ - public static function get_css_box_rules( $style_value, $style_property ) { + 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; @@ -166,17 +283,28 @@ public static function get_css_box_rules( $style_value, $style_property ) { } else { $rules[ $style_property ] = $style_value; } + return $rules; } } /** - * This function returns the Style Engine instance. + * Global public interface method to WP_Style_Engine->generate. + * + * Returns an CSS ruleset. + * 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. * - * @return WP_Style_Engine + * @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_get_style_engine() { +function wp_style_engine_generate( $block_styles ) { if ( class_exists( 'WP_Style_Engine' ) ) { - return WP_Style_Engine::get_instance(); + $style_engine = WP_Style_Engine::get_instance(); + return $style_engine->generate( $block_styles ); } + 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 2e0e9afe17bfc..f9830e173e11a 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -13,16 +13,12 @@ */ class WP_Style_Engine_Test extends WP_UnitTestCase { /** - * Tests various manifestations of the $block_styles argument. + * Tests generating styles and classnames based on various manifestations of the $block_styles argument. * - * @dataProvider data_block_styles_fixtures + * @dataProvider data_generate_styles_fixtures */ - function test_generate_css( $block_styles, $options, $expected_output ) { - $style_engine = WP_Style_Engine::get_instance(); - $generated_styles = $style_engine->generate( - $block_styles, - $options - ); + function test_generate_styles( $block_styles, $expected_output ) { + $generated_styles = wp_style_engine_generate( $block_styles ); $this->assertSame( $expected_output, $generated_styles ); } @@ -31,42 +27,30 @@ function test_generate_css( $block_styles, $options, $expected_output ) { * * @return array */ - public function data_block_styles_fixtures() { + public function data_generate_styles_fixtures() { return array( 'default_return_value' => array( 'block_styles' => array(), - 'options' => null, - 'expected_output' => '', + 'expected_output' => null, ), 'inline_invalid_block_styles_empty' => array( - 'block_styles' => array(), - 'options' => array( - 'path' => array( 'spacing', 'padding' ), - 'inline' => true, - ), - 'expected_output' => '', + 'block_styles' => 'hello world!', + 'expected_output' => null, ), 'inline_invalid_block_styles_unknown_style' => array( 'block_styles' => array( 'pageBreakAfter' => 'verso', ), - 'options' => array( - 'inline' => true, - ), - 'expected_output' => '', + 'expected_output' => array(), ), 'inline_invalid_block_styles_unknown_definition' => array( 'block_styles' => array( 'pageBreakAfter' => 'verso', ), - 'options' => array( - 'path' => array( 'pageBreakAfter', 'verso' ), - 'inline' => true, - ), - 'expected_output' => '', + 'expected_output' => array(), ), 'inline_invalid_block_styles_unknown_property' => array( @@ -75,39 +59,25 @@ public function data_block_styles_fixtures() { 'gap' => '1000vw', ), ), - 'options' => array( - 'path' => array( 'spacing', 'padding' ), - 'inline' => true, - ), - 'expected_output' => '', + 'expected_output' => array(), ), - 'inline_invalid_multiple_style_unknown_property' => array( + 'valid_inline_css_and_classnames' => array( 'block_styles' => array( - 'spacing' => array( - 'gavin' => '1000vw', + 'color' => array( + 'text' => 'var:preset|color|texas-flood', ), - ), - 'options' => array( - 'inline' => true, - ), - 'expected_output' => '', - ), - - 'inline_valid_single_style_string' => array( - 'block_styles' => array( 'spacing' => array( 'margin' => '111px', ), ), - 'options' => array( - 'path' => array( 'spacing', 'margin' ), - 'inline' => true, + 'expected_output' => array( + 'css' => 'margin: 111px;', + 'classnames' => 'has-text-color has-texas-flood-color', ), - 'expected_output' => 'margin:111px;', ), - 'inline_valid_single_style' => array( + 'inline_valid_box_model_style' => array( 'block_styles' => array( 'spacing' => array( 'padding' => array( @@ -124,34 +94,83 @@ public function data_block_styles_fixtures() { ), ), ), - 'options' => array( - 'path' => array( 'spacing', 'padding' ), - 'inline' => true, + '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;', ), - 'expected_output' => 'padding-top:42px;padding-left:2%;padding-bottom:44px;padding-right:5rem;', ), - 'inline_valid_multiple_style' => array( + 'inline_valid_typography_style' => array( + 'block_styles' => array( + 'typography' => array( + 'fontSize' => 'clamp(2em, 2vw, 4em)', + 'fontFamily' => 'Roboto,Oxygen-Sans,Ubuntu,sans-serif', + 'fontStyle' => 'italic', + 'fontWeight' => '800', + 'lineHeight' => '1.3', + 'textDecoration' => 'underline', + 'textTransform' => 'uppercase', + 'letterSpacing' => '2', + ), + ), + '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;', + ), + ), + 'valid_classnames_deduped' => array( + 'block_styles' => array( + 'color' => array( + 'text' => 'var:preset|color|copper-socks', + 'background' => 'var:preset|color|splendid-carrot', + 'gradient' => 'var:preset|gradient|like-wow-dude', + ), + 'typography' => array( + 'fontSize' => 'var:preset|font-size|fantastic', + 'fontFamily' => 'var:preset|font-family|totally-awesome', + ), + ), + '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( + 'text' => '#fff', + 'background' => null, + ), + ), + 'expected_output' => array( + 'css' => 'color: #fff;', + 'classnames' => 'has-text-color', + ), + ), + 'invalid_classnames_preset_value' => array( + 'block_styles' => array( + 'color' => array( + 'text' => 'var:cheese|color|fantastic', + 'background' => 'var:preset|fromage|fantastic', + ), 'spacing' => array( - 'padding' => array( - 'top' => '42px', - 'left' => '2%', - 'bottom' => '44px', - 'right' => '5rem', + 'margin' => 'var:cheese|spacing|margin', + 'padding' => 'var:preset|spacing|padding', + ), + ), + 'expected_output' => array( + 'classnames' => 'has-text-color has-background', + ), + ), + 'invalid_classnames_options' => array( + 'block_styles' => array( + 'typography' => array( + 'fontSize' => array( + 'tomodachi' => 'friends', ), - 'margin' => array( - 'top' => '12rem', - 'left' => '2vh', - 'bottom' => '2px', - 'right' => '10em', + 'fontFamily' => array( + 'oishii' => 'tasty', ), ), ), - 'options' => array( - 'inline' => true, - ), - 'expected_output' => 'padding-top:42px;padding-left:2%;padding-bottom:44px;padding-right:5rem;margin-top:12rem;margin-left:2vh;margin-bottom:2px;margin-right:10em;', + 'expected_output' => array(), ), ); } diff --git a/phpunit/block-supports/colors-test.php b/phpunit/block-supports/colors-test.php index a9708c569c1f7..11be67427a972 100644 --- a/phpunit/block-supports/colors-test.php +++ b/phpunit/block-supports/colors-test.php @@ -59,7 +59,7 @@ function test_color_slugs_with_numbers_are_kebab_cased_properly() { ); $actual = gutenberg_apply_colors_support( $block_type, $block_atts ); - $expected = array( 'class' => 'has-text-color has-fg-1-color has-background has-bg-2-background-color has-background has-gr-3-gradient-background' ); + $expected = array( 'class' => 'has-text-color has-fg-1-color has-background has-bg-2-background-color has-gr-3-gradient-background' ); $this->assertSame( $expected, $actual ); } diff --git a/phpunit/block-supports/spacing-test.php b/phpunit/block-supports/spacing-test.php index 618c809b84f6a..613f415557cfd 100644 --- a/phpunit/block-supports/spacing-test.php +++ b/phpunit/block-supports/spacing-test.php @@ -62,7 +62,7 @@ function test_spacing_style_is_applied() { $actual = gutenberg_apply_spacing_support( $block_type, $block_atts ); $expected = array( - 'style' => 'padding:111px;margin-top:1px;margin-right:2px;margin-bottom:3px;margin-left:4px;', + 'style' => 'padding: 111px; margin-top: 1px; margin-right: 2px; margin-bottom: 3px; margin-left: 4px;', ); $this->assertSame( $expected, $actual ); @@ -152,7 +152,7 @@ function test_margin_with_individual_skipped_serialization_block_supports() { $actual = gutenberg_apply_spacing_support( $block_type, $block_atts ); $expected = array( - 'style' => 'padding-top:1px;padding-right:2px;padding-bottom:3px;padding-left:4px;', + 'style' => 'padding-top: 1px; padding-right: 2px; padding-bottom: 3px; padding-left: 4px;', ); $this->assertSame( $expected, $actual );