Skip to content
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

Block Styles: Extend block style variations as mechanism for achieving section styling #57908

Merged
merged 52 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5958faa
Fix list block's has-background padding
aaronrobertshaw May 24, 2024
23537e6
Fix paragraph block's has-padding style
aaronrobertshaw May 24, 2024
16de34e
Make global styles data available in block editor
aaronrobertshaw Apr 9, 2024
6549ff3
Update util to retrieve style variations from specified directory
aaronrobertshaw Apr 9, 2024
7ea4780
Add filters to resolve shared block style variation definitions
aaronrobertshaw Apr 9, 2024
93fa1f8
Add new top-level theme.json property to identify supported block typ…
aaronrobertshaw Apr 9, 2024
2c73a62
Support skipping root layout styles from theme.json stylesheet genera…
aaronrobertshaw Apr 9, 2024
3af0d24
Add variations from block style registry to block metadata in theme.json
aaronrobertshaw Apr 9, 2024
d966c38
Update theme.json sanitization to support extended block style variat…
aaronrobertshaw Apr 9, 2024
daa82d3
Prevent block style variation element styles from being removed for u…
aaronrobertshaw Apr 9, 2024
6274c28
Resolve user origin shared block style variation definitions e.g. fro…
aaronrobertshaw Apr 9, 2024
cf79868
Update theme.json schema for extended block style variations
aaronrobertshaw Apr 9, 2024
24b14fa
Allow non-core block style variations in global styles variations panel
aaronrobertshaw Apr 9, 2024
35a1d2f
Generate per-application variation styles and enforce unique classname
aaronrobertshaw Apr 9, 2024
a1ea2e3
Update useGlobalStylesOutput hook's node generation to support extend…
aaronrobertshaw Apr 9, 2024
b7219e5
Allow opting out of certain sets of styles when generating JS global …
aaronrobertshaw Apr 9, 2024
785d9cd
Support instanced variation selectors via getBlockSelectors
aaronrobertshaw Apr 9, 2024
e4c21ca
Add block support hook for variations in the editor
aaronrobertshaw Apr 9, 2024
c8ff6e7
Sort style overrides by the rendered order of associated blocks
aaronrobertshaw Apr 9, 2024
0f63b37
Add test theme and child theme with block style variations
aaronrobertshaw Apr 10, 2024
8786df0
Update theme.json resolver unit tests for get_style_variations
aaronrobertshaw Apr 10, 2024
5206c33
Test for variations support resolving variation definitions into them…
aaronrobertshaw Apr 10, 2024
6f83b55
Clean up retrieval of settings and styles
aaronrobertshaw Apr 12, 2024
611498c
Use cloneDeep util
aaronrobertshaw Apr 12, 2024
6be673c
Register block style variations defined in user origin
aaronrobertshaw Apr 12, 2024
723ed63
Make PHP function, block support, and register styles naming consistent
aaronrobertshaw Apr 15, 2024
09426ff
Rename block style variation PHP block support files
aaronrobertshaw Apr 15, 2024
cefe0cb
Rename JS variation block support to block style variation
aaronrobertshaw Apr 15, 2024
fca1981
Fix typo
aaronrobertshaw Apr 15, 2024
24f1863
Tweaks to resolve and register util function
aaronrobertshaw Apr 15, 2024
c54d3a3
Consolidate block style variations under theme styles directory
aaronrobertshaw Apr 15, 2024
db0402c
Fix references to /block-styles in comments
aaronrobertshaw Apr 16, 2024
800d452
Switch scoping of variations to check supportedBlockTypes
aaronrobertshaw Apr 17, 2024
9108b71
Move scopeFeatureSelectors to utils and add docblock
aaronrobertshaw Apr 17, 2024
a524bfb
Make comment docblock
aaronrobertshaw Apr 17, 2024
1cede2a
Use shorthand for retrieving getBlockStyles from blocksStore
aaronrobertshaw Apr 17, 2024
402a234
Split out user origin based block style registration to useEffect
aaronrobertshaw Apr 17, 2024
a8690c4
Fix issue with application of variations in the site editor
aaronrobertshaw Apr 17, 2024
2b97374
Rename supportedBlockTypes to blockTypes
aaronrobertshaw Apr 18, 2024
92809ea
Fix merging of theme style variations config
aaronrobertshaw Apr 20, 2024
fca03a0
Try optimisation of variation resolution in global styles provider
aaronrobertshaw Apr 22, 2024
00c22e5
Move sorting of style overrides into getStyleOverrides selector
aaronrobertshaw Apr 24, 2024
afe5c98
Undo whitespace change
aaronrobertshaw Apr 24, 2024
2481889
Revert "Make global styles data available in block editor"
aaronrobertshaw May 10, 2024
c579963
Switch variation hook to use new global styles editor setting
aaronrobertshaw May 10, 2024
113c269
Fix variations panel in site editor
aaronrobertshaw May 17, 2024
e8ac4b2
Make block style variation retrieval more robust
aaronrobertshaw May 28, 2024
e2d27d3
Update test block theme json versions to latest
aaronrobertshaw May 28, 2024
e963725
Update missed theme json version numbers in tests
aaronrobertshaw May 28, 2024
df4a518
Pass clientId directly through to block props component
aaronrobertshaw May 28, 2024
33830c4
Add @since comments
aaronrobertshaw May 29, 2024
168b5e4
Add backport change log
aaronrobertshaw May 29, 2024
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
375 changes: 375 additions & 0 deletions lib/block-supports/block-style-variations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,375 @@
<?php
/**
* Block support to enable per-section styling of block types via
* block style variations.
*
* @package gutenberg
*/

