Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Style engine: elements backend support #40987

Merged
merged 10 commits into from
Jun 3, 2022
42 changes: 19 additions & 23 deletions lib/block-supports/elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,40 +87,36 @@ 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'] );
$element_block_styles = isset( $block['attrs']['style']['elements'] ) ? $block['attrs']['style']['elements'] : null;

/*
* For now we only care about link color.
* This code in the future when we have a public API
* 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 );
$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",
'css_vars' => true,
)
);

$class_name = gutenberg_get_elements_class_name( $block );

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;
}
Expand Down
134 changes: 82 additions & 52 deletions packages/style-engine/class-wp-style-engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -45,6 +47,9 @@ class WP_Style_Engine {
'text' => array(
'property_key' => 'color',
'path' => array( 'color', 'text' ),
'css_vars' => array(
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
'--wp--preset--color--$slug' => 'color',
),
'classnames' => array(
'has-text-color' => true,
'has-%s-color' => 'color',
Expand Down Expand Up @@ -137,8 +142,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.
*/
Expand Down Expand Up @@ -186,41 +191,81 @@ 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<string> $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
* @param boolean $should_return_css_vars Whether to try to build and return CSS var values.
*
* @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.
protected static function get_css( $style_value, $style_definition, $should_return_css_vars ) {
$rules = array();

if ( ! $style_value ) {
return $rules;
}

$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 ) {
return array();
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 ) {
$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, 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 $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`.
* );.
*
* @return array|null array(
* 'styles' => (string) A CSS ruleset formatted to be placed in an HTML `style` attribute or tag.
* '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( $block_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();
$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 ) {
if ( ! $definition_group ) {
continue;
}

foreach ( $definition_group as $style_definition ) {
$style_value = _wp_array_get( $block_styles, $style_definition['path'], null );

Expand All @@ -229,63 +274,45 @@ public function generate( $block_styles ) {
}

$classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) );
$css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition ) );
$css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, $should_return_css_vars ) );
}
}

// Build CSS rules output.
$css_output = '';
$selector = isset( $options['selector'] ) ? $options['selector'] : null;
$css = array();
$styles_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[] = $filtered_css . ';';
}
}
}

if ( ! empty( $css_output ) ) {
$styles_output['css'] = trim( $css_output );
// 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;
} else {
$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;
}
}

/**
Expand All @@ -294,17 +321,20 @@ 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.
*
* @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(
* '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;
}
Loading