@@ -865,6 +865,7 @@ protected static function prepend_to_selector( $selector, $to_prepend ) {
865865 * @since 5.8.0
866866 * @since 5.9.0 Added `duotone` key with CSS selector.
867867 * @since 6.1.0 Added `features` key with block support feature level selectors.
868+ * @since 6.3.0 Refactored and stabilized selectors API.
868869 *
869870 * @return array Block metadata.
870871 */
@@ -879,56 +880,33 @@ protected static function get_blocks_metadata() {
879880 }
880881
881882 foreach ( $ blocks as $ block_name => $ block_type ) {
882- if (
883- isset ( $ block_type ->supports ['__experimentalSelector ' ] ) &&
884- is_string ( $ block_type ->supports ['__experimentalSelector ' ] )
885- ) {
886- static ::$ blocks_metadata [ $ block_name ]['selector ' ] = $ block_type ->supports ['__experimentalSelector ' ];
887- } else {
888- static ::$ blocks_metadata [ $ block_name ]['selector ' ] = '.wp-block- ' . str_replace ( '/ ' , '- ' , str_replace ( 'core/ ' , '' , $ block_name ) );
889- }
883+ $ root_selector = wp_get_block_css_selector ( $ block_type );
890884
891- if (
892- isset ( $ block_type ->supports ['color ' ]['__experimentalDuotone ' ] ) &&
893- is_string ( $ block_type ->supports ['color ' ]['__experimentalDuotone ' ] )
894- ) {
895- static ::$ blocks_metadata [ $ block_name ]['duotone ' ] = $ block_type ->supports ['color ' ]['__experimentalDuotone ' ];
885+ static ::$ blocks_metadata [ $ block_name ]['selector ' ] = $ root_selector ;
886+ static ::$ blocks_metadata [ $ block_name ]['selectors ' ] = static ::get_block_selectors ( $ block_type , $ root_selector );
887+
888+ $ elements = static ::get_block_element_selectors ( $ root_selector );
889+ if ( ! empty ( $ elements ) ) {
890+ static ::$ blocks_metadata [ $ block_name ]['elements ' ] = $ elements ;
896891 }
897892
898- // Generate block support feature level selectors if opted into
899- // for the current block.
900- $ features = array ();
901- foreach ( static ::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $ key => $ feature ) {
902- if (
903- isset ( $ block_type ->supports [ $ key ]['__experimentalSelector ' ] ) &&
904- $ block_type ->supports [ $ key ]['__experimentalSelector ' ]
905- ) {
906- $ features [ $ feature ] = static ::scope_selector (
907- static ::$ blocks_metadata [ $ block_name ]['selector ' ],
908- $ block_type ->supports [ $ key ]['__experimentalSelector ' ]
909- );
893+ // The block may or may not have a duotone selector.
894+ $ duotone_selector = wp_get_block_css_selector ( $ block_type , 'filter.duotone ' );
895+
896+ // Keep backwards compatibility for support.color.__experimentalDuotone.
897+ if ( null === $ duotone_selector ) {
898+ $ duotone_support = _wp_array_get ( $ block_type ->supports , array ( 'color ' , '__experimentalDuotone ' ), null );
899+
900+ if ( $ duotone_support ) {
901+ $ root_selector = wp_get_block_css_selector ( $ block_type );
902+ $ duotone_selector = WP_Theme_JSON::scope_selector ( $ root_selector , $ duotone_support );
910903 }
911904 }
912905
913- if ( ! empty ( $ features ) ) {
914- static ::$ blocks_metadata [ $ block_name ]['features ' ] = $ features ;
906+ if ( null !== $ duotone_selector ) {
907+ static ::$ blocks_metadata [ $ block_name ]['duotone ' ] = $ duotone_selector ;
915908 }
916909
917- // Assign defaults, then overwrite those that the block sets by itself.
918- // If the block selector is compounded, will append the element to each
919- // individual block selector.
920- $ block_selectors = explode ( ', ' , static ::$ blocks_metadata [ $ block_name ]['selector ' ] );
921- foreach ( static ::ELEMENTS as $ el_name => $ el_selector ) {
922- $ element_selector = array ();
923- foreach ( $ block_selectors as $ selector ) {
924- if ( $ selector === $ el_selector ) {
925- $ element_selector = array ( $ el_selector );
926- break ;
927- }
928- $ element_selector [] = static ::prepend_to_selector ( $ el_selector , $ selector . ' ' );
929- }
930- static ::$ blocks_metadata [ $ block_name ]['elements ' ][ $ el_name ] = implode ( ', ' , $ element_selector );
931- }
932910 // If the block has style variations, append their selectors to the block metadata.
933911 if ( ! empty ( $ block_type ->styles ) ) {
934912 $ style_selectors = array ();
@@ -2222,6 +2200,7 @@ private static function update_separator_declarations( $declarations ) {
22222200 * An internal method to get the block nodes from a theme.json file.
22232201 *
22242202 * @since 6.1.0
2203+ * @since 6.3.0 Refactored and stabilized selectors API.
22252204 *
22262205 * @param array $theme_json The theme.json converted to an array.
22272206 * @return array The block nodes in theme.json.
@@ -2250,8 +2229,8 @@ private static function get_block_nodes( $theme_json ) {
22502229 }
22512230
22522231 $ feature_selectors = null ;
2253- if ( isset ( $ selectors [ $ name ]['features ' ] ) ) {
2254- $ feature_selectors = $ selectors [ $ name ]['features ' ];
2232+ if ( isset ( $ selectors [ $ name ]['selectors ' ] ) ) {
2233+ $ feature_selectors = $ selectors [ $ name ]['selectors ' ];
22552234 }
22562235
22572236 $ variation_selectors = array ();
@@ -2268,6 +2247,7 @@ private static function get_block_nodes( $theme_json ) {
22682247 'name ' => $ name ,
22692248 'path ' => array ( 'styles ' , 'blocks ' , $ name ),
22702249 'selector ' => $ selector ,
2250+ 'selectors ' => $ feature_selectors ,
22712251 'duotone ' => $ duotone_selector ,
22722252 'features ' => $ feature_selectors ,
22732253 'variations ' => $ variation_selectors ,
@@ -2310,45 +2290,11 @@ private static function get_block_nodes( $theme_json ) {
23102290 * @return string Styles for the block.
23112291 */
23122292 public function get_styles_for_block ( $ block_metadata ) {
2313- $ node = _wp_array_get ( $ this ->theme_json , $ block_metadata ['path ' ], array () );
2314- $ use_root_padding = isset ( $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ] ) && true === $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ];
2315- $ selector = $ block_metadata ['selector ' ];
2316- $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
2317-
2318- /*
2319- * Process style declarations for block support features the current
2320- * block contains selectors for. Values for a feature with a custom
2321- * selector are filtered from the theme.json node before it is
2322- * processed as normal.
2323- */
2324- $ feature_declarations = array ();
2325-
2326- if ( ! empty ( $ block_metadata ['features ' ] ) ) {
2327- foreach ( $ block_metadata ['features ' ] as $ feature_name => $ feature_selector ) {
2328- if ( ! empty ( $ node [ $ feature_name ] ) ) {
2329- // Create temporary node containing only the feature data
2330- // to leverage existing `compute_style_properties` function.
2331- $ feature = array ( $ feature_name => $ node [ $ feature_name ] );
2332- // Generate the feature's declarations only.
2333- $ new_feature_declarations = static ::compute_style_properties ( $ feature , $ settings , null , $ this ->theme_json );
2334-
2335- // Merge new declarations with any that already exist for
2336- // the feature selector. This may occur when multiple block
2337- // support features use the same custom selector.
2338- if ( isset ( $ feature_declarations [ $ feature_selector ] ) ) {
2339- foreach ( $ new_feature_declarations as $ new_feature_declaration ) {
2340- $ feature_declarations [ $ feature_selector ][] = $ new_feature_declaration ;
2341- }
2342- } else {
2343- $ feature_declarations [ $ feature_selector ] = $ new_feature_declarations ;
2344- }
2345-
2346- // Remove the feature from the block's node now the
2347- // styles will be included under the feature level selector.
2348- unset( $ node [ $ feature_name ] );
2349- }
2350- }
2351- }
2293+ $ node = _wp_array_get ( $ this ->theme_json , $ block_metadata ['path ' ], array () );
2294+ $ use_root_padding = isset ( $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ] ) && true === $ this ->theme_json ['settings ' ]['useRootPaddingAwareAlignments ' ];
2295+ $ selector = $ block_metadata ['selector ' ];
2296+ $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
2297+ $ feature_declarations = static ::get_feature_declarations_for_node ( $ block_metadata , $ node );
23522298
23532299 // If there are style variations, generate the declarations for them, including any feature selectors the block may have.
23542300 $ style_variation_declarations = array ();
@@ -3486,4 +3432,158 @@ public function set_spacing_sizes() {
34863432
34873433 _wp_array_set ( $ this ->theme_json , array ( 'settings ' , 'spacing ' , 'spacingSizes ' , 'default ' ), $ spacing_sizes );
34883434 }
3435+
3436+
3437+ /**
3438+ * Returns the selectors metadata for a block.
3439+ *
3440+ * @since 6.3.0
3441+ *
3442+ * @param object $block_type The block type.
3443+ * @param string $root_selector The block's root selector.
3444+ *
3445+ * @return array The custom selectors set by the block.
3446+ */
3447+ protected static function get_block_selectors ( $ block_type , $ root_selector ) {
3448+ if ( ! empty ( $ block_type ->selectors ) ) {
3449+ return $ block_type ->selectors ;
3450+ }
3451+
3452+ $ selectors = array ( 'root ' => $ root_selector );
3453+ foreach ( static ::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $ key => $ feature ) {
3454+ $ feature_selector = wp_get_block_css_selector ( $ block_type , $ key );
3455+ if ( null !== $ feature_selector ) {
3456+ $ selectors [ $ feature ] = array ( 'root ' => $ feature_selector );
3457+ }
3458+ }
3459+
3460+ return $ selectors ;
3461+ }
3462+
3463+ /**
3464+ * Generates all the element selectors for a block.
3465+ *
3466+ * @since 6.3.0
3467+ *
3468+ * @param string $root_selector The block's root CSS selector.
3469+ * @return array The block's element selectors.
3470+ */
3471+ protected static function get_block_element_selectors ( $ root_selector ) {
3472+ // Assign defaults, then override those that the block sets by itself.
3473+ // If the block selector is compounded, will append the element to each
3474+ // individual block selector.
3475+ $ block_selectors = explode ( ', ' , $ root_selector );
3476+ $ element_selectors = array ();
3477+
3478+ foreach ( static ::ELEMENTS as $ el_name => $ el_selector ) {
3479+ $ element_selector = array ();
3480+ foreach ( $ block_selectors as $ selector ) {
3481+ if ( $ selector === $ el_selector ) {
3482+ $ element_selector = array ( $ el_selector );
3483+ break ;
3484+ }
3485+ $ element_selector [] = static ::prepend_to_selector ( $ el_selector , $ selector . ' ' );
3486+ }
3487+ $ element_selectors [ $ el_name ] = implode ( ', ' , $ element_selector );
3488+ }
3489+
3490+ return $ element_selectors ;
3491+ }
3492+
3493+ /**
3494+ * Generates style declarations for a node's features e.g., color, border,
3495+ * typography etc. that have custom selectors in their related block's
3496+ * metadata.
3497+ *
3498+ * @since 6.3.0
3499+ *
3500+ * @param object $metadata The related block metadata containing selectors.
3501+ * @param object $node A merged theme.json node for block or variation.
3502+ *
3503+ * @return array The style declarations for the node's features with custom
3504+ * selectors.
3505+ */
3506+ protected function get_feature_declarations_for_node ( $ metadata , &$ node ) {
3507+ $ declarations = array ();
3508+
3509+ if ( ! isset ( $ metadata ['selectors ' ] ) ) {
3510+ return $ declarations ;
3511+ }
3512+
3513+ $ settings = _wp_array_get ( $ this ->theme_json , array ( 'settings ' ) );
3514+
3515+ foreach ( $ metadata ['selectors ' ] as $ feature => $ feature_selectors ) {
3516+ // Skip if this is the block's root selector or the block doesn't
3517+ // have any styles for the feature.
3518+ if ( 'root ' === $ feature || empty ( $ node [ $ feature ] ) ) {
3519+ continue ;
3520+ }
3521+
3522+ if ( is_array ( $ feature_selectors ) ) {
3523+ foreach ( $ feature_selectors as $ subfeature => $ subfeature_selector ) {
3524+ if ( 'root ' === $ subfeature || empty ( $ node [ $ feature ][ $ subfeature ] ) ) {
3525+ continue ;
3526+ }
3527+
3528+ // Create temporary node containing only the subfeature data
3529+ // to leverage existing `compute_style_properties` function.
3530+ $ subfeature_node = array (
3531+ $ feature => array (
3532+ $ subfeature => $ node [ $ feature ][ $ subfeature ],
3533+ ),
3534+ );
3535+
3536+ // Generate style declarations.
3537+ $ new_declarations = static ::compute_style_properties ( $ subfeature_node , $ settings , null , $ this ->theme_json );
3538+
3539+ // Merge subfeature declarations into feature declarations.
3540+ if ( isset ( $ declarations [ $ subfeature_selector ] ) ) {
3541+ foreach ( $ new_declarations as $ new_declaration ) {
3542+ $ declarations [ $ subfeature_selector ][] = $ new_declaration ;
3543+ }
3544+ } else {
3545+ $ declarations [ $ subfeature_selector ] = $ new_declarations ;
3546+ }
3547+
3548+ // Remove the subfeature from the block's node now its
3549+ // styles will be included under its own selector not the
3550+ // block's.
3551+ unset( $ node [ $ feature ][ $ subfeature ] );
3552+ }
3553+ }
3554+
3555+ // Now subfeatures have been processed and removed we can process
3556+ // feature root selector or simple string selector.
3557+ if (
3558+ is_string ( $ feature_selectors ) ||
3559+ ( isset ( $ feature_selectors ['root ' ] ) && $ feature_selectors ['root ' ] )
3560+ ) {
3561+ $ feature_selector = is_string ( $ feature_selectors ) ? $ feature_selectors : $ feature_selectors ['root ' ];
3562+
3563+ // Create temporary node containing only the feature data
3564+ // to leverage existing `compute_style_properties` function.
3565+ $ feature_node = array ( $ feature => $ node [ $ feature ] );
3566+
3567+ // Generate the style declarations.
3568+ $ new_declarations = static ::compute_style_properties ( $ feature_node , $ settings , null , $ this ->theme_json );
3569+
3570+ // Merge new declarations with any that already exist for
3571+ // the feature selector. This may occur when multiple block
3572+ // support features use the same custom selector.
3573+ if ( isset ( $ declarations [ $ feature_selector ] ) ) {
3574+ foreach ( $ new_declarations as $ new_declaration ) {
3575+ $ declarations [ $ feature_selector ][] = $ new_declaration ;
3576+ }
3577+ } else {
3578+ $ declarations [ $ feature_selector ] = $ new_declarations ;
3579+ }
3580+
3581+ // Remove the feature from the block's node now its styles
3582+ // will be included under its own selector not the block's.
3583+ unset( $ node [ $ feature ] );
3584+ }
3585+ }
3586+
3587+ return $ declarations ;
3588+ }
34893589}
0 commit comments