/**
* Get the class name for this application of this block's variation styles.
*
* @since 6.6.0
*
* @param array $block Block object.
* @param string $variation Slug for the block style variation.
*
* @return string The unique class name.
*/
function gutenberg_get_block_style_variation_class_name( $block, $variation ) {
return 'is-style-' . $variation . '--' . md5( serialize( $block ) );
}

/**
* Determines the block style variation names within a CSS class string.
*
* @since 6.6.0
*
* @param string $class_string CSS class string to look for a variation in.
*
* @return array|null The block style variation name if found.
*/
function gutenberg_get_block_style_variation_name_from_class( $class_string ) {
if ( ! is_string( $class_string ) ) {
return null;
}

preg_match_all( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches );
return $matches[1] ?? null;
}

/**
* Render the block style variation's styles.
*
* In the case of nested blocks with variations applied, we want the parent
* variation's styles to be rendered before their descendants. This solves the
* issue of a block type being styled in both the parent and descendant: we want
* the descendant style to take priority, and this is done by loading it after,
* in the DOM order. This is why the variation stylesheet generation is in a
* different filter.
*
* @since 6.6.0
*
* @param array $parsed_block The parsed block.
*
* @return array The parsed block with block style variation classname added.
*/
function gutenberg_render_block_style_variation_support_styles( $parsed_block ) {
$classes = $parsed_block['attrs']['className'] ?? null;
$variations = gutenberg_get_block_style_variation_name_from_class( $classes );

if ( ! $variations ) {
return $parsed_block;
}

$tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data();
$theme_json = $tree->get_raw_data();

// Only the first block style variation with data is supported.
$variation_data = array();
foreach ( $variations as $variation ) {
$variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array();

if ( ! empty( $variation_data ) ) {
break;
}
}

if ( empty( $variation_data ) ) {
return $parsed_block;
}

$config = array(
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
'styles' => $variation_data,
);

$class_name = gutenberg_get_block_style_variation_class_name( $parsed_block, $variation );
$updated_class_name = $parsed_block['attrs']['className'] . " $class_name";

$class_name = ".$class_name";

if ( ! is_admin() ) {
remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
}

$variation_theme_json = new WP_Theme_JSON_Gutenberg( $config, 'blocks' );
$variation_styles = $variation_theme_json->get_stylesheet(
array( 'styles' ),
array( 'custom' ),
array(
'root_selector' => $class_name,
'skip_root_layout_styles' => true,
'scope' => $class_name,
)
);

if ( ! is_admin() ) {
add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
}

if ( empty( $variation_styles ) ) {
return $parsed_block;
}

wp_register_style( 'block-style-variation-styles', false, array( 'global-styles' ) );
wp_add_inline_style( 'block-style-variation-styles', $variation_styles );

/*
* Add variation instance class name to block's className string so it can
* be enforced in the block markup via render_block filter.
*/
_wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name );

