@@ -879,6 +879,7 @@ protected static function prepend_to_selector( $selector, $to_prepend ) {
879879 * @since 5.8.0
880880 * @since 5.9.0 Added `duotone` key with CSS selector.
881881 * @since 6.1.0 Added `features` key with block support feature level selectors.
882+ * @since 6.3.0 Refactored and stabilized selectors API.
882883 *
883884 * @return array Block metadata.
884885 */
@@ -893,56 +894,33 @@ protected static function get_blocks_metadata() {
893894 }
894895
895896 foreach ( $ blocks as $ block_name => $ block_type ) {
896- if (
897- isset ( $ block_type ->supports ['__experimentalSelector ' ] ) &&
898- is_string ( $ block_type ->supports ['__experimentalSelector ' ] )
899- ) {
900- static ::$ blocks_metadata [ $ block_name ]['selector ' ] = $ block_type ->supports ['__experimentalSelector ' ];
901- } else {
902- static ::$ blocks_metadata [ $ block_name ]['selector ' ] = '.wp-block- ' . str_replace ( '/ ' , '- ' , str_replace ( 'core/ ' , '' , $ block_name ) );
903- }
897+ $ root_selector = wp_get_block_css_selector ( $ block_type );
904898
905- if (
906- isset ( $ block_type ->supports ['color ' ]['__experimentalDuotone ' ] ) &&
907- is_string ( $ block_type ->supports ['color ' ]['__experimentalDuotone ' ] )
908- ) {
909- static ::$ blocks_metadata [ $ block_name ]['duotone ' ] = $ block_type ->supports ['color ' ]['__experimentalDuotone ' ];
899+ static ::$ blocks_metadata [ $ block_name ]['selector ' ] = $ root_selector ;
900+ static ::$ blocks_metadata [ $ block_name ]['selectors ' ] = static ::get_block_selectors ( $ block_type , $ root_selector );
901+
902+ $ elements = static ::get_block_element_selectors ( $ root_selector );
903+ if ( ! empty ( $ elements ) ) {
904+ static ::$ blocks_metadata [ $ block_name ]['elements ' ] = $ elements ;
910905 }
911906
912- // Generate block support feature level selectors if opted into
913- // for the current block.
914- $ features = array ();
915- foreach ( static ::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $ key => $ feature ) {
916- if (
917- isset ( $ block_type ->supports [ $ key ]['__experimentalSelector ' ] ) &&
918- $ block_type ->supports [ $ key ]['__experimentalSelector ' ]
919- ) {
920- $ features [ $ feature ] = static ::scope_selector (
921- static ::$ blocks_metadata [ $ block_name ]['selector ' ],
922- $ block_type ->supports [ $ key ]['__experimentalSelector ' ]
923- );
907+ // The block may or may not have a duotone selector.
908+ $ duotone_selector = wp_get_block_css_selector ( $ block_type , 'filter.duotone ' );
909+
910+ // Keep backwards compatibility for support.color.__experimentalDuotone.
911+ if ( null === $ duotone_selector ) {
912+ $ duotone_support = _wp_array_get ( $ block_type ->supports , array ( 'color ' , '__experimentalDuotone ' ), null );
913+
914+ if ( $ duotone_support ) {
915+ $ root_selector = wp_get_block_css_selector ( $ block_type );
916+ $ duotone_selector = WP_Theme_JSON::scope_selector ( $ root_selector , $ duotone_support );
924917 }
925918 }
926919
927- if ( ! empty ( $ features ) ) {
928- static ::$ blocks_metadata [ $ block_name ]['features ' ] = $ features ;
920+ if ( null !== $ duotone_selector ) {
921+ static ::$ blocks_metadata [ $ block_name ]['duotone ' ] = $ duotone_selector ;
929922 }
930923
931- // Assign defaults, then overwrite those that the block sets by itself.
932- // If the block selector is compounded, will append the element to each
933- // individual block selector.
934- $ block_selectors = explode ( ', ' , static ::$ blocks_metadata [ $ block_name ]['selector ' ] );
935- foreach ( static ::ELEMENTS as $ el_name => $ el_selector ) {
936- $ element_selector = array ();
937- foreach ( $ block_selectors as $ selector ) {
938- if ( $ selector === $ el_selector ) {
939- $ element_selector = array ( $ el_selector );
940- break ;
941- }
942- $ element_selector [] = static ::prepend_to_selector ( $ el_selector , $ selector . ' ' );
943- }
944- static ::$ blocks_metadata [ $ block_name ]['elements ' ][ $ el_name ] = implode ( ', ' , $ element_selector );
945- }
946924 // If the block has style variations, append their selectors to the block metadata.
947925 if ( ! empty ( $ block_type ->styles ) ) {
948926 $ style_selectors = array ();
@@ -2223,6 +2201,7 @@ private static function update_separator_declarations( $declarations ) {
22232201 * An internal method to get the block nodes from a theme.json file.
22242202 *
22252203 * @since 6.1.0
2204+ * @since 6.3.0 Refactored and stabilized selectors API.
22262205 *
22272206 * @param array $theme_json The theme.json converted to an array.
22282207 * @return array The block nodes in theme.json.
@@ -2251,8 +2230,8 @@ private static function get_block_nodes( $theme_json ) {
22512230 }
22522231
22532232 $ feature_selectors = null ;
2254- if ( isset ( $ selectors [ $ name ]['features ' ] ) ) {
2255- $ feature_selectors = $ selectors [ $ name ]['features ' ];
2233+ if ( isset ( $ selectors [ $ name ]['selectors ' ] ) ) {
2234+ $ feature_selectors = $ selectors [ $ name ]['selectors ' ];
22562235 }
22572236
22582237 $ variation_selectors = array ();
@@ -2269,6 +2248,7 @@ private static function get_block_nodes( $theme_json ) {
22692248 'name ' => $ name ,
22702249 'path ' => array ( 'styles ' , 'blocks ' , $ name ),
22712250 'selector ' => $ selector ,
2251+ 'selectors ' => $ feature_selectors ,
22722252 'duotone ' => $ duotone_selector ,
22732253 'features ' => $ feature_selectors ,
22742254 'variations ' => $ variation_selectors ,
@@ -2311,45 +2291,11 @@ private static function get_block_nodes( $theme_json ) {
23112291 * @return string Styles for the block.
23122292 */
23132293 public function get_styles_for_block ( $ block_metadata ) {
2314- $ node = _wp_array_get ( $ this ->theme_json , $ block_metadata ['path ' ], array () );
2315- $ use_root_padding = isset ( $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ] ) && true === $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ];
2316- $ selector = $ block_metadata ['selector ' ];
2317- $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
2318-
2319- /*
2320- * Process style declarations for block support features the current
2321- * block contains selectors for. Values for a feature with a custom
2322- * selector are filtered from the theme.json node before it is
2323- * processed as normal.
2324- */
2325- $ feature_declarations = array ();
2326-
2327- if ( ! empty ( $ block_metadata ['features ' ] ) ) {
2328- foreach ( $ block_metadata ['features ' ] as $ feature_name => $ feature_selector ) {
2329- if ( ! empty ( $ node [ $ feature_name ] ) ) {
2330- // Create temporary node containing only the feature data
2331- // to leverage existing `compute_style_properties` function.
2332- $ feature = array ( $ feature_name => $ node [ $ feature_name ] );
2333- // Generate the feature's declarations only.
2334- $ new_feature_declarations = static ::compute_style_properties ( $ feature , $ settings , null , $ this ->theme_json );
2335-
2336- // Merge new declarations with any that already exist for
2337- // the feature selector. This may occur when multiple block
2338- // support features use the same custom selector.
2339- if ( isset ( $ feature_declarations [ $ feature_selector ] ) ) {
2340- foreach ( $ new_feature_declarations as $ new_feature_declaration ) {
2341- $ feature_declarations [ $ feature_selector ][] = $ new_feature_declaration ;
2342- }
2343- } else {
2344- $ feature_declarations [ $ feature_selector ] = $ new_feature_declarations ;
2345- }
2346-
2347- // Remove the feature from the block's node now the
2348- // styles will be included under the feature level selector.
2349- unset( $ node [ $ feature_name ] );
2350- }
2351- }
2352- }
2294+ $ node = _wp_array_get ( $ this ->theme_json , $ block_metadata ['path ' ], array () );
2295+ $ use_root_padding = isset ( $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ] ) && true === $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ];
2296+ $ selector = $ block_metadata ['selector ' ];
2297+ $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
2298+ $ feature_declarations = static ::get_feature_declarations_for_node ( $ block_metadata , $ node );
23532299
23542300 // If there are style variations, generate the declarations for them, including any feature selectors the block may have.
23552301 $ style_variation_declarations = array ();
@@ -3536,6 +3482,159 @@ private static function resolve_custom_css_format( $tree ) {
35363482 return $ tree ;
35373483 }
35383484
3485+ /**
3486+ * Returns the selectors metadata for a block.
3487+ *
3488+ * @since 6.3.0
3489+ *
3490+ * @param object $block_type The block type.
3491+ * @param string $root_selector The block's root selector.
3492+ *
3493+ * @return array The custom selectors set by the block.
3494+ */
3495+ protected static function get_block_selectors ( $ block_type , $ root_selector ) {
3496+ if ( ! empty ( $ block_type ->selectors ) ) {
3497+ return $ block_type ->selectors ;
3498+ }
3499+
3500+ $ selectors = array ( 'root ' => $ root_selector );
3501+ foreach ( static ::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $ key => $ feature ) {
3502+ $ feature_selector = wp_get_block_css_selector ( $ block_type , $ key );
3503+ if ( null !== $ feature_selector ) {
3504+ $ selectors [ $ feature ] = array ( 'root ' => $ feature_selector );
3505+ }
3506+ }
3507+
3508+ return $ selectors ;
3509+ }
3510+
3511+ /**
3512+ * Generates all the element selectors for a block.
3513+ *
3514+ * @since 6.3.0
3515+ *
3516+ * @param string $root_selector The block's root CSS selector.
3517+ * @return array The block's element selectors.
3518+ */
3519+ protected static function get_block_element_selectors ( $ root_selector ) {
3520+ // Assign defaults, then override those that the block sets by itself.
3521+ // If the block selector is compounded, will append the element to each
3522+ // individual block selector.
3523+ $ block_selectors = explode ( ', ' , $ root_selector );
3524+ $ element_selectors = array ();
3525+
3526+ foreach ( static ::ELEMENTS as $ el_name => $ el_selector ) {
3527+ $ element_selector = array ();
3528+ foreach ( $ block_selectors as $ selector ) {
3529+ if ( $ selector === $ el_selector ) {
3530+ $ element_selector = array ( $ el_selector );
3531+ break ;
3532+ }
3533+ $ element_selector [] = static ::prepend_to_selector ( $ el_selector , $ selector . ' ' );
3534+ }
3535+ $ element_selectors [ $ el_name ] = implode ( ', ' , $ element_selector );
3536+ }
3537+
3538+ return $ element_selectors ;
3539+ }
3540+
3541+ /**
3542+ * Generates style declarations for a node's features e.g., color, border,
3543+ * typography etc. that have custom selectors in their related block's
3544+ * metadata.
3545+ *
3546+ * @since 6.3.0
3547+ *
3548+ * @param object $metadata The related block metadata containing selectors.
3549+ * @param object $node A merged theme.json node for block or variation.
3550+ *
3551+ * @return array The style declarations for the node's features with custom
3552+ * selectors.
3553+ */
3554+ protected function get_feature_declarations_for_node ( $ metadata , &$ node ) {
3555+ $ declarations = array ();
3556+
3557+ if ( ! isset ( $ metadata ['selectors ' ] ) ) {
3558+ return $ declarations ;
3559+ }
3560+
3561+ $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
3562+
3563+ foreach ( $ metadata ['selectors ' ] as $ feature => $ feature_selectors ) {
3564+ // Skip if this is the block's root selector or the block doesn't
3565+ // have any styles for the feature.
3566+ if ( 'root ' === $ feature || empty ( $ node [ $ feature ] ) ) {
3567+ continue ;
3568+ }
3569+
3570+ if ( is_array ( $ feature_selectors ) ) {
3571+ foreach ( $ feature_selectors as $ subfeature => $ subfeature_selector ) {
3572+ if ( 'root ' === $ subfeature || empty ( $ node [ $ feature ][ $ subfeature ] ) ) {
3573+ continue ;
3574+ }
3575+
3576+ // Create temporary node containing only the subfeature data
3577+ // to leverage existing `compute_style_properties` function.
3578+ $ subfeature_node = array (
3579+ $ feature => array (
3580+ $ subfeature => $ node [ $ feature ][ $ subfeature ],
3581+ ),
3582+ );
3583+
3584+ // Generate style declarations.
3585+ $ new_declarations = static ::compute_style_properties ( $ subfeature_node , $ settings , null , $ this ->theme_json );
3586+
3587+ // Merge subfeature declarations into feature declarations.
3588+ if ( isset ( $ declarations [ $ subfeature_selector ] ) ) {
3589+ foreach ( $ new_declarations as $ new_declaration ) {
3590+ $ declarations [ $ subfeature_selector ][] = $ new_declaration ;
3591+ }
3592+ } else {
3593+ $ declarations [ $ subfeature_selector ] = $ new_declarations ;
3594+ }
3595+
3596+ // Remove the subfeature from the block's node now its
3597+ // styles will be included under its own selector not the
3598+ // block's.
3599+ unset( $ node [ $ feature ][ $ subfeature ] );
3600+ }
3601+ }
3602+
3603+ // Now subfeatures have been processed and removed we can process
3604+ // feature root selector or simple string selector.
3605+ if (
3606+ is_string ( $ feature_selectors ) ||
3607+ ( isset ( $ feature_selectors ['root ' ] ) && $ feature_selectors ['root ' ] )
3608+ ) {
3609+ $ feature_selector = is_string ( $ feature_selectors ) ? $ feature_selectors : $ feature_selectors ['root ' ];
3610+
3611+ // Create temporary node containing only the feature data
3612+ // to leverage existing `compute_style_properties` function.
3613+ $ feature_node = array ( $ feature => $ node [ $ feature ] );
3614+
3615+ // Generate the style declarations.
3616+ $ new_declarations = static ::compute_style_properties ( $ feature_node , $ settings , null , $ this ->theme_json );
3617+
3618+ // Merge new declarations with any that already exist for
3619+ // the feature selector. This may occur when multiple block
3620+ // support features use the same custom selector.
3621+ if ( isset ( $ declarations [ $ feature_selector ] ) ) {
3622+ foreach ( $ new_declarations as $ new_declaration ) {
3623+ $ declarations [ $ feature_selector ][] = $ new_declaration ;
3624+ }
3625+ } else {
3626+ $ declarations [ $ feature_selector ] = $ new_declarations ;
3627+ }
3628+
3629+ // Remove the feature from the block's node now its styles
3630+ // will be included under its own selector not the block's.
3631+ unset( $ node [ $ feature ] );
3632+ }
3633+ }
3634+
3635+ return $ declarations ;
3636+ }
3637+
35393638 /**
35403639 * Replaces CSS variables with their values in place.
35413640 *
@@ -3608,5 +3707,4 @@ function( $carry, $item ) {
36083707 $ theme_json ->theme_json ['styles ' ] = self ::convert_variables_to_value ( $ styles , $ vars );
36093708 return $ theme_json ;
36103709 }
3611-
36123710}
0 commit comments