PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
← All changes | lib/block-supports/states.php +168 -25 23.4.0 → 24.1.0 View file →
@@ -3,9 +3,9 @@
3 3 * Block state support for frontend CSS generation.
4 4 *
5 5 * Generates scoped CSS for per-instance state styles declared in block attributes,
6 6 * including pseudo-states (e.g., `style[':hover']`) and responsive states
7 - * (e.g., `style['mobile']` and `style['mobile'][':hover']`).
7 + * (e.g., `style['@mobile']` and `style['@mobile'][':hover']`).
8 8 *
9 9 * @package WordPress
10 10 */
11 11
@@ -31,10 +31,23 @@
31 31 if ( ! is_string( $value ) || ! str_starts_with( $value, 'var:preset|' ) ) {
32 32 return $value;
33 33 }
34 34
35 - $unwrapped_name = str_replace( '|', '--', substr( $value, strlen( 'var:' ) ) );
36 - return "var(--wp--$unwrapped_name)";
35 + $parts = explode( '|', substr( $value, strlen( 'var:' ) ) );
36 +
37 + /*
38 + * The slug is kebab-cased when the preset's custom property is generated,
39 + * so a reference has to be converted the same way or it points at a
40 + * property that does not exist (`--wp--preset--font-size--3xl` for a preset
41 + * generated as `--wp--preset--font-size--3-xl`). Mirrors
42 + * `WP_Theme_JSON_Gutenberg::convert_custom_properties()` and the JS style
43 + * engine's `getCSSValueFromRawStyle()`.
44 + */
45 + if ( 3 === count( $parts ) ) {
46 + $parts[2] = _wp_to_kebab_case( $parts[2] );
47 + }
48 +
49 + return 'var(--wp--' . implode( '--', $parts ) . ')';
37 50 }
38 51
39 52 /**
40 53 * Normalizes a state style object before generating CSS declarations.
@@ -90,8 +103,91 @@
90 103 return $declarations;
91 104 }
92 105
93 106 /**
107 + * Adds background reset declarations to prevent gradient/solid color conflicts.
108 + *
109 + * When a state sets a solid background-color, any gradient applied to the
110 + * default state (via `background` shorthand or `background-image`) must be
111 + * explicitly cleared. Without this, the gradient image layer remains visible
112 + * on top of the solid hover color even when `!important` is used, because
113 + * `background-color` and `background-image` are separate CSS properties.
114 + *
115 + * @param array $declarations CSS declarations generated by the style engine.
116 + * @return array CSS declarations with background resets applied where needed.
117 + */
118 +function gutenberg_get_state_declarations_with_background_resets( $declarations ) {
119 + if ( ! is_array( $declarations ) ) {
120 + return $declarations;
121 + }
122 +
123 + $has_background_color = isset( $declarations['background-color'] ) && '' !== $declarations['background-color'];
124 + $has_background = isset( $declarations['background'] ) && '' !== $declarations['background'];
125 + $has_background_image = isset( $declarations['background-image'] ) && '' !== $declarations['background-image'];
126 +
127 + /*
128 + * When the state sets a solid background-color but no gradient of its own,
129 + * emit `background-image: unset` to clear any gradient (whether stored as
130 + * the `background` shorthand or as `background-image`) that was applied to
131 + * the default / normal state via an inline style attribute. The declaration
132 + * is marked important when the state rule is registered with the style engine.
133 + */
134 + if ( $has_background_color && ! $has_background && ! $has_background_image ) {
135 + $declarations['background-image'] = 'unset';
136 + }
137 +
138 + return $declarations;
139 +}
140 +
141 +/**
142 + * Adds fallback dimension styles for aspectRatio and height block-support values.
143 + *
144 + * @param array $state_style State style object.
145 + * @return array State style object with fallback dimension styles applied where needed.
146 + */
147 +function gutenberg_get_state_style_with_fallback_dimension_styles( $state_style ) {
148 + if ( ! is_array( $state_style ) ) {
149 + return $state_style;
150 + }
151 +
152 + $dimensions = isset( $state_style['dimensions'] ) && is_array( $state_style['dimensions'] )
153 + ? $state_style['dimensions']
154 + : array();
155 +
156 + if ( empty( $dimensions ) ) {
157 + return $state_style;
158 + }
159 +
160 + if ( gutenberg_is_explicit_aspect_ratio_value( $dimensions['aspectRatio'] ?? null ) ) {
161 + return array_replace_recursive(
162 + $state_style,
163 + array(
164 + 'dimensions' => array(
165 + 'minHeight' => 'unset',
166 + 'height' => 'unset',
167 + ),
168 + )
169 + );
170 + }
171 +
172 + $has_min_height = isset( $dimensions['minHeight'] ) && ( is_string( $dimensions['minHeight'] ) || is_numeric( $dimensions['minHeight'] ) ) && '' !== trim( (string) $dimensions['minHeight'] );
173 + $has_height = isset( $dimensions['height'] ) && ( is_string( $dimensions['height'] ) || is_numeric( $dimensions['height'] ) ) && '' !== trim( (string) $dimensions['height'] );
174 +
175 + if ( $has_min_height || $has_height ) {
176 + return array_replace_recursive(
177 + $state_style,
178 + array(
179 + 'dimensions' => array(
180 + 'aspectRatio' => 'unset',
181 + ),
182 + )
183 + );
184 + }
185 +
186 + return $state_style;
187 +}
188 +
189 +/**
94 190 * Adds a style fragment to a selector-keyed state style group.
95 191 *
96 192 * @param array $groups Selector-keyed style groups.
97 193 * @param string|null $selector Block or feature selector.
@@ -258,13 +354,21 @@
258 354 if ( empty( $style ) || ! is_array( $style ) ) {
259 355 return;
260 356 }
261 357
262 - $compiled = gutenberg_style_engine_get_styles(
358 + $style = gutenberg_get_state_style_with_fallback_dimension_styles( $style );
359 +
360 + $compiled = gutenberg_style_engine_get_styles(
263 361 gutenberg_normalize_state_style_for_css_output( $style )
264 362 );
363 + $declarations = $compiled['declarations'] ?? array();
364 + $text_align = $style['typography']['textAlign'] ?? null;
365 + // Base text alignment is class-based, so state styles need a declaration.
366 + if ( is_string( $text_align ) && '' !== trim( $text_align ) ) {
367 + $declarations['text-align'] = $text_align;
368 + }
265 369
266 - if ( empty( $compiled['declarations'] ) ) {
370 + if ( empty( $declarations ) ) {
267 371 return;
268 372 }
269 373
270 374 $css_rules[] = array(
@@ -269,9 +373,9 @@
269 373
270 374 $css_rules[] = array(
271 375 'state' => $state,
272 376 'selector' => $selector,
273 - 'declarations' => $compiled['declarations'],
377 + 'declarations' => $declarations,
274 378 );
275 379 if ( ! empty( $rules_group ) ) {
276 380 $css_rules[ count( $css_rules ) - 1 ]['rules_group'] = $rules_group;
277 381 }
@@ -422,16 +526,31 @@
422 526 return $block_content;
423 527 }
424 528
425 529 $block_name = $block['blockName'];
530 +
531 + /*
532 + * Every CSS rule this function can produce is keyed off the block's `style`
533 + * attribute. Without it there is nothing to generate, so bail before doing
534 + * any of the lookups below — this runs for every block on every request.
535 + */
536 + $raw_style = $block['attrs']['style'] ?? array();
537 + if ( empty( $raw_style ) || ! is_array( $raw_style ) ) {
538 + return $block_content;
539 + }
540 +
541 + // Keep the existing block registry lookup here.
542 + $style = gutenberg_resolve_style_state_aliases( $raw_style, $block_name );
543 +
426 544 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
427 545 if ( ! $block_type ) {
428 546 return $block_content;
429 547 }
430 548
431 - $supported_pseudo_states = WP_Theme_JSON_Gutenberg::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ?? array();
432 - $style = $block['attrs']['style'] ?? array();
433 - $css_rules = array();
549 + $supported_pseudo_states = WP_Theme_JSON_Gutenberg::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ?? array();
550 + $css_rules = array();
551 + $viewport_settings = gutenberg_get_global_settings( array( 'viewport' ) );
552 + $responsive_media_queries = WP_Theme_JSON_Gutenberg::get_viewport_media_queries( $viewport_settings );
434 553
435 554 foreach ( $supported_pseudo_states as $pseudo_state ) {
436 555 if ( empty( $style[ $pseudo_state ] ) || ! is_array( $style[ $pseudo_state ] ) ) {
437 556 continue;
@@ -445,9 +564,9 @@
445 564 )
446 565 );
447 566 }
448 567
449 - foreach ( WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS as $breakpoint => $media_query ) {
568 + foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
450 569 if ( empty( $style[ $breakpoint ] ) || ! is_array( $style[ $breakpoint ] ) ) {
451 570 continue;
452 571 }
453 572
@@ -557,27 +676,51 @@
557 676 * with the base layout and target the correct (inner) wrapper element.
558 677 */
559 678 $style_rules = array();
560 679 foreach ( $css_rules as $rule ) {
561 - $declarations = array();
562 - foreach ( $rule['declarations'] as $property => $value ) {
563 - $declarations[ $property ] = is_string( $value ) && str_contains( $value, '!important' )
564 - ? $value
565 - : $value . ' !important';
680 + $declarations = $rule['declarations'];
681 + $important_declaration_values = gutenberg_get_state_declarations_with_background_resets( $declarations );
682 + $important_declarations = new WP_Style_Engine_CSS_Declarations_Gutenberg();
683 + foreach ( $important_declaration_values as $property => $value ) {
684 + $important_declarations->add_declaration(
685 + $property,
686 + $value,
687 + array(
688 + 'important' => true,
689 + )
690 + );
566 691 }
567 - $declarations = gutenberg_get_state_declarations_with_fallback_border_styles( $declarations );
568 - $style_rule = array(
569 - 'selector' => gutenberg_build_state_selector(
570 - ".$unique_class",
571 - $rule['selector'],
572 - $rule['state']
573 - ),
574 - 'declarations' => $declarations,
692 + $selector = gutenberg_build_state_selector(
693 + ".$unique_class",
694 + $rule['selector'],
695 + $rule['state']
575 696 );
697 + $important_style_rule = array(
698 + 'selector' => $selector,
699 + 'declarations' => $important_declarations,
700 + );
576 701 if ( ! empty( $rule['rules_group'] ) ) {
577 - $style_rule['rules_group'] = $rule['rules_group'];
702 + $important_style_rule['rules_group'] = $rule['rules_group'];
578 703 }
579 - $style_rules[] = $style_rule;
704 + $style_rules[] = $important_style_rule;
705 +
706 + $fallback_declarations = gutenberg_get_state_declarations_with_fallback_border_styles( $declarations );
707 + foreach ( array_keys( $declarations ) as $property ) {
708 + unset( $fallback_declarations[ $property ] );
709 + }
710 +
711 + if ( empty( $fallback_declarations ) ) {
712 + continue;
713 + }
714 +
715 + $fallback_style_rule = array(
716 + 'selector' => $selector,
717 + 'declarations' => $fallback_declarations,
718 + );
719 + if ( ! empty( $rule['rules_group'] ) ) {
720 + $fallback_style_rule['rules_group'] = $rule['rules_group'];
721 + }
722 + $style_rules[] = $fallback_style_rule;
580 723 }
581 724
582 725 gutenberg_style_engine_get_stylesheet_from_css_rules(
583 726 $style_rules,