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

Use a var for user presets to avoid having to use ! important to enforce them #40335

Closed
wants to merge 12 commits into from
Closed
168 changes: 168 additions & 0 deletions lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php
Original file line number Diff line number Diff line change
Expand Up @@ -677,4 +677,172 @@ protected static function do_opt_in_into_settings( &$context ) {

unset( $context['appearanceTools'] );
}

/**
* Given a settings array, it returns the generated rulesets
* for the preset classes.
*
* @param array $settings Settings to process.
* @param string $selector Selector wrapping the classes.
* @param array $origins List of origins to process.
* @return string The result of processing the presets.
*/
protected static function compute_preset_classes( $settings, $selector, $origins ) {
if ( static::ROOT_BLOCK_SELECTOR === $selector ) {
// Classes at the global level do not need any CSS prefixed,
// and we don't want to increase its specificity.
$selector = '';
}

$stylesheet = '';
foreach ( static::PRESETS_METADATA as $preset_metadata ) {
$slugs = static::get_settings_slugs( $settings, $preset_metadata, $origins );
foreach ( $preset_metadata['classes'] as $class => $property ) {
foreach ( $slugs as $slug ) {
$css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug );
$class_name = static::replace_slug_in_string( $class, $slug );
if ( ! str_contains( $class_name, '-color' ) && ! str_contains( $class_name, '-background' ) ) {
$stylesheet .= static::to_ruleset(
static::append_to_selector( $selector, $class_name ),
array(
array(
'name' => '--wp--user--preset--' . $property,
'value' => 'var(' . $css_var . ')',
),
array(
'name' => $property,
'value' => 'var(--wp--user--preset--' . $property . ')',
),
)
);
} else {
if ( str_contains( $class_name, '-gradient-background' ) ) {
$property = 'background-color';
}

$stylesheet .= static::to_ruleset(
static::append_to_selector( $selector, $class_name ),
array(
array(
'name' => '--wp--user--preset--' . $property,
'value' => 'var(' . $css_var . ')',
),
)
);
}
}
}
}

return $stylesheet;
}

/**
* Given a styles array, it extracts the style properties
* and adds them to the $declarations array following the format:
*
* ```php
* array(
* 'name' => 'property_name',
* 'value' => 'property_value,
* )
* ```
*
* @param array $styles Styles to process.
* @param array $settings Theme settings.
* @param array $properties Properties metadata.
* @return array Returns the modified $declarations.
*/
protected static function compute_style_properties( $styles, $settings = array(), $properties = null ) {
if ( null === $properties ) {
$properties = static::PROPERTIES_METADATA;
}

$declarations = array();
if ( empty( $styles ) ) {
return $declarations;
}

foreach ( $properties as $css_property => $value_path ) {
$value = static::get_property_value( $styles, $value_path );

// Look up protected properties, keyed by value path.
// Skip protected properties that are explicitly set to `null`.
if ( is_array( $value_path ) ) {
$path_string = implode( '.', $value_path );
if (
array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) &&
_wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null
) {
continue;
}
}

// Skip if empty and not "0" or value represents array of longhand values.
$has_missing_value = empty( $value ) && ! is_numeric( $value );
if ( $has_missing_value || is_array( $value ) ) {
continue;
}

$user_preset_var = 'background' === $css_property
? '--wp--user--preset--' . $css_property . '-color'
: '--wp--user--preset--' . $css_property;

$declarations[] = array(
'name' => $css_property,
'value' => 'var(' . $user_preset_var . ',' . $value . ')',
);
}

return $declarations;
}

