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: add typography to backend #40082

Closed
wants to merge 6 commits into from

Conversation

ramonjd
Copy link
Member

@ramonjd ramonjd commented Apr 6, 2022

Tracking Issue: #38167

What?

Following on from #39446 that introduced spacing to the Style Engine backend, this PR takes on typography ⚔️

Why?

To introduce the Style Engine incrementally at first, and not change any output for now.

How?

  1. Using an existing method to output inline styles generate
  2. Creating a new method get_classnames to output classnames inc. slugs.

Testing Instructions

For dynamic blocks (Site Title, Navigation, Post Title, Post Date for example), check that the typography is output correctly on the frontend.

I've been testing so far with the Site Title block. To test with all controls you can update that block's JSON supports with the following:

		"typography": {
			"fontSize": true,
			"lineHeight": true,
			"__experimentalFontFamily": true,
			"__experimentalTextTransform": true,
			"__experimentalFontStyle": true,
			"__experimentalFontWeight": true,
			"__experimentalLetterSpacing": true,
			"__experimentalTextDecoration": true,
			"__experimentalDefaultControls": {
				"fontSize": true,
				"lineHeight": true,
				"fontAppearance": true,
				"letterSpacing": true,
				"textTransform": true
			}
		}

Test:

  • Preset color classnames and font sizez
  • Inline styles

Some test code (using TwentyTwentyTwo theme)

<!-- wp:site-title {"style":{"typography":{"lineHeight":1.7,"textTransform":"uppercase","letterSpacing":"113px","textDecoration":"line-through"},"color":{"background":"#c2c9d0"}},"fontSize":"large","fontFamily":"source-serif-pro"} /-->

Deprecated HTML with presets

<!-- wp:navigation {"orientation":"horizontal","style":{"typography":{"textTransform":"var:preset|text-transform|lowercase","textDecoration":"var:preset|text-decoration|strikethrough","fontStyle":"var:preset|font-style|italic","fontFamily":"var:preset|font-family|someAwesomeFont-right-here","fontWeight":"var:preset|font-weight|100"}}} -->
<!-- wp:navigation-link {"label":"WordPress","url":"https://www.wordpress.org/"} /-->
<!-- /wp:navigation -->

Screenshots or screencast

@ramonjd ramonjd added the CSS Styling Related to editor and front end styles, CSS-specific issues. label Apr 6, 2022
@ramonjd ramonjd self-assigned this Apr 6, 2022
@ramonjd ramonjd force-pushed the update/style-engine-backend-typography branch from ebbb509 to 48e733f Compare April 7, 2022 02:29
}
$styles[] = sprintf( 'font-family: %s;', $font_family_custom );
// Before using classes, the value was serialized as a CSS Custom Property.
// We don't need to check for a preset when it lands in core.
Copy link
Member Author

@ramonjd ramonjd Apr 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to check for a preset when it lands in core

TODO: check what this means

cc @ockham Do you know if this comment is still valid? Grabbed your name from 1e17b5a#diff-2c54d3c8305590cf826f83511a9fd85d5b405470addd62edc3183ff9118177fbR115

Or would we need the code permanently for backwards compatibility?