return $parsed_block;
}

/**
* Ensure the variation block support class name generated and added to
* block attributes in the `render_block_data` filter gets applied to the
* block's markup.
*
* @see gutenberg_render_block_style_variation_support_styles
*
* @since 6.6.0
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
*
* @return string Filtered block content.
*/
function gutenberg_render_block_style_variation_class_name( $block_content, $block ) {
if ( ! $block_content || empty( $block['attrs']['className'] ) ) {
return $block_content;
}

/*
* Matches a class prefixed by `is-style`, followed by the
* variation slug, then `--`, and finally a hash.
*
* See `gutenberg_get_block_style_variation_class_name` for class generation.
*/
preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches );

if ( empty( $matches ) ) {
return $block_content;
}

$tags = new WP_HTML_Tag_Processor( $block_content );

if ( $tags->next_tag() ) {
/*
* Ensure the variation instance class name set in the
* `render_block_data` filter is applied in markup.
* See `gutenberg_render_block_style_variation_support_styles`.
*/
$tags->add_class( $matches[0] );
}

return $tags->get_updated_html();
}

/**
* Collects block style variation data for merging with theme.json data.
* As each block style variation is processed it is registered if it hasn't
* been already. This registration is required for later sanitization of
* theme.json data.
*
* @since 6.6.0
*
* @param array $variations Shared block style variations.
*
* @return array Block variations data to be merged under styles.blocks
*/
function gutenberg_resolve_and_register_block_style_variations( $variations ) {
$variations_data = array();

if ( empty( $variations ) ) {
return $variations_data;
}

$registry = WP_Block_Styles_Registry::get_instance();
$have_named_variations = ! wp_is_numeric_array( $variations );

foreach ( $variations as $key => $variation ) {
$supported_blocks = $variation['blockTypes'] ?? array();

/*
* Standalone theme.json partial files for block style variations
* will have their styles under a top-level property by the same name.
* Variations defined within an existing theme.json or theme style
* variation will themselves already be the required styles data.
*/
$variation_data = $variation['styles'] ?? $variation;

if ( empty( $variation_data ) ) {
continue;
}

/*
* Block style variations read in via standalone theme.json partials
* need to have their name set to the kebab case version of their title.
*/
$variation_name = $have_named_variations ? $key : _wp_to_kebab_case( $variation['title'] );
$variation_label = $variation['title'] ?? $variation_name;
Copy link
Contributor

Choose a reason for hiding this comment

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

Something I missed on the first pass through - how are the labels internationalized for theme.json partials?

Guessing that must be possible as we have theme variations with labels.

Copy link
Contributor Author

@aaronrobertshaw aaronrobertshaw May 28, 2024

Choose a reason for hiding this comment

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

The style variations (theme or block) are translated when they are retrieved by the theme.json resolver.


foreach ( $supported_blocks as $block_type ) {
$registered_styles = $registry->get_registered_styles_for_block( $block_type );

// Register block style variation if it hasn't already been registered.
if ( ! array_key_exists( $variation_name, $registered_styles ) ) {
gutenberg_register_block_style(
$block_type,
array(
'name' => $variation_name,
'label' => $variation_label,
)
);
}

// Add block style variation data under current block type.
$path = array( $block_type, 'variations', $variation_name );
_wp_array_set( $variations_data, $path, $variation_data );
}
}

return $variations_data;
}

