Make WordPress Core


Ignore:
Timestamp:
06/27/2023 08:46:45 AM (3 years ago)
Author:
isabel_brison
Message:

Editor: refactor and stabilize selectors API.

Restructures the block.json selectors API by moving __experimentalSelector props into their own config, stabilizing the selectors API, and enabling more flexible styling options.

Props ramonopoly, spacedmonkey, aaronrobertshaw, onemaggie.
Fixes #58586.

File:
1 edited

Legend:

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

    r56055 r56058  
    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.
     
    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                         }
    904 
    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'];
    910                         }
    911 
    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                                         );
    924                                 }
    925                         }
    926 
    927                         if ( ! empty( $features ) ) {
    928                                 static::$blocks_metadata[ $block_name ]['features'] = $features;
    929                         }
    930 
    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                         }
     897                        $root_selector = wp_get_block_css_selector( $block_type );
     898
     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;
     905                        }
     906
     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 );
     917                                }
     918                        }
     919
     920                        if ( null !== $duotone_selector ) {
     921                                static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector;
     922                        }
     923
    946924                        // If the block has style variations, append their selectors to the block metadata.
    947925                        if ( ! empty( $block_type->styles ) ) {
     
    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.
     
    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
     
    22702249                                'path'       => array( 'styles', 'blocks', $name ),
    22712250                                'selector'   => $selector,
     2251                                'selectors'  => $feature_selectors,
    22722252                                'duotone'    => $duotone_selector,
    22732253                                'features'   => $feature_selectors,
     
    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.
     
    23562302                if ( ! empty( $block_metadata['variations'] ) ) {
    23572303                        foreach ( $block_metadata['variations'] as $style_variation ) {
    2358                                 $style_variation_node     = _wp_array_get( $this->theme_json, $style_variation['path'], array() );
    2359                                 $style_variation_selector = $style_variation['selector'];
    2360 
    2361                                 // If the block has feature selectors, generate the declarations for them within the current style variation.
    2362                                 if ( ! empty( $block_metadata['features'] ) ) {
    2363                                         $clean_style_variation_selector = trim( $style_variation_selector );
    2364                                         foreach ( $block_metadata['features'] as $feature_name => $feature_selector ) {
    2365                                                 if ( empty( $style_variation_node[ $feature_name ] ) ) {
    2366                                                         continue;
    2367                                                 }
    2368                                                 // Prepend the variation selector to the feature selector.
    2369                                                 $split_feature_selectors    = explode( ',', $feature_selector );
    2370                                                 $feature_selectors          = array_map(
    2371                                                         static function( $split_feature_selector ) use ( $clean_style_variation_selector ) {
    2372                                                                 return $clean_style_variation_selector . trim( $split_feature_selector );
    2373                                                         },
    2374                                                         $split_feature_selectors
    2375                                                 );
    2376                                                 $combined_feature_selectors = implode( ',', $feature_selectors );
    2377 
    2378                                                 // Compute declarations for the feature.
    2379                                                 $new_feature_declarations = static::compute_style_properties( array( $feature_name => $style_variation_node[ $feature_name ] ), $settings, null, $this->theme_json );
    2380 
    2381                                                 /*
    2382                                                  * Merge new declarations with any that already exist for
    2383                                                  * the feature selector. This may occur when multiple block
    2384                                                  * support features use the same custom selector.
    2385                                                  */
    2386                                                 if ( isset( $style_variation_declarations[ $combined_feature_selectors ] ) ) {
    2387                                                         $style_variation_declarations[ $combined_feature_selectors ] = array_merge( $style_variation_declarations[ $combined_feature_selectors ], $new_feature_declarations );
    2388                                                 } else {
    2389                                                         $style_variation_declarations[ $combined_feature_selectors ] = $new_feature_declarations;
    2390                                                 }
    2391                                                 /*
    2392                                                  * Remove the feature from the variation's node now the
    2393                                                  * styles will be included under the feature level selector.
    2394                                                  */
    2395                                                 unset( $style_variation_node[ $feature_name ] );
    2396                                         }
    2397                                 }
     2304                                $style_variation_node           = _wp_array_get( $this->theme_json, $style_variation['path'], array() );
     2305                                $clean_style_variation_selector = trim( $style_variation['selector'] );
     2306
     2307                                // Generate any feature/subfeature style declarations for the current style variation.
     2308                                $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node );
     2309
     2310                                // Combine selectors with style variation's selector and add to overall style variation declarations.
     2311                                foreach ( $variation_declarations as $current_selector => $new_declarations ) {
     2312                                        // If current selector includes block classname, remove it but leave the whitespace in.
     2313                                        $shortened_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $current_selector );
     2314
     2315                                        // Prepend the variation selector to the current selector.
     2316                                        $split_selectors    = explode( ',', $shortened_selector );
     2317                                        $updated_selectors  = array_map(
     2318                                                static function( $split_selector ) use ( $clean_style_variation_selector ) {
     2319                                                        return $clean_style_variation_selector . $split_selector;
     2320                                                },
     2321                                                $split_selectors
     2322                                        );
     2323                                        $combined_selectors = implode( ',', $updated_selectors );
     2324
     2325                                        // Add the new declarations to the overall results under the modified selector.
     2326                                        $style_variation_declarations[ $combined_selectors ] = $new_declarations;
     2327                                }
     2328
    23982329                                // Compute declarations for remaining styles not covered by feature level selectors.
    2399                                 $style_variation_declarations[ $style_variation_selector ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json );
     2330                                $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json );
    24002331                        }
    24012332                }
     
    24732404                // 3. Generate and append the rules that use the duotone selector.
    24742405                if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) {
    2475                         $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] );
    2476                         $block_rules     .= static::to_ruleset( $selector_duotone, $declarations_duotone );
     2406                        $block_rules .= static::to_ruleset( $block_metadata['duotone'], $declarations_duotone );
    24772407                }
    24782408
     
    35383468
    35393469        /**
     3470         * Returns the selectors metadata for a block.
     3471         *
     3472         * @since 6.3.0
     3473         *
     3474         * @param object $block_type    The block type.
     3475         * @param string $root_selector The block's root selector.
     3476         *
     3477         * @return array The custom selectors set by the block.
     3478         */
     3479        protected static function get_block_selectors( $block_type, $root_selector ) {
     3480                if ( ! empty( $block_type->selectors ) ) {
     3481                        return $block_type->selectors;
     3482                }
     3483
     3484                $selectors = array( 'root' => $root_selector );
     3485                foreach ( static::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $key => $feature ) {
     3486                        $feature_selector = wp_get_block_css_selector( $block_type, $key );
     3487                        if ( null !== $feature_selector ) {
     3488                                $selectors[ $feature ] = array( 'root' => $feature_selector );
     3489                        }
     3490                }
     3491
     3492                return $selectors;
     3493        }
     3494
     3495        /**
     3496         * Generates all the element selectors for a block.
     3497         *
     3498         * @since 6.3.0
     3499         *
     3500         * @param string $root_selector The block's root CSS selector.
     3501         * @return array The block's element selectors.
     3502         */
     3503        protected static function get_block_element_selectors( $root_selector ) {
     3504                // Assign defaults, then override those that the block sets by itself.
     3505                // If the block selector is compounded, will append the element to each
     3506                // individual block selector.
     3507                $block_selectors   = explode( ',', $root_selector );
     3508                $element_selectors = array();
     3509
     3510                foreach ( static::ELEMENTS as $el_name => $el_selector ) {
     3511                        $element_selector = array();
     3512                        foreach ( $block_selectors as $selector ) {
     3513                                if ( $selector === $el_selector ) {
     3514                                        $element_selector = array( $el_selector );
     3515                                        break;
     3516                                }
     3517                                $element_selector[] = static::prepend_to_selector( $el_selector, $selector . ' ' );
     3518                        }
     3519                        $element_selectors[ $el_name ] = implode( ',', $element_selector );
     3520                }
     3521
     3522                return $element_selectors;
     3523        }
     3524
     3525        /**
     3526         * Generates style declarations for a node's features e.g., color, border,
     3527         * typography etc. that have custom selectors in their related block's
     3528         * metadata.
     3529         *
     3530         * @since 6.3.0
     3531         *
     3532         * @param object $metadata The related block metadata containing selectors.
     3533         * @param object $node     A merged theme.json node for block or variation.
     3534         *
     3535         * @return array The style declarations for the node's features with custom
     3536         * selectors.
     3537         */
     3538        protected function get_feature_declarations_for_node( $metadata, &$node ) {
     3539                $declarations = array();
     3540
     3541                if ( ! isset( $metadata['selectors'] ) ) {
     3542                        return $declarations;
     3543                }
     3544
     3545                $settings = _wp_array_get( $this->theme_json, array( 'settings' ) );
     3546
     3547                foreach ( $metadata['selectors'] as $feature => $feature_selectors ) {
     3548                        // Skip if this is the block's root selector or the block doesn't
     3549                        // have any styles for the feature.
     3550                        if ( 'root' === $feature || empty( $node[ $feature ] ) ) {
     3551                                continue;
     3552                        }
     3553
     3554                        if ( is_array( $feature_selectors ) ) {
     3555                                foreach ( $feature_selectors as $subfeature => $subfeature_selector ) {
     3556                                        if ( 'root' === $subfeature || empty( $node[ $feature ][ $subfeature ] ) ) {
     3557                                                continue;
     3558                                        }
     3559
     3560                                        // Create temporary node containing only the subfeature data
     3561                                        // to leverage existing `compute_style_properties` function.
     3562                                        $subfeature_node = array(
     3563                                                $feature => array(
     3564                                                        $subfeature => $node[ $feature ][ $subfeature ],
     3565                                                ),
     3566                                        );
     3567
     3568                                        // Generate style declarations.
     3569                                        $new_declarations = static::compute_style_properties( $subfeature_node, $settings, null, $this->theme_json );
     3570
     3571                                        // Merge subfeature declarations into feature declarations.
     3572                                        if ( isset( $declarations[ $subfeature_selector ] ) ) {
     3573                                                foreach ( $new_declarations as $new_declaration ) {
     3574                                                        $declarations[ $subfeature_selector ][] = $new_declaration;
     3575                                                }
     3576                                        } else {
     3577                                                $declarations[ $subfeature_selector ] = $new_declarations;
     3578                                        }
     3579
     3580                                        // Remove the subfeature from the block's node now its
     3581                                        // styles will be included under its own selector not the
     3582                                        // block's.
     3583                                        unset( $node[ $feature ][ $subfeature ] );
     3584                                }
     3585                        }
     3586
     3587                        // Now subfeatures have been processed and removed we can process
     3588                        // feature root selector or simple string selector.
     3589                        if (
     3590                                is_string( $feature_selectors ) ||
     3591                                ( isset( $feature_selectors['root'] ) && $feature_selectors['root'] )
     3592                        ) {
     3593                                $feature_selector = is_string( $feature_selectors ) ? $feature_selectors : $feature_selectors['root'];
     3594
     3595                                // Create temporary node containing only the feature data
     3596                                // to leverage existing `compute_style_properties` function.
     3597                                $feature_node = array( $feature => $node[ $feature ] );
     3598
     3599                                // Generate the style declarations.
     3600                                $new_declarations = static::compute_style_properties( $feature_node, $settings, null, $this->theme_json );
     3601
     3602                                // Merge new declarations with any that already exist for
     3603                                // the feature selector. This may occur when multiple block
     3604                                // support features use the same custom selector.
     3605                                if ( isset( $declarations[ $feature_selector ] ) ) {
     3606                                        foreach ( $new_declarations as $new_declaration ) {
     3607                                                $declarations[ $feature_selector ][] = $new_declaration;
     3608                                        }
     3609                                } else {
     3610                                        $declarations[ $feature_selector ] = $new_declarations;
     3611                                }
     3612
     3613                                // Remove the feature from the block's node now its styles
     3614                                // will be included under its own selector not the block's.
     3615                                unset( $node[ $feature ] );
     3616                        }
     3617                }
     3618
     3619                return $declarations;
     3620        }
     3621
     3622        /**
    35403623         * Replaces CSS variables with their values in place.
    35413624         *
     
    36093692                return $theme_json;
    36103693        }
    3611 
    36123694}
Note: See TracChangeset for help on using the changeset viewer.