PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
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 → trunk 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,