/**
* Returns the stylesheet that results of processing
* the theme.json structure this object represents.
*
* @param array $types Types of styles to load. Will load all by default. It accepts:
* 'variables': only the CSS Custom Properties for presets & custom ones.
* 'styles': only the styles section in theme.json.
* 'presets': only the classes for the presets.
* @param array $origins A list of origins to include. By default it includes VALID_ORIGINS.
* @return string Stylesheet.
*/
public function get_stylesheet( $types = array( 'variables', 'styles' ), $origins = null ) {
if ( null === $origins ) {
$origins = static::VALID_ORIGINS;
}

if ( is_string( $types ) ) {
// Dispatch error and map old arguments to new ones.
_deprecated_argument( __FUNCTION__, '5.9' );
if ( 'block_styles' === $types ) {
$types = array( 'styles', 'presets' );
} elseif ( 'css_variables' === $types ) {
$types = array( 'variables' );
} else {
$types = array( 'variables', 'styles', 'presets' );
}
}

$blocks_metadata = static::get_blocks_metadata();
$style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata );
$setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata );

$stylesheet = '';

if ( in_array( 'variables', $types, true ) ) {
$stylesheet .= $this->get_css_variables( $setting_nodes, $origins );
}

if ( in_array( 'styles', $types, true ) ) {
$stylesheet .= $this->get_block_classes( $style_nodes );
}

if ( in_array( 'presets', $types, true ) ) {
$stylesheet .= $this->get_preset_classes( $setting_nodes, $origins );
}

return $stylesheet;
}
}
4 changes: 2 additions & 2 deletions lib/compat/wordpress-6.0/get-global-styles-and-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ function gutenberg_get_global_stylesheet( $types = array() ) {
$tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data();
$supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support();
if ( empty( $types ) && ! $supports_theme_json ) {
$types = array( 'variables', 'presets' );
$types = array( 'variables' );
} elseif ( empty( $types ) ) {
$types = array( 'variables', 'styles', 'presets' );
$types = array( 'variables', 'styles' );
}

/*
Expand Down
14 changes: 14 additions & 0 deletions lib/compat/wordpress-6.0/script-loader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
/**
* Load the user preset styles separately with lower priority to they will be more
* likely to load after any theme css that they need to override.
*/

function gutenberg_enqueue_user_preset_styles() {
$presets_stylesheet = gutenberg_get_global_stylesheet( array( 'presets' ) );

wp_register_style( 'use-preset-styles', false, array(), true, true );
wp_add_inline_style( 'use-preset-styles', $presets_stylesheet );
wp_enqueue_style( 'use-preset-styles' );
}
add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_user_preset_styles', 100 );
Copy link
Member

Choose a reason for hiding this comment

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

If the goal is to prevent overriding the preset classes defined by a theme (if they enqueue any), why do we try to load the core preset classes after?

Choose a reason for hiding this comment

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

It's so they come later in the cascade and win specificity over any previous styles without using !important or an ID selector. This is common practice for using utility-style CSS alongside traditional styles: Bootstrap, ITCSS

1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.0/edit-form-blocks.php';
require __DIR__ . '/compat/wordpress-6.0/block-patterns-update.php';
require __DIR__ . '/compat/wordpress-6.0/client-assets.php';
require __DIR__ . '/compat/wordpress-6.0/script-loader.php';

// WordPress 6.1 compat.
require __DIR__ . '/compat/wordpress-6.1/blocks.php';
Expand Down
8 changes: 8 additions & 0 deletions packages/block-library/src/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
text-align: right;
}

.has-background {
background: var(--wp--user--preset--background-color);
}

.has-text-color {
color: var(--wp--user--preset--color);
}

// This tag marks the end of the styles that apply to editing canvas contents and need to be manipulated when we resize the editor.
#end-resizable-editor-section {
display: none;
Expand Down
4 changes: 4 additions & 0 deletions packages/block-library/src/cover/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
background-color: $black;
}

[class*="-background-color"] {
background-color: var(--wp--user--preset--background-color);
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand why we do this if the cover block doesn't have any block supports related to this (color, background)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just a side effect of the potentially premature optimisation of .has-background class, ie. Cover uses the likes of the has-white-background-color preset classes, but doesn't assign the corresponding .has-background class. Adding this style just avoids a deprecation to add that class, but better options are probably to rethink that optimisation as you note, or to refactor the cover block to use the color supports and add the associated classes.

}

.has-background-dim.has-background-gradient {
background-color: transparent;
}
Expand Down