Commit 7483c46357 for wordpress.org
commit 7483c463578c340adb525e952c1e307ff1474ed9
Author: isabel_brison <isabel_brison@git.wordpress.org>
Date: Thu Oct 15 05:53:50 2026 +0000
Editor: enable custom CSS on style states.
Enables viewport and pseudo state-specific custom CSS on block instances.
Props isabel_brison, ramonopoly.
Fixes #66288.
Built from https://develop.svn.wordpress.org/trunk@64353
git-svn-id: http://core.svn.wordpress.org/trunk@63503 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/block-supports/custom-css.php b/wp-includes/block-supports/custom-css.php
index 66fd9469dd..f5318d075f 100644
--- a/wp-includes/block-supports/custom-css.php
+++ b/wp-includes/block-supports/custom-css.php
@@ -5,6 +5,124 @@
* @package WordPress
*/
+/**
+ * Checks whether a style node has a non-empty custom CSS string.
+ *
+ * @since 7.2.0
+ * @access private
+ *
+ * @param mixed $style_node A style node, possibly containing a `css` key.
+ * @return bool Whether the node has a non-empty custom CSS string.
+ */
+function wp_custom_css_has_value( $style_node ) {
+ return is_array( $style_node )
+ && isset( $style_node['css'] )
+ && is_string( $style_node['css'] )
+ && '' !== trim( $style_node['css'] );
+}
+
+/**
+ * Collects custom CSS defined for a block instance across its default,
+ * pseudo-selector, and viewport states.
+ *
+ * @since 7.2.0
+ * @access private
+ *
+ * @param array $style The block's `style` attribute.
+ * @param string $block_name Block name.
+ * @param array $responsive_media_queries Media queries keyed by viewport state name.
+ * @return array[] Custom CSS entries with `css`, `pseudo`, and `media_query` keys.
+ */
+function wp_get_custom_css_state_entries( $style, $block_name, $responsive_media_queries ) {
+ $entries = array();
+ if ( empty( $style ) || ! is_array( $style ) ) {
+ return $entries;
+ }
+
+ $supported_pseudo_states = WP_Theme_JSON::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ?? array();
+
+ if ( wp_custom_css_has_value( $style ) ) {
+ $entries[] = array(
+ 'css' => $style['css'],
+ 'pseudo' => null,
+ 'media_query' => null,
+ );
+ }
+
+ foreach ( $supported_pseudo_states as $pseudo_state ) {
+ $pseudo_style = $style[ $pseudo_state ] ?? null;
+ if ( wp_custom_css_has_value( $pseudo_style ) ) {
+ $entries[] = array(
+ 'css' => $pseudo_style['css'],
+ 'pseudo' => $pseudo_state,
+ 'media_query' => null,
+ );
+ }
+ }
+
+ foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
+ $breakpoint_style = $style[ $breakpoint ] ?? null;
+ if ( ! is_array( $breakpoint_style ) ) {
+ continue;
+ }
+
+ if ( wp_custom_css_has_value( $breakpoint_style ) ) {
+ $entries[] = array(
+ 'css' => $breakpoint_style['css'],
+ 'pseudo' => null,
+ 'media_query' => $media_query,
+ );
+ }
+
+ foreach ( $supported_pseudo_states as $pseudo_state ) {
+ $breakpoint_pseudo_style = $breakpoint_style[ $pseudo_state ] ?? null;
+ if ( wp_custom_css_has_value( $breakpoint_pseudo_style ) ) {
+ $entries[] = array(
+ 'css' => $breakpoint_pseudo_style['css'],
+ 'pseudo' => $pseudo_state,
+ 'media_query' => $media_query,
+ );
+ }
+ }
+ }
+
+ return $entries;
+}
+
+/**
+ * Validates and processes custom CSS for each block style state.
+ *
+ * @since 7.2.0
+ * @access private
+ *
+ * @param array[] $state_entries Custom CSS entries.
+ * @param string $selector Selector scoping the block instance.
+ * @return string Processed CSS.
+ */
+function wp_process_custom_css_state_entries( $state_entries, $selector ) {
+ $processed_css = '';
+
+ foreach ( $state_entries as $entry ) {
+ // Validate CSS doesn't contain HTML markup (same validation as global styles REST API).
+ if ( preg_match( '#</?\w+#', $entry['css'] ) ) {
+ continue;
+ }
+
+ $entry_selector = null !== $entry['pseudo'] ? $selector . $entry['pseudo'] : $selector;
+ $entry_css = WP_Theme_JSON::process_blocks_custom_css( $entry['css'], $entry_selector );
+
+ if ( empty( $entry_css ) ) {
+ continue;
+ }
+
+ $processed_css .= null !== $entry['media_query']
+ ? $entry['media_query'] . '{' . $entry_css . '}'
+ : $entry_css;
+ }
+
+ return $processed_css;
+}
+
/**
* Render the custom CSS stylesheet and add class name to block as required.
*
@@ -39,8 +157,8 @@
* }
*/
function wp_render_custom_css_support_styles( $parsed_block ) {
- $custom_css = $parsed_block['attrs']['style']['css'] ?? null;
- if ( ! is_string( $custom_css ) || '' === trim( $custom_css ) ) {
+ $style = $parsed_block['attrs']['style'] ?? null;
+ if ( empty( $style ) || ! is_array( $style ) ) {
return $parsed_block;
}
@@ -49,13 +167,20 @@ function wp_render_custom_css_support_styles( $parsed_block ) {
return $parsed_block;
}
- // Validate CSS doesn't contain HTML markup (same validation as global styles REST API).
- if ( preg_match( '#</?\w+#', $custom_css ) ) {
+ $viewport_settings = wp_get_global_settings( array( 'viewport' ) );
+ $responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings );
+ $state_entries = wp_get_custom_css_state_entries( $style, $parsed_block['blockName'], $responsive_media_queries );
+ if ( empty( $state_entries ) ) {
return $parsed_block;
}
// Generate a unique class name for this block instance.
- $class_name = wp_unique_id_from_values( $parsed_block, 'wp-custom-css-' );
+ $class_name = wp_unique_id_from_values( $parsed_block, 'wp-custom-css-' );
+ $processed_css = wp_process_custom_css_state_entries( $state_entries, '.' . $class_name );
+ if ( '' === $processed_css ) {
+ return $parsed_block;
+ }
+
$existing_class_name = $parsed_block['attrs']['className'] ?? null;
$updated_class_name = is_string( $existing_class_name )
? "$existing_class_name $class_name"
@@ -63,28 +188,22 @@ function wp_render_custom_css_support_styles( $parsed_block ) {
$parsed_block['attrs']['className'] = $updated_class_name;
- // Process the custom CSS using the same method as global styles.
- $selector = '.' . $class_name;
- $processed_css = WP_Theme_JSON::process_blocks_custom_css( $custom_css, $selector );
-
- if ( ! empty( $processed_css ) ) {
- /**
- * Reuse one handle so identical custom CSS is enqueued only once via
- * {@see wp_unique_id_from_values()}. Explicitly declare the `wp-block-library`
- * dependency so `global-styles` is guaranteed to print after it, preventing
- * block default styles from unintentionally overriding global styles.
- */
- $handle = 'wp-block-custom-css';
- if ( ! wp_style_is( $handle, 'registered' ) ) {
- wp_register_style( $handle, false, array( 'wp-block-library', 'global-styles' ) );
- }
- $after_styles = wp_styles()->get_data( $handle, 'after' );
- if ( ! is_array( $after_styles ) ) {
- $after_styles = array();
- }
- if ( ! in_array( $processed_css, $after_styles, true ) ) {
- wp_add_inline_style( $handle, $processed_css );
- }
+ /**
+ * Reuse one handle so identical custom CSS is enqueued only once via
+ * {@see wp_unique_id_from_values()}. Explicitly declare the `wp-block-library`
+ * dependency so `global-styles` is guaranteed to print after it, preventing
+ * block default styles from unintentionally overriding global styles.
+ */
+ $handle = 'wp-block-custom-css';
+ if ( ! wp_style_is( $handle, 'registered' ) ) {
+ wp_register_style( $handle, false, array( 'wp-block-library', 'global-styles' ) );
+ }
+ $after_styles = wp_styles()->get_data( $handle, 'after' );
+ if ( ! is_array( $after_styles ) ) {
+ $after_styles = array();
+ }
+ if ( ! in_array( $processed_css, $after_styles, true ) ) {
+ wp_add_inline_style( $handle, $processed_css );
}
return $parsed_block;
@@ -181,6 +300,36 @@ function wp_register_custom_css_support( $block_type ) {
}
}
+/**
+ * Removes custom CSS from a style attribute and any nested style states.
+ *
+ * @since 7.2.0
+ * @access private
+ *
+ * @param array $style The block's `style` attribute.
+ * @return array The style attribute with all `css` keys removed.
+ */
+function wp_strip_custom_css_from_style_array( $style ) {
+ if ( ! is_array( $style ) ) {
+ return $style;
+ }
+
+ unset( $style['css'] );
+
+ foreach ( $style as $key => $value ) {
+ if ( is_string( $key ) && is_array( $value ) && ( str_starts_with( $key, ':' ) || str_starts_with( $key, '@' ) ) ) {
+ $nested_style = wp_strip_custom_css_from_style_array( $value );
+ if ( empty( $nested_style ) ) {
+ unset( $style[ $key ] );
+ } else {
+ $style[ $key ] = $nested_style;
+ }
+ }
+ }
+
+ return $style;
+}
+
/**
* Strips custom CSS (`style.css` in attributes) from all blocks in post content.
*
@@ -222,14 +371,20 @@ function wp_strip_custom_css_from_blocks( $content ) {
continue;
}
- if ( ! isset( $attrs['style']['css'] ) ) {
+ if ( ! isset( $attrs['style'] ) || ! is_array( $attrs['style'] ) ) {
+ continue;
+ }
+
+ $stripped_style = wp_strip_custom_css_from_style_array( $attrs['style'] );
+ if ( $stripped_style === $attrs['style'] ) {
continue;
}
// Remove css and clean up empty style.
- unset( $attrs['style']['css'] );
- if ( empty( $attrs['style'] ) ) {
+ if ( empty( $stripped_style ) ) {
unset( $attrs['style'] );
+ } else {
+ $attrs['style'] = $stripped_style;
}
// Locate the JSON portion within the token.
diff --git a/wp-includes/version.php b/wp-includes/version.php
index aaab11a042..261f66d56d 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-alpha-64352';
+$wp_version = '7.2-alpha-64353';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.