Skip to content
Closed
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
156 changes: 142 additions & 14 deletions src/wp-includes/block-supports/states.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,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 ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WP_Theme_JSON::get_block_element_selectors() seems like it's doing almost the same job with a bit of drift.

Just a note to self for later, maybe WP_Theme_JSON::get_block_element_selectors() could delegate to this shared helper one day

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there are a couple scenarios where we could replace theme json class functions with shared helpers. I'll check where the opportunities are

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 @@ -317,21 +402,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 ) {
$style = wp_get_state_style_with_fallback_dimension_styles( $group['style'] );
$compiled = wp_style_engine_get_styles(
wp_normalize_state_style_for_css_output( $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 @@ -503,6 +580,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
43 changes: 43 additions & 0 deletions tests/phpunit/tests/block-supports/states.php
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,49 @@ public function test_responsive_root_state_generates_media_query_scoped_css() {
);
}

/**
* Tests that a responsive element color generates media-query scoped CSS.
*
* @covers ::wp_render_block_states_support
*
* @ticket 65164
*/
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