Skip to content

Commit d062e28

Browse files
aaronrobertshawtellthemachinesramonjdtalldanyouknowriad
authored andcommitted
Block Styles: Extend block style variations as mechanism for achieving section styling (WordPress#57908)
Co-authored-by: aaronrobertshaw <[email protected]> Co-authored-by: tellthemachines <[email protected]> Co-authored-by: ramonjd <[email protected]> Co-authored-by: talldan <[email protected]> Co-authored-by: youknowriad <[email protected]> Co-authored-by: ellatrix <[email protected]> Co-authored-by: SaxonF <[email protected]> Co-authored-by: richtabor <[email protected]> Co-authored-by: fabiankaegy <[email protected]> Co-authored-by: cbirdsong <[email protected]> Co-authored-by: bacoords <[email protected]> Co-authored-by: getdave <[email protected]> Co-authored-by: colorful-tones <[email protected]> Co-authored-by: hanneslsm <[email protected]> Co-authored-by: andrewserong <[email protected]> Co-authored-by: kevin940726 <[email protected]> Co-authored-by: ajlende <[email protected]> Co-authored-by: MaggieCabrera <[email protected]> Co-authored-by: scruffian <[email protected]>
1 parent 849bcc8 commit d062e28

File tree

24 files changed

+1629
-111
lines changed

24 files changed

+1629
-111
lines changed

backport-changelog/6.6/6662.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
https://github.com/WordPress/wordpress-develop/pull/6662
2+
3+
* https://github.com/WordPress/gutenberg/pull/57908
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
<?php
2+
/**
3+
* Block support to enable per-section styling of block types via
4+
* block style variations.
5+
*
6+
* @package gutenberg
7+
*/
8+
9+
/**
10+
* Get the class name for this application of this block's variation styles.
11+
*
12+
* @since 6.6.0
13+
*
14+
* @param array $block Block object.
15+
* @param string $variation Slug for the block style variation.
16+
*
17+
* @return string The unique class name.
18+
*/
19+
function gutenberg_get_block_style_variation_class_name( $block, $variation ) {
20+
return 'is-style-' . $variation . '--' . md5( serialize( $block ) );
21+
}
22+
23+
/**
24+
* Determines the block style variation names within a CSS class string.
25+
*
26+
* @since 6.6.0
27+
*
28+
* @param string $class_string CSS class string to look for a variation in.
29+
*
30+
* @return array|null The block style variation name if found.
31+
*/
32+
function gutenberg_get_block_style_variation_name_from_class( $class_string ) {
33+
if ( ! is_string( $class_string ) ) {
34+
return null;
35+
}
36+
37+
preg_match_all( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches );
38+
return $matches[1] ?? null;
39+
}
40+
41+
/**
42+
* Render the block style variation's styles.
43+
*
44+
* In the case of nested blocks with variations applied, we want the parent
45+
* variation's styles to be rendered before their descendants. This solves the
46+
* issue of a block type being styled in both the parent and descendant: we want
47+
* the descendant style to take priority, and this is done by loading it after,
48+
* in the DOM order. This is why the variation stylesheet generation is in a
49+
* different filter.
50+
*
51+
* @since 6.6.0
52+
*
53+
* @param array $parsed_block The parsed block.
54+
*
55+
* @return array The parsed block with block style variation classname added.
56+
*/
57+
function gutenberg_render_block_style_variation_support_styles( $parsed_block ) {
58+
$classes = $parsed_block['attrs']['className'] ?? null;
59+
$variations = gutenberg_get_block_style_variation_name_from_class( $classes );
60+
61+
if ( ! $variations ) {
62+
return $parsed_block;
63+
}
64+
65+
$tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data();
66+
$theme_json = $tree->get_raw_data();
67+
68+
// Only the first block style variation with data is supported.
69+
$variation_data = array();
70+
foreach ( $variations as $variation ) {
71+
$variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array();
72+
73+
if ( ! empty( $variation_data ) ) {
74+
break;
75+
}
76+
}
77+
78+
if ( empty( $variation_data ) ) {
79+
return $parsed_block;
80+
}
81+
82+
$config = array(
83+
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
84+
'styles' => $variation_data,
85+
);
86+
87+
$class_name = gutenberg_get_block_style_variation_class_name( $parsed_block, $variation );
88+
$updated_class_name = $parsed_block['attrs']['className'] . " $class_name";
89+
90+
$class_name = ".$class_name";
91+
92+
if ( ! is_admin() ) {
93+
remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
94+
}
95+
96+
$variation_theme_json = new WP_Theme_JSON_Gutenberg( $config, 'blocks' );
97+
$variation_styles = $variation_theme_json->get_stylesheet(
98+
array( 'styles' ),
99+
array( 'custom' ),
100+
array(
101+
'root_selector' => $class_name,
102+
'skip_root_layout_styles' => true,
103+
'scope' => $class_name,
104+
)
105+
);
106+
107+
if ( ! is_admin() ) {
108+
add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
109+
}
110+
111+
if ( empty( $variation_styles ) ) {
112+
return $parsed_block;
113+
}
114+
115+
wp_register_style( 'block-style-variation-styles', false, array( 'global-styles' ) );
116+
wp_add_inline_style( 'block-style-variation-styles', $variation_styles );
117+
118+
/*
119+
* Add variation instance class name to block's className string so it can
120+
* be enforced in the block markup via render_block filter.
121+
*/
122+
_wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name );
123+
124+
return $parsed_block;
125+
}
126+
127+
/**
128+
* Ensure the variation block support class name generated and added to
129+
* block attributes in the `render_block_data` filter gets applied to the
130+
* block's markup.
131+
*
132+
* @see gutenberg_render_block_style_variation_support_styles
133+
*
134+
* @since 6.6.0
135+
*
136+
* @param string $block_content Rendered block content.
137+
* @param array $block Block object.
138+
*
139+
* @return string Filtered block content.
140+
*/
141+
function gutenberg_render_block_style_variation_class_name( $block_content, $block ) {
142+
if ( ! $block_content || empty( $block['attrs']['className'] ) ) {
143+
return $block_content;
144+
}
145+
146+
/*
147+
* Matches a class prefixed by `is-style`, followed by the
148+
* variation slug, then `--`, and finally a hash.
149+
*
150+
* See `gutenberg_get_block_style_variation_class_name` for class generation.
151+
*/
152+
preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches );
153+
154+
if ( empty( $matches ) ) {
155+
return $block_content;
156+
}
157+
158+
$tags = new WP_HTML_Tag_Processor( $block_content );
159+
160+
if ( $tags->next_tag() ) {
161+
/*
162+
* Ensure the variation instance class name set in the
163+
* `render_block_data` filter is applied in markup.
164+
* See `gutenberg_render_block_style_variation_support_styles`.
165+
*/
166+
$tags->add_class( $matches[0] );
167+
}
168+
169+
return $tags->get_updated_html();
170+
}
171+
172+
/**
173+
* Collects block style variation data for merging with theme.json data.
174+
* As each block style variation is processed it is registered if it hasn't
175+
* been already. This registration is required for later sanitization of
176+
* theme.json data.
177+
*
178+
* @since 6.6.0
179+
*
180+
* @param array $variations Shared block style variations.
181+
*
182+
* @return array Block variations data to be merged under styles.blocks
183+
*/
184+
function gutenberg_resolve_and_register_block_style_variations( $variations ) {
185+
$variations_data = array();
186+
187+
if ( empty( $variations ) ) {
188+
return $variations_data;
189+
}
190+
191+
$registry = WP_Block_Styles_Registry::get_instance();
192+
$have_named_variations = ! wp_is_numeric_array( $variations );
193+
194+
foreach ( $variations as $key => $variation ) {
195+
$supported_blocks = $variation['blockTypes'] ?? array();
196+
197+
/*
198+
* Standalone theme.json partial files for block style variations
199+
* will have their styles under a top-level property by the same name.
200+
* Variations defined within an existing theme.json or theme style
201+
* variation will themselves already be the required styles data.
202+
*/
203+
$variation_data = $variation['styles'] ?? $variation;
204+
205+
if ( empty( $variation_data ) ) {
206+
continue;
207+
}
208+
209+
/*
210+
* Block style variations read in via standalone theme.json partials
211+
* need to have their name set to the kebab case version of their title.
212+
*/
213+
$variation_name = $have_named_variations ? $key : _wp_to_kebab_case( $variation['title'] );
214+
$variation_label = $variation['title'] ?? $variation_name;
215+
216+
foreach ( $supported_blocks as $block_type ) {
217+
$registered_styles = $registry->get_registered_styles_for_block( $block_type );
218+
219+
// Register block style variation if it hasn't already been registered.
220+
if ( ! array_key_exists( $variation_name, $registered_styles ) ) {
221+
gutenberg_register_block_style(
222+
$block_type,
223+
array(
224+
'name' => $variation_name,
225+
'label' => $variation_label,
226+
)
227+
);
228+
}
229+
230+
// Add block style variation data under current block type.
231+
$path = array( $block_type, 'variations', $variation_name );
232+
_wp_array_set( $variations_data, $path, $variation_data );
233+
}
234+
}
235+
236+
return $variations_data;
237+
}
238+
239+
/**
240+
* Merges variations data with existing theme.json data ensuring that the
241+
* current theme.json data values take precedence.
242+
*
243+
* @since 6.6.0
244+
*
245+
* @param array $variations_data Block style variations data keyed by block type.
246+
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
247+
* @param string $origin Origin for the theme.json data.
248+
*
249+
* @return WP_Theme_JSON_Gutenberg The merged theme.json data.
250+
*/
251+
function gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, $origin = 'theme' ) {
252+
if ( empty( $variations_data ) ) {
253+
return $theme_json;
254+
}
255+
256+
$variations_theme_json_data = array(
257+
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
258+
'styles' => array( 'blocks' => $variations_data ),
259+
);
260+
261+
$variations_theme_json = new WP_Theme_JSON_Data_Gutenberg( $variations_theme_json_data, $origin );
262+
263+
/*
264+
* Merge the current theme.json data over shared variation data so that
265+
* any explicit per block variation values take precedence.
266+
*/
267+
return $variations_theme_json->update_with( $theme_json->get_data() );
268+
}
269+
270+
/**
271+
* Merges any shared block style variation definitions from a theme style
272+
* variation into their appropriate block type within theme json styles. Any
273+
* custom user selections already made will take precedence over the shared
274+
* style variation value.
275+
*
276+
* @since 6.6.0
277+
*
278+
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
279+
*
280+
* @return WP_Theme_JSON_Data_Gutenberg
281+
*/
282+
function gutenberg_resolve_block_style_variations_from_theme_style_variation( $theme_json ) {
283+
$theme_json_data = $theme_json->get_data();
284+
$shared_variations = $theme_json_data['styles']['blocks']['variations'] ?? array();
285+
$variations_data = gutenberg_resolve_and_register_block_style_variations( $shared_variations );
286+
287+
return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, 'user' );
288+
}
289+
290+
/**
291+
* Merges block style variation data sourced from standalone partial
292+
* theme.json files.
293+
*
294+
* @since 6.6.0
295+
*
296+
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
297+
*
298+
* @return WP_Theme_JSON_Data_Gutenberg
299+
*/
300+
function gutenberg_resolve_block_style_variations_from_theme_json_partials( $theme_json ) {
301+
$block_style_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' );
302+
$variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations );
303+
304+
return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json );
305+
}
306+
307+
/**
308+
* Merges shared block style variations registered within the
309+
* `styles.blocks.variations` property of the primary theme.json file.
310+
*
311+
* @since 6.6.0
312+
*
313+
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
314+
*
315+
* @return WP_Theme_JSON_Data_Gutenberg
316+
*/
317+
function gutenberg_resolve_block_style_variations_from_primary_theme_json( $theme_json ) {
318+
$theme_json_data = $theme_json->get_data();
319+
$block_style_variations = $theme_json_data['styles']['blocks']['variations'] ?? array();
320+
$variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations );
321+
322+
return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json );
323+
}
324+
325+
/**
326+
* Merges block style variations registered via the block styles registry with a
327+
* style object, under their appropriate block types within theme.json styles.
328+
* Any variation values defined within the theme.json specific to a block type
329+
* will take precedence over these shared definitions.
330+
*
331+
* @since 6.6.0
332+
*
333+
* @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data.
334+
*
335+
* @return WP_Theme_JSON_Data_Gutenberg
336+
*/
337+
function gutenberg_resolve_block_style_variations_from_styles_registry( $theme_json ) {
338+
$registry = WP_Block_Styles_Registry::get_instance();
339+
$styles = $registry->get_all_registered();
340+
$variations_data = array();
341+
342+
foreach ( $styles as $block_type => $variations ) {
343+
foreach ( $variations as $variation_name => $variation ) {
344+
if ( ! empty( $variation['style_data'] ) ) {
345+
$path = array( $block_type, 'variations', $variation_name );
346+
_wp_array_set( $variations_data, $path, $variation['style_data'] );
347+
}
348+
}
349+
}
350+
351+
return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json );
352+
}
353+
354+
/**
355+
* Enqueues styles for block style variations.
356+
*
357+
* @since 6.6.0
358+
*/
359+
function gutenberg_enqueue_block_style_variation_styles() {
360+
wp_enqueue_style( 'block-style-variation-styles' );
361+
}
362+
363+
// Register the block support.
364+
WP_Block_Supports::get_instance()->register( 'block-style-variation', array() );
365+
366+
add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles', 10, 2 );
367+
add_filter( 'render_block', 'gutenberg_render_block_style_variation_class_name', 10, 2 );
368+
add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_style_variation_styles', 1 );
369+
370+
// Resolve block style variations from all their potential sources. The order here is deliberate.
371+
add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_primary_theme_json', 10, 1 );
372+
add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_theme_json_partials', 10, 1 );
373+
add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_styles_registry', 10, 1 );
374+
375+
add_filter( 'wp_theme_json_data_user', 'gutenberg_resolve_block_style_variations_from_theme_style_variation', 10, 1 );

0 commit comments

Comments
 (0)