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
155 changes: 142 additions & 13 deletions src/wp-includes/block-supports/states.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,91 @@ function wp_get_root_state_style( $state_style, $nested_keys ) {
return $root_style;
}

/**
* Generates all element selectors for a block root selector.
*
* @since 7.1.0
*
* @param string $root_selector The block root CSS selector.
* @return string[] Element selectors keyed by element name.
*/
function wp_get_block_state_element_selectors( $root_selector ) {
if ( ! is_string( $root_selector ) || '' === trim( $root_selector ) ) {
return array();
}

$block_selectors = wp_split_selector_list( $root_selector );
$element_selectors = array();

foreach ( WP_Theme_JSON::ELEMENTS as $element_name => $element_selector ) {
$selectors = array();

foreach ( $block_selectors as $block_selector ) {
$block_selector = trim( $block_selector );
if ( '' === $block_selector ) {
continue;
}

if ( $block_selector === $element_selector ) {
$selectors = array( $element_selector );
break;
}

$selector_prefix = "$block_selector ";
if ( ! str_contains( $element_selector, ',' ) ) {
$selectors[] = $selector_prefix . $element_selector;
continue;
}

$prepended_selectors = array();
foreach ( wp_split_selector_list( $element_selector ) as $selector ) {
$prepended_selectors[] = $selector_prefix . $selector;
}
$selectors[] = implode( ',', $prepended_selectors );
}

if ( ! empty( $selectors ) ) {
$element_selectors[ $element_name ] = implode( ',', $selectors );
}
}

return $element_selectors;
}

/**
* Adds a compiled state style rule to a rule list.
*
* @since 7.1.0
*
* @param array $css_rules Style rules.
* @param string $state Pseudo-state selector.
* @param string|null $selector Block, feature, or element selector.
* @param array $style Style object.
* @param string|null $rules_group Optional CSS grouping rule, e.g. a media query.
*/
function wp_add_block_state_style_rule( &$css_rules, $state, $selector, $style, $rules_group = null ) {
if ( empty( $style ) || ! is_array( $style ) ) {
return;
}

$compiled = wp_style_engine_get_styles(
wp_normalize_state_style_for_css_output( $style )
);

if ( empty( $compiled['declarations'] ) ) {
return;
}

$css_rules[] = array(
'state' => $state,
'selector' => $selector,
'declarations' => $compiled['declarations'],
);
if ( ! empty( $rules_group ) ) {
$css_rules[ count( $css_rules ) - 1 ]['rules_group'] = $rules_group;
}
}

/**
* Builds compiled state style rules, preserving the selector each rule targets.
*
Expand All @@ -231,20 +316,13 @@ function wp_get_block_state_style_rules( $state_styles, $block_type, $rules_grou
}

foreach ( wp_get_state_style_groups( $state_style, $block_selectors ) as $group ) {
$compiled = wp_style_engine_get_styles(
wp_normalize_state_style_for_css_output( $group['style'] )
wp_add_block_state_style_rule(
$css_rules,
$state,
$group['selector'],
$group['style'],
$rules_group
);

if ( ! empty( $compiled['declarations'] ) ) {
$css_rules[] = array(
'state' => $state,
'selector' => $group['selector'],
'declarations' => $compiled['declarations'],
);
if ( ! empty( $rules_group ) ) {
$css_rules[ count( $css_rules ) - 1 ]['rules_group'] = $rules_group;
}
}
}
}

Expand Down Expand Up @@ -416,6 +494,57 @@ function wp_render_block_states_support( $block_content, $block ) {
);
}

if (
! empty( $style[ $breakpoint ]['elements'] ) &&
is_array( $style[ $breakpoint ]['elements'] )
) {
$element_selectors = wp_get_block_state_element_selectors(
wp_get_block_css_selector( $block_type )
);

foreach ( $style[ $breakpoint ]['elements'] as $element_name => $element_style ) {
if (
empty( $element_style ) ||
! is_array( $element_style ) ||
empty( $element_selectors[ $element_name ] )
) {
continue;
}

$element_pseudo_states = WP_Theme_JSON::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ]
?? array();
$root_element_style = wp_get_root_state_style(
$element_style,
$element_pseudo_states
);

wp_add_block_state_style_rule(
$css_rules,
'',
$element_selectors[ $element_name ],
$root_element_style,
$media_query
);

foreach ( $element_pseudo_states as $pseudo_state ) {
if (
empty( $element_style[ $pseudo_state ] ) ||
! is_array( $element_style[ $pseudo_state ] )
) {
continue;
}

wp_add_block_state_style_rule(
$css_rules,
$pseudo_state,
$element_selectors[ $element_name ],
$element_style[ $pseudo_state ],
$media_query
);
}
}
}

foreach ( $supported_pseudo_states as $pseudo_state ) {
if ( empty( $style[ $breakpoint ][ $pseudo_state ] ) || ! is_array( $style[ $breakpoint ][ $pseudo_state ] ) ) {
continue;
Expand Down
41 changes: 41 additions & 0 deletions tests/phpunit/tests/block-supports/states.php
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,47 @@ public function test_responsive_root_state_generates_media_query_scoped_css() {
);
}

/**
* Tests that a responsive element color generates media-query scoped CSS.
*
* @ticket 65239
*/
public function test_responsive_element_color_generates_media_query_scoped_css() {
$this->ensure_block_registered( 'core/group' );

$block_content = '<div class="wp-block-group"><p><a href="#">Link</a></p></div>';
$block = array(
'blockName' => 'core/group',
'attrs' => array(
'style' => array(
'mobile' => array(
'elements' => array(
'link' => array(
'color' => array(
'text' => '#00ff00',
),
),
),
),
),
),
);

$actual = wp_render_block_states_support( $block_content, $block );

$this->assertMatchesRegularExpression(
'/^<div class="wp-block-group (wp-states-[a-f0-9]{8})"><p><a href="#">Link<\/a><\/p><\/div>$/',
$actual
);
preg_match( '/wp-states-[a-f0-9]{8}/', $actual, $matches );
$actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );

$this->assertStringContainsString(
'@media (width <= 480px){.' . $matches[0] . ' a:where(:not(.wp-element-button)){color:#00ff00 !important;}}',
$actual_stylesheet
);
}

/**
* Tests that a responsive pseudo-state generates media-query scoped CSS.
*
Expand Down
Loading