Repository navigation
Editor: Generate custom CSS for blocks rendered by their parent block #14538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -124,12 +124,13 @@ function wp_process_custom_css_state_entries( $state_entries, $selector ) { | |
| } | ||
|
|
||
| /** | ||
| * Render the custom CSS stylesheet and add class name to block as required. | ||
| * Generates the custom CSS class name for a block and registers its stylesheet. | ||
| * | ||
| * @since 7.0.0 | ||
| * @since 7.2.0 | ||
| * @access private | ||
| * | ||
| * @param array $parsed_block The parsed block. | ||
| * @return array The same parsed block with custom CSS class name added if appropriate. | ||
| * @return string|null The generated class name, or null if the block has no custom CSS to render. | ||
| * | ||
| * @phpstan-param array{ | ||
| * blockName: string|null, | ||
|
|
@@ -143,51 +144,32 @@ function wp_process_custom_css_state_entries( $state_entries, $selector ) { | |
| * }, | ||
| * ... | ||
| * } $parsed_block | ||
| * @phpstan-return array{ | ||
| * blockName: string|null, | ||
| * attrs: array{ | ||
| * className?: string, | ||
| * style?: array{ | ||
| * css?: string, | ||
| * ... | ||
| * }, | ||
| * ... | ||
| * }, | ||
| * ... | ||
| * } | ||
| */ | ||
| function wp_render_custom_css_support_styles( $parsed_block ) { | ||
| function wp_generate_custom_css_class_name( $parsed_block ) { | ||
| $style = $parsed_block['attrs']['style'] ?? null; | ||
| if ( empty( $style ) || ! is_array( $style ) ) { | ||
| return $parsed_block; | ||
| return null; | ||
| } | ||
|
|
||
| $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] ); | ||
| if ( ! block_has_support( $block_type, 'customCSS', true ) ) { | ||
| return $parsed_block; | ||
| return null; | ||
| } | ||
|
|
||
| $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; | ||
| return null; | ||
| } | ||
|
|
||
| // Generate a unique class name for this block instance. | ||
| $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; | ||
| return null; | ||
| } | ||
|
|
||
| $existing_class_name = $parsed_block['attrs']['className'] ?? null; | ||
| $updated_class_name = is_string( $existing_class_name ) | ||
| ? "$existing_class_name $class_name" | ||
| : $class_name; | ||
|
|
||
| $parsed_block['attrs']['className'] = $updated_class_name; | ||
|
|
||
| /** | ||
| * Reuse one handle so identical custom CSS is enqueued only once via | ||
| * {@see wp_unique_id_from_values()}. Explicitly declare the `wp-block-library` | ||
|
|
@@ -206,6 +188,55 @@ function wp_render_custom_css_support_styles( $parsed_block ) { | |
| wp_add_inline_style( $handle, $processed_css ); | ||
| } | ||
|
|
||
| return $class_name; | ||
| } | ||
|
|
||
| /** | ||
| * Render the custom CSS stylesheet and add class name to block as required. | ||
| * | ||
| * @since 7.0.0 | ||
| * | ||
| * @param array $parsed_block The parsed block. | ||
| * @return array The same parsed block with custom CSS class name added if appropriate. | ||
| * | ||
| * @phpstan-param array{ | ||
| * blockName: string|null, | ||
| * attrs: array{ | ||
| * className?: string, | ||
| * style?: array{ | ||
| * css?: string, | ||
| * ... | ||
| * }, | ||
| * ... | ||
| * }, | ||
| * ... | ||
| * } $parsed_block | ||
| * @phpstan-return array{ | ||
| * blockName: string|null, | ||
| * attrs: array{ | ||
| * className?: string, | ||
| * style?: array{ | ||
| * css?: string, | ||
| * ... | ||
| * }, | ||
| * ... | ||
| * }, | ||
| * ... | ||
| * } | ||
| */ | ||
| function wp_render_custom_css_support_styles( $parsed_block ) { | ||
| $class_name = wp_generate_custom_css_class_name( $parsed_block ); | ||
| if ( null === $class_name ) { | ||
| return $parsed_block; | ||
| } | ||
|
|
||
| $existing_class_name = $parsed_block['attrs']['className'] ?? null; | ||
| $updated_class_name = is_string( $existing_class_name ) | ||
| ? "$existing_class_name $class_name" | ||
| : $class_name; | ||
|
|
||
| $parsed_block['attrs']['className'] = $updated_class_name; | ||
|
|
||
| return $parsed_block; | ||
| } | ||
|
|
||
|
|
@@ -221,41 +252,54 @@ function wp_enqueue_block_custom_css() { | |
| /** | ||
| * Applies the custom CSS class name to the block's rendered HTML. | ||
| * | ||
| * The class name is generated in {@see wp_render_custom_css_support_styles()} | ||
| * and stored in block attributes. This filter adds it to the actual markup. | ||
| * Uses the class name that {@see wp_render_custom_css_support_styles()} | ||
| * stored in the block attributes. Blocks that a parent parsed and rendered | ||
| * itself never reach that filter, so the class name is generated here instead | ||
| * via {@see wp_generate_custom_css_class_name()}. | ||
| * | ||
| * @since 7.0.0 | ||
| * @since 7.2.0 Generates the class name when the block attributes lack one. | ||
| * | ||
| * @param string $block_content Rendered block content. | ||
| * @param array $block Block object. | ||
| * @return string Filtered block content. | ||
| * | ||
| * @phpstan-param array{ | ||
| * blockName: string|null, | ||
| * attrs: array{ | ||
| * className?: string, | ||
| * style?: array{ | ||
| * css?: string, | ||
| * ... | ||
| * }, | ||
| * ... | ||
| * }, | ||
| * ... | ||
| * } $block | ||
| */ | ||
| function wp_render_custom_css_class_name( $block_content, $block ) { | ||
| $class_name_attr = $block['attrs']['className'] ?? null; | ||
| $class_name_prefix = 'wp-custom-css-'; | ||
| if ( ! is_string( $class_name_attr ) || ! str_contains( $class_name_attr, $class_name_prefix ) ) { | ||
| return $block_content; | ||
| } | ||
| $class_name_attr = $block['attrs']['className'] ?? null; | ||
| $class_name_prefix = 'wp-custom-css-'; | ||
| $matched_class_name = null; | ||
|
|
||
| // Parse out the 'wp-custom-css-*' class name added by wp_render_custom_css_support_styles(). | ||
| $matched_class_name = null; | ||
| $token_delimiter = " \t\f\r\n"; | ||
| $class_token = strtok( $class_name_attr, $token_delimiter ); | ||
| while ( false !== $class_token ) { | ||
| if ( str_starts_with( $class_token, $class_name_prefix ) ) { | ||
| $matched_class_name = $class_token; | ||
| break; | ||
| if ( is_string( $class_name_attr ) && str_contains( $class_name_attr, $class_name_prefix ) ) { | ||
| $token_delimiter = " \t\f\r\n"; | ||
| $class_token = strtok( $class_name_attr, $token_delimiter ); | ||
| while ( false !== $class_token ) { | ||
| if ( str_starts_with( $class_token, $class_name_prefix ) ) { | ||
| $matched_class_name = $class_token; | ||
| break; | ||
| } | ||
| $class_token = strtok( $token_delimiter ); | ||
| } | ||
| $class_token = strtok( $token_delimiter ); | ||
| } | ||
|
|
||
| // Direct WP_Block::render() calls skip render_block_data for the block itself. | ||
| if ( null === $matched_class_name ) { | ||
| $matched_class_name = wp_generate_custom_css_class_name( $block ); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One behaviour change worth knowing about. If a site has switched off block custom CSS by removing this one filter: remove_filter( 'render_block_data', 'wp_render_custom_css_support_styles' );Before this PR, that was enough. After this PR, it isn't, and that site's block custom CSS shows up on the front end again. That's because the other callback, That helper outputs the CSS for any block that doesn't have its custom CSS class yet. To keep custom CSS switched off, a site needs to remove both callbacks: remove_filter( 'render_block_data', 'wp_render_custom_css_support_styles' );
remove_filter( 'render_block', 'wp_render_custom_css_class_name' );To switch it off for one block type only, set The Gutenberg plugin already removes both callbacks, so it isn't affected. I don't know whether any sites rely on removing just the one filter. Should this go in the dev note, or is it too small to mention?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, I think we could mention this in the dev note, and I don't think we need to make any code changes. I've had a quick search around on GH, and I can only find one theme (xenolito/vertiGO-web) that removes both callbacks, but it removes the filter to replace it with its own class, not to turn custom CSS off. This should still work with this PR.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the confidence check @mikachan 🙇🏻 |
||
| } | ||
|
|
||
| if ( null === $matched_class_name ) { | ||
| return $block_content; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternative might be
That does couple the fallback to the registration state of a private callback. I think it's okay.
A cleaner long term design would be an explicit opt-out filter checked by
wp_generate_custom_css_class_name(). Sites that currently useremove_filter()would need to migrate to it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we need to make either of these changes now. The
has_filter()guard would stop the navigation fix for sites that replace the filter rather than remove it, like the theme I mentioned above. We're not sure if a newwp_should_render_block_custom_cssfilter is needed yet, andsupports.customCSSalready covers turning it off per block type. Could we handle these in follow-ups if they're required?