/**
* Merges variations data with existing theme.json data ensuring that the
* current theme.json data values take precedence.
*
* @since 6.6.0
*
* @param array $variations_data Block style variations data keyed by block type.
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
* @param string $origin Origin for the theme.json data.
*
* @return WP_Theme_JSON_Gutenberg The merged theme.json data.
*/
function gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, $origin = 'theme' ) {
if ( empty( $variations_data ) ) {
return $theme_json;
}

$variations_theme_json_data = array(
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
'styles' => array( 'blocks' => $variations_data ),
);

$variations_theme_json = new WP_Theme_JSON_Data_Gutenberg( $variations_theme_json_data, $origin );

/*
* Merge the current theme.json data over shared variation data so that
* any explicit per block variation values take precedence.
*/
return $variations_theme_json->update_with( $theme_json->get_data() );
}

/**
* Merges any shared block style variation definitions from a theme style
* variation into their appropriate block type within theme json styles. Any
* custom user selections already made will take precedence over the shared
* style variation value.
*
* @since 6.6.0
*
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
*
* @return WP_Theme_JSON_Data_Gutenberg
*/
function gutenberg_resolve_block_style_variations_from_theme_style_variation( $theme_json ) {
$theme_json_data = $theme_json->get_data();
$shared_variations = $theme_json_data['styles']['blocks']['variations'] ?? array();
$variations_data = gutenberg_resolve_and_register_block_style_variations( $shared_variations );

return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, 'user' );
}

/**
* Merges block style variation data sourced from standalone partial
* theme.json files.
*
* @since 6.6.0
*
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
*
* @return WP_Theme_JSON_Data_Gutenberg
*/
function gutenberg_resolve_block_style_variations_from_theme_json_partials( $theme_json ) {
$block_style_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' );
$variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations );

return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json );
}

/**
* Merges shared block style variations registered within the
* `styles.blocks.variations` property of the primary theme.json file.
*
* @since 6.6.0
*
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
*
* @return WP_Theme_JSON_Data_Gutenberg
*/
function gutenberg_resolve_block_style_variations_from_primary_theme_json( $theme_json ) {
$theme_json_data = $theme_json->get_data();
$block_style_variations = $theme_json_data['styles']['blocks']['variations'] ?? array();
$variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations );

return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json );
}

/**
* Merges block style variations registered via the block styles registry with a
* style object, under their appropriate block types within theme.json styles.
* Any variation values defined within the theme.json specific to a block type
* will take precedence over these shared definitions.
*
* @since 6.6.0
*
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
*
* @return WP_Theme_JSON_Data_Gutenberg
*/
function gutenberg_resolve_block_style_variations_from_styles_registry( $theme_json ) {
$registry = WP_Block_Styles_Registry::get_instance();
$styles = $registry->get_all_registered();
$variations_data = array();

foreach ( $styles as $block_type => $variations ) {
foreach ( $variations as $variation_name => $variation ) {
if ( ! empty( $variation['style_data'] ) ) {
$path = array( $block_type, 'variations', $variation_name );
_wp_array_set( $variations_data, $path, $variation['style_data'] );
}
}
}

return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json );
}

/**
* Enqueues styles for block style variations.
*
* @since 6.6.0
*/
function gutenberg_enqueue_block_style_variation_styles() {
wp_enqueue_style( 'block-style-variation-styles' );
}

// Register the block support.
WP_Block_Supports::get_instance()->register( 'block-style-variation', array() );

add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles', 10, 2 );
add_filter( 'render_block', 'gutenberg_render_block_style_variation_class_name', 10, 2 );
add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_style_variation_styles', 1 );

// Resolve block style variations from all their potential sources. The order here is deliberate.
add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_primary_theme_json', 10, 1 );
add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_theme_json_partials', 10, 1 );
add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_styles_registry', 10, 1 );

add_filter( 'wp_theme_json_data_user', 'gutenberg_resolve_block_style_variations_from_theme_style_variation', 10, 1 );
Loading
Loading