Make WordPress Core

Changeset 56058


Ignore:
Timestamp:
06/27/2023 08:46:45 AM (18 months 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.

Location:
trunk
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-supports/settings.php

    r55255 r56058  
    6767 *
    6868 * @since 6.2.0
     69 * @since 6.3.0 Updated preset styles to use Selectors API.
    6970 * @access private
    7071 *
     
    9697    $blocks                  = $registry->get_all_registered();
    9798    foreach ( $blocks as $block_type ) {
    98         if (
    99             isset( $block_type->supports['__experimentalSelector'] ) &&
    100             is_string( $block_type->supports['__experimentalSelector'] )
    101         ) {
    102             $variables_root_selector .= ',' . $block_type->supports['__experimentalSelector'];
     99        /*
     100        * We only want to append selectors for block's using custom selectors
     101        * i.e. not `wp-block-<name>`.
     102        */
     103        $has_custom_selector =
     104            ( isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) ||
     105            ( isset( $block_type->selectors['root'] ) && is_string( $block_type->selectors['root'] ) );
     106
     107        if ( $has_custom_selector ) {
     108            $variables_root_selector .= ',' . wp_get_block_css_selector( $block_type );
    103109        }
    104110    }
  • 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}
  • trunk/src/wp-includes/global-styles-and-settings.php

    r56042 r56058  
    444444    return WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_patterns();
    445445}
     446
     447/**
     448 * Determines the CSS selector for the block type and property provided,
     449 * returning it if available.
     450 *
     451 * @since 6.3.0
     452 *
     453 * @param WP_Block_Type $block_type The block's type.
     454 * @param string|array  $target     The desired selector's target, `root` or array path.
     455 * @param boolean       $fallback   Whether to fall back to broader selector.
     456 *
     457 * @return string|null CSS selector or `null` if no selector available.
     458 */
     459function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = false ) {
     460    if ( empty( $target ) ) {
     461        return null;
     462    }
     463
     464    $has_selectors = ! empty( $block_type->selectors );
     465
     466    // Root Selector.
     467
     468    // Calculated before returning as it can be used as fallback for
     469    // feature selectors later on.
     470    $root_selector = null;
     471
     472    if ( $has_selectors && isset( $block_type->selectors['root'] ) ) {
     473        // Use the selectors API if available.
     474        $root_selector = $block_type->selectors['root'];
     475    } elseif ( isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) {
     476        // Use the old experimental selector supports property if set.
     477        $root_selector = $block_type->supports['__experimentalSelector'];
     478    } else {
     479        // If no root selector found, generate default block class selector.
     480        $block_name    = str_replace( '/', '-', str_replace( 'core/', '', $block_type->name ) );
     481        $root_selector = ".wp-block-{$block_name}";
     482    }
     483
     484    // Return selector if it's the root target we are looking for.
     485    if ( 'root' === $target ) {
     486        return $root_selector;
     487    }
     488
     489    // If target is not `root` we have a feature or subfeature as the target.
     490    // If the target is a string convert to an array.
     491    if ( is_string( $target ) ) {
     492        $target = explode( '.', $target );
     493    }
     494
     495    // Feature Selectors ( May fallback to root selector ).
     496    if ( 1 === count( $target ) ) {
     497        $fallback_selector = $fallback ? $root_selector : null;
     498
     499        // Prefer the selectors API if available.
     500        if ( $has_selectors ) {
     501            // Look for selector under `feature.root`.
     502            $path             = array_merge( $target, array( 'root' ) );
     503            $feature_selector = _wp_array_get( $block_type->selectors, $path, null );
     504
     505            if ( $feature_selector ) {
     506                return $feature_selector;
     507            }
     508
     509            // Check if feature selector is set via shorthand.
     510            $feature_selector = _wp_array_get( $block_type->selectors, $target, null );
     511
     512            return is_string( $feature_selector ) ? $feature_selector : $fallback_selector;
     513        }
     514
     515        // Try getting old experimental supports selector value.
     516        $path             = array_merge( $target, array( '__experimentalSelector' ) );
     517        $feature_selector = _wp_array_get( $block_type->supports, $path, null );
     518
     519        // Nothing to work with, provide fallback or null.
     520        if ( null === $feature_selector ) {
     521            return $fallback_selector;
     522        }
     523
     524        // Scope the feature selector by the block's root selector.
     525        return WP_Theme_JSON::scope_selector( $root_selector, $feature_selector );
     526    }
     527
     528    // Subfeature selector
     529    // This may fallback either to parent feature or root selector.
     530    $subfeature_selector = null;
     531
     532    // Use selectors API if available.
     533    if ( $has_selectors ) {
     534        $subfeature_selector = _wp_array_get( $block_type->selectors, $target, null );
     535    }
     536
     537    // Only return if we have a subfeature selector.
     538    if ( $subfeature_selector ) {
     539        return $subfeature_selector;
     540    }
     541
     542    // To this point we don't have a subfeature selector. If a fallback
     543    // has been requested, remove subfeature from target path and return
     544    // results of a call for the parent feature's selector.
     545    if ( $fallback ) {
     546        return wp_get_block_css_selector( $block_type, $target[0], $fallback );
     547    }
     548
     549    return null;
     550}
Note: See TracChangeset for help on using the changeset viewer.