Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/wp-includes/block-supports/block-style-variations.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) {
'include_block_style_variations' => true,
'skip_root_layout_styles' => true,
'scope' => ".$class_name",
// The merged block type styles, so a text clip set there can be escaped.
'text_clip_base_blocks' => $theme_json['styles']['blocks'] ?? array(),
)
);

Expand Down
53 changes: 50 additions & 3 deletions src/wp-includes/class-wp-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,9 @@ private function process_pseudo_selectors( $node, $base_selector, $settings, $bl
$base_styles[] = $layer;
}
}
if ( is_array( $block_metadata ) ) {
$base_styles[] = self::get_text_clip_base_block_styles( $block_metadata );
}

foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) {
if ( isset( $node[ $pseudo_selector ] ) ) {
Expand Down Expand Up @@ -2110,6 +2113,7 @@ public function get_settings() {
* @since 6.6.0 Added boolean `skip_root_layout_styles` and `include_block_style_variations` options
* to control styles output as desired.
* @since 7.0.0 Deprecated 'base-layout-styles' type; added `base_layout_styles` option for classic themes.
* @since 7.3.0 Added the private `text_clip_base_blocks` option.
*
* @param string[] $types Types of styles to load. Will load all by default. It accepts:
* - `variables`: only the CSS Custom Properties for presets & custom ones.
Expand All @@ -2126,6 +2130,8 @@ public function get_settings() {
* @type bool $skip_root_layout_styles Omits root layout styles from the generated stylesheet. Default false.
* @type bool $base_layout_styles When true generates only base layout styles without alignment rules. Default false.
* @type bool $include_block_style_variations Includes styles for block style variations in the generated stylesheet. Default false.
* @type array $text_clip_base_blocks Private. Block type styles the nodes layer over, read only to escape
* a text clip set there. They are never output.
* }
* @return string The resulting stylesheet.
*/
Expand All @@ -2150,6 +2156,14 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets'
$style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata, $options );
$setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata );

// `get_block_classes()` passes only node metadata to `get_styles_for_block()`.
if ( ! empty( $options['text_clip_base_blocks'] ) ) {
foreach ( $style_nodes as &$node ) {
$node['text_clip_base_blocks'] = $options['text_clip_base_blocks'];
}
unset( $node );
}

$root_style_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $style_nodes, 'selector' ), true );
$root_settings_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $setting_nodes, 'selector' ), true );

Expand Down Expand Up @@ -4074,6 +4088,29 @@ private static function get_state_base_styles( array $path, array $tree, array $
return $base_styles;
}

/**
* Returns the block type styles a block node layers over, from the private
* `text_clip_base_blocks` stylesheet option.
*
* A block style variation instance stylesheet is built from the variation
* alone, so it cannot otherwise see a text clip set for its block type.
*
* @since 7.3.0
*
* @param array $block_metadata Metadata about the block to get styles for.
* @return array<string, mixed> The block type styles, empty when there are none.
*/
private static function get_text_clip_base_block_styles( array $block_metadata ): array {
$path = $block_metadata['path'] ?? array();
if ( empty( $block_metadata['text_clip_base_blocks'] ) || 'blocks' !== ( $path[1] ?? null ) || ! isset( $path[2] ) || in_array( 'elements', $path, true ) ) {
return array();
}

$base_styles = $block_metadata['text_clip_base_blocks'][ $path[2] ] ?? array();

return is_array( $base_styles ) ? $base_styles : array();
}

