Skip to content

Commit

Permalink
Style engine: elements backend support (#40987)
Browse files Browse the repository at this point in the history
* Initial commit - not working
Trying to come up with a model for elements

* 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

* 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.

* 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.

* LINT!

* 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.

* Removing registration, bundling and rendering of block styles. Will separate that out into another PR>

* Remove unused var

* Typo in comments

* Updating comments
Fixing typos
Renaming variables for clarity
Props @andrewserong
  • Loading branch information
ramonjd authored Jun 3, 2022
1 parent d9e339b commit a3f478b
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 77 deletions.
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(
'--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

0 comments on commit a3f478b

Please sign in to comment.