@@ -872,6 +872,7 @@ protected static function prepend_to_selector( $selector, $to_prepend ) {
872872 * @since 5.8.0
873873 * @since 5.9.0 Added `duotone` key with CSS selector.
874874 * @since 6.1.0 Added `features` key with block support feature level selectors.
875+ * @since 6.3.0 Refactored and stabilized selectors API.
875876 *
876877 * @return array Block metadata.
877878 */
@@ -886,56 +887,33 @@ protected static function get_blocks_metadata() {
886887 }
887888
888889 foreach ( $ blocks as $ block_name => $ block_type ) {
889- if (
890- isset ( $ block_type ->supports ['__experimentalSelector ' ] ) &&
891- is_string ( $ block_type ->supports ['__experimentalSelector ' ] )
892- ) {
893- static ::$ blocks_metadata [ $ block_name ]['selector ' ] = $ block_type ->supports ['__experimentalSelector ' ];
894- } else {
895- static ::$ blocks_metadata [ $ block_name ]['selector ' ] = '.wp-block- ' . str_replace ( '/ ' , '- ' , str_replace ( 'core/ ' , '' , $ block_name ) );
896- }
890+ $ root_selector = wp_get_block_css_selector ( $ block_type );
897891
898- if (
899- isset ( $ block_type ->supports ['color ' ]['__experimentalDuotone ' ] ) &&
900- is_string ( $ block_type ->supports ['color ' ]['__experimentalDuotone ' ] )
901- ) {
902- static ::$ blocks_metadata [ $ block_name ]['duotone ' ] = $ block_type ->supports ['color ' ]['__experimentalDuotone ' ];
892+ static ::$ blocks_metadata [ $ block_name ]['selector ' ] = $ root_selector ;
893+ static ::$ blocks_metadata [ $ block_name ]['selectors ' ] = static ::get_block_selectors ( $ block_type , $ root_selector );
894+
895+ $ elements = static ::get_block_element_selectors ( $ root_selector );
896+ if ( ! empty ( $ elements ) ) {
897+ static ::$ blocks_metadata [ $ block_name ]['elements ' ] = $ elements ;
903898 }
904899
905- // Generate block support feature level selectors if opted into
906- // for the current block.
907- $ features = array ();
908- foreach ( static ::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $ key => $ feature ) {
909- if (
910- isset ( $ block_type ->supports [ $ key ]['__experimentalSelector ' ] ) &&
911- $ block_type ->supports [ $ key ]['__experimentalSelector ' ]
912- ) {
913- $ features [ $ feature ] = static ::scope_selector (
914- static ::$ blocks_metadata [ $ block_name ]['selector ' ],
915- $ block_type ->supports [ $ key ]['__experimentalSelector ' ]
916- );
900+ // The block may or may not have a duotone selector.
901+ $ duotone_selector = wp_get_block_css_selector ( $ block_type , 'filter.duotone ' );
902+
903+ // Keep backwards compatibility for support.color.__experimentalDuotone.
904+ if ( null === $ duotone_selector ) {
905+ $ duotone_support = _wp_array_get ( $ block_type ->supports , array ( 'color ' , '__experimentalDuotone ' ), null );
906+
907+ if ( $ duotone_support ) {
908+ $ root_selector = wp_get_block_css_selector ( $ block_type );
909+ $ duotone_selector = WP_Theme_JSON::scope_selector ( $ root_selector , $ duotone_support );
917910 }
918911 }
919912
920- if ( ! empty ( $ features ) ) {
921- static ::$ blocks_metadata [ $ block_name ]['features ' ] = $ features ;
913+ if ( null !== $ duotone_selector ) {
914+ static ::$ blocks_metadata [ $ block_name ]['duotone ' ] = $ duotone_selector ;
922915 }
923916
924- // Assign defaults, then overwrite those that the block sets by itself.
925- // If the block selector is compounded, will append the element to each
926- // individual block selector.
927- $ block_selectors = explode ( ', ' , static ::$ blocks_metadata [ $ block_name ]['selector ' ] );
928- foreach ( static ::ELEMENTS as $ el_name => $ el_selector ) {
929- $ element_selector = array ();
930- foreach ( $ block_selectors as $ selector ) {
931- if ( $ selector === $ el_selector ) {
932- $ element_selector = array ( $ el_selector );
933- break ;
934- }
935- $ element_selector [] = static ::prepend_to_selector ( $ el_selector , $ selector . ' ' );
936- }
937- static ::$ blocks_metadata [ $ block_name ]['elements ' ][ $ el_name ] = implode ( ', ' , $ element_selector );
938- }
939917 // If the block has style variations, append their selectors to the block metadata.
940918 if ( ! empty ( $ block_type ->styles ) ) {
941919 $ style_selectors = array ();
@@ -2216,6 +2194,7 @@ private static function update_separator_declarations( $declarations ) {
22162194 * An internal method to get the block nodes from a theme.json file.
22172195 *
22182196 * @since 6.1.0
2197+ * @since 6.3.0 Refactored and stabilized selectors API.
22192198 *
22202199 * @param array $theme_json The theme.json converted to an array.
22212200 * @return array The block nodes in theme.json.
@@ -2244,8 +2223,8 @@ private static function get_block_nodes( $theme_json ) {
22442223 }
22452224
22462225 $ feature_selectors = null ;
2247- if ( isset ( $ selectors [ $ name ]['features ' ] ) ) {
2248- $ feature_selectors = $ selectors [ $ name ]['features ' ];
2226+ if ( isset ( $ selectors [ $ name ]['selectors ' ] ) ) {
2227+ $ feature_selectors = $ selectors [ $ name ]['selectors ' ];
22492228 }
22502229
22512230 $ variation_selectors = array ();
@@ -2262,6 +2241,7 @@ private static function get_block_nodes( $theme_json ) {
22622241 'name ' => $ name ,
22632242 'path ' => array ( 'styles ' , 'blocks ' , $ name ),
22642243 'selector ' => $ selector ,
2244+ 'selectors ' => $ feature_selectors ,
22652245 'duotone ' => $ duotone_selector ,
22662246 'features ' => $ feature_selectors ,
22672247 'variations ' => $ variation_selectors ,
@@ -2304,45 +2284,11 @@ private static function get_block_nodes( $theme_json ) {
23042284 * @return string Styles for the block.
23052285 */
23062286 public function get_styles_for_block ( $ block_metadata ) {
2307- $ node = _wp_array_get ( $ this ->theme_json , $ block_metadata ['path ' ], array () );
2308- $ use_root_padding = isset ( $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ] ) && true === $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ];
2309- $ selector = $ block_metadata ['selector ' ];
2310- $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
2311-
2312- /*
2313- * Process style declarations for block support features the current
2314- * block contains selectors for. Values for a feature with a custom
2315- * selector are filtered from the theme.json node before it is
2316- * processed as normal.
2317- */
2318- $ feature_declarations = array ();
2319-
2320- if ( ! empty ( $ block_metadata ['features ' ] ) ) {
2321- foreach ( $ block_metadata ['features ' ] as $ feature_name => $ feature_selector ) {
2322- if ( ! empty ( $ node [ $ feature_name ] ) ) {
2323- // Create temporary node containing only the feature data
2324- // to leverage existing `compute_style_properties` function.
2325- $ feature = array ( $ feature_name => $ node [ $ feature_name ] );
2326- // Generate the feature's declarations only.
2327- $ new_feature_declarations = static ::compute_style_properties ( $ feature , $ settings , null , $ this ->theme_json );
2328-
2329- // Merge new declarations with any that already exist for
2330- // the feature selector. This may occur when multiple block
2331- // support features use the same custom selector.
2332- if ( isset ( $ feature_declarations [ $ feature_selector ] ) ) {
2333- foreach ( $ new_feature_declarations as $ new_feature_declaration ) {
2334- $ feature_declarations [ $ feature_selector ][] = $ new_feature_declaration ;
2335- }
2336- } else {
2337- $ feature_declarations [ $ feature_selector ] = $ new_feature_declarations ;
2338- }
2339-
2340- // Remove the feature from the block's node now the
2341- // styles will be included under the feature level selector.
2342- unset( $ node [ $ feature_name ] );
2343- }
2344- }
2345- }
2287+ $ node = _wp_array_get ( $ this ->theme_json , $ block_metadata ['path ' ], array () );
2288+ $ use_root_padding = isset ( $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ] ) && true === $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ];
2289+ $ selector = $ block_metadata ['selector ' ];
2290+ $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
2291+ $ feature_declarations = static ::get_feature_declarations_for_node ( $ block_metadata , $ node );
23462292
23472293 // If there are style variations, generate the declarations for them, including any feature selectors the block may have.
23482294 $ style_variation_declarations = array ();
@@ -3529,6 +3475,159 @@ private static function resolve_custom_css_format( $tree ) {
35293475 return $ tree ;
35303476 }
35313477
3478+ /**
3479+ * Returns the selectors metadata for a block.
3480+ *
3481+ * @since 6.3.0
3482+ *
3483+ * @param object $block_type The block type.
3484+ * @param string $root_selector The block's root selector.
3485+ *
3486+ * @return array The custom selectors set by the block.
3487+ */
3488+ protected static function get_block_selectors ( $ block_type , $ root_selector ) {
3489+ if ( ! empty ( $ block_type ->selectors ) ) {
3490+ return $ block_type ->selectors ;
3491+ }
3492+
3493+ $ selectors = array ( 'root ' => $ root_selector );
3494+ foreach ( static ::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $ key => $ feature ) {
3495+ $ feature_selector = wp_get_block_css_selector ( $ block_type , $ key );
3496+ if ( null !== $ feature_selector ) {
3497+ $ selectors [ $ feature ] = array ( 'root ' => $ feature_selector );
3498+ }
3499+ }
3500+
3501+ return $ selectors ;
3502+ }
3503+
3504+ /**
3505+ * Generates all the element selectors for a block.
3506+ *
3507+ * @since 6.3.0
3508+ *
3509+ * @param string $root_selector The block's root CSS selector.
3510+ * @return array The block's element selectors.
3511+ */
3512+ protected static function get_block_element_selectors ( $ root_selector ) {
3513+ // Assign defaults, then override those that the block sets by itself.
3514+ // If the block selector is compounded, will append the element to each
3515+ // individual block selector.
3516+ $ block_selectors = explode ( ', ' , $ root_selector );
3517+ $ element_selectors = array ();
3518+
3519+ foreach ( static ::ELEMENTS as $ el_name => $ el_selector ) {
3520+ $ element_selector = array ();
3521+ foreach ( $ block_selectors as $ selector ) {
3522+ if ( $ selector === $ el_selector ) {
3523+ $ element_selector = array ( $ el_selector );
3524+ break ;
3525+ }
3526+ $ element_selector [] = static ::prepend_to_selector ( $ el_selector , $ selector . ' ' );
3527+ }
3528+ $ element_selectors [ $ el_name ] = implode ( ', ' , $ element_selector );
3529+ }
3530+
3531+ return $ element_selectors ;
3532+ }
3533+
3534+ /**
3535+ * Generates style declarations for a node's features e.g., color, border,
3536+ * typography etc. that have custom selectors in their related block's
3537+ * metadata.
3538+ *
3539+ * @since 6.3.0
3540+ *
3541+ * @param object $metadata The related block metadata containing selectors.
3542+ * @param object $node A merged theme.json node for block or variation.
3543+ *
3544+ * @return array The style declarations for the node's features with custom
3545+ * selectors.
3546+ */
3547+ protected function get_feature_declarations_for_node ( $ metadata , &$ node ) {
3548+ $ declarations = array ();
3549+
3550+ if ( ! isset ( $ metadata ['selectors ' ] ) ) {
3551+ return $ declarations ;
3552+ }
3553+
3554+ $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
3555+
3556+ foreach ( $ metadata ['selectors ' ] as $ feature => $ feature_selectors ) {
3557+ // Skip if this is the block's root selector or the block doesn't
3558+ // have any styles for the feature.
3559+ if ( 'root ' === $ feature || empty ( $ node [ $ feature ] ) ) {
3560+ continue ;
3561+ }
3562+
3563+ if ( is_array ( $ feature_selectors ) ) {
3564+ foreach ( $ feature_selectors as $ subfeature => $ subfeature_selector ) {
3565+ if ( 'root ' === $ subfeature || empty ( $ node [ $ feature ][ $ subfeature ] ) ) {
3566+ continue ;
3567+ }
3568+
3569+ // Create temporary node containing only the subfeature data
3570+ // to leverage existing `compute_style_properties` function.
3571+ $ subfeature_node = array (
3572+ $ feature => array (
3573+ $ subfeature => $ node [ $ feature ][ $ subfeature ],
3574+ ),
3575+ );
3576+
3577+ // Generate style declarations.
3578+ $ new_declarations = static ::compute_style_properties ( $ subfeature_node , $ settings , null , $ this ->theme_json );
3579+
3580+ // Merge subfeature declarations into feature declarations.
3581+ if ( isset ( $ declarations [ $ subfeature_selector ] ) ) {
3582+ foreach ( $ new_declarations as $ new_declaration ) {
3583+ $ declarations [ $ subfeature_selector ][] = $ new_declaration ;
3584+ }
3585+ } else {
3586+ $ declarations [ $ subfeature_selector ] = $ new_declarations ;
3587+ }
3588+
3589+ // Remove the subfeature from the block's node now its
3590+ // styles will be included under its own selector not the
3591+ // block's.
3592+ unset( $ node [ $ feature ][ $ subfeature ] );
3593+ }
3594+ }
3595+
3596+ // Now subfeatures have been processed and removed we can process
3597+ // feature root selector or simple string selector.
3598+ if (
3599+ is_string ( $ feature_selectors ) ||
3600+ ( isset ( $ feature_selectors ['root ' ] ) && $ feature_selectors ['root ' ] )
3601+ ) {
3602+ $ feature_selector = is_string ( $ feature_selectors ) ? $ feature_selectors : $ feature_selectors ['root ' ];
3603+
3604+ // Create temporary node containing only the feature data
3605+ // to leverage existing `compute_style_properties` function.
3606+ $ feature_node = array ( $ feature => $ node [ $ feature ] );
3607+
3608+ // Generate the style declarations.
3609+ $ new_declarations = static ::compute_style_properties ( $ feature_node , $ settings , null , $ this ->theme_json );
3610+
3611+ // Merge new declarations with any that already exist for
3612+ // the feature selector. This may occur when multiple block
3613+ // support features use the same custom selector.
3614+ if ( isset ( $ declarations [ $ feature_selector ] ) ) {
3615+ foreach ( $ new_declarations as $ new_declaration ) {
3616+ $ declarations [ $ feature_selector ][] = $ new_declaration ;
3617+ }
3618+ } else {
3619+ $ declarations [ $ feature_selector ] = $ new_declarations ;
3620+ }
3621+
3622+ // Remove the feature from the block's node now its styles
3623+ // will be included under its own selector not the block's.
3624+ unset( $ node [ $ feature ] );
3625+ }
3626+ }
3627+
3628+ return $ declarations ;
3629+ }
3630+
35323631 /**
35333632 * Replaces CSS variables with their values in place.
35343633 *
@@ -3601,5 +3700,4 @@ function( $carry, $item ) {
36013700 $ theme_json ->theme_json ['styles ' ] = self ::convert_variables_to_value ( $ styles , $ vars );
36023701 return $ theme_json ;
36033702 }
3604-
36053703}
0 commit comments