PluginProbe
Gutenberg / 22.7.0
Gutenberg v22.7.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 12.6.0 All 403 releases
← All changes | lib/block-supports/layout.php +205 -482 23.5.222.7.0 View file →
@@ -237,228 +237,8 @@
237 237 }
238 238 }
239 239
240 240 /**
241 - * Returns the child-layout-only subset of a layout object.
242 - *
243 - * Child layout keys are the ones controlling how the block lays itself out
244 - * inside its parent's grid or flex container.
245 - *
246 - * @param mixed $layout Layout object.
247 - * @return array Child layout values, or an empty array.
248 - */
249 -function gutenberg_get_layout_child_values( $layout ) {
250 - if ( ! is_array( $layout ) ) {
251 - return array();
252 - }
253 -
254 - return array_intersect_key(
255 - $layout,
256 - array_flip(
257 - array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' )
258 - )
259 - );
260 -}
261 -
262 -/**
263 - * Returns the container-layout subset of a layout object (everything except child layout keys).
264 - *
265 - * @param mixed $layout Layout object.
266 - * @return array Container layout values, or an empty array.
267 - */
268 -function gutenberg_get_layout_container_values( $layout ) {
269 - if ( ! is_array( $layout ) ) {
270 - return array();
271 - }
272 -
273 - return array_diff_key(
274 - $layout,
275 - array_flip(
276 - array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' )
277 - )
278 - );
279 -}
280 -
281 -/**
282 - * Sanitizes a block gap value before layout style generation.
283 - *
284 - * Regex for CSS value borrowed from `safecss_filter_attr`, used here to only match
285 - * against the value, not the CSS attribute.
286 - *
287 - * @param string|array|null $gap_value Block gap value.
288 - * @return string|array|null Sanitized block gap value.
289 - */
290 -function gutenberg_sanitize_block_gap_value( $gap_value ) {
291 - if ( is_array( $gap_value ) ) {
292 - foreach ( $gap_value as $key => $value ) {
293 - $gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
294 - }
295 - return $gap_value;
296 - }
297 -
298 - return $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
299 -}
300 -
301 -/**
302 - * Returns child layout styles for a block affected by its parent's layout.
303 - *
304 - * @param string $selector CSS selector.
305 - * @param array $child_layout Child layout values.
306 - * @param array $parent_layout Parent layout values.
307 - * @param array|null $viewport_overrides Optional. Child viewport layout overrides to emit.
308 - * @return array Child layout style rules.
309 - */
310 -function gutenberg_get_child_layout_style_rules( $selector, $child_layout, $parent_layout = array(), $viewport_overrides = null ) {
311 - $base_child_layout = is_array( $child_layout ) ? $child_layout : array();
312 - $viewport_overrides = is_array( $viewport_overrides ) ? $viewport_overrides : null;
313 - $child_layout = null === $viewport_overrides ? $base_child_layout : array_replace( $base_child_layout, $viewport_overrides );
314 - $child_layout_declarations = array();
315 - $child_layout_styles = array();
316 - $has_viewport_property_override = static function ( $property ) use ( $viewport_overrides ) {
317 - return array_key_exists( $property, $viewport_overrides );
318 - };
319 -
320 - $self_stretch = $child_layout['selfStretch'] ?? null;
321 - $base_self_stretch = $base_child_layout['selfStretch'] ?? null;
322 -
323 - /*
324 - * These are the serialized `selfStretch` values. `max` used to be called
325 - * "Fixed" in the UI, but was renamed and replaced by `fixedNoShrink`.
326 - */
327 - $flex_child_layout_values = array(
328 - 'fit' => 'fit',
329 - 'grow' => 'fill',
330 - 'max' => 'fixed',
331 - 'fixed' => 'fixedNoShrink',
332 - );
333 - $flex_size_values = array(
334 - $flex_child_layout_values['max'],
335 - $flex_child_layout_values['fixed'],
336 - );
337 -
338 - if ( null === $viewport_overrides || $has_viewport_property_override( 'selfStretch' ) || $has_viewport_property_override( 'flexSize' ) ) {
339 - if (
340 - null !== $viewport_overrides &&
341 - ( $flex_child_layout_values['fit'] === $self_stretch || $flex_child_layout_values['grow'] === $self_stretch ) &&
342 - in_array( $base_self_stretch, $flex_size_values, true ) &&
343 - isset( $base_child_layout['flexSize'] )
344 - ) {
345 - $child_layout_declarations['flex-basis'] = 'unset';
346 - if ( $flex_child_layout_values['fixed'] === $base_self_stretch ) {
347 - $child_layout_declarations['flex-shrink'] = 'unset';
348 - }
349 - }
350 - if ( in_array( $self_stretch, $flex_size_values, true ) && isset( $child_layout['flexSize'] ) ) {
351 - $child_layout_declarations['flex-basis'] = $child_layout['flexSize'];
352 - if ( $flex_child_layout_values['fixed'] === $self_stretch ) {
353 - $child_layout_declarations['flex-shrink'] = '0';
354 - } elseif ( null !== $viewport_overrides && $flex_child_layout_values['fixed'] === $base_self_stretch ) {
355 - $child_layout_declarations['flex-shrink'] = 'unset';
356 - }
357 - $child_layout_declarations['box-sizing'] = 'border-box';
358 - } elseif ( $flex_child_layout_values['grow'] === $self_stretch ) {
359 - $child_layout_declarations['flex-grow'] = '1';
360 - }
361 - }
362 -
363 - $column_start = $child_layout['columnStart'] ?? null;
364 - $column_span = $child_layout['columnSpan'] ?? null;
365 - if ( null === $viewport_overrides || $has_viewport_property_override( 'columnStart' ) || $has_viewport_property_override( 'columnSpan' ) ) {
366 - if ( $column_start && $column_span ) {
367 - $child_layout_declarations['grid-column'] = "$column_start / span $column_span";
368 - } elseif ( $column_start ) {
369 - $child_layout_declarations['grid-column'] = "$column_start";
370 - } elseif ( $column_span ) {
371 - $child_layout_declarations['grid-column'] = "span $column_span";
372 - }
373 - }
374 -
375 - $row_start = $child_layout['rowStart'] ?? null;
376 - $row_span = $child_layout['rowSpan'] ?? null;
377 - if ( null === $viewport_overrides || $has_viewport_property_override( 'rowStart' ) || $has_viewport_property_override( 'rowSpan' ) ) {
378 - if ( $row_start && $row_span ) {
379 - $child_layout_declarations['grid-row'] = "$row_start / span $row_span";
380 - } elseif ( $row_start ) {
381 - $child_layout_declarations['grid-row'] = "$row_start";
382 - } elseif ( $row_span ) {
383 - $child_layout_declarations['grid-row'] = "span $row_span";
384 - }
385 - }
386 -
387 - if ( ! empty( $child_layout_declarations ) ) {
388 - $child_layout_styles[] = array(
389 - 'selector' => $selector,
390 - 'declarations' => $child_layout_declarations,
391 - );
392 - }
393 -
394 - $minimum_column_width = $parent_layout['minimumColumnWidth'] ?? null;
395 - $column_count = $parent_layout['columnCount'] ?? null;
396 -
397 - /*
398 - * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set,
399 - * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed
400 - * once the grid has less columns than the start.
401 - * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set.
402 - * In that case, if columnCount doesn't exist, we can assume that the grid is responsive.
403 - */
404 - if ( null === $viewport_overrides && ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) {
405 - $column_span_number = floatval( $column_span );
406 - $column_start_number = floatval( $column_start );
407 - $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem';
408 - $parent_column_value = floatval( $parent_column_width );
409 - $parent_column_unit = explode( $parent_column_value, $parent_column_width );
410 -
411 - $num_cols_to_break_at = 2;
412 - if ( $column_span_number && $column_start_number ) {
413 - $num_cols_to_break_at = $column_start_number + $column_span_number - 1;
414 - } elseif ( $column_span_number ) {
415 - $num_cols_to_break_at = $column_span_number;
416 - } else {
417 - $num_cols_to_break_at = $column_start_number;
418 - }
419 -
420 - /*
421 - * If there is no unit, the width has somehow been mangled so we reset both unit and value
422 - * to defaults.
423 - * Additionally, the unit should be one of px, rem or em, so that also needs to be checked.
424 - */
425 - if ( count( $parent_column_unit ) <= 1 ) {
426 - $parent_column_unit = 'rem';
427 - $parent_column_value = 12;
428 - } else {
429 - $parent_column_unit = $parent_column_unit[1];
430 -
431 - if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) {
432 - $parent_column_unit = 'rem';
433 - }
434 - }
435 -
436 - /*
437 - * A default gap value is used for this computation because custom gap values may not be
438 - * viable to use in the computation of the container query value.
439 - */
440 - $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
441 - $container_query_value = $num_cols_to_break_at * $parent_column_value + ( $num_cols_to_break_at - 1 ) * $default_gap_value;
442 - $minimum_container_query_value = $parent_column_value * 2 + $default_gap_value - 1;
443 - $container_query_value = max( $container_query_value, $minimum_container_query_value ) . $parent_column_unit;
444 - // If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
445 - $grid_column_value = $column_span && $column_span > 1 ? '1/-1' : 'auto';
446 -
447 - $child_layout_styles[] = array(
448 - 'rules_group' => "@container (max-width: $container_query_value )",
449 - 'selector' => $selector,
450 - 'declarations' => array(
451 - 'grid-column' => $grid_column_value,
452 - 'grid-row' => 'auto',
453 - ),
454 - );
455 - }
456 -
457 - return $child_layout_styles;
458 -}
459 -
460 -/**
461 241 * Generates the CSS corresponding to the provided layout.
462 242 *
463 243 * @param string $selector CSS selector.
464 244 * @param array $layout Layout object. The one that is passed has already checked
@@ -467,26 +247,16 @@
467 247 * @param string|string[]|null $gap_value Optional. The block gap value to apply. Default null.
468 248 * @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the editor. Default false.
469 249 * @param string|array $fallback_gap_value Optional. The block gap value to apply. If it's an array expected properties are "top" and/or "left". Default '0.5em'.
470 250 * @param array|null $block_spacing Optional. Custom spacing set on the block. Default null.
471 - * @param array $options Optional. Extra options for internal callers. Default empty array.
472 - * @return string CSS styles, or empty string.
251 + * @return string CSS styles on success. Else, empty string.
473 252 */
474 -function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false, $fallback_gap_value = '0.5em', $block_spacing = null, $options = array() ) {
475 - $base_layout = is_array( $layout ) ? $layout : array();
476 - $viewport_overrides = $options['viewport_overrides'] ?? null;
477 - $layout_for_styles = null === $viewport_overrides ? $base_layout : array_replace( $base_layout, $viewport_overrides );
478 - $layout_type = $base_layout['type'] ?? 'default';
479 - $rules_group = $options['rules_group'] ?? null;
480 - $has_block_gap_override = ! empty( $options['has_block_gap_override'] );
481 - $should_output_block_gap = null === $viewport_overrides || $has_block_gap_override;
482 - $has_viewport_property_override = static function ( $property ) use ( $viewport_overrides ) {
483 - return array_key_exists( $property, $viewport_overrides );
484 - };
485 - $layout_styles = array();
253 +function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false, $fallback_gap_value = '0.5em', $block_spacing = null ) {
254 + $layout_type = $layout['type'] ?? 'default';
255 + $layout_styles = array();
486 256
487 257 if ( 'default' === $layout_type ) {
488 - if ( $has_block_gap_support && $should_output_block_gap ) {
258 + if ( $has_block_gap_support ) {
489 259 if ( is_array( $gap_value ) ) {
490 260 $gap_value = $gap_value['top'] ?? null;
491 261 }
492 262 if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
@@ -516,11 +286,11 @@
516 286 );
517 287 }
518 288 }
519 289 } elseif ( 'constrained' === $layout_type ) {
520 - $content_size = $layout_for_styles['contentSize'] ?? '';
521 - $wide_size = $layout_for_styles['wideSize'] ?? '';
522 - $justify_content = $layout_for_styles['justifyContent'] ?? 'center';
290 + $content_size = $layout['contentSize'] ?? '';
291 + $wide_size = $layout['wideSize'] ?? '';
292 + $justify_content = $layout['justifyContent'] ?? 'center';
523 293
524 294 $all_max_width_value = $content_size ? $content_size : $wide_size;
525 295 $wide_max_width_value = $wide_size ? $wide_size : $content_size;
526 296
@@ -530,25 +300,18 @@
530 300
531 301 $margin_left = 'left' === $justify_content ? '0 !important' : 'auto !important';
532 302 $margin_right = 'right' === $justify_content ? '0 !important' : 'auto !important';
533 303
534 - $has_justify_content_override = null !== $viewport_overrides && $has_viewport_property_override( 'justifyContent' );
535 - $should_output_constrained_sizes = null === $viewport_overrides || $has_viewport_property_override( 'contentSize' ) || $has_viewport_property_override( 'wideSize' );
536 - if ( $should_output_constrained_sizes && ( $content_size || $wide_size ) ) {
537 - $content_size_declarations = array(
538 - 'max-width' => $all_max_width_value,
539 - );
540 -
541 - if ( null === $viewport_overrides || $has_justify_content_override ) {
542 - $content_size_declarations['margin-left'] = $margin_left;
543 - $content_size_declarations['margin-right'] = $margin_right;
544 - }
545 -
304 + if ( $content_size || $wide_size ) {
546 305 array_push(
547 306 $layout_styles,
548 307 array(
549 308 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
550 - 'declarations' => $content_size_declarations,
309 + 'declarations' => array(
310 + 'max-width' => $all_max_width_value,
311 + 'margin-left' => $margin_left,
312 + 'margin-right' => $margin_right,
313 + ),
551 314 ),
552 315 array(
553 316 'selector' => "$selector > .alignwide",
554 317 'declarations' => array( 'max-width' => $wide_max_width_value ),
@@ -559,9 +322,9 @@
559 322 )
560 323 );
561 324 }
562 325
563 - if ( null === $viewport_overrides && isset( $block_spacing ) ) {
326 + if ( isset( $block_spacing ) ) {
564 327 $block_spacing_values = gutenberg_style_engine_get_styles(
565 328 array(
566 329 'spacing' => $block_spacing,
567 330 )
@@ -594,33 +357,23 @@
594 357 );
595 358 }
596 359 }
597 360
598 - if ( $has_justify_content_override && ! $should_output_constrained_sizes ) {
361 + if ( 'left' === $justify_content ) {
599 362 $layout_styles[] = array(
600 363 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
601 - 'declarations' => array(
602 - 'margin-left' => $margin_left,
603 - 'margin-right' => $margin_right,
604 - ),
364 + 'declarations' => array( 'margin-left' => '0 !important' ),
605 365 );
606 - } elseif ( null === $viewport_overrides ) {
607 - if ( 'left' === $justify_content ) {
608 - $layout_styles[] = array(
609 - 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
610 - 'declarations' => array( 'margin-left' => '0 !important' ),
611 - );
612 - }
366 + }
613 367
614 - if ( 'right' === $justify_content ) {
615 - $layout_styles[] = array(
616 - 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
617 - 'declarations' => array( 'margin-right' => '0 !important' ),
618 - );
619 - }
368 + if ( 'right' === $justify_content ) {
369 + $layout_styles[] = array(
370 + 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
371 + 'declarations' => array( 'margin-right' => '0 !important' ),
372 + );
620 373 }
621 374
622 - if ( $has_block_gap_support && $should_output_block_gap ) {
375 + if ( $has_block_gap_support ) {
623 376 if ( is_array( $gap_value ) ) {
624 377 $gap_value = $gap_value['top'] ?? null;
625 378 }
626 379 if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
@@ -650,9 +403,9 @@
650 403 );
651 404 }
652 405 }
653 406 } elseif ( 'flex' === $layout_type ) {
654 - $layout_orientation = $layout_for_styles['orientation'] ?? 'horizontal';
407 + $layout_orientation = $layout['orientation'] ?? 'horizontal';
655 408
656 409 $justify_content_options = array(
657 410 'left' => 'flex-start',
658 411 'right' => 'flex-end',
@@ -672,14 +425,9 @@
672 425 $justify_content_options += array( 'stretch' => 'stretch' );
673 426 $vertical_alignment_options += array( 'space-between' => 'space-between' );
674 427 }
675 428
676 - $should_output_flex_wrap = null === $viewport_overrides || $has_viewport_property_override( 'flexWrap' );
677 - $should_output_flex_orientation = null === $viewport_overrides || $has_viewport_property_override( 'orientation' );
678 - $should_output_flex_justification = null === $viewport_overrides || $has_viewport_property_override( 'justifyContent' ) || $has_viewport_property_override( 'orientation' );
679 - $should_output_flex_alignment = null === $viewport_overrides || $has_viewport_property_override( 'verticalAlignment' ) || $has_viewport_property_override( 'orientation' );
680 -
681 - if ( $should_output_flex_wrap && ! empty( $layout_for_styles['flexWrap'] ) && 'nowrap' === $layout_for_styles['flexWrap'] ) {
429 + if ( ! empty( $layout['flexWrap'] ) && 'nowrap' === $layout['flexWrap'] ) {
682 430 $layout_styles[] = array(
683 431 'selector' => $selector,
684 432 'declarations' => array( 'flex-wrap' => 'nowrap' ),
685 433 );
@@ -684,9 +432,9 @@
684 432 'declarations' => array( 'flex-wrap' => 'nowrap' ),
685 433 );
686 434 }
687 435
688 - if ( $has_block_gap_support && $should_output_block_gap && isset( $gap_value ) ) {
436 + if ( $has_block_gap_support && isset( $gap_value ) ) {
689 437 $combined_gap_value = '';
690 438 $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
691 439
692 440 foreach ( $gap_sides as $gap_side ) {
@@ -722,43 +470,41 @@
722 470 * Add this style only if is not empty for backwards compatibility,
723 471 * since we intend to convert blocks that had flex layout implemented
724 472 * by custom css.
725 473 */
726 - if ( $should_output_flex_justification && ! empty( $layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) {
474 + if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
727 475 $layout_styles[] = array(
728 476 'selector' => $selector,
729 - 'declarations' => array( 'justify-content' => $justify_content_options[ $layout_for_styles['justifyContent'] ] ),
477 + 'declarations' => array( 'justify-content' => $justify_content_options[ $layout['justifyContent'] ] ),
730 478 );
731 479 }
732 480
733 - if ( $should_output_flex_alignment && ! empty( $layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) {
481 + if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) {
734 482 $layout_styles[] = array(
735 483 'selector' => $selector,
736 - 'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout_for_styles['verticalAlignment'] ] ),
484 + 'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ),
737 485 );
738 486 }
739 487 } else {
740 - if ( $should_output_flex_orientation ) {
488 + $layout_styles[] = array(
489 + 'selector' => $selector,
490 + 'declarations' => array( 'flex-direction' => 'column' ),
491 + );
492 + if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
741 493 $layout_styles[] = array(
742 494 'selector' => $selector,
743 - 'declarations' => array( 'flex-direction' => 'column' ),
495 + 'declarations' => array( 'align-items' => $justify_content_options[ $layout['justifyContent'] ] ),
744 496 );
745 - }
746 - if ( $should_output_flex_justification && ! empty( $layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) {
497 + } else {
747 498 $layout_styles[] = array(
748 499 'selector' => $selector,
749 - 'declarations' => array( 'align-items' => $justify_content_options[ $layout_for_styles['justifyContent'] ] ),
750 - );
751 - } elseif ( $should_output_flex_justification ) {
752 - $layout_styles[] = array(
753 - 'selector' => $selector,
754 500 'declarations' => array( 'align-items' => 'flex-start' ),
755 501 );
756 502 }
757 - if ( $should_output_flex_alignment && ! empty( $layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) {
503 + if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) {
758 504 $layout_styles[] = array(
759 505 'selector' => $selector,
760 - 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $layout_for_styles['verticalAlignment'] ] ),
506 + 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ),
761 507 );
762 508 }
763 509 }
764 510 } elseif ( 'grid' === $layout_type ) {
@@ -802,48 +548,47 @@
802 548 if ( '0' === $responsive_gap_value || 0 === $responsive_gap_value ) {
803 549 $responsive_gap_value = '0px';
804 550 }
805 551
806 - $should_output_grid_columns = null === $viewport_overrides || $has_viewport_property_override( 'minimumColumnWidth' ) || $has_viewport_property_override( 'columnCount' );
807 - $uses_gap_in_grid_columns = ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['minimumColumnWidth'] );
808 - if ( $has_block_gap_override && $uses_gap_in_grid_columns ) {
809 - $should_output_grid_columns = true;
810 - }
811 -
812 - $should_output_grid_rows = ( null === $viewport_overrides || $has_viewport_property_override( 'rowCount' ) ) && ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['rowCount'] );
813 - $grid_declarations = array();
814 -
815 - if ( $should_output_grid_columns && ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {
816 - $max_value = 'max(min(' . $layout_for_styles['minimumColumnWidth'] . ', 100%), (100% - (' . $responsive_gap_value . ' * (' . $layout_for_styles['columnCount'] . ' - 1))) /' . $layout_for_styles['columnCount'] . ')';
817 - $grid_declarations['grid-template-columns'] = 'repeat(auto-fill, minmax(' . $max_value . ', 1fr))';
818 - } elseif ( $should_output_grid_columns && ! empty( $layout_for_styles['columnCount'] ) ) {
819 - $grid_declarations['grid-template-columns'] = 'repeat(' . $layout_for_styles['columnCount'] . ', minmax(0, 1fr))';
820 - } elseif ( $should_output_grid_columns ) {
821 - $minimum_column_width = ! empty( $layout_for_styles['minimumColumnWidth'] ) ? $layout_for_styles['minimumColumnWidth'] : '12rem';
822 - $grid_declarations['grid-template-columns'] = 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))';
823 - }
824 -
825 - if ( ! empty( $grid_declarations ) ) {
826 - $base_has_container_type = empty( $base_layout['columnCount'] ) || ( ! empty( $base_layout['columnCount'] ) && ! empty( $base_layout['minimumColumnWidth'] ) );
827 - if ( empty( $layout_for_styles['columnCount'] ) || ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {
828 - if ( null === $viewport_overrides || ! $base_has_container_type ) {
829 - $grid_declarations['container-type'] = 'inline-size';
830 - }
552 + if ( ! empty( $layout['columnCount'] ) && ! empty( $layout['minimumColumnWidth'] ) ) {
553 + $max_value = 'max(min(' . $layout['minimumColumnWidth'] . ', 100%), (100% - (' . $responsive_gap_value . ' * (' . $layout['columnCount'] . ' - 1))) /' . $layout['columnCount'] . ')';
554 + $layout_styles[] = array(
555 + 'selector' => $selector,
556 + 'declarations' => array(
557 + 'grid-template-columns' => 'repeat(auto-fill, minmax(' . $max_value . ', 1fr))',
558 + 'container-type' => 'inline-size',
559 + ),
560 + );
561 + if ( ! empty( $layout['rowCount'] ) ) {
562 + $layout_styles[] = array(
563 + 'selector' => $selector,
564 + 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(1rem, auto))' ),
565 + );
831 566 }
567 + } elseif ( ! empty( $layout['columnCount'] ) ) {
832 568 $layout_styles[] = array(
833 569 'selector' => $selector,
834 - 'declarations' => $grid_declarations,
570 + 'declarations' => array( 'grid-template-columns' => 'repeat(' . $layout['columnCount'] . ', minmax(0, 1fr))' ),
835 571 );
836 - }
572 + if ( ! empty( $layout['rowCount'] ) ) {
573 + $layout_styles[] = array(
574 + 'selector' => $selector,
575 + 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(1rem, auto))' ),
576 + );
577 + }
578 + } else {
579 + $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem';
837 580
838 - if ( $should_output_grid_rows ) {
839 581 $layout_styles[] = array(
840 582 'selector' => $selector,
841 - 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout_for_styles['rowCount'] . ', minmax(1rem, auto))' ),
583 + 'declarations' => array(
584 + 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))',
585 + 'container-type' => 'inline-size',
586 + ),
842 587 );
843 588 }
844 589
845 - if ( $has_block_gap_support && $should_output_block_gap && null !== $gap_value && ! $should_skip_gap_serialization ) {
590 + if ( $has_block_gap_support && null !== $gap_value && ! $should_skip_gap_serialization ) {
846 591 $layout_styles[] = array(
847 592 'selector' => $selector,
848 593 'declarations' => array( 'gap' => $gap_value ),
849 594 );
@@ -850,14 +595,8 @@
850 595 }
851 596 }
852 597
853 598 if ( ! empty( $layout_styles ) ) {
854 - if ( ! empty( $rules_group ) ) {
855 - foreach ( $layout_styles as $index => $layout_style ) {
856 - $layout_styles[ $index ]['rules_group'] = $rules_group;
857 - }
858 - }
859 -
860 599 /*
861 600 * Add to the style engine store to enqueue and render layout styles.
862 601 * Return compiled layout styles to retain backwards compatibility.
863 602 * Since https://github.com/WordPress/gutenberg/pull/42452,
@@ -924,29 +663,12 @@
924 663 static $global_styles = null;
925 664
926 665 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
927 666 $block_supports_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
928 - $style_attr = gutenberg_resolve_style_state_aliases(
929 - $block['attrs']['style'] ?? array(),
930 - $block['blockName']
931 - );
932 667 // If there is any value in style -> layout, the block has a child layout.
933 - $child_layout = $style_attr['layout'] ?? null;
668 + $child_layout = $block['attrs']['style']['layout'] ?? null;
934 669
935 - // Collect responsive viewport child layout overrides so that a block with
936 - // only responsive child layout (no base child layout) is still processed.
937 - $viewport_child_layouts = array();
938 - foreach ( WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS as $breakpoint => $media_query ) {
939 - $viewport_child = gutenberg_get_layout_child_values( $style_attr[ $breakpoint ]['layout'] ?? null );
940 - if ( ! empty( $viewport_child ) ) {
941 - $viewport_child_layouts[ $breakpoint ] = array(
942 - 'media_query' => $media_query,
943 - 'child_layout' => $viewport_child,
944 - );
945 - }
946 - }
947 -
948 - if ( ! $block_supports_layout && ! $child_layout && empty( $viewport_child_layouts ) ) {
670 + if ( ! $block_supports_layout && ! $child_layout ) {
949 671 return $block_content;
950 672 }
951 673
952 674 $outer_class_names = array();
@@ -951,58 +673,134 @@
951 673
952 674 $outer_class_names = array();
953 675
954 676 // Child layout specific logic.
955 - if ( $child_layout || ! empty( $viewport_child_layouts ) ) {
956 - $base_child_layout = gutenberg_get_layout_child_values( $child_layout );
957 - $parent_layout = $block['parentLayout'] ?? array();
958 -
677 + if ( $child_layout ) {
959 678 /*
960 679 * Generates a unique class for child block layout styles.
961 680 *
962 681 * To ensure consistent class generation across different page renders,
963 682 * only properties that affect layout styling are used. These properties
964 - * come from `$block['attrs']['style']['layout']`, viewport overrides in
965 - * `$block['attrs']['style'][$breakpoint]['layout']`, and
966 - * `$block['parentLayout']`.
683 + * come from `$block['attrs']['style']['layout']` and `$block['parentLayout']`.
967 684 *
968 685 * As long as these properties coincide, the generated class will be the same.
969 686 */
970 - $container_content_hash_input = array(
971 - 'layout' => $base_child_layout,
972 - 'parentLayout' => array_intersect_key(
973 - $parent_layout,
974 - array_flip( array( 'minimumColumnWidth', 'columnCount' ) )
687 + $container_content_class = gutenberg_unique_id_from_values(
688 + array(
689 + 'layout' => array_intersect_key(
690 + $block['attrs']['style']['layout'] ?? array(),
691 + array_flip(
692 + array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' )
693 + )
694 + ),
695 + 'parentLayout' => array_intersect_key(
696 + $block['parentLayout'] ?? array(),
697 + array_flip(
698 + array( 'minimumColumnWidth', 'columnCount' )
699 + )
700 + ),
975 701 ),
702 + 'wp-container-content-'
976 703 );
977 - foreach ( $viewport_child_layouts as $breakpoint => $viewport_data ) {
978 - $container_content_hash_input[ $breakpoint ] = $viewport_data['child_layout'];
704 +
705 + $child_layout_declarations = array();
706 + $child_layout_styles = array();
707 +
708 + $self_stretch = $block['attrs']['style']['layout']['selfStretch'] ?? null;
709 +
710 + if ( 'fixed' === $self_stretch && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
711 + $child_layout_declarations['flex-basis'] = $block['attrs']['style']['layout']['flexSize'];
712 + $child_layout_declarations['box-sizing'] = 'border-box';
713 + } elseif ( 'fill' === $self_stretch ) {
714 + $child_layout_declarations['flex-grow'] = '1';
979 715 }
980 - $container_content_class = gutenberg_unique_id_from_values(
981 - $container_content_hash_input,
982 - 'wp-container-content-'
716 +
717 + $column_start = $block['attrs']['style']['layout']['columnStart'] ?? null;
718 + $column_span = $block['attrs']['style']['layout']['columnSpan'] ?? null;
719 + if ( $column_start && $column_span ) {
720 + $child_layout_declarations['grid-column'] = "$column_start / span $column_span";
721 + } elseif ( $column_start ) {
722 + $child_layout_declarations['grid-column'] = "$column_start";
723 + } elseif ( $column_span ) {
724 + $child_layout_declarations['grid-column'] = "span $column_span";
725 + }
726 +
727 + $row_start = $block['attrs']['style']['layout']['rowStart'] ?? null;
728 + $row_span = $block['attrs']['style']['layout']['rowSpan'] ?? null;
729 + if ( $row_start && $row_span ) {
730 + $child_layout_declarations['grid-row'] = "$row_start / span $row_span";
731 + } elseif ( $row_start ) {
732 + $child_layout_declarations['grid-row'] = "$row_start";
733 + } elseif ( $row_span ) {
734 + $child_layout_declarations['grid-row'] = "span $row_span";
735 + }
736 +
737 + $child_layout_styles[] = array(
738 + 'selector' => ".$container_content_class",
739 + 'declarations' => $child_layout_declarations,
983 740 );
984 741
985 - $child_layout_styles = gutenberg_get_child_layout_style_rules(
986 - ".$container_content_class",
987 - $base_child_layout,
988 - $parent_layout
989 - );
742 + $minimum_column_width = $block['parentLayout']['minimumColumnWidth'] ?? null;
743 + $column_count = $block['parentLayout']['columnCount'] ?? null;
990 744
991 - // Emit responsive child layout CSS using the same container-content class
992 - // so that base and responsive child layout share the exact same selector.
993 - foreach ( $viewport_child_layouts as $viewport_data ) {
994 - $viewport_child_styles = gutenberg_get_child_layout_style_rules(
995 - ".$container_content_class",
996 - $base_child_layout,
997 - $parent_layout,
998 - $viewport_data['child_layout']
999 - );
1000 - foreach ( $viewport_child_styles as $index => $rule ) {
1001 - $viewport_child_styles[ $index ]['rules_group'] = $viewport_data['media_query'];
745 + /*
746 + * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set,
747 + * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed
748 + * once the grid has less columns than the start.
749 + * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set.
750 + * In that case, if columnCount doesn't exist, we can assume that the grid is responsive.
751 + */
752 + if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) {
753 + $column_span_number = floatval( $column_span );
754 + $column_start_number = floatval( $column_start );
755 + $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem';
756 + $parent_column_value = floatval( $parent_column_width );
757 + $parent_column_unit = explode( $parent_column_value, $parent_column_width );
758 +
759 + $num_cols_to_break_at = 2;
760 + if ( $column_span_number && $column_start_number ) {
761 + $num_cols_to_break_at = $column_start_number + $column_span_number - 1;
762 + } elseif ( $column_span_number ) {
763 + $num_cols_to_break_at = $column_span_number;
764 + } else {
765 + $num_cols_to_break_at = $column_start_number;
1002 766 }
1003 767
1004 - $child_layout_styles = array_merge( $child_layout_styles, $viewport_child_styles );
768 + /*
769 + * If there is no unit, the width has somehow been mangled so we reset both unit and value
770 + * to defaults.
771 + * Additionally, the unit should be one of px, rem or em, so that also needs to be checked.
772 + */
773 + if ( count( $parent_column_unit ) <= 1 ) {
774 + $parent_column_unit = 'rem';
775 + $parent_column_value = 12;
776 + } else {
777 + $parent_column_unit = $parent_column_unit[1];
778 +
779 + if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) {
780 + $parent_column_unit = 'rem';
781 + }
782 + }
783 +
784 + /*
785 + * A default gap value is used for this computation because custom gap values may not be
786 + * viable to use in the computation of the container query value.
787 + */
788 + $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
789 + $container_query_value = $num_cols_to_break_at * $parent_column_value + ( $num_cols_to_break_at - 1 ) * $default_gap_value;
790 + $minimum_container_query_value = $parent_column_value * 2 + $default_gap_value - 1;
791 + $container_query_value = max( $container_query_value, $minimum_container_query_value ) . $parent_column_unit;
792 + // If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
793 + $grid_column_value = $column_span && $column_span > 1 ? '1/-1' : 'auto';
794 +
795 + $child_layout_styles[] = array(
796 + 'rules_group' => "@container (max-width: $container_query_value )",
797 + 'selector' => ".$container_content_class",
798 + 'declarations' => array(
799 + 'grid-column' => $grid_column_value,
800 + 'grid-row' => 'auto',
801 + ),
802 + );
1005 803 }
1006 804
1007 805 /*
1008 806 * Add to the style engine store to enqueue and render layout styles.
@@ -1100,10 +898,23 @@
1100 898 * Attribute-based Layout classnames are output in all cases.
1101 899 */
1102 900 if ( ! current_theme_supports( 'disable-layout-styles' ) ) {
1103 901
1104 - $gap_value = gutenberg_sanitize_block_gap_value( $block['attrs']['style']['spacing']['blockGap'] ?? null );
902 + $gap_value = $block['attrs']['style']['spacing']['blockGap'] ?? null;
1105 903
904 + /*
905 + * Skip if gap value contains unsupported characters.
906 + * Regex for CSS value borrowed from `safecss_filter_attr`, and used here
907 + * to only match against the value, not the CSS attribute.
908 + */
909 + if ( is_array( $gap_value ) ) {
910 + foreach ( $gap_value as $key => $value ) {
911 + $gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
912 + }
913 + } else {
914 + $gap_value = $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
915 + }
916 +
1106 917 $fallback_gap_value = $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? '0.5em';
1107 918 $block_spacing = $block['attrs']['style']['spacing'] ?? null;
1108 919
1109 920 /*
@@ -1147,41 +958,17 @@
1147 958 * even for different blocks with the same layout definition. We need this to
1148 959 * make the CSS class names stable across paginations for features like the
1149 960 * enhanced pagination of the Query block.
1150 961 */
1151 - $container_class_hash_input = array(
1152 - $used_layout,
1153 - $has_block_gap_support,
1154 - $gap_value,
1155 - $should_skip_gap_serialization,
1156 - $fallback_gap_value,
1157 - $block_spacing,
1158 - );
1159 -
1160 - foreach ( array_keys( WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
1161 - $viewport_style = $style_attr[ $breakpoint ] ?? null;
1162 - if ( ! is_array( $viewport_style ) ) {
1163 - continue;
1164 - }
1165 -
1166 - $viewport_container_layout = gutenberg_get_layout_container_values( $viewport_style['layout'] ?? null );
1167 - if ( ! empty( $viewport_container_layout ) ) {
1168 - $container_class_hash_input[] = array(
1169 - 'breakpoint' => $breakpoint,
1170 - 'layout' => $viewport_container_layout,
1171 - );
1172 - }
1173 -
1174 - if ( isset( $viewport_style['spacing']['blockGap'] ) ) {
1175 - $container_class_hash_input[] = array(
1176 - 'breakpoint' => $breakpoint,
1177 - 'blockGap' => gutenberg_sanitize_block_gap_value( $viewport_style['spacing']['blockGap'] ),
1178 - );
1179 - }
1180 - }
1181 -
1182 962 $container_class = gutenberg_unique_id_from_values(
1183 - $container_class_hash_input,
963 + array(
964 + $used_layout,
965 + $has_block_gap_support,
966 + $gap_value,
967 + $should_skip_gap_serialization,
968 + $fallback_gap_value,
969 + $block_spacing,
970 + ),
1184 971 'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-'
1185 972 );
1186 973
1187 974 $style = gutenberg_get_layout_style(
@@ -1197,53 +984,8 @@
1197 984 // Only add container class and enqueue block support styles if unique styles were generated.
1198 985 if ( ! empty( $style ) ) {
1199 986 $class_names[] = $container_class;
1200 987 }
1201 -
1202 - /*
1203 - * Emit responsive container layout styles using the same $container_class
1204 - * selector as the base layout so they target the inner block wrapper.
1205 - */
1206 - foreach ( WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS as $breakpoint => $media_query ) {
1207 - $viewport_style = $style_attr[ $breakpoint ] ?? null;
1208 - if ( ! is_array( $viewport_style ) ) {
1209 - continue;
1210 - }
1211 -
1212 - $viewport_container_layout = gutenberg_get_layout_container_values( $viewport_style['layout'] ?? null );
1213 - $has_viewport_layout = ! empty( $viewport_container_layout );
1214 - $has_viewport_block_gap = isset( $viewport_style['spacing']['blockGap'] );
1215 -
1216 - if ( ! $has_viewport_layout && ! $has_viewport_block_gap ) {
1217 - continue;
1218 - }
1219 -
1220 - $viewport_gap_value = $has_viewport_block_gap
1221 - ? gutenberg_sanitize_block_gap_value( $viewport_style['spacing']['blockGap'] )
1222 - : $gap_value;
1223 - $viewport_block_spacing = is_array( $viewport_style['spacing'] ?? null )
1224 - ? array_replace( is_array( $block_spacing ) ? $block_spacing : array(), $viewport_style['spacing'] )
1225 - : $block_spacing;
1226 -
1227 - $viewport_styles = gutenberg_get_layout_style(
1228 - ".$container_class",
1229 - $used_layout,
1230 - $has_block_gap_support,
1231 - $viewport_gap_value,
1232 - $should_skip_gap_serialization,
1233 - $fallback_gap_value,
1234 - $viewport_block_spacing,
1235 - array(
1236 - 'rules_group' => $media_query,
1237 - 'viewport_overrides' => $viewport_container_layout,
1238 - 'has_block_gap_override' => $has_viewport_block_gap,
1239 - )
1240 - );
1241 -
1242 - if ( ! empty( $viewport_styles ) && ! in_array( $container_class, $class_names, true ) ) {
1243 - $class_names[] = $container_class;
1244 - }
1245 - }
1246 988 }
1247 989
1248 990 // Add combined layout and block classname for global styles to hook onto.
1249 991 $split_block_name = explode( '/', $block['blockName'] );
@@ -1307,31 +1049,12 @@
1307 1049 $inner_block_wrapper_classes = null;
1308 1050 $first_chunk = $block['innerContent'][0] ?? null;
1309 1051 if ( is_string( $first_chunk ) && count( $block['innerContent'] ) > 1 ) {
1310 1052 $first_chunk_processor = new WP_HTML_Tag_Processor( $first_chunk );
1311 - /*
1312 - * Use a stack to track open elements as tags are visited. Void elements
1313 - * (those without a matching closing tag) are excluded so they don't
1314 - * accumulate on the stack. At the end of the chunk, every element still
1315 - * on the stack is unclosed — meaning its closing tag lives in a later
1316 - * innerContent entry alongside the inner blocks, which makes it the
1317 - * inner-block container. Elements that open and close within this chunk
1318 - * are siblings that precede the inner blocks and should be ignored.
1319 - * The last unclosed element with a class attribute is the best candidate
1320 - * for the inner-block wrapper.
1321 - */
1322 - $tag_stack = array();
1323 - while ( $first_chunk_processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
1324 - if ( $first_chunk_processor->is_tag_closer() ) {
1325 - array_pop( $tag_stack );
1326 - } elseif ( ! WP_HTML_Processor::is_void( $first_chunk_processor->get_tag() ) ) {
1327 - $tag_stack[] = $first_chunk_processor->get_attribute( 'class' );
1328 - }
1329 - }
1330 - foreach ( array_reverse( $tag_stack ) as $class_attribute ) {
1053 + while ( $first_chunk_processor->next_tag() ) {
1054 + $class_attribute = $first_chunk_processor->get_attribute( 'class' );
1331 1055 if ( is_string( $class_attribute ) && ! empty( $class_attribute ) ) {
1332 1056 $inner_block_wrapper_classes = $class_attribute;
1333 - break;
1334 1057 }
1335 1058 }
1336 1059 }
1337 1060