/**
* Adds the declarations a state needs to escape a text clip set below it.
*
Expand Down Expand Up @@ -4136,6 +4173,8 @@ public function get_styles_for_block( $block_metadata ) {
$media_query = $block_metadata['media_query'] ?? null;
$responsive_media_queries = static::get_viewport_media_queries( $settings['viewport'] ?? null );

$text_clip_base_block_styles = self::get_text_clip_base_block_styles( $block_metadata );

$css_feature_selector = $block_metadata['selectors']['css'] ?? null;
if ( is_array( $css_feature_selector ) ) {
$css_feature_selector = $css_feature_selector['root'] ?? null;
Expand All @@ -4159,6 +4198,9 @@ public function get_styles_for_block( $block_metadata ) {
$style_variation_responsive_pseudo_css = array();
$style_variation_layout_metadata = array();
if ( ! $media_query && ! empty( $block_metadata['variations'] ) ) {
// A block pseudo node lists the variations too, so read the block's resting styles, not `$node`.
$variation_block_node = _wp_array_get( $this->theme_json, array_slice( $block_metadata['path'], 0, 3 ), array() );

foreach ( $block_metadata['variations'] as $style_variation ) {
$style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() );

Expand All @@ -4180,7 +4222,11 @@ public function get_styles_for_block( $block_metadata ) {
}

// Compute declarations for remaining styles not covered by feature level selectors.
$style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json );
$style_variation_declarations[ $style_variation['selector'] ] = self::get_state_declarations_with_text_clip_escapes(
static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ),
$style_variation_node,
array( $variation_block_node, $text_clip_base_block_styles )
);

// Process pseudo-selectors for this variation (e.g., :hover, :focus)
if ( isset( $block_metadata['name'] ) ) {
Expand Down Expand Up @@ -4253,7 +4299,7 @@ public function get_styles_for_block( $block_metadata ) {
$breakpoint_declarations = self::get_state_declarations_with_text_clip_escapes(
$breakpoint_declarations,
$breakpoint_node,
array( $style_variation_node, $node )
array( $style_variation_node, $variation_block_node, $text_clip_base_block_styles )
);
if ( ! empty( $breakpoint_declarations ) ) {
$base_ruleset = static::to_ruleset( ':root :where(' . $style_variation['selector'] . ')', $breakpoint_declarations );
Expand Down Expand Up @@ -4379,7 +4425,8 @@ static function ( $pseudo_selector ) use ( $selector ) {
* element then compute the style properties for it.
* Otherwise just compute the styles for the default selector as normal.
*/
$base_styles = self::get_state_base_styles( $block_metadata['path'] ?? array(), $this->theme_json, $settings );
$base_styles = self::get_state_base_styles( $block_metadata['path'] ?? array(), $this->theme_json, $settings );
$base_styles[] = $text_clip_base_block_styles;

if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) &&
isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] )
Expand Down
259 changes: 259 additions & 0 deletions tests/phpunit/tests/block-supports/block-style-variations.php
Original file line number Diff line number Diff line change
Expand Up @@ -662,4 +662,263 @@ public function test_block_style_variation_class_name_with_missing_class_name()
wp_render_block_style_variation_class_name( $block_content, $block )
);
}

/**
* Renders a block's style variation and returns the instance stylesheet,
* with the given block type styles in the theme origin.
*
* @param string $block_name Block name.
* @param string $class_name Block class name.
* @param array $block_types Block type styles to merge into the theme origin.
* @return string The block style variation instance stylesheet.
*/
private function render_variation_styles_with_block_type_styles( $block_name, $class_name, $block_types ) {
$filter = static function ( $theme_json ) use ( $block_types ) {
return $theme_json->update_with(
array(
'version' => WP_Theme_JSON::LATEST_SCHEMA,
'styles' => array(
'blocks' => $block_types,
),
)
);
};

add_filter( 'wp_theme_json_data_theme', $filter );
WP_Theme_JSON_Resolver::clean_cached_data();

try {
wp_render_block_style_variation_support_styles(
array(
'blockName' => $block_name,
'attrs' => array(
'className' => $class_name,
),
)
);
$inline_styles = wp_styles()->get_data( 'block-style-variation-styles', 'after' );
return is_array( $inline_styles ) ? implode( '', $inline_styles ) : '';
} finally {
remove_filter( 'wp_theme_json_data_theme', $filter );
wp_deregister_style( 'block-style-variation-styles' );
WP_Theme_JSON_Resolver::clean_cached_data();
}
}

/**
* Returns block type styles with a text gradient for the given block.
*
* @param string $block_name Block name.
* @return array Block type styles.
*/
private static function get_text_gradient_block_type_styles( $block_name ) {
return array(
$block_name => array(
'background' => array(
'gradient' => 'linear-gradient(90deg, #ff0000 0%, #0000ff 100%)',
'backgroundClip' => 'text',
),
),
);
}

/**
* Tests that a variation text color refills the letters over a block type text gradient.
*
* @ticket 66438
*
* @covers ::wp_render_block_style_variation_support_styles
*/
public function test_block_style_variation_text_color_escapes_block_type_text_clip() {
switch_theme( 'block-theme' );
register_block_style(
'core/paragraph',
array(
'name' => 'clip-text-color',
'style_data' => array(
'color' => array(
'text' => '#00aa00',
),
),
)
);

try {
$actual = $this->render_variation_styles_with_block_type_styles(
'core/paragraph',
'is-style-clip-text-color',
self::get_text_gradient_block_type_styles( 'core/paragraph' )
);
} finally {
unregister_block_style( 'core/paragraph', 'clip-text-color' );
}

$this->assertMatchesRegularExpression(
'/:root :where\(p\.is-style-clip-text-color--\d+\)\{color: #00aa00;-webkit-text-fill-color: currentColor;\}/',
$actual
);
}

