Skip to content

Commit fab5967

Browse files
committed
backport of PR 76491
1 parent 609f25f commit fab5967

3 files changed

Lines changed: 624 additions & 0 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* Block states support for frontend CSS generation.
4+
*
5+
* Generates scoped CSS for per-instance pseudo-state styles (e.g., :hover, :focus)
6+
* declared in block attributes under `style[':hover']`, `style[':focus']`, etc.
7+
*
8+
* @package WordPress
9+
*/
10+
11+
/**
12+
* Renders per-instance state styles on the frontend for blocks that declare
13+
* `__experimentalStates` support.
14+
*
15+
* @param string $block_content The block's rendered HTML.
16+
* @param array $block The block data including blockName and attrs.
17+
* @return string Modified block content with injected state styles.
18+
*/
19+
function wp_render_block_states_support( $block_content, $block ) {
20+
if ( empty( $block['blockName'] ) || empty( $block_content ) ) {
21+
return $block_content;
22+
}
23+
24+
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
25+
if ( ! $block_type ) {
26+
return $block_content;
27+
}
28+
29+
$supported_states = $block_type->supports['__experimentalStates'] ?? null;
30+
if ( empty( $supported_states ) || ! is_array( $supported_states ) ) {
31+
return $block_content;
32+
}
33+
34+
$style = $block['attrs']['style'] ?? array();
35+
$css_rules = array();
36+
37+
foreach ( $supported_states as $state ) {
38+
if ( empty( $style[ $state ] ) || ! is_array( $style[ $state ] ) ) {
39+
continue;
40+
}
41+
42+
$compiled = wp_style_engine_get_styles( $style[ $state ] );
43+
if ( ! empty( $compiled['css'] ) ) {
44+
$css_rules[] = array(
45+
'state' => $state,
46+
'css' => $compiled['css'],
47+
);
48+
}
49+
}
50+
51+
if ( empty( $css_rules ) ) {
52+
return $block_content;
53+
}
54+
55+
$unique_class = 'wp-states-' . substr( md5( wp_json_encode( $css_rules ) ), 0, 8 );
56+
$css = '';
57+
58+
foreach ( $css_rules as $rule ) {
59+
// Use !important to override utility classes like
60+
// .has-accent-3-background-color which are generated with !important.
61+
$declarations = str_replace( ';', ' !important;', $rule['css'] );
62+
$css .= ".$unique_class$rule[state] { $declarations }\n";
63+
}
64+
65+
// Add the unique class to the interactive element so that state selectors
66+
// like `.$unique_class:hover` match directly without needing a descendant.
67+
// If the block declares selectors.root with a descendant (e.g. the button
68+
// block's ".wp-block-button .wp-block-button__link"), we extract the last
69+
// class and walk to that element. Otherwise we fall back to the wrapper.
70+
$root_selector = $block_type->selectors['root'] ?? null;
71+
$target_class = null;
72+
if ( $root_selector && preg_match( '/\.([a-zA-Z0-9_-]+)\s*$/', $root_selector, $matches ) ) {
73+
$target_class = $matches[1];
74+
}
75+
76+
$processor = new WP_HTML_Tag_Processor( $block_content );
77+
if ( $target_class ) {
78+
while ( $processor->next_tag() ) {
79+
if ( $processor->has_class( $target_class ) ) {
80+
$processor->add_class( $unique_class );
81+
break;
82+
}
83+
}
84+
} elseif ( $processor->next_tag() ) {
85+
$processor->add_class( $unique_class );
86+
}
87+
$block_content = $processor->get_updated_html();
88+
89+
return '<style>' . $css . '</style>' . $block_content;
90+
}
91+
add_filter( 'render_block', 'wp_render_block_states_support', 10, 2 );

src/wp-settings.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@
430430
require ABSPATH . WPINC . '/block-supports/anchor.php';
431431
require ABSPATH . WPINC . '/block-supports/block-visibility.php';
432432
require ABSPATH . WPINC . '/block-supports/custom-css.php';
433+
require ABSPATH . WPINC . '/block-supports/states.php';
433434
require ABSPATH . WPINC . '/style-engine.php';
434435
require ABSPATH . WPINC . '/style-engine/class-wp-style-engine.php';
435436
require ABSPATH . WPINC . '/style-engine/class-wp-style-engine-css-declarations.php';

0 commit comments

Comments
 (0)