PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
gutenberg / lib / block-supports / states.php

states.php in Gutenberg 24.1.0, at lib/block-supports/states.php

740 lines 22.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Block state support for frontend CSS generation.
4 *
5 * Generates scoped CSS for per-instance state styles declared in block attributes,
6 * including pseudo-states (e.g., `style[':hover']`) and responsive states
7 * (e.g., `style['@mobile']` and `style['@mobile'][':hover']`).
8 *
9 * @package WordPress
10 */
11
12 /**
13 * Converts internal preset references to CSS custom property references.
14 *
15 * State styles are emitted as CSS rules and cannot rely on preset classnames.
16 * Converting `var:preset|color|contrast` to
17 * `var(--wp--preset--color--contrast)` ensures preset values are emitted as
18 * declarations by the style engine.
19 *
20 * @param mixed $value Style value to normalize.
21 * @return mixed Normalized style value.
22 */
23 function gutenberg_normalize_state_preset_vars( $value ) {
24 if ( is_array( $value ) ) {
25 foreach ( $value as $key => $nested_value ) {
26 $value[ $key ] = gutenberg_normalize_state_preset_vars( $nested_value );
27 }
28 return $value;
29 }
30
31 if ( ! is_string( $value ) || ! str_starts_with( $value, 'var:preset|' ) ) {
32 return $value;
33 }
34
35 $parts = explode( '|', substr( $value, strlen( 'var:' ) ) );
36
37 /*
38 * The slug is kebab-cased when the preset's custom property is generated,
39 * so a reference has to be converted the same way or it points at a
40 * property that does not exist (`--wp--preset--font-size--3xl` for a preset
41 * generated as `--wp--preset--font-size--3-xl`). Mirrors
42 * `WP_Theme_JSON_Gutenberg::convert_custom_properties()` and the JS style
43 * engine's `getCSSValueFromRawStyle()`.
44 */
45 if ( 3 === count( $parts ) ) {
46 $parts[2] = _wp_to_kebab_case( $parts[2] );
47 }
48
49 return 'var(--wp--' . implode( '--', $parts ) . ')';
50 }
51
52 /**
53 * Normalizes a state style object before generating CSS declarations.
54 *
55 * @param array $style State style object.
56 * @return array Normalized state style object.
57 */
58 function gutenberg_normalize_state_style_for_css_output( $style ) {
59 // Layout is processed separately by gutenberg_render_layout_support_flag(), so we remove it before declaration generation.
60 unset( $style['layout'] );
61 $style = gutenberg_normalize_state_preset_vars( $style );
62 return $style;
63 }
64
65 /**
66 * Adds fallback border-style declarations for visible border declarations.
67 *
68 * CSS does not render border color or width unless a border style is also set.
69 * State styles are emitted as stylesheet rules rather than inline styles, so
70 * they cannot rely on the block-library inline-style attribute fallback rules.
71 *
72 * @param array $declarations CSS declarations generated by the style engine.
73 * @return array CSS declarations with fallback border styles applied where needed.
74 */
75 function gutenberg_get_state_declarations_with_fallback_border_styles( $declarations ) {
76 if ( ! is_array( $declarations ) ) {
77 return $declarations;
78 }
79
80 $has_border_style = isset( $declarations['border-style'] ) && '' !== $declarations['border-style'];
81 $has_border_color = isset( $declarations['border-color'] ) && '' !== $declarations['border-color'];
82 $has_border_width = isset( $declarations['border-width'] ) && '' !== $declarations['border-width'];
83
84 if ( ! $has_border_style && ( $has_border_color || $has_border_width ) ) {
85 $declarations['border-style'] = 'solid';
86 }
87
88 $sides = array( 'top', 'right', 'bottom', 'left' );
89 foreach ( $sides as $side ) {
90 $side_style_property = "border-$side-style";
91 $side_color_property = "border-$side-color";
92 $side_width_property = "border-$side-width";
93
94 $has_side_style = isset( $declarations[ $side_style_property ] ) && '' !== $declarations[ $side_style_property ];
95 $has_side_color = isset( $declarations[ $side_color_property ] ) && '' !== $declarations[ $side_color_property ];
96 $has_side_width = isset( $declarations[ $side_width_property ] ) && '' !== $declarations[ $side_width_property ];
97
98 if ( ! $has_border_style && ! $has_side_style && ( $has_side_color || $has_side_width ) ) {
99 $declarations[ $side_style_property ] = 'solid';
100 }
101 }
102
103 return $declarations;
104 }
105
106 /**
107 * Adds background reset declarations to prevent gradient/solid color conflicts.
108 *
109 * When a state sets a solid background-color, any gradient applied to the
110 * default state (via `background` shorthand or `background-image`) must be
111 * explicitly cleared. Without this, the gradient image layer remains visible
112 * on top of the solid hover color even when `!important` is used, because
113 * `background-color` and `background-image` are separate CSS properties.
114 *
115 * @param array $declarations CSS declarations generated by the style engine.
116 * @return array CSS declarations with background resets applied where needed.
117 */
118 function gutenberg_get_state_declarations_with_background_resets( $declarations ) {
119 if ( ! is_array( $declarations ) ) {
120 return $declarations;
121 }
122
123 $has_background_color = isset( $declarations['background-color'] ) && '' !== $declarations['background-color'];
124 $has_background = isset( $declarations['background'] ) && '' !== $declarations['background'];
125 $has_background_image = isset( $declarations['background-image'] ) && '' !== $declarations['background-image'];
126
127 /*
128 * When the state sets a solid background-color but no gradient of its own,
129 * emit `background-image: unset` to clear any gradient (whether stored as
130 * the `background` shorthand or as `background-image`) that was applied to
131 * the default / normal state via an inline style attribute. The declaration
132 * is marked important when the state rule is registered with the style engine.
133 */
134 if ( $has_background_color && ! $has_background && ! $has_background_image ) {
135 $declarations['background-image'] = 'unset';
136 }
137
138 return $declarations;
139 }
140
141 /**
142 * Adds fallback dimension styles for aspectRatio and height block-support values.
143 *
144 * @param array $state_style State style object.
145 * @return array State style object with fallback dimension styles applied where needed.
146 */
147 function gutenberg_get_state_style_with_fallback_dimension_styles( $state_style ) {
148 if ( ! is_array( $state_style ) ) {
149 return $state_style;
150 }
151
152 $dimensions = isset( $state_style['dimensions'] ) && is_array( $state_style['dimensions'] )
153 ? $state_style['dimensions']
154 : array();
155
156 if ( empty( $dimensions ) ) {
157 return $state_style;
158 }
159
160 if ( gutenberg_is_explicit_aspect_ratio_value( $dimensions['aspectRatio'] ?? null ) ) {
161 return array_replace_recursive(
162 $state_style,
163 array(
164 'dimensions' => array(
165 'minHeight' => 'unset',
166 'height' => 'unset',
167 ),
168 )
169 );
170 }
171
172 $has_min_height = isset( $dimensions['minHeight'] ) && ( is_string( $dimensions['minHeight'] ) || is_numeric( $dimensions['minHeight'] ) ) && '' !== trim( (string) $dimensions['minHeight'] );
173 $has_height = isset( $dimensions['height'] ) && ( is_string( $dimensions['height'] ) || is_numeric( $dimensions['height'] ) ) && '' !== trim( (string) $dimensions['height'] );
174
175 if ( $has_min_height || $has_height ) {
176 return array_replace_recursive(
177 $state_style,
178 array(
179 'dimensions' => array(
180 'aspectRatio' => 'unset',
181 ),
182 )
183 );
184 }
185
186 return $state_style;
187 }
188
189 /**
190 * Adds a style fragment to a selector-keyed state style group.
191 *
192 * @param array $groups Selector-keyed style groups.
193 * @param string|null $selector Block or feature selector.
194 * @param array $style Style fragment.
195 */
196 function gutenberg_add_state_style_group( &$groups, $selector, $style ) {
197 $key = is_string( $selector ) ? $selector : '';
198
199 if ( ! isset( $groups[ $key ] ) ) {
200 $groups[ $key ] = array(
201 'selector' => $selector,
202 'style' => array(),
203 );
204 }
205
206 $groups[ $key ]['style'] = array_replace_recursive( $groups[ $key ]['style'], $style );
207 }
208
209 /**
210 * Splits a state style object into groups based on block feature selectors.
211 *
212 * @param array $state_style State style object.
213 * @param array $block_selectors Block selectors metadata.
214 * @return array[] Selector/style groups.
215 */
216 function gutenberg_get_state_style_groups( $state_style, $block_selectors ) {
217 $groups = array();
218
219 foreach ( $state_style as $feature => $feature_styles ) {
220 $feature_selectors = $block_selectors[ $feature ] ?? null;
221
222 if ( is_string( $feature_selectors ) ) {
223 gutenberg_add_state_style_group(
224 $groups,
225 $feature_selectors,
226 array( $feature => $feature_styles )
227 );
228 continue;
229 }
230
231 if ( is_array( $feature_selectors ) && is_array( $feature_styles ) ) {
232 $remaining_styles = $feature_styles;
233
234 foreach ( $feature_selectors as $subfeature => $subfeature_selector ) {
235 if (
236 'root' === $subfeature ||
237 ! is_string( $subfeature_selector ) ||
238 ! array_key_exists( $subfeature, $feature_styles )
239 ) {
240 continue;
241 }
242
243 gutenberg_add_state_style_group(
244 $groups,
245 $subfeature_selector,
246 array(
247 $feature => array(
248 $subfeature => $feature_styles[ $subfeature ],
249 ),
250 )
251 );
252 unset( $remaining_styles[ $subfeature ] );
253 }
254
255 if ( array() !== $remaining_styles ) {
256 gutenberg_add_state_style_group(
257 $groups,
258 $feature_selectors['root'] ?? ( $block_selectors['root'] ?? null ),
259 array( $feature => $remaining_styles )
260 );
261 }
262 continue;
263 }
264
265 gutenberg_add_state_style_group(
266 $groups,
267 $block_selectors['root'] ?? null,
268 array( $feature => $feature_styles )
269 );
270 }
271
272 return array_values( $groups );
273 }
274
275 /**
276 * Returns a style object with nested state keys removed.
277 *
278 * @param array $state_style State style object.
279 * @param array $nested_keys Keys to remove from the root style object.
280 * @return array Root-only style object.
281 */
282 function gutenberg_get_root_state_style( $state_style, $nested_keys ) {
283 if ( ! is_array( $state_style ) ) {
284 return $state_style;
285 }
286
287 $root_style = $state_style;
288 foreach ( $nested_keys as $key ) {
289 unset( $root_style[ $key ] );
290 }
291
292 return $root_style;
293 }
294
295 /**
296 * Generates all element selectors for a block root selector.
297 *
298 * @param string $root_selector The block root CSS selector.
299 * @return string[] Element selectors keyed by element name.
300 */
301 function gutenberg_get_block_state_element_selectors( $root_selector ) {
302 if ( ! is_string( $root_selector ) || '' === trim( $root_selector ) ) {
303 return array();
304 }
305
306 $block_selectors = gutenberg_split_selector_list( $root_selector );
307 $element_selectors = array();
308
309 foreach ( WP_Theme_JSON_Gutenberg::ELEMENTS as $element_name => $element_selector ) {
310 $selectors = array();
311
312 foreach ( $block_selectors as $block_selector ) {
313 $block_selector = trim( $block_selector );
314 if ( '' === $block_selector ) {
315 continue;
316 }
317
318 if ( $block_selector === $element_selector ) {
319 $selectors = array( $element_selector );
320 break;
321 }
322
323 $selector_prefix = "$block_selector ";
324 if ( ! str_contains( $element_selector, ',' ) ) {
325 $selectors[] = $selector_prefix . $element_selector;
326 continue;
327 }
328
329 $prepended_selectors = array();
330 foreach ( gutenberg_split_selector_list( $element_selector ) as $selector ) {
331 $prepended_selectors[] = $selector_prefix . $selector;
332 }
333 $selectors[] = implode( ',', $prepended_selectors );
334 }
335
336 if ( ! empty( $selectors ) ) {
337 $element_selectors[ $element_name ] = implode( ',', $selectors );
338 }
339 }
340
341 return $element_selectors;
342 }
343
344 /**
345 * Adds a compiled state style rule to a rule list.
346 *
347 * @param array $css_rules Style rules.
348 * @param string $state Pseudo-state selector.
349 * @param string|null $selector Block, feature, or element selector.
350 * @param array $style Style object.
351 * @param string|null $rules_group Optional CSS grouping rule, e.g. a media query.
352 */
353 function gutenberg_add_block_state_style_rule( &$css_rules, $state, $selector, $style, $rules_group = null ) {
354 if ( empty( $style ) || ! is_array( $style ) ) {
355 return;
356 }
357
358 $style = gutenberg_get_state_style_with_fallback_dimension_styles( $style );
359
360 $compiled = gutenberg_style_engine_get_styles(
361 gutenberg_normalize_state_style_for_css_output( $style )
362 );
363 $declarations = $compiled['declarations'] ?? array();
364 $text_align = $style['typography']['textAlign'] ?? null;
365 // Base text alignment is class-based, so state styles need a declaration.
366 if ( is_string( $text_align ) && '' !== trim( $text_align ) ) {
367 $declarations['text-align'] = $text_align;
368 }
369
370 if ( empty( $declarations ) ) {
371 return;
372 }
373
374 $css_rules[] = array(
375 'state' => $state,
376 'selector' => $selector,
377 'declarations' => $declarations,
378 );
379 if ( ! empty( $rules_group ) ) {
380 $css_rules[ count( $css_rules ) - 1 ]['rules_group'] = $rules_group;
381 }
382 }
383
384 /**
385 * Builds compiled state style rules, preserving the selector each rule targets.
386 *
387 * @param array $state_styles Map of state to style array.
388 * @param WP_Block_Type $block_type Block type.
389 * @param string|null $rules_group Optional CSS grouping rule, e.g. a media query.
390 * @return array[] State style rules.
391 */
392 function gutenberg_get_block_state_style_rules( $state_styles, $block_type, $rules_group = null ) {
393 $css_rules = array();
394 $block_selectors = isset( $block_type->selectors ) && is_array( $block_type->selectors )
395 ? $block_type->selectors
396 : array();
397
398 foreach ( $state_styles as $state => $state_style ) {
399 if ( empty( $state_style ) || ! is_array( $state_style ) ) {
400 continue;
401 }
402
403 foreach ( gutenberg_get_state_style_groups( $state_style, $block_selectors ) as $group ) {
404 gutenberg_add_block_state_style_rule(
405 $css_rules,
406 $state,
407 $group['selector'],
408 $group['style'],
409 $rules_group
410 );
411 }
412 }
413
414 return $css_rules;
415 }
416
417 /**
418 * Returns a unique class for a set of state style rules.
419 *
420 * @param string $block_name Block name.
421 * @param array $css_rules State style rules.
422 * @return string Unique class name.
423 */
424 function gutenberg_get_block_state_unique_class( $block_name, $css_rules ) {
425 return 'wp-states-' . substr(
426 md5(
427 wp_json_encode(
428 array(
429 'blockName' => $block_name,
430 'rules' => $css_rules,
431 )
432 )
433 ),
434 0,
435 8
436 );
437 }
438
439 /**
440 * Splits a selector list by top-level commas.
441 *
442 * @param string $selector CSS selector list.
443 * @return string[] Selectors.
444 */
445 function gutenberg_split_selector_list( $selector ) {
446 if ( ! str_contains( $selector, ',' ) ) {
447 return array( $selector );
448 }
449
450 $selectors = array();
451 $current_selector = '';
452 $parentheses_depth = 0;
453 $selector_length = strlen( $selector );
454
455 for ( $i = 0; $i < $selector_length; $i++ ) {
456 $char = $selector[ $i ];
457
458 if ( '(' === $char ) {
459 ++$parentheses_depth;
460 } elseif ( ')' === $char && $parentheses_depth > 0 ) {
461 --$parentheses_depth;
462 } elseif ( ',' === $char && 0 === $parentheses_depth ) {
463 $selectors[] = $current_selector;
464 $current_selector = '';
465 continue;
466 }
467
468 $current_selector .= $char;
469 }
470
471 $selectors[] = $current_selector;
472
473 return $selectors;
474 }
475
476 /**
477 * Builds a scoped selector from a block selector and optional pseudo-state.
478 *
479 * @param string $base_selector Block-instance scoping selector.
480 * @param string|null $block_selector Block or feature selector from metadata.
481 * @param string $state Pseudo-state selector.
482 * @return string Scoped selector.
483 */
484 function gutenberg_build_state_selector( $base_selector, $block_selector, $state ) {
485 if ( ! is_string( $block_selector ) || '' === trim( $block_selector ) ) {
486 return $base_selector . $state;
487 }
488
489 $selectors = gutenberg_split_selector_list( $block_selector );
490 $scoped_selectors = array();
491
492 foreach ( $selectors as $selector ) {
493 $selector = trim( $selector );
494 if ( '' === $selector ) {
495 continue;
496 }
497
498 /*
499 * Replace only the leading block selector part (e.g. class name,
500 * attribute selector, ID, or tag name) with the block instance selector.
501 * Preserve anything after that prefix, including modifier classes on the
502 * same element and combinators without spaces.
503 */
504 if ( preg_match( '/^([.#]?[-_a-zA-Z0-9]+|\[[^\]]+\])/', $selector, $matches ) ) {
505 $scoped_selectors[] = $base_selector . substr( $selector, strlen( $matches[0] ) ) . $state;
506 continue;
507 }
508
509 $scoped_selectors[] = $base_selector . $state;
510 }
511
512 return empty( $scoped_selectors )
513 ? $base_selector . $state
514 : implode( ', ', $scoped_selectors );
515 }
516
517 /**
518 * Renders per-instance state styles on the frontend.
519 *
520 * @param string $block_content The block's rendered HTML.
521 * @param array $block The block data including blockName and attrs.
522 * @return string Modified block content with injected state styles.
523 */
524 function gutenberg_render_block_states_support( $block_content, $block ) {
525 if ( empty( $block['blockName'] ) || empty( $block_content ) ) {
526 return $block_content;
527 }
528
529 $block_name = $block['blockName'];
530
531 /*
532 * Every CSS rule this function can produce is keyed off the block's `style`
533 * attribute. Without it there is nothing to generate, so bail before doing
534 * any of the lookups below — this runs for every block on every request.
535 */
536 $raw_style = $block['attrs']['style'] ?? array();
537 if ( empty( $raw_style ) || ! is_array( $raw_style ) ) {
538 return $block_content;
539 }
540
541 // Keep the existing block registry lookup here.
542 $style = gutenberg_resolve_style_state_aliases( $raw_style, $block_name );
543
544 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
545 if ( ! $block_type ) {
546 return $block_content;
547 }
548
549 $supported_pseudo_states = WP_Theme_JSON_Gutenberg::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ?? array();
550 $css_rules = array();
551 $viewport_settings = gutenberg_get_global_settings( array( 'viewport' ) );
552 $responsive_media_queries = WP_Theme_JSON_Gutenberg::get_viewport_media_queries( $viewport_settings );
553
554 foreach ( $supported_pseudo_states as $pseudo_state ) {
555 if ( empty( $style[ $pseudo_state ] ) || ! is_array( $style[ $pseudo_state ] ) ) {
556 continue;
557 }
558
559 $css_rules = array_merge(
560 $css_rules,
561 gutenberg_get_block_state_style_rules(
562 array( $pseudo_state => $style[ $pseudo_state ] ),
563 $block_type
564 )
565 );
566 }
567
568 foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
569 if ( empty( $style[ $breakpoint ] ) || ! is_array( $style[ $breakpoint ] ) ) {
570 continue;
571 }
572
573 $root_state_style = gutenberg_get_root_state_style(
574 $style[ $breakpoint ],
575 array_merge( array( 'elements' ), $supported_pseudo_states )
576 );
577
578 if ( ! empty( $root_state_style ) ) {
579 $css_rules = array_merge(
580 $css_rules,
581 gutenberg_get_block_state_style_rules(
582 array( '' => $root_state_style ),
583 $block_type,
584 $media_query
585 )
586 );
587 }
588
589 if (
590 ! empty( $style[ $breakpoint ]['elements'] ) &&
591 is_array( $style[ $breakpoint ]['elements'] )
592 ) {
593 $element_selectors = gutenberg_get_block_state_element_selectors(
594 wp_get_block_css_selector( $block_type )
595 );
596
597 foreach (
598 $style[ $breakpoint ]['elements'] as $element_name => $element_style
599 ) {
600 if (
601 empty( $element_style ) ||
602 ! is_array( $element_style ) ||
603 empty( $element_selectors[ $element_name ] )
604 ) {
605 continue;
606 }
607
608 $element_pseudo_states = WP_Theme_JSON_Gutenberg::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ]
609 ?? array();
610 $root_element_style = gutenberg_get_root_state_style(
611 $element_style,
612 $element_pseudo_states
613 );
614
615 gutenberg_add_block_state_style_rule(
616 $css_rules,
617 '',
618 $element_selectors[ $element_name ],
619 $root_element_style,
620 $media_query
621 );
622
623 foreach ( $element_pseudo_states as $pseudo_state ) {
624 if (
625 empty( $element_style[ $pseudo_state ] ) ||
626 ! is_array( $element_style[ $pseudo_state ] )
627 ) {
628 continue;
629 }
630
631 gutenberg_add_block_state_style_rule(
632 $css_rules,
633 $pseudo_state,
634 $element_selectors[ $element_name ],
635 $element_style[ $pseudo_state ],
636 $media_query
637 );
638 }
639 }
640 }
641
642 foreach ( $supported_pseudo_states as $pseudo_state ) {
643 if ( empty( $style[ $breakpoint ][ $pseudo_state ] ) || ! is_array( $style[ $breakpoint ][ $pseudo_state ] ) ) {
644 continue;
645 }
646
647 $css_rules = array_merge(
648 $css_rules,
649 gutenberg_get_block_state_style_rules(
650 array( $pseudo_state => $style[ $breakpoint ][ $pseudo_state ] ),
651 $block_type,
652 $media_query
653 )
654 );
655 }
656 }
657
658 if ( empty( $css_rules ) ) {
659 return $block_content;
660 }
661
662 $unique_class = gutenberg_get_block_state_unique_class( $block_name, $css_rules );
663
664 /*
665 * Register each state's CSS rules with the block-supports style engine store.
666 * The store deduplicates rules by selector — two block instances with identical
667 * state styles share the same hash class and therefore the same selector,
668 * so only one CSS rule is emitted. The store is flushed to the page by
669 * gutenberg_enqueue_stored_styles() rather than injected inline here.
670 *
671 * State declarations need !important to apply reliably over inline styles and
672 * preset utility classes such as .has-accent-3-background-color.
673 *
674 * Layout-driven state styles (responsive layout, blockGap, child layout) are
675 * handled by gutenberg_render_layout_support_flag() so they share a selector
676 * with the base layout and target the correct (inner) wrapper element.
677 */
678 $style_rules = array();
679 foreach ( $css_rules as $rule ) {
680 $declarations = $rule['declarations'];
681 $important_declaration_values = gutenberg_get_state_declarations_with_background_resets( $declarations );
682 $important_declarations = new WP_Style_Engine_CSS_Declarations_Gutenberg();
683 foreach ( $important_declaration_values as $property => $value ) {
684 $important_declarations->add_declaration(
685 $property,
686 $value,
687 array(
688 'important' => true,
689 )
690 );
691 }
692 $selector = gutenberg_build_state_selector(
693 ".$unique_class",
694 $rule['selector'],
695 $rule['state']
696 );
697 $important_style_rule = array(
698 'selector' => $selector,
699 'declarations' => $important_declarations,
700 );
701 if ( ! empty( $rule['rules_group'] ) ) {
702 $important_style_rule['rules_group'] = $rule['rules_group'];
703 }
704 $style_rules[] = $important_style_rule;
705
706 $fallback_declarations = gutenberg_get_state_declarations_with_fallback_border_styles( $declarations );
707 foreach ( array_keys( $declarations ) as $property ) {
708 unset( $fallback_declarations[ $property ] );
709 }
710
711 if ( empty( $fallback_declarations ) ) {
712 continue;
713 }
714
715 $fallback_style_rule = array(
716 'selector' => $selector,
717 'declarations' => $fallback_declarations,
718 );
719 if ( ! empty( $rule['rules_group'] ) ) {
720 $fallback_style_rule['rules_group'] = $rule['rules_group'];
721 }
722 $style_rules[] = $fallback_style_rule;
723 }
724
725 gutenberg_style_engine_get_stylesheet_from_css_rules(
726 $style_rules,
727 array(
728 'context' => 'block-supports',
729 'prettify' => false,
730 )
731 );
732
733 $processor = new WP_HTML_Tag_Processor( $block_content );
734 if ( $processor->next_tag() ) {
735 $processor->add_class( $unique_class );
736 }
737 return $processor->get_updated_html();
738 }
739 add_filter( 'render_block', 'gutenberg_render_block_states_support', 10, 2 );
740