diff --git a/src/wp-includes/block-supports/states.php b/src/wp-includes/block-supports/states.php index 5220d060a731e..787a55b659814 100644 --- a/src/wp-includes/block-supports/states.php +++ b/src/wp-includes/block-supports/states.php @@ -97,6 +97,42 @@ function wp_get_state_declarations_with_fallback_border_styles( $declarations ) return $declarations; } +/** + * Adds background reset declarations to prevent gradient/solid color conflicts. + * + * When a state sets a solid background-color, any gradient applied to the + * default state (via `background` shorthand or `background-image`) must be + * explicitly cleared. Without this, the gradient image layer remains visible + * on top of the solid hover color even when `!important` is used, because + * `background-color` and `background-image` are separate CSS properties. + * + * @since 7.1.0 + * + * @param array $declarations CSS declarations generated by the style engine. + * @return array CSS declarations with background resets applied where needed. + */ +function wp_get_state_declarations_with_background_resets( $declarations ) { + if ( ! is_array( $declarations ) ) { + return $declarations; + } + + $has_background_color = isset( $declarations['background-color'] ) && '' !== $declarations['background-color']; + $has_background = isset( $declarations['background'] ) && '' !== $declarations['background']; + $has_background_image = isset( $declarations['background-image'] ) && '' !== $declarations['background-image']; + + /* + * When the state sets a solid background-color but no gradient of its own, + * emit `background-image: unset !important` to clear any gradient (whether + * stored as the `background` shorthand or as `background-image`) that was + * applied to the default / normal state via an inline style attribute. + */ + if ( $has_background_color && ! $has_background && ! $has_background_image ) { + $declarations['background-image'] = 'unset !important'; + } + + return $declarations; +} + /** * Adds a style fragment to a selector-keyed state style group. * @@ -461,6 +497,7 @@ function wp_render_block_states_support( $block_content, $block ) { : $value . ' !important'; } $declarations = wp_get_state_declarations_with_fallback_border_styles( $declarations ); + $declarations = wp_get_state_declarations_with_background_resets( $declarations ); $style_rule = array( 'selector' => wp_build_state_selector( ".$unique_class", diff --git a/tests/phpunit/tests/block-supports/states.php b/tests/phpunit/tests/block-supports/states.php index 83bace976277d..1d1e6da33408d 100644 --- a/tests/phpunit/tests/block-supports/states.php +++ b/tests/phpunit/tests/block-supports/states.php @@ -136,6 +136,93 @@ public function test_preserves_authored_border_style_declarations() { ); } + /** + * Tests that background-image reset is added when a state sets a solid background-color. + * + * @covers ::wp_get_state_declarations_with_background_resets + * + * @ticket 65239 + */ + public function test_adds_background_image_reset_for_solid_background_color() { + $actual = wp_get_state_declarations_with_background_resets( + array( + 'background-color' => '#ff0000 !important', + ) + ); + + $this->assertSame( + array( + 'background-color' => '#ff0000 !important', + 'background-image' => 'unset !important', + ), + $actual + ); + } + + /** + * Tests that background-image reset is not added when the state also sets a legacy gradient. + * + * @covers ::wp_get_state_declarations_with_background_resets + * + * @ticket 65239 + */ + public function test_no_background_image_reset_when_state_sets_legacy_gradient() { + $actual = wp_get_state_declarations_with_background_resets( + array( + 'background-color' => '#ff0000 !important', + 'background' => 'linear-gradient(135deg, #ff0000, #0000ff) !important', + ) + ); + + $this->assertSame( + array( + 'background-color' => '#ff0000 !important', + 'background' => 'linear-gradient(135deg, #ff0000, #0000ff) !important', + ), + $actual + ); + } + + /** + * Tests that background-image reset is not added when the state also sets a modern gradient. + * + * @covers ::wp_get_state_declarations_with_background_resets + * + * @ticket 65239 + */ + public function test_no_background_image_reset_when_state_sets_modern_gradient() { + $actual = wp_get_state_declarations_with_background_resets( + array( + 'background-color' => '#ff0000 !important', + 'background-image' => 'linear-gradient(135deg, #ff0000, #0000ff) !important', + ) + ); + + $this->assertSame( + array( + 'background-color' => '#ff0000 !important', + 'background-image' => 'linear-gradient(135deg, #ff0000, #0000ff) !important', + ), + $actual + ); + } + + /** + * Tests that declarations without background-color are returned unchanged. + * + * @covers ::wp_get_state_declarations_with_background_resets + * + * @ticket 65239 + */ + public function test_no_background_reset_when_no_background_color() { + $input = array( + 'color' => '#ff0000 !important', + ); + $actual = wp_get_state_declarations_with_background_resets( $input ); + + $this->assertSame( $input, $actual ); + } + /** * Tests that modifier classes on the first compound selector are preserved * when state selectors are scoped to the block wrapper. @@ -835,7 +922,7 @@ public function test_responsive_pseudo_state_generates_media_query_scoped_css() $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) ); $this->assertStringContainsString( - '@media (width <= 480px){.' . $matches[0] . ' .wp-block-button__link:hover{background-color:#ff00d0 !important;}}', + '@media (width <= 480px){.' . $matches[0] . ' .wp-block-button__link:hover{background-color:#ff00d0 !important;background-image:unset !important;}}', $actual_stylesheet ); }