/**
* Tests that an inner block color in a variation shared by several blocks
* refills the letters over the inner block type text gradient.
*
* @ticket 66438
*
* @covers ::wp_render_block_style_variation_support_styles
*/
public function test_block_style_variation_inner_block_text_color_escapes_block_type_text_clip() {
switch_theme( 'block-theme' );
register_block_style(
array( 'core/group', 'core/columns' ),
array(
'name' => 'clip-section',
'style_data' => array(
'blocks' => array(
'core/paragraph' => array(
'color' => array(
'text' => '#aa00aa',
),
),
),
),
)
);

try {
$actual = $this->render_variation_styles_with_block_type_styles(
'core/group',
'is-style-clip-section',
self::get_text_gradient_block_type_styles( 'core/paragraph' )
);
} finally {
unregister_block_style( 'core/group', 'clip-section' );
unregister_block_style( 'core/columns', 'clip-section' );
}

$this->assertMatchesRegularExpression(
'/:root :where\(\.is-style-clip-section--\d+ p\)\{color: #aa00aa;-webkit-text-fill-color: currentColor;\}/',
$actual
);
}

/**
* Tests that a variation breakpoint text color refills the letters over a
* block type text gradient.
*
* @ticket 66438
*
* @covers ::wp_render_block_style_variation_support_styles
*/
public function test_block_style_variation_breakpoint_text_color_escapes_block_type_text_clip() {
switch_theme( 'block-theme' );
register_block_style(
'core/paragraph',
array(
'name' => 'clip-mobile',
'style_data' => array(
'@mobile' => array(
'color' => array(
'text' => '#0000aa',
),
),
),
)
);

try {
$actual = $this->render_variation_styles_with_block_type_styles(
'core/paragraph',
'is-style-clip-mobile',
self::get_text_gradient_block_type_styles( 'core/paragraph' )
);
} finally {
unregister_block_style( 'core/paragraph', 'clip-mobile' );
}

$this->assertMatchesRegularExpression(
'/@media \(width <= 480px\)\{:root :where\(p\.is-style-clip-mobile--\d+\)\{color: #0000aa;-webkit-text-fill-color: currentColor;\}\}/',
$actual
);
}

/**
* Tests that a variation pseudo-state text color refills the letters over
* a block type text gradient.
*
* @ticket 66438
*
* @covers ::wp_render_block_style_variation_support_styles
*/
public function test_block_style_variation_pseudo_state_text_color_escapes_block_type_text_clip() {
switch_theme( 'block-theme' );
register_block_style(
'core/button',
array(
'name' => 'clip-hover',
'style_data' => array(
':hover' => array(
'color' => array(
'text' => '#aa0000',
),
),
),
)
);

try {
$actual = $this->render_variation_styles_with_block_type_styles(
'core/button',
'is-style-clip-hover',
self::get_text_gradient_block_type_styles( 'core/button' )
);
} finally {
unregister_block_style( 'core/button', 'clip-hover' );
}

$this->assertMatchesRegularExpression(
'/:root :where\(\.wp-block-button\.is-style-clip-hover--\d+ \.wp-block-button__link:hover\)\{color: #aa0000;-webkit-text-fill-color: currentColor;\}/',
$actual
);
}

/**
* Tests that variation styles are unchanged when no block type sets a text clip.
*
* @ticket 66438
*
* @covers ::wp_render_block_style_variation_support_styles
*/
public function test_block_style_variation_without_block_type_text_clip_is_unchanged() {
switch_theme( 'block-theme' );
register_block_style(
'core/paragraph',
array(
'name' => 'no-clip',
'style_data' => array(
'color' => array(
'text' => '#00aa00',
),
),
)
);

try {
$actual = $this->render_variation_styles_with_block_type_styles(
'core/paragraph',
'is-style-no-clip',
array(
'core/paragraph' => array(
'background' => array(
'gradient' => 'linear-gradient(90deg, #ff0000 0%, #0000ff 100%)',
),
),
)
);
} finally {
unregister_block_style( 'core/paragraph', 'no-clip' );
}

$this->assertMatchesRegularExpression( '/:root :where\(p\.is-style-no-clip--\d+\)\{color: #00aa00;\}/', $actual );
}
}
Loading
Loading