Skip to content
Closed
6 changes: 4 additions & 2 deletions src/wp-includes/block-supports/states.php
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,11 @@ function wp_add_state_style_group( &$groups, $selector, $style ) {
*
* @since 7.1.0
*
* @param array $state_style State style object.
* @param array $block_selectors Block selectors metadata.
* @param array<string, mixed> $state_style State style object.
* @param array<string, string|array<string, string>> $block_selectors Block selectors metadata.
* @return array[] Selector/style groups.
*
* @phpstan-param array{ root?: string, ...<string, string|array<string, string>> } $block_selectors
*/
function wp_get_state_style_groups( $state_style, $block_selectors ) {
$groups = array();
Expand Down
71 changes: 39 additions & 32 deletions src/wp-includes/class-wp-block-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,15 @@ class WP_Block_Type {
/**
* Custom CSS selectors for theme.json style generation.
*
* The `root` key holds the selector for the block itself. Each other key is a block support
* feature (such as `color` or `typography`) whose value is either a selector string, or an
* array of selectors keyed by subfeature (such as `root` or `text`). The exception is the
* `states` key, which holds the selectors for the block's custom states, keyed by state
* name (such as `-current`).
*
* @since 6.3.0
* @var array
* @var array<string, string|array<string, string>>
* @phpstan-var array{ root?: string, ...<string, string|array<string, string>> }
*/
public $selectors = array();

Expand Down Expand Up @@ -311,37 +318,37 @@ class WP_Block_Type {
* Optional. Array or string of arguments for registering a block type. Any arguments may be defined,
* however the ones described below are supported by default. Default empty array.
*
* @type string $api_version Block API version.
* @type string $title Human-readable block type label.
* @type string|null $category Block type category classification, used in
* search interfaces to arrange block types by category.
* @type string[]|null $parent Setting parent lets a block require that it is only
* available when nested within the specified blocks.
* @type string[]|null $ancestor Setting ancestor makes a block available only inside the specified
* block types at any position of the ancestor's block subtree.
* @type string[]|null $allowed_blocks Limits which block types can be inserted as children of this block type.
* @type string|null $icon Block type icon.
* @type string $description A detailed block type description.
* @type string[] $keywords Additional keywords to produce block type as
* result in search interfaces.
* @type string|null $textdomain The translation textdomain.
* @type array[] $styles Alternative block styles.
* @type array[] $variations Block variations.
* @type array $selectors Custom CSS selectors for theme.json style generation.
* @type array|null $supports Supported features.
* @type array|null $example Structured data for the block preview.
* @type callable|null $render_callback Block type render callback.
* @type callable|null $variation_callback Block type variations callback.
* @type array|null $attributes Block type attributes property schemas.
* @type string[] $uses_context Context values inherited by blocks of this type.
* @type string[]|null $provides_context Context provided by blocks of this type.
* @type string[] $block_hooks Block hooks.
* @type string[] $editor_script_handles Block type editor only script handles.
* @type string[] $script_handles Block type front end and editor script handles.
* @type string[] $view_script_handles Block type front end only script handles.
* @type string[] $editor_style_handles Block type editor only style handles.
* @type string[] $style_handles Block type front end and editor style handles.
* @type string[] $view_style_handles Block type front end only style handles.
* @type string $api_version Block API version.
* @type string $title Human-readable block type label.
* @type string|null $category Block type category classification, used in
* search interfaces to arrange block types by category.
* @type string[]|null $parent Setting parent lets a block require that it is only
* available when nested within the specified blocks.
* @type string[]|null $ancestor Setting ancestor makes a block available only inside the specified
* block types at any position of the ancestor's block subtree.
* @type string[]|null $allowed_blocks Limits which block types can be inserted as children of this block type.
* @type string|null $icon Block type icon.
* @type string $description A detailed block type description.
* @type string[] $keywords Additional keywords to produce block type as
* result in search interfaces.
* @type string|null $textdomain The translation textdomain.
* @type array[] $styles Alternative block styles.
* @type array[] $variations Block variations.
* @type array<string, string|array<string, string>> $selectors Custom CSS selectors for theme.json style generation.
* @type array|null $supports Supported features.
* @type array|null $example Structured data for the block preview.
* @type callable|null $render_callback Block type render callback.
* @type callable|null $variation_callback Block type variations callback.
* @type array|null $attributes Block type attributes property schemas.
* @type string[] $uses_context Context values inherited by blocks of this type.
* @type string[]|null $provides_context Context provided by blocks of this type.
* @type string[] $block_hooks Block hooks.
* @type string[] $editor_script_handles Block type editor only script handles.
* @type string[] $script_handles Block type front end and editor script handles.
* @type string[] $view_script_handles Block type front end only script handles.
* @type string[] $editor_style_handles Block type editor only style handles.
* @type string[] $style_handles Block type front end and editor style handles.
* @type string[] $view_style_handles Block type front end only style handles.
* }
*/
public function __construct( $block_type, $args = array() ) {
Expand Down
24 changes: 16 additions & 8 deletions src/wp-includes/class-wp-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,13 @@ class WP_Theme_JSON {
*
* @type bool $include_desktop Whether to include the desktop media query. Default false.
* }
* @return array Responsive media queries.
* @return array<string, string> Responsive media queries, keyed by style-state name.
*
* @phpstan-return array{
* '@mobile'?: non-falsy-string,
* '@tablet'?: non-falsy-string,
* '@desktop'?: non-falsy-string,
* }
*/
public static function get_viewport_media_queries( $viewport_settings = null, $options = array() ) {
$breakpoints = self::sanitize_viewport_settings( $viewport_settings );
Expand Down Expand Up @@ -5993,24 +5999,26 @@ private static function resolve_custom_css_format( $tree ) {
*
* @since 6.3.0
*
* @param object $block_type The block type.
* @param string $root_selector The block's root selector.
* @return array The custom selectors set by the block.
* @param WP_Block_Type $block_type The block type.
* @param string $root_selector The block's root selector.
* @return array<string, string|array<string, string>> The custom selectors set by the block.
*
* @phpstan-return array{ root?: string, ...<string, string|array<string, string>> }
*/
protected static function get_block_selectors( $block_type, $root_selector ) {
if ( ! empty( $block_type->selectors ) ) {
return $block_type->selectors;
}

$selectors = array( 'root' => $root_selector );
$feature_selectors = array();
foreach ( static::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $key => $feature ) {
$feature_selector = wp_get_block_css_selector( $block_type, $key );
if ( null !== $feature_selector ) {
$selectors[ $feature ] = array( 'root' => $feature_selector );
$feature_selectors[ $feature ] = array( 'root' => $feature_selector );
}
}

return $selectors;
return array( 'root' => $root_selector ) + $feature_selectors;
}

/**
Expand Down Expand Up @@ -6057,7 +6065,7 @@ protected static function get_block_element_selectors( $root_selector ) {
* @return array<string, array[]> The style declarations for the node's features with custom
* selectors, keyed by selector.
*
* @phpstan-param array{ selectors?: array<string, string|array<string, string>> } $metadata
* @phpstan-param array{ selectors?: array{ root?: string, ...<string, string|array<string, string>> } } $metadata
* @phpstan-return array<string, list<array{ name: non-falsy-string, value: scalar }>>
*/
protected function get_feature_declarations_for_node( $metadata, &$node ) {
Expand Down
118 changes: 118 additions & 0 deletions tests/phpunit/tests/theme/wpThemeJson.php
Original file line number Diff line number Diff line change
Expand Up @@ -10584,4 +10584,122 @@ public function test_get_styles_for_block_variation_custom_css_uses_css_feature_
$this->assertStringContainsString( '{:root :where(.custom-p.is-style-fancy){color:blue;}}', $actual );
$this->assertStringNotContainsString( ':root :where(p.is-style-fancy){color:', $actual );
}

/**
* Tests that the default selectors for a block without custom selectors list the root
* selector first, followed by the feature selectors in the order of
* {@see WP_Theme_JSON::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS}.
*
* @ticket 65817
*
* @covers WP_Theme_JSON::get_block_selectors
*/
public function test_get_block_selectors_builds_default_selectors_with_root_first() {
$block_type = new WP_Block_Type(
'test/selectors',
array(
'supports' => array(
'typography' => array( '__experimentalSelector' => '.t' ),
'color' => array( '__experimentalSelector' => '.c' ),
),
)
);

$this->assertSame(
array(
'root' => '.wp-block-test-selectors',
'color' => array( 'root' => '.wp-block-test-selectors .c' ),
'typography' => array( 'root' => '.wp-block-test-selectors .t' ),
),
$this->get_block_selectors( WP_Theme_JSON::class, $block_type, '.wp-block-test-selectors' )
);
}

/**
* Tests that custom selectors set by the block are returned as they are.
*
* @ticket 65817
*
* @covers WP_Theme_JSON::get_block_selectors
*/
public function test_get_block_selectors_returns_custom_selectors() {
$selectors = array(
'color' => array( 'text' => '.custom-text' ),
'root' => '.custom-root',
'states' => array( '-current' => '.custom-root.is-current' ),
);
$block_type = new WP_Block_Type(
'test/selectors',
array(
'selectors' => $selectors,
'supports' => array(
'color' => array( '__experimentalSelector' => '.c' ),
),
)
);

$this->assertSame(
$selectors,
$this->get_block_selectors( WP_Theme_JSON::class, $block_type, '.custom-root' )
);
}

/**
* Tests that a feature named `root` does not replace the root selector string in the
* default selectors.
*
* {@see WP_Theme_JSON::get_block_selectors()} reads the feature level selectors with
* `static::`, so a subclass can override them. The subclass has an empty constructor, so
* that creating it does not fill the shared blocks metadata cache with its feature level
* selectors, which would affect later tests.
*
* @ticket 65817
*
* @covers WP_Theme_JSON::get_block_selectors
*/
public function test_get_block_selectors_keeps_root_selector_string() {
$subclass = get_class(
new class() extends WP_Theme_JSON {
const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array(
'color' => 'root',
'typography' => 'typography',
);

public function __construct() {}
}
);
$block_type = new WP_Block_Type(
'test/selectors',
array(
'supports' => array(
'color' => array( '__experimentalSelector' => '.c' ),
'typography' => array( '__experimentalSelector' => '.t' ),
),
)
);

$this->assertSame(
array(
'root' => '.wp-block-test-selectors',
'typography' => array( 'root' => '.wp-block-test-selectors .t' ),
),
$this->get_block_selectors( $subclass, $block_type, '.wp-block-test-selectors' )
);
}

/**
* Calls the protected {@see WP_Theme_JSON::get_block_selectors()} method.
*
* @param class-string<WP_Theme_JSON> $class_name The name of WP_Theme_JSON or a subclass of it.
* @param WP_Block_Type $block_type The block type.
* @param string $root_selector The block's root selector.
* @return mixed The selectors for the block, as returned through reflection.
*/
private function get_block_selectors( string $class_name, WP_Block_Type $block_type, string $root_selector ) {
$reflection = new ReflectionMethod( $class_name, 'get_block_selectors' );
if ( PHP_VERSION_ID < 80100 ) {
$reflection->setAccessible( true );
}
return $reflection->invoke( null, $block_type, $root_selector );
}
}
Loading