@ramonjd ramonjd force-pushed the update/style-engine-backend-typography branch from 0cfe185 to 6edbfa4 Compare April 12, 2022 03:44
@ramonjd ramonjd requested a review from andrewserong April 12, 2022 04:16
@ramonjd ramonjd added [Package] Style Engine /packages/style-engine [Type] Experimental Experimental feature or API. labels Apr 12, 2022
@ramonjd ramonjd marked this pull request as ready for review April 12, 2022 04:19
@@ -109,56 +192,49 @@ public function generate( $block_styles, $options = array() ) {

$rules = 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'] ) ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't actually use these feature, so I'm removing it.

*
* @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 ) {
public static function get_css_rules( $style_value, $style_property ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To match the clientside naming.

@@ -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;',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use spaces to improve readability.

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @ramonjd, this is testing very nicely!

image

✅ Each of the Typography styles are rendered correctly on the front end
✅ The generated classnames appear to be rendered and injected correctly into the markup

I've left a few comments, I hope you don't mind — mostly poking at the idea of whether it'd be feasible for us to consolidate into a single call to generate, move the logic of inferring classnames from attribute values to the style engine, and return an array with keys for the inline styles and classes.

It probably doesn't make a huge difference to do that at this stage, but I wondered if it'd be easier to lay the groundwork for that now, in prep for other block supports that will likely use classnames (and generated / constructed or random id classnames) more verbosely or frequently (mostly thinking of elements and layout supports).

What do you think? I can imagine there's a good argument either way for keeping separate for now / versus consolidating now.

Happy to do more testing tomorrow!

Comment on lines 178 to 188
$style_engine = gutenberg_get_style_engine();
$inline_styles = $style_engine->generate(
array( 'typography' => $styles ),
array(
'inline' => true,
)
);

$classnames = $style_engine->get_classnames(
array( 'typography' => $classes ),
array(
'use_schema' => true,
)
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing these two calls side-by-side, I was wondering if we should consolidate these calls so that get_classnames becomes an implementation detail of generate within the style engine? Then, we could adjust the return signature of generate to be an array with keys for classes and a key for inline_styles (or whatever we want to name the keys).

By the time we get to refactoring the elements block support to use the style engine, we'll need to handle returning generated class names, so I wondered if it's worth factoring that in now?

Copy link
Member Author

@ramonjd ramonjd Apr 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing these two calls side-by-side, I was wondering if we should consolidate these calls so that get_classnames becomes an implementation detail of generate within the style engine?

I agree it's something we probably should do.

I spent some time thinking about this before I landed on two methods, and I got stuck on the structure of the arguments signature more than the return. That is, how show we should tell the style engine to deal with the different values?

The $block_styles arg matches Gutenberg's style object and has a very particular structure, and I couldn't see how classname slugs would fit into that.

We could always expand the options array. I did try that but wasn't satisfied with my approach as it appeared haphazard and unintuitive, and not related at all to the main argument $block_styles.

generate( 
     $block_styles,
      [ classnames => $array_of_classnames ]
)

The first argument could be a collection of [ $block_styles, $block_classnames ] I suppose.

Another thought I had was to extend the style value with metadata, which would mean the style engine had its own implementation of $block_styles. For example:

// Classname/slug
$style['color'] = [ 'classname' => 'crimson'  ]; 

// Regular style
$style['color'] = '#fff'; // or  [ 'value' => '#fff'  ];

The reason why I left it at two methods was so we could indeed have separate methods that we could ultimately call in a single generate.

So maybe the current generate should rather be named get_styles or whatever, and we create a new generate that calls get_styles or get_classnames accordingly. 🤷

What do you reckon 😄 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that is an interesting challenge! Thanks for explaining the logic there, and apologies when I was reading this yesterday, I totally missed that the following lines are grabbing root level attributes, not values within the style object 😅:

$classes['fontSize'] = _wp_to_kebab_case( $block_attributes['fontSize'] );
$classes['fontFamily'] = _wp_to_kebab_case( $block_attributes['fontFamily'] );

I think we're now bumping into one of the tricky areas where there's a part of the feature here that we probably need to decide between "do we change how it works now, or later?".

As in the border PR (#37770) I think there's been discussion in various places about whether these root level attributes will (long-term) better belong within the style object. Ideally, we'd have everything we need right there in the style object, whether that style object is coming from block attributes, or global styles.

However, we're not there yet!

So in the spirit of the approach we've been taking thus far, which is — let's see if we can refactor what's currently working (pretty much as-is) and then iteratively switch over to new approaches — I reckon we go in the direction you've been exploring 👍

We could always expand the options array. I did try that but wasn't satisfied with my approach as it appeared haphazard and unintuitive, and not related at all to the main argument $block_styles.

You're right, it's tricky to come up with something that feels intuitive. I was kinda wondering if we could nearly have a value in the options array called attributes or additionalAttributes or something, which is a way to pass in attributes that are not part of the style object — but that could feel a bit awkward, too...

Basically, I was thinking of something like:

$style_engine->generate(
		array( 'typography' => $styles ),
		array(
			'inline' => true,
			'attributes' => array(
				'fontSize' => 'large', // just the raw attribute, let the style engine do the kebab-ing?
				'fontFamily' => 'papyrus'
			)
		)
	);

But I agree, it does feel awkward including those attributes separately in the list of options, so it might not be the right approach...

Another thought I had was to extend the style value with metadata, which would mean the style engine had its own implementation of $block_styles

That's actually not such a bad idea 🤔😀 — if we think of this as an opportunity to come up with the structure of how we long-term hope to structure the style object so that it contains those root level attributes, if we go with this approach, it might be a good way to confidence check what that shape will eventually look like.

It could also mean we kinda have an implementation which is "if the values are all in the style object, we don't need to look for anything else, otherwise, migrate over those other attributes to the style object for backwards compat" — that way we might be able to handle the desired location in the style object without ever having to worry about migrating old attributes? Just a thought....

The reason why I left it at two methods was so we could indeed have separate methods that we could ultimately call in a single generate.
So maybe the current generate should rather be named get_styles or whatever, and we create a new generate that calls get_styles or get_classnames accordingly.

I really like this idea, it means we've got two inner methods that are doing different things, and we have generate as an abstraction that sits just above, so we can have a single entry point to generating styles 👍

packages/style-engine/class-wp-style-engine.php Outdated Show resolved Hide resolved
}

$classnames = array();
$use_schema = isset( $options['use_schema'] ) ? $options['use_schema'] : false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be an option, or could we infer it based on the preference within BLOCK_STYLE_DEFINITIONS_METADATA? Because $options is set for the overall call to generate (or get_classnames in this case) I was wondering if use_schema is a little too broad, in that each property / support might need a different approach?

Or did you have in mind a use case where we really do want to be able to globally switch between the two approaches when calling the style engine? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question.

We don't need this right now, true. I was just playing with an idea of being able to pass a custom schema to the style engine for styles that might not be defined in BLOCK_STYLE_DEFINITIONS_METADATA , like layout container classes wp-%s.

$options['use_schema'] is fairly generic, but it was supposed to mean "use the pattern/schema stored in the style engine if there is one".

I'm not really sure to be honest.

It's not needed for this PR, so I'll probably dump it until we have a real-world use case.

$has_named_font_size = array_key_exists( 'fontSize', $block_attributes );
$has_custom_font_size = isset( $block_attributes['style']['typography']['fontSize'] );

if ( $has_named_font_size ) {
$classes[] = sprintf( 'has-%s-font-size', _wp_to_kebab_case( $block_attributes['fontSize'] ) );
$classes['fontSize'] = _wp_to_kebab_case( $block_attributes['fontSize'] );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if it'd be worth moving this call to convert the value to kebab case to the style engine, too, so that the caller doesn't have to worry about the detail of how the class name is ultimately constructed, and the style engine can be responsible for that?

What I have in mind, is whether it'd be feasible for the style engine to infer the class names based on the attributes passed in via the style object handed to generate, instead of having to call get_classnames separately with the $classes value 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convert the value to kebab case to the style engine, too, so that the caller doesn't have to worry about the detail of how the class name is ultimately constructed

It probably should, but would it need to if it's inferring classnames and building them "in house" based on attributes alone?

The tricky part of the puzzle here is building classnames based on existing patterns. For typography only font size received the kebab case treatment. That's the reason why I left it to the block support to pass the right slug, and kept the style engine "dumb" in that regard.

I can't think of any reason not to do this - in fact I agree that we definitely should ensure that all classnames are consistent in that way - however if ever a block/block support didn't want kebab case (an extreme edge case) and something else, we might have painted ourselves into a corner too early.

Once again, I'm not sure. Maybe we'll know as we add more supports to the mix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again, I'm not sure. Maybe we'll know as we add more supports to the mix.

Good points! I think if we're not sure, then perhaps let's leave it in the block support so that we haven't painted ourselves into a corner, like you say. We could always move it over in a follow-up, if need be.

ramonjd added 5 commits April 13, 2022 14:39
First stab at generating classnames based on slugs
…sing it yet.

Adding a new public function to generate_classnames.
Updated tests.
Remove value_func for common style definitions since I'm pretty sure we'll be using the get_css_rules method for 80%++ of cases.
@ramonjd ramonjd force-pushed the update/style-engine-backend-typography branch from 6edbfa4 to 9dff885 Compare April 13, 2022 04:39
@ramonjd
Copy link
Member Author

ramonjd commented Apr 14, 2022

Alternative that includes typography, but takes into account classname generation for colors here:

@ramonjd
Copy link
Member Author

ramonjd commented Apr 19, 2022

I think I'll close this in favour of

Since that PR takes classnames/presets into account.

@ramonjd ramonjd closed this Apr 19, 2022
ramonjd added a commit that referenced this pull request Apr 22, 2022
Building on work in #40082 and #40332 by adding border support.
Allowing for border side styles, e.g., border.top.width et. al.
ramonjd added a commit that referenced this pull request Apr 27, 2022
Allowing for border side styles, e.g., border.top.width et. al.
@ramonjd ramonjd deleted the update/style-engine-backend-typography branch May 24, 2022 05:53
ramonjd added a commit that referenced this pull request May 24, 2022
Allowing for border side styles, e.g., border.top.width et. al.
ramonjd added a commit that referenced this pull request May 30, 2022
Allowing for border side styles, e.g., border.top.width et. al.
ramonjd added a commit that referenced this pull request Jun 2, 2022
Allowing for border side styles, e.g., border.top.width et. al.
ramonjd added a commit that referenced this pull request Jun 3, 2022
Allowing for border side styles, e.g., border.top.width et. al.
ramonjd added a commit that referenced this pull request Jun 8, 2022
Allowing for border side styles, e.g., border.top.width et. al.
ramonjd added a commit that referenced this pull request Jun 8, 2022
* Building on work in #40082 and #40332 by adding border support.
Allowing for border side styles, e.g., border.top.width et. al.

* Update docs and comments

* The linter strikes again. Be gone, thou flinty, fermented scallion!

* Addressing nitpickers :D
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CSS Styling Related to editor and front end styles, CSS-specific issues. [Package] Style Engine /packages/style-engine [Type] Experimental Experimental feature or API.
Projects
Status: 🗄 Closed / not merged
Development

Successfully merging this pull request may close these issues.

2 participants