@@ -641,7 +641,30 @@ class WP_Theme_JSON {
641641 * @var array
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.
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
647670 /**
@@ -1094,6 +1117,21 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n
10941117 $ schema_styles_blocks [ $ block ][ $ pseudo_selector ] = $ styles_non_top_level ;
10951118 }
10961119 }
1120+
1121+ // Add custom states for blocks that support them (e.g. '@current' for navigation).
1122+ if ( isset ( static ::VALID_BLOCK_CUSTOM_STATES [ $ block ] ) ) {
1123+ foreach ( static ::VALID_BLOCK_CUSTOM_STATES [ $ block ] as $ custom_state ) {
1124+ $ custom_state_schema = $ styles_non_top_level ;
1125+ // The same pseudo-selectors valid for the block at the top level
1126+ // are also valid within each custom state.
1127+ if ( isset ( static ::VALID_BLOCK_PSEUDO_SELECTORS [ $ block ] ) ) {
1128+ foreach ( static ::VALID_BLOCK_PSEUDO_SELECTORS [ $ block ] as $ pseudo ) {
1129+ $ custom_state_schema [ $ pseudo ] = $ styles_non_top_level ;
1130+ }
1131+ }
1132+ $ schema_styles_blocks [ $ block ][ $ custom_state ] = $ custom_state_schema ;
1133+ }
1134+ }
10971135 }
10981136
10991137 $ block_style_variation_styles = static ::VALID_STYLES ;
@@ -1321,6 +1359,11 @@ protected static function get_blocks_metadata() {
13211359 if ( ! empty ( $ style_selectors ) ) {
13221360 static ::$ blocks_metadata [ $ block_name ]['styleVariations ' ] = $ style_selectors ;
13231361 }
1362+
1363+ // If the block has custom states defined in block.json, store their selectors.
1364+ if ( ! empty ( $ block_type ->selectors ['states ' ] ) && is_array ( $ block_type ->selectors ['states ' ] ) ) {
1365+ static ::$ blocks_metadata [ $ block_name ]['states ' ] = $ block_type ->selectors ['states ' ];
1366+ }
13241367 }
13251368
13261369 return static ::$ blocks_metadata ;
@@ -2897,6 +2940,45 @@ private static function get_block_nodes( $theme_json, $selectors = array(), $opt
28972940 }
28982941 }
28992942 }
2943+
2944+ // Handle custom states (e.g. '@current' for navigation).
2945+ if ( isset ( static ::VALID_BLOCK_CUSTOM_STATES [ $ name ] ) ) {
2946+ foreach ( static ::VALID_BLOCK_CUSTOM_STATES [ $ name ] as $ custom_state ) {
2947+ if (
2948+ isset ( $ theme_json ['styles ' ]['blocks ' ][ $ name ][ $ custom_state ] ) &&
2949+ isset ( $ selectors [ $ name ]['states ' ][ $ custom_state ] )
2950+ ) {
2951+ $ custom_css_selector = $ selectors [ $ name ]['states ' ][ $ custom_state ];
2952+ $ nodes [] = array (
2953+ 'name ' => $ name ,
2954+ 'path ' => array ( 'styles ' , 'blocks ' , $ name , $ custom_state ),
2955+ 'selector ' => $ custom_css_selector ,
2956+ 'selectors ' => $ feature_selectors ,
2957+ 'duotone ' => $ duotone_selector ,
2958+ 'variations ' => $ variation_selectors ,
2959+ 'css ' => $ custom_css_selector ,
2960+ );
2961+
2962+ // Sub-pseudo-selectors within the custom state.
2963+ if ( isset ( static ::VALID_BLOCK_PSEUDO_SELECTORS [ $ name ] ) ) {
2964+ foreach ( static ::VALID_BLOCK_PSEUDO_SELECTORS [ $ name ] as $ pseudo ) {
2965+ if ( isset ( $ theme_json ['styles ' ]['blocks ' ][ $ name ][ $ custom_state ][ $ pseudo ] ) ) {
2966+ $ compound_css_selector = static ::append_to_selector ( $ custom_css_selector , $ pseudo );
2967+ $ nodes [] = array (
2968+ 'name ' => $ name ,
2969+ 'path ' => array ( 'styles ' , 'blocks ' , $ name , $ custom_state , $ pseudo ),
2970+ 'selector ' => $ compound_css_selector ,
2971+ 'selectors ' => $ feature_selectors ,
2972+ 'duotone ' => $ duotone_selector ,
2973+ 'variations ' => $ variation_selectors ,
2974+ 'css ' => $ compound_css_selector ,
2975+ );
2976+ }
2977+ }
2978+ }
2979+ }
2980+ }
2981+ }
29002982 }
29012983
29022984 if ( isset ( $ theme_json ['styles ' ]['blocks ' ][ $ name ]['elements ' ] ) ) {
@@ -3046,23 +3128,6 @@ static function ( $split_selector ) use ( $clean_style_variation_selector ) {
30463128 $ element_pseudo_allowed = static ::VALID_ELEMENT_PSEUDO_SELECTORS [ $ current_element ];
30473129 }
30483130
3049- /*
3050- * Check if we're processing a block pseudo-selector.
3051- * $block_metadata['path'] = array( 'styles', 'blocks', 'core/button', ':hover' );
3052- */
3053- $ is_processing_block_pseudo = false ;
3054- $ block_pseudo_selector = null ;
3055- if ( in_array ( 'blocks ' , $ block_metadata ['path ' ], true ) && count ( $ block_metadata ['path ' ] ) >= 4 ) {
3056- $ block_name = $ block_metadata ['path ' ][2 ]; // 'core/button'
3057- $ last_path_element = $ block_metadata ['path ' ][ count ( $ block_metadata ['path ' ] ) - 1 ]; // ':hover'
3058-
3059- if ( isset ( static ::VALID_BLOCK_PSEUDO_SELECTORS [ $ block_name ] ) &&
3060- in_array ( $ last_path_element , static ::VALID_BLOCK_PSEUDO_SELECTORS [ $ block_name ], true ) ) {
3061- $ is_processing_block_pseudo = true ;
3062- $ block_pseudo_selector = $ last_path_element ;
3063- }
3064- }
3065-
30663131 /*
30673132 * Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover").
30683133 * This also resets the array keys.
@@ -3092,14 +3157,6 @@ static function ( $pseudo_selector ) use ( $selector ) {
30923157 && in_array ( $ pseudo_selector , static ::VALID_ELEMENT_PSEUDO_SELECTORS [ $ current_element ], true )
30933158 ) {
30943159 $ declarations = static ::compute_style_properties ( $ node [ $ pseudo_selector ], $ settings , null , $ this ->theme_json , $ selector , $ use_root_padding );
3095- } elseif ( $ is_processing_block_pseudo ) {
3096- // Process block pseudo-selector styles
3097- // For block pseudo-selectors, we need to get the block data first, then access the pseudo-selector
3098- $ block_name = $ block_metadata ['path ' ][2 ]; // 'core/button'
3099- $ block_data = _wp_array_get ( $ this ->theme_json , array ( 'styles ' , 'blocks ' , $ block_name ), array () );
3100- $ pseudo_data = $ block_data [ $ block_pseudo_selector ] ?? array ();
3101-
3102- $ declarations = static ::compute_style_properties ( $ pseudo_data , $ settings , null , $ this ->theme_json , $ selector , $ use_root_padding );
31033160 } else {
31043161 $ declarations = static ::compute_style_properties ( $ node , $ settings , null , $ this ->theme_json , $ selector , $ use_root_padding );
31053162 }
0 commit comments