Make WordPress Core

Changeset 62641


Ignore:
Timestamp:
07/06/2026 04:23:49 AM (6 weeks ago)
Author:
isabel_brison
Message:

Editor: add support to style current menu item via theme.json.

Adds processing of custom states from theme.json and a new -current custom state type that is enabled for navigation link block.

Props onemaggie, isabel_brison.
Fixes #64806.

Location:
trunk
Files:
2 edited

Legend:

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

    r62639 r62641  
    642642         */
    643643        const VALID_BLOCK_PSEUDO_SELECTORS = array(
    644                 'core/button' => array( ':hover', ':focus', ':focus-visible', ':active' ),
     644                'core/button'          => array( ':hover', ':focus', ':focus-visible', ':active' ),
     645                'core/navigation-link' => array( ':hover', ':focus', ':focus-visible', ':active' ),
     646        );
     647
     648        /**
     649         * Custom states for blocks that map to CSS class selectors rather than
     650         * CSS pseudo-selectors. Values use the '-' prefix (e.g. '-current') to
     651         * distinguish them from real CSS pseudo-selectors and breakpoint states.
     652         *
     653         * The CSS selector for each state is defined in the block's block.json
     654         * under `selectors.states`, e.g.:
     655         *
     656         *   "selectors": { "states": { "-current": ".some-css-selector" } }
     657         *
     658         * This constant controls which states are valid in theme.json for a given
     659         * block. Blocks listed here also inherit their VALID_BLOCK_PSEUDO_SELECTORS
     660         * as valid sub-states, producing compound selectors such as
     661         * `.wp-block-navigation-item.current-menu-item:hover`.
     662         *
     663         * @since 7.1.0
     664         * @var array
     665         */
     666        const VALID_BLOCK_CUSTOM_STATES = array(
     667                'core/navigation-link' => array( '-current' ),
    645668        );
    646669
     
    11561179                                foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
    11571180                                        $schema_styles_blocks[ $block ][ $pseudo_selector ] = $styles_non_top_level;
     1181                                }
     1182                        }
     1183
     1184                        // Add custom states for blocks that support them (e.g. '-current' for navigation).
     1185                        if ( isset( static::VALID_BLOCK_CUSTOM_STATES[ $block ] ) ) {
     1186                                foreach ( static::VALID_BLOCK_CUSTOM_STATES[ $block ] as $custom_state ) {
     1187                                        $custom_state_schema = $styles_non_top_level;
     1188                                        /*
     1189                                         * The same pseudo-selectors valid for the block at the top level
     1190                                         * are also valid within each custom state.
     1191                                         */
     1192                                        if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
     1193                                                foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo ) {
     1194                                                        $custom_state_schema[ $pseudo ] = $styles_non_top_level;
     1195                                                }
     1196                                        }
     1197                                        $schema_styles_blocks[ $block ][ $custom_state ] = $custom_state_schema;
    11581198                                }
    11591199                        }
     
    15561596                        if ( ! empty( $style_selectors ) ) {
    15571597                                static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors;
     1598                        }
     1599
     1600                        // If the block has custom states defined in block.json, store their selectors.
     1601                        if ( ! empty( $block_type->selectors['states'] ) && is_array( $block_type->selectors['states'] ) ) {
     1602                                static::$blocks_metadata[ $block_name ]['states'] = $block_type->selectors['states'];
    15581603                        }
    15591604                }
     
    32153260                                                                        'css'         => static::append_to_selector( $selector, $pseudo_selector ),
    32163261                                                                );
     3262                                                        }
     3263                                                }
     3264                                        }
     3265                                }
     3266
     3267                                // Handle custom states (e.g. '-current' for navigation).
     3268                                if ( isset( static::VALID_BLOCK_CUSTOM_STATES[ $name ] ) ) {
     3269                                        foreach ( static::VALID_BLOCK_CUSTOM_STATES[ $name ] as $custom_state ) {
     3270                                                if (
     3271                                                        isset( $theme_json['styles']['blocks'][ $name ][ $custom_state ] ) &&
     3272                                                        isset( $selectors[ $name ]['states'][ $custom_state ] )
     3273                                                ) {
     3274                                                        $custom_css_selector = $selectors[ $name ]['states'][ $custom_state ];
     3275                                                        $nodes[]             = array(
     3276                                                                'name'       => $name,
     3277                                                                'path'       => array( 'styles', 'blocks', $name, $custom_state ),
     3278                                                                'selector'   => $custom_css_selector,
     3279                                                                'selectors'  => $feature_selectors,
     3280                                                                'elements'   => $selectors[ $name ]['elements'] ?? array(),
     3281                                                                'duotone'    => $duotone_selector,
     3282                                                                'variations' => $variation_selectors,
     3283                                                                'css'        => $custom_css_selector,
     3284                                                        );
     3285
     3286                                                        // Sub-pseudo-selectors within the custom state.
     3287                                                        if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) {
     3288                                                                foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo ) {
     3289                                                                        if ( isset( $theme_json['styles']['blocks'][ $name ][ $custom_state ][ $pseudo ] ) ) {
     3290                                                                                $compound_css_selector = static::append_to_selector( $custom_css_selector, $pseudo );
     3291                                                                                $nodes[]               = array(
     3292                                                                                        'name'       => $name,
     3293                                                                                        'path'       => array( 'styles', 'blocks', $name, $custom_state, $pseudo ),
     3294                                                                                        'selector'   => $compound_css_selector,
     3295                                                                                        'selectors'  => $feature_selectors,
     3296                                                                                        'elements'   => $selectors[ $name ]['elements'] ?? array(),
     3297                                                                                        'duotone'    => $duotone_selector,
     3298                                                                                        'variations' => $variation_selectors,
     3299                                                                                        'css'        => $compound_css_selector,
     3300                                                                                );
     3301                                                                        }
     3302                                                                }
    32173303                                                        }
    32183304                                                }
  • trunk/tests/phpunit/tests/theme/wpThemeJson.php

    r62639 r62641  
    75717571                $this->assertStringNotContainsString( 'gap', $result, 'Array value should be skipped' );
    75727572        }
     7573
     7574        /**
     7575         * Test that block custom states (e.g. -current) are processed correctly.
     7576         *
     7577         * @covers WP_Theme_JSON::get_styles_for_block
     7578         *
     7579         * @ticket 64806
     7580         */
     7581        public function test_block_custom_states_are_processed() {
     7582                // Only -current styles, no base block styles, so we can assert the
     7583                // output uses the current-menu-item selector and not the block selector.
     7584                $theme_json = new WP_Theme_JSON(
     7585                        array(
     7586                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     7587                                'styles'  => array(
     7588                                        'blocks' => array(
     7589                                                'core/navigation-link' => array(
     7590                                                        '-current' => array(
     7591                                                                'color' => array(
     7592                                                                        'text'       => 'red',
     7593                                                                        'background' => 'blue',
     7594                                                                ),
     7595                                                        ),
     7596                                                ),
     7597                                        ),
     7598                                ),
     7599                        )
     7600                );
     7601
     7602                $current_node = array(
     7603                        'path'     => array( 'styles', 'blocks', 'core/navigation-link', '-current' ),
     7604                        'selector' => '.wp-block-navigation .current-menu-item',
     7605                );
     7606                $expected     = ':root :where(.wp-block-navigation .current-menu-item){background-color: blue;color: red;}';
     7607
     7608                $this->assertSame( $expected, $theme_json->get_styles_for_block( $current_node ) );
     7609        }
     7610
     7611        /**
     7612         * Test that block custom states compound correctly with pseudo-selectors (e.g. -current + :hover).
     7613         *
     7614         * @covers WP_Theme_JSON::get_styles_for_block
     7615         *
     7616         * @ticket 64806
     7617         */
     7618        public function test_block_custom_states_compound_with_pseudo_selectors() {
     7619                $theme_json = new WP_Theme_JSON(
     7620                        array(
     7621                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     7622                                'styles'  => array(
     7623                                        'blocks' => array(
     7624                                                'core/navigation-link' => array(
     7625                                                        '-current' => array(
     7626                                                                'color'  => array(
     7627                                                                        'text'       => 'red',
     7628                                                                        'background' => 'blue',
     7629                                                                ),
     7630                                                                ':hover' => array(
     7631                                                                        'color' => array(
     7632                                                                                'text'       => 'blue',
     7633                                                                                'background' => 'white',
     7634                                                                        ),
     7635                                                                ),
     7636                                                                ':focus' => array(
     7637                                                                        'color' => array(
     7638                                                                                'text'       => 'green',
     7639                                                                                'background' => 'yellow',
     7640                                                                        ),
     7641                                                                ),
     7642                                                        ),
     7643                                                ),
     7644                                        ),
     7645                                ),
     7646                        )
     7647                );
     7648
     7649                $current_node = array(
     7650                        'path'     => array( 'styles', 'blocks', 'core/navigation-link', '-current' ),
     7651                        'selector' => '.wp-block-navigation .current-menu-item',
     7652                );
     7653                $hover_node   = array(
     7654                        'path'     => array( 'styles', 'blocks', 'core/navigation-link', '-current', ':hover' ),
     7655                        'selector' => '.wp-block-navigation .current-menu-item:hover',
     7656                );
     7657                $focus_node   = array(
     7658                        'path'     => array( 'styles', 'blocks', 'core/navigation-link', '-current', ':focus' ),
     7659                        'selector' => '.wp-block-navigation .current-menu-item:focus',
     7660                );
     7661
     7662                $expected  = ':root :where(.wp-block-navigation .current-menu-item){background-color: blue;color: red;}';
     7663                $expected .= ':root :where(.wp-block-navigation .current-menu-item:hover){background-color: white;color: blue;}';
     7664                $expected .= ':root :where(.wp-block-navigation .current-menu-item:focus){background-color: yellow;color: green;}';
     7665
     7666                $actual  = $theme_json->get_styles_for_block( $current_node );
     7667                $actual .= $theme_json->get_styles_for_block( $hover_node );
     7668                $actual .= $theme_json->get_styles_for_block( $focus_node );
     7669
     7670                $this->assertSame( $expected, $actual );
     7671        }
     7672
     7673        /**
     7674         * Test that non-whitelisted custom states are ignored, and that custom states
     7675         * are ignored on blocks that do not declare support for them.
     7676         *
     7677         * @covers WP_Theme_JSON::get_stylesheet
     7678         *
     7679         * @ticket 64806
     7680         */
     7681        public function test_block_custom_states_ignores_non_whitelisted() {
     7682                // A non-whitelisted state key on a block that supports custom states.
     7683                $theme_json_bogus_state = new WP_Theme_JSON(
     7684                        array(
     7685                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     7686                                'styles'  => array(
     7687                                        'blocks' => array(
     7688                                                'core/navigation-link' => array(
     7689                                                        'color'  => array(
     7690                                                                'text' => 'black',
     7691                                                        ),
     7692                                                        '-bogus' => array(
     7693                                                                'color' => array(
     7694                                                                        'text' => 'yellow',
     7695                                                                ),
     7696                                                        ),
     7697                                                ),
     7698                                        ),
     7699                                ),
     7700                        )
     7701                );
     7702
     7703                $stylesheet_bogus = $theme_json_bogus_state->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) );
     7704                $this->assertStringNotContainsString( '-bogus', $stylesheet_bogus );
     7705                $this->assertStringNotContainsString( 'yellow', $stylesheet_bogus );
     7706
     7707                // A valid custom state key on a block that does not support custom states.
     7708                $theme_json_unsupported_block = new WP_Theme_JSON(
     7709                        array(
     7710                                'version' => WP_Theme_JSON::LATEST_SCHEMA,
     7711                                'styles'  => array(
     7712                                        'blocks' => array(
     7713                                                'core/paragraph' => array(
     7714                                                        'color'    => array(
     7715                                                                'text' => 'black',
     7716                                                        ),
     7717                                                        '-current' => array(
     7718                                                                'color' => array(
     7719                                                                        'text' => 'red',
     7720                                                                ),
     7721                                                        ),
     7722                                                ),
     7723                                        ),
     7724                                ),
     7725                        )
     7726                );
     7727
     7728                $stylesheet_unsupported = $theme_json_unsupported_block->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) );
     7729                $expected               = ':root :where(p){color: black;}';
     7730                $this->assertSame( $expected, $stylesheet_unsupported );
     7731                $this->assertStringNotContainsString( '-current', $stylesheet_unsupported );
     7732                $this->assertStringNotContainsString( 'current-menu-item', $stylesheet_unsupported );
     7733        }
    75737734}
Note: See TracChangeset for help on using the changeset viewer.