-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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: generate root global styles from global styles object #42143
Changes from all commits
4965b94
78e79d7
e8a0dd0
629e8c6
31ef9cb
5cc7119
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -274,6 +274,20 @@ protected static function get_css_var_value( $style_value, $css_vars ) { | |
return null; | ||
} | ||
|
||
/** | ||
* Using a given path, return a value from the $block_styles object. | ||
* | ||
* @param array $block_styles Styles from a block's attributes object. | ||
* @param string $ref A dot syntax path to another value in the $block_styles object, e.g., `styles.color.text`. | ||
* | ||
* @return string|array A style value from the block styles object. | ||
*/ | ||
protected static function get_ref_value( $block_styles = array(), $ref = '' ) { | ||
$ref = preg_replace( '/^styles\./', '', $ref ); | ||
$path = explode( '.', $ref ); | ||
return _wp_array_get( $block_styles, $path, null ); | ||
} | ||
|
||
/** | ||
* Checks whether an incoming block style value is valid. | ||
* | ||
|
@@ -334,20 +348,22 @@ 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_skip_css_vars Whether to skip compiling CSS var values. | ||
* @param array $block_styles Styles from a block's attributes object. | ||
* @param array<string> $options Options passed to the public generator functions. | ||
* | ||
* @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). | ||
*/ | ||
protected static function get_css_declarations( $style_value, $style_definition, $should_skip_css_vars = false ) { | ||
protected static function get_css_declarations( $style_value, $style_definition, $block_styles, $options ) { | ||
if ( | ||
isset( $style_definition['value_func'] ) && | ||
is_callable( $style_definition['value_func'] ) | ||
) { | ||
return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $should_skip_css_vars ); | ||
return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $block_styles, $options ); | ||
} | ||
|
||
$css_declarations = array(); | ||
$style_property_keys = $style_definition['property_keys']; | ||
$css_declarations = array(); | ||
$style_property_keys = $style_definition['property_keys']; | ||
$should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; | ||
|
||
// 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. | ||
|
@@ -370,6 +386,12 @@ protected static function get_css_declarations( $style_value, $style_definition, | |
$value = static::get_css_var_value( $value, $style_definition['css_vars'] ); | ||
} | ||
$individual_property = sprintf( $style_property_keys['individual'], _wp_to_kebab_case( $key ) ); | ||
|
||
// If the style value contains a reference to another value in the tree. | ||
if ( isset( $value['ref'] ) ) { | ||
$value = static::get_ref_value( $block_styles, $value['ref'] ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this assume that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it'd have to in this scenario, though it'll fail reasonably gracefully if the value isn't found. It's another argument for the one-node-at-a-time approach discussed above: letting WP_Theme_JSON do the ref value fetching for us. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to have a method that accepts an entire theme.json styles tree, then it could be a separate one where we'd assume that the entire tree is passed, e.g., There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's so fascinating these sorts of problems: even just the wording of your comment there makes it feel clearer that the theme JSON class is probably a better place to do ref lookups (as the owner of that data structure), and the style engine should probably treat whatever it's given as the "real" thing. I don't think there's any real right or wrong way to go about it, but these sorts of subtle distinctions really do help us tease apart what the style engine is and isn't! (Or how much scope we're comfortable taking on at any one time 😄) |
||
} | ||
|
||
if ( static::is_valid_style_value( $style_value ) ) { | ||
$css_declarations[ $individual_property ] = $value; | ||
} | ||
|
@@ -387,7 +409,7 @@ protected static function get_css_declarations( $style_value, $style_definition, | |
* | ||
* @param array $block_styles Styles from a block's attributes object. | ||
* @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. | ||
* 'selector' => (string) When a selector is passed, the style engine will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. | ||
* 'convert_vars_to_classnames' => (boolean) Whether to skip converting CSS var:? values to var( --wp--preset--* ) values. Default is `false`. | ||
* );. | ||
* | ||
|
@@ -401,9 +423,8 @@ public function get_block_supports_styles( $block_styles, $options ) { | |
return null; | ||
} | ||
|
||
$css_declarations = array(); | ||
$classnames = array(); | ||
$should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; | ||
$css_declarations = array(); | ||
$classnames = array(); | ||
|
||
// Collect CSS and classnames. | ||
foreach ( static::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group_key => $definition_group_style ) { | ||
|
@@ -413,12 +434,17 @@ public function get_block_supports_styles( $block_styles, $options ) { | |
foreach ( $definition_group_style as $style_definition ) { | ||
$style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); | ||
|
||
// If the style value contains a reference to another value in the tree. | ||
if ( isset( $style_value['ref'] ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rise in new global styles features such as |
||
$style_value = static::get_ref_value( $block_styles, $style_value['ref'] ); | ||
} | ||
|
||
if ( ! static::is_valid_style_value( $style_value ) ) { | ||
continue; | ||
} | ||
|
||
$classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); | ||
$css_declarations = array_merge( $css_declarations, static::get_css_declarations( $style_value, $style_definition, $should_skip_css_vars ) ); | ||
$css_declarations = array_merge( $css_declarations, static::get_css_declarations( $style_value, $style_definition, $block_styles, $options ) ); | ||
} | ||
} | ||
|
||
|
@@ -448,26 +474,72 @@ public function get_block_supports_styles( $block_styles, $options ) { | |
return $styles_output; | ||
} | ||
|
||
/** | ||
* Returns a stylesheet of CSS rules from a theme.json/global styles object. | ||
* | ||
* @param array $global_styles Styles object from theme.json. | ||
* @param array $options array( | ||
* 'selector' => (string) When a selector is passed, the style engine will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. | ||
* );. | ||
* | ||
* @return string A stylesheet. | ||
*/ | ||
public function generate_global_styles( $global_styles, $options ) { | ||
if ( empty( $global_styles ) || ! is_array( $global_styles ) ) { | ||
return null; | ||
} | ||
|
||
// The return stylesheet. | ||
$global_stylesheet = ''; | ||
|
||
// Layer 0: Root. | ||
$root_level_options_defaults = array( | ||
'selector' => 'body', | ||
); | ||
$root_level_options = wp_parse_args( $options, $root_level_options_defaults ); | ||
$root_level_styles = $this->get_block_supports_styles( | ||
$global_styles, | ||
$root_level_options | ||
); | ||
|
||
if ( ! empty( $root_level_styles['css'] ) ) { | ||
$global_stylesheet .= $root_level_styles['css'] . ' '; | ||
} | ||
|
||
// @TODO Layer 1: Elements. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be a question for a follow-up PR instead, but just curious about the potential layers in follow-ups. Currently the theme JSON class iterates over nodes, so (I wasn't sure which would be ideal, so curious to hear your thoughts about the role/responsibility of the theme JSON class versus style engine here). One way I was imagining, incorporating the ideas from #42222 is that (long term) we could potentially call the style engine once per iteration of the loop in the theme JSON class, but instead of it returning a css string immediately, it adds the rules to the style engine CSS rules store, and then once it finishes looping over each of the nodes, it calls the style engine method to retrieve all the generated CSS for output? I don't have any strong ideas about how any of that should work, so just thinking out loud 🙂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for these thoughts @andrewserong !
Very good questions. To be honest, I don't know. 😄 The main motivation behind this PR was to experiment with throwing the entire merged theme.json at the style engine and generating an entire "global styles" stylesheet. I think this could be a cool feature of the public API anyway. So you're right, this approach won't work in the current
This sounds like it would be a more sensible approach, especially if it keeps any custom values parsing (such as fetching those It's also similar to the approach we had in mind for block supports, so it would make doubly more sense to have a global styles analogue. We could put this PR to the side and concentrate our efforts on the store, which we'll need first anyway. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, that might be a good next step. Each of these explorations is a great way of working out which puzzle piece to do next, and I think the store is probably the thing that's most helpful for illuminating the path forward for this PR. It'll probably also be the thing that highlights the value of switching over to the style engine? (The clearer separation between generating and outputting styles) But this PR's great for highlighting how we can hook in the style engine, and it's really exciting seeing all the style engine efforts starting to come together like this! |
||
|
||
// @TODO Layer 2: Blocks. | ||
|
||
if ( ! empty( $global_stylesheet ) ) { | ||
return rtrim( $global_stylesheet ); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Style value parser that returns a CSS definition array comprising style properties | ||
* that have keys representing individual style properties, otherwise known as longhand CSS properties. | ||
* e.g., "$style_property-$individual_feature: $value;", which could represent the following: | ||
* "border-{top|right|bottom|left}-{color|width|style}: {value};" or, | ||
* "border-image-{outset|source|width|repeat|slice}: {value};" | ||
* | ||
* @param array $style_value A single raw Gutenberg style attributes value for a CSS property. | ||
* @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. | ||
* @param boolean $should_skip_css_vars Whether to skip compiling CSS var values. | ||
* @param array $style_value A single raw Gutenberg style attributes value for a CSS property. | ||
* @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. | ||
* @param array $block_styles Styles from a block's attributes object. | ||
* @param array<string> $options Options passed to the public generator functions. | ||
* | ||
* @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). | ||
*/ | ||
protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $should_skip_css_vars ) { | ||
protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $block_styles, $options ) { | ||
$css_declarations = array(); | ||
|
||
if ( ! is_array( $style_value ) || empty( $style_value ) || empty( $individual_property_definition['path'] ) ) { | ||
return $css_declarations; | ||
} | ||
|
||
$should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; | ||
|
||
// The first item in $individual_property_definition['path'] array tells us the style property, e.g., "border". | ||
// We use this to get a corresponding CSS style definition such as "color" or "width" from the same group. | ||
// The second item in $individual_property_definition['path'] array refers to the individual property marker, e.g., "top". | ||
|
@@ -484,6 +556,11 @@ protected static function get_individual_property_css_declarations( $style_value | |
$style_definition = _wp_array_get( static::BLOCK_STYLE_DEFINITIONS_METADATA, $style_definition_path, null ); | ||
|
||
if ( $style_definition && isset( $style_definition['property_keys']['individual'] ) ) { | ||
// If the style value contains a reference to another value in the tree. | ||
if ( isset( $value['ref'] ) ) { | ||
$value = static::get_ref_value( $block_styles, $value['ref'] ); | ||
} | ||
|
||
// Set a CSS var if there is a valid preset value. | ||
if ( is_string( $value ) && strpos( $value, 'var:' ) !== false && ! $should_skip_css_vars && ! empty( $individual_property_definition['css_vars'] ) ) { | ||
$value = static::get_css_var_value( $value, $individual_property_definition['css_vars'] ); | ||
|
@@ -522,3 +599,27 @@ function wp_style_engine_get_block_supports_styles( $block_styles, $options = ar | |
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Global public interface method to WP_Style_Engine->generate_global_styles to generate a stylesheet styles from a single theme.json style object. | ||
* See: https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/#styles | ||
* | ||
* Example usage: | ||
* | ||
* $styles = wp_style_engine_generate_global_styles( array( 'color' => array( 'text' => '#cccccc' ) ) ); | ||
* // Returns `body { color: #cccccc }`. | ||
* | ||
* @access public | ||
* | ||
* @param array $global_styles The value of a block's attributes.style. | ||
* @param array<string> $options An array of options to determine the output. | ||
* | ||
* @return string A stylesheet. | ||
*/ | ||
function wp_style_engine_generate_global_styles( $global_styles, $options = array() ) { | ||
if ( class_exists( 'WP_Style_Engine' ) ) { | ||
$style_engine = WP_Style_Engine::get_instance(); | ||
return $style_engine->generate_global_styles( $global_styles, $options ); | ||
} | ||
return null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the simple if/else to figure out how we can incrementally switch over to the style engine by just tackling root to begin with 👍