Make WordPress Core

Changeset 61607


Ignore:
Timestamp:
02/10/2026 03:19:36 PM (7 months ago)
Author:
scruffian
Message:

Editor: Add support for pseudo elements for the block and its variations on theme.json.

Adds support for pseudo elements on the core/button block for ( ':hover', ':focus', ':focus-visible', ':active' ) at the theme.json level. This is also allowing the block's variations to control the same pseudo elements, so now we can style hover for the outline variation too.

Example usage:

"styles": {
	"blocks": {
		"core/button": {
			"color": {
				"background": "blue"
			},
			":hover": {
				"color": {
					"background": "green"
				}
			},
			":focus": {
				"color": {
					"background": "purple"
				}
			},
			"variations": {
				"outline": {
					":hover": {
						"color": {
							"background": "pink"
						}
					}
				}
			}
		}
	}
}

Reviewed by palak678, getdave, MaggieCabrera.

Props MaggieCabrera, scruffian, palak678. joedolson, getdave, mikachan.
Fixes #64263.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-theme-json.php

    r61570 r61607  
    611611
    612612        /**
     613         * The valid pseudo-selectors that can be used for blocks.
     614         *
     615         * @since 7.0
     616         * @var array
     617         */
     618        const VALID_BLOCK_PSEUDO_SELECTORS = array(
     619                'core/button' => array( ':hover', ':focus', ':focus-visible', ':active' ),
     620        );
     621
     622        /**
    613623         * The valid elements that can be found under styles.
    614624         *
     
    700710        }
    701711
     712
     713        /**
     714         * Processes pseudo-selectors for any node (block or variation).
     715         *
     716         * @param array  $node The node data (block or variation).
     717         * @param string $base_selector The base selector.
     718         * @param array  $settings The theme settings.
     719         * @param string $block_name The block name.
     720         * @return array Array of pseudo-selector declarations.
     721         */
     722        private static function process_pseudo_selectors( $node, $base_selector, $settings, $block_name ) {
     723                $pseudo_declarations = array();
     724
     725                if ( ! isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) ) {
     726                        return $pseudo_declarations;
     727                }
     728
     729                foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) {
     730                        if ( isset( $node[ $pseudo_selector ] ) ) {
     731                                $combined_selector                         = static::append_to_selector( $base_selector, $pseudo_selector );
     732                                $declarations                              = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, null );
     733                                $pseudo_declarations[ $combined_selector ] = $declarations;
     734                        }
     735                }
     736
     737                return $pseudo_declarations;
     738        }
     739
     740
    702741        /**
    703742         * Returns a class name by an element name.
     
    10191058                        $schema_styles_blocks[ $block ]             = $styles_non_top_level;
    10201059                        $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements;
     1060
     1061                        // Add pseudo-selectors for blocks that support them
     1062                        if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
     1063                                foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
     1064                                        $schema_styles_blocks[ $block ][ $pseudo_selector ] = $styles_non_top_level;
     1065                                }
     1066                        }
    10211067                }
    10221068
     
    10411087                        $schema_styles_variations = array();
    10421088                        if ( ! empty( $style_variation_names ) ) {
    1043                                 $schema_styles_variations = array_fill_keys( $style_variation_names, $block_style_variation_styles );
     1089                                foreach ( $style_variation_names as $variation_name ) {
     1090                                        $variation_schema = $block_style_variation_styles;
     1091
     1092                                        // Add pseudo-selectors to variations for blocks that support them
     1093                                        if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
     1094                                                foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
     1095                                                        $variation_schema[ $pseudo_selector ] = $styles_non_top_level;
     1096                                                }
     1097                                        }
     1098
     1099                                        $schema_styles_variations[ $variation_name ] = $variation_schema;
     1100                                }
    10441101                        }
    10451102
     
    27432800                                        'css'        => $selector,
    27442801                                );
     2802
     2803                                // Handle any pseudo selectors for the block.
     2804                                if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) {
     2805                                        foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo_selector ) {
     2806                                                if ( isset( $theme_json['styles']['blocks'][ $name ][ $pseudo_selector ] ) ) {
     2807                                                        $nodes[] = array(
     2808                                                                'name'       => $name,
     2809                                                                'path'       => array( 'styles', 'blocks', $name, $pseudo_selector ),
     2810                                                                'selector'   => static::append_to_selector( $selector, $pseudo_selector ),
     2811                                                                'selectors'  => $feature_selectors,
     2812                                                                'duotone'    => $duotone_selector,
     2813                                                                'variations' => $variation_selectors,
     2814                                                                'css'        => static::append_to_selector( $selector, $pseudo_selector ),
     2815                                                        );
     2816                                                }
     2817                                        }
     2818                                }
    27452819                        }
    27462820
     
    28392913                                // Compute declarations for remaining styles not covered by feature level selectors.
    28402914                                $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json );
     2915
     2916                                // Process pseudo-selectors for this variation (e.g., :hover, :focus)
     2917                                $block_name                    = isset( $block_metadata['name'] ) ? $block_metadata['name'] : ( in_array( 'blocks', $block_metadata['path'], true ) && count( $block_metadata['path'] ) >= 3 ? $block_metadata['path'][2] : null );
     2918                                $variation_pseudo_declarations = static::process_pseudo_selectors( $style_variation_node, $style_variation['selector'], $settings, $block_name );
     2919                                $style_variation_declarations  = array_merge( $style_variation_declarations, $variation_pseudo_declarations );
     2920
    28412921                                // Store custom CSS for the style variation.
    28422922                                if ( isset( $style_variation_node['css'] ) ) {
     
    28712951                if ( isset( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) ) {
    28722952                        $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ];
     2953                }
     2954
     2955                /*
     2956                 * Check if we're processing a block pseudo-selector.
     2957                 * $block_metadata['path'] = array( 'styles', 'blocks', 'core/button', ':hover' );
     2958                 */
     2959                $is_processing_block_pseudo = false;
     2960                $block_pseudo_selector      = null;
     2961                if ( in_array( 'blocks', $block_metadata['path'], true ) && count( $block_metadata['path'] ) >= 4 ) {
     2962                        $block_name        = $block_metadata['path'][2]; // 'core/button'
     2963                        $last_path_element = $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ]; // ':hover'
     2964
     2965                        if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) &&
     2966                                in_array( $last_path_element, static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ], true ) ) {
     2967                                $is_processing_block_pseudo = true;
     2968                                $block_pseudo_selector      = $last_path_element;
     2969                        }
    28732970                }
    28742971
     
    29022999                ) {
    29033000                        $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding );
     3001                } elseif ( $is_processing_block_pseudo ) {
     3002                        // Process block pseudo-selector styles
     3003                        // For block pseudo-selectors, we need to get the block data first, then access the pseudo-selector
     3004                        $block_name  = $block_metadata['path'][2]; // 'core/button'
     3005                        $block_data  = _wp_array_get( $this->theme_json, array( 'styles', 'blocks', $block_name ), array() );
     3006                        $pseudo_data = isset( $block_data[ $block_pseudo_selector ] ) ? $block_data[ $block_pseudo_selector ] : array();
     3007
     3008                        $declarations = static::compute_style_properties( $pseudo_data, $settings, null, $this->theme_json, $selector, $use_root_padding );
    29043009                } else {
    29053010                        $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding );
  • trunk/src/wp-includes/global-styles-and-settings.php

    r61473 r61607  
    277277
    278278                if ( $can_use_cached ) {
    279                         // Use the block name as the key for cached CSS data. Otherwise, use a hash of the metadata.
    280                         $cache_node_key = $metadata['name'] ?? md5( wp_json_encode( $metadata ) );
     279                        // Generate a unique cache key based on the full metadata to ensure pseudo-selectors and other variations get unique keys.
     280                        $cache_node_key = md5( wp_json_encode( $metadata ) );
    281281
    282282                        if ( isset( $cached['blocks'][ $cache_node_key ] ) ) {
  • trunk/tests/phpunit/tests/theme/wpThemeJson.php

    r61573 r61607  
    60706070         * @ticket 60613
    60716071         *
    6072          * @covers WP_Theme_JSON_Gutenberg::resolve_variables
    6073          * @covers WP_Theme_JSON_Gutenberg::convert_variables_to_value
     6072         * @covers WP_Theme_JSON::resolve_variables
     6073         * @covers WP_Theme_JSON::convert_variables_to_value
    60746074         */
    60756075        public function test_resolve_variables() {
     
    66946694                $this->assertEqualSetsWithIndex( $expected, $actual );
    66956695        }
     6696
     6697        /**
     6698         * Test that block pseudo selectors are processed correctly.
     6699         */
     6700        public function test_block_pseudo_selectors_are_processed() {
     6701                $theme_json = new WP_Theme_JSON(
     6702                        array(
     6703                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     6704                                'styles'  => array(
     6705                                        'blocks' => array(
     6706                                                'core/button' => array(
     6707                                                        'color'  => array(
     6708                                                                'text'       => 'white',
     6709                                                                'background' => 'blue',
     6710                                                        ),
     6711                                                        ':hover' => array(
     6712                                                                'color' => array(
     6713                                                                        'text'       => 'blue',
     6714                                                                        'background' => 'white',
     6715                                                                ),
     6716                                                        ),
     6717                                                        ':focus' => array(
     6718                                                                'color' => array(
     6719                                                                        'text'       => 'red',
     6720                                                                        'background' => 'yellow',
     6721                                                                ),
     6722                                                        ),
     6723                                                ),
     6724                                        ),
     6725                                ),
     6726                        )
     6727                );
     6728
     6729                $expected = ':root :where(.wp-block-button .wp-block-button__link){background-color: blue;color: white;}:root :where(.wp-block-button .wp-block-button__link:hover){background-color: white;color: blue;}:root :where(.wp-block-button .wp-block-button__link:focus){background-color: yellow;color: red;}';
     6730                $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) );
     6731        }
     6732
     6733        /**
     6734         * Test that block pseudo selectors are processed correctly within variations.
     6735         */
     6736        public function test_block_variation_pseudo_selectors_are_processed() {
     6737                register_block_style(
     6738                        'core/button',
     6739                        array(
     6740                                'name'  => 'outline',
     6741                                'label' => 'Outline',
     6742                        )
     6743                );
     6744
     6745                $theme_json = new WP_Theme_JSON(
     6746                        array(
     6747                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     6748                                'styles'  => array(
     6749                                        'blocks' => array(
     6750                                                'core/button' => array(
     6751                                                        'color'      => array(
     6752                                                                'text'       => 'white',
     6753                                                                'background' => 'blue',
     6754                                                        ),
     6755                                                        'variations' => array(
     6756                                                                'outline' => array(
     6757                                                                        'color'  => array(
     6758                                                                                'text'       => 'currentColor',
     6759                                                                                'background' => 'transparent',
     6760                                                                        ),
     6761                                                                        'border' => array(
     6762                                                                                'color' => 'currentColor',
     6763                                                                                'width' => '1px',
     6764                                                                                'style' => 'solid',
     6765                                                                        ),
     6766                                                                        ':hover' => array(
     6767                                                                                'color' => array(
     6768                                                                                        'text'       => 'white',
     6769                                                                                        'background' => 'red',
     6770                                                                                ),
     6771                                                                        ),
     6772                                                                        ':focus' => array(
     6773                                                                                'color' => array(
     6774                                                                                        'text'       => 'black',
     6775                                                                                        'background' => 'yellow',
     6776                                                                                ),
     6777                                                                        ),
     6778                                                                ),
     6779                                                        ),
     6780                                                ),
     6781                                        ),
     6782                                ),
     6783                        )
     6784                );
     6785
     6786                $expected = ':root :where(.wp-block-button .wp-block-button__link){background-color: blue;color: white;}:root :where(.wp-block-button.is-style-outline .wp-block-button__link){background-color: transparent;border-color: currentColor;border-width: 1px;border-style: solid;color: currentColor;}:root :where(.wp-block-button.is-style-outline .wp-block-button__link:hover){background-color: red;color: white;}:root :where(.wp-block-button.is-style-outline .wp-block-button__link:focus){background-color: yellow;color: black;}';
     6787                $actual   = $theme_json->get_stylesheet(
     6788                        array( 'styles' ),
     6789                        null,
     6790                        array(
     6791                                'skip_root_layout_styles'        => true,
     6792                                'include_block_style_variations' => true,
     6793                        )
     6794                );
     6795
     6796                unregister_block_style( 'core/button', 'outline' );
     6797
     6798                $this->assertSame( $expected, $actual );
     6799        }
     6800
     6801        /**
     6802         * Test that non-whitelisted pseudo selectors are ignored for blocks.
     6803         */
     6804        public function test_block_pseudo_selectors_ignores_non_whitelisted() {
     6805                $theme_json = new WP_Theme_JSON(
     6806                        array(
     6807                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     6808                                'styles'  => array(
     6809                                        'blocks' => array(
     6810                                                'core/button' => array(
     6811                                                        'color'     => array(
     6812                                                                'text'       => 'white',
     6813                                                                'background' => 'blue',
     6814                                                        ),
     6815                                                        ':hover'    => array(
     6816                                                                'color' => array(
     6817                                                                        'text'       => 'blue',
     6818                                                                        'background' => 'white',
     6819                                                                ),
     6820                                                        ),
     6821                                                        ':levitate' => array(
     6822                                                                'color' => array(
     6823                                                                        'text'       => 'yellow',
     6824                                                                        'background' => 'black',
     6825                                                                ),
     6826                                                        ),
     6827                                                ),
     6828                                        ),
     6829                                ),
     6830                        )
     6831                );
     6832
     6833                $expected = ':root :where(.wp-block-button .wp-block-button__link){background-color: blue;color: white;}:root :where(.wp-block-button .wp-block-button__link:hover){background-color: white;color: blue;}';
     6834                $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) );
     6835                $this->assertStringNotContainsString( '.wp-block-button .wp-block-button__link:levitate{', $theme_json->get_stylesheet( array( 'styles' ) ) );
     6836        }
     6837
     6838        /**
     6839         * Test that blocks without pseudo selector support ignore pseudo selectors.
     6840         */
     6841        public function test_blocks_without_pseudo_support_ignore_pseudo_selectors() {
     6842                $theme_json = new WP_Theme_JSON(
     6843                        array(
     6844                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     6845                                'styles'  => array(
     6846                                        'blocks' => array(
     6847                                                'core/paragraph' => array(
     6848                                                        'color'  => array(
     6849                                                                'text' => 'black',
     6850                                                        ),
     6851                                                        ':hover' => array(
     6852                                                                'color' => array(
     6853                                                                        'text' => 'red',
     6854                                                                ),
     6855                                                        ),
     6856                                                ),
     6857                                        ),
     6858                                ),
     6859                        )
     6860                );
     6861
     6862                $expected = ':root :where(p){color: black;}';
     6863                $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) );
     6864                $this->assertStringNotContainsString( 'p:hover{', $theme_json->get_stylesheet( array( 'styles' ) ) );
     6865        }
     6866
     6867        /**
     6868         * Test that block pseudo selectors work with elements within blocks.
     6869         */
     6870        public function test_block_pseudo_selectors_with_elements() {
     6871                $theme_json = new WP_Theme_JSON(
     6872                        array(
     6873                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     6874                                'styles'  => array(
     6875                                        'blocks' => array(
     6876                                                'core/button' => array(
     6877                                                        'color'    => array(
     6878                                                                'text'       => 'white',
     6879                                                                'background' => 'blue',
     6880                                                        ),
     6881                                                        ':hover'   => array(
     6882                                                                'color' => array(
     6883                                                                        'text'       => 'blue',
     6884                                                                        'background' => 'white',
     6885                                                                ),
     6886                                                        ),
     6887                                                        'elements' => array(
     6888                                                                'button' => array(
     6889                                                                        'color'  => array(
     6890                                                                                'text' => 'green',
     6891                                                                        ),
     6892                                                                        ':hover' => array(
     6893                                                                                'color' => array(
     6894                                                                                        'text' => 'orange',
     6895                                                                                ),
     6896                                                                        ),
     6897                                                                ),
     6898                                                        ),
     6899                                                ),
     6900                                        ),
     6901                                ),
     6902                        )
     6903                );
     6904
     6905                $expected = ':root :where(.wp-block-button .wp-block-button__link){background-color: blue;color: white;}:root :where(.wp-block-button .wp-block-button__link:hover){background-color: white;color: blue;}:root :where(.wp-block-button .wp-block-button__link .wp-element-button,.wp-block-button .wp-block-button__link  .wp-block-button__link){color: green;}:root :where(.wp-block-button .wp-block-button__link .wp-element-button:hover,.wp-block-button .wp-block-button__link  .wp-block-button__link:hover){color: orange;}';
     6906                $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) );
     6907        }
    66966908}
Note: See TracChangeset for help on using the changeset viewer.