diff --git a/backport-changelog/6.7/6910.md b/backport-changelog/6.7/6910.md
new file mode 100644
index 00000000000000..8e6be0dc8e7a5a
--- /dev/null
+++ b/backport-changelog/6.7/6910.md
@@ -0,0 +1,5 @@
+https://github.com/WordPress/wordpress-develop/pull/6910
+
+* https://github.com/WordPress/gutenberg/pull/59483
+* https://github.com/WordPress/gutenberg/pull/60652
+* https://github.com/WordPress/gutenberg/pull/62777
\ No newline at end of file
diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php
index dc5fe2e6a87dae..b5e2fe37faecdc 100644
--- a/lib/block-supports/layout.php
+++ b/lib/block-supports/layout.php
@@ -466,29 +466,8 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support
}
}
} elseif ( 'grid' === $layout_type ) {
- if ( ! empty( $layout['columnCount'] ) ) {
- $layout_styles[] = array(
- 'selector' => $selector,
- 'declarations' => array( 'grid-template-columns' => 'repeat(' . $layout['columnCount'] . ', minmax(0, 1fr))' ),
- );
- if ( ! empty( $layout['rowCount'] ) ) {
- $layout_styles[] = array(
- 'selector' => $selector,
- 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(0, 1fr))' ),
- );
- }
- } else {
- $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem';
-
- $layout_styles[] = array(
- 'selector' => $selector,
- 'declarations' => array(
- 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))',
- 'container-type' => 'inline-size',
- ),
- );
- }
-
+ // Deal with block gap first so it can be used for responsive computation.
+ $responsive_gap_value = '1.2rem';
if ( $has_block_gap_support && isset( $gap_value ) ) {
$combined_gap_value = '';
$gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
@@ -506,14 +485,53 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support
}
$combined_gap_value .= "$process_value ";
}
- $gap_value = trim( $combined_gap_value );
+ $gap_value = trim( $combined_gap_value );
+ $responsive_gap_value = $gap_value;
+ }
- if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
+ if ( ! empty( $layout['columnCount'] ) && ! empty( $layout['minimumColumnWidth'] ) ) {
+ $max_value = 'max(' . $layout['minimumColumnWidth'] . ', (100% - (' . $responsive_gap_value . ' * (' . $layout['columnCount'] . ' - 1))) /' . $layout['columnCount'] . ')';
+ $layout_styles[] = array(
+ 'selector' => $selector,
+ 'declarations' => array(
+ 'grid-template-columns' => 'repeat(auto-fill, minmax(' . $max_value . ', 1fr))',
+ 'container-type' => 'inline-size',
+ ),
+ );
+ if ( ! empty( $layout['rowCount'] ) ) {
$layout_styles[] = array(
'selector' => $selector,
- 'declarations' => array( 'gap' => $gap_value ),
+ 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(8px, auto))' ),
+ );
+ }
+ } elseif ( ! empty( $layout['columnCount'] ) ) {
+ $layout_styles[] = array(
+ 'selector' => $selector,
+ 'declarations' => array( 'grid-template-columns' => 'repeat(' . $layout['columnCount'] . ', minmax(0, 1fr))' ),
+ );
+ if ( ! empty( $layout['rowCount'] ) ) {
+ $layout_styles[] = array(
+ 'selector' => $selector,
+ 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(8px, auto))' ),
);
}
+ } else {
+ $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem';
+
+ $layout_styles[] = array(
+ 'selector' => $selector,
+ 'declarations' => array(
+ 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))',
+ 'container-type' => 'inline-size',
+ ),
+ );
+ }
+
+ if ( $has_block_gap_support && null !== $gap_value && ! $should_skip_gap_serialization ) {
+ $layout_styles[] = array(
+ 'selector' => $selector,
+ 'declarations' => array( 'gap' => $gap_value ),
+ );
}
}
@@ -653,17 +671,19 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
* A default gap value is used for this computation because custom gap values may not be
* viable to use in the computation of the container query value.
*/
- $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
- $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value;
- $container_query_value = $container_query_value . $parent_column_unit;
+ $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
+ $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value;
+ $minimum_container_query_value = $parent_column_value * 2 + $default_gap_value - 1;
+ $container_query_value = max( $container_query_value, $minimum_container_query_value ) . $parent_column_unit;
// If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
- $grid_column_value = $column_span ? '1/-1' : 'auto';
+ $grid_column_value = $column_span && $column_span > 1 ? '1/-1' : 'auto';
$child_layout_styles[] = array(
'rules_group' => "@container (max-width: $container_query_value )",
'selector' => ".$container_content_class",
'declarations' => array(
'grid-column' => $grid_column_value,
+ 'grid-row' => 'auto',
),
);
}
diff --git a/packages/block-editor/src/components/grid/use-grid-layout-sync.js b/packages/block-editor/src/components/grid/use-grid-layout-sync.js
index 6a3a05e52fcb9a..f626dd7eb96170 100644
--- a/packages/block-editor/src/components/grid/use-grid-layout-sync.js
+++ b/packages/block-editor/src/components/grid/use-grid-layout-sync.js
@@ -30,12 +30,11 @@ export function useGridLayoutSync( { clientId: gridClientId } ) {
useEffect( () => {
const updates = {};
- const { columnCount, rowCount = 2 } = gridLayout;
- const isManualGrid = !! columnCount;
+ const { columnCount, rowCount, isManualPlacement } = gridLayout;
+ const isManualGrid = !! isManualPlacement;
if ( isManualGrid ) {
const rects = [];
- let cellsTaken = 0;
// Respect the position of blocks that already have a columnStart and rowStart value.
for ( const clientId of blockOrder ) {
@@ -46,7 +45,6 @@ export function useGridLayoutSync( { clientId: gridClientId } ) {
columnSpan = 1,
rowSpan = 1,
} = attributes.style?.layout || {};
- cellsTaken += columnSpan * rowSpan;
if ( ! columnStart || ! rowStart ) {
continue;
}
@@ -60,29 +58,21 @@ export function useGridLayoutSync( { clientId: gridClientId } ) {
);
}
- // Ensure there's enough rows to fit all blocks.
- const minimumNeededRows = Math.ceil( cellsTaken / columnCount );
- if ( rowCount < minimumNeededRows ) {
- updates[ gridClientId ] = {
- layout: {
- ...gridLayout,
- rowCount: minimumNeededRows,
- },
- };
- }
-
// When in manual mode, ensure that every block has a columnStart and rowStart value.
for ( const clientId of blockOrder ) {
const attributes = getBlockAttributes( clientId );
- const { columnStart, rowStart, columnSpan, rowSpan } =
- attributes.style?.layout || {};
+ const {
+ columnStart,
+ rowStart,
+ columnSpan = 1,
+ rowSpan = 1,
+ } = attributes.style?.layout || {};
if ( columnStart && rowStart ) {
continue;
}
const [ newColumnStart, newRowStart ] = getFirstEmptyCell(
rects,
columnCount,
- minimumNeededRows,
columnSpan,
rowSpan
);
@@ -105,6 +95,17 @@ export function useGridLayoutSync( { clientId: gridClientId } ) {
},
};
}
+
+ // Ensure there's enough rows to fit all blocks.
+ const bottomMostRow = Math.max( ...rects.map( ( r ) => r.rowEnd ) );
+ if ( ! rowCount || rowCount < bottomMostRow ) {
+ updates[ gridClientId ] = {
+ layout: {
+ ...gridLayout,
+ rowCount: bottomMostRow,
+ },
+ };
+ }
} else {
// When in auto mode, remove all of the columnStart and rowStart values.
for ( const clientId of blockOrder ) {
@@ -121,6 +122,16 @@ export function useGridLayoutSync( { clientId: gridClientId } ) {
};
}
}
+
+ // Remove row styles in auto mode
+ if ( rowCount ) {
+ updates[ gridClientId ] = {
+ layout: {
+ ...gridLayout,
+ rowCount: undefined,
+ },
+ };
+ }
}
if ( Object.keys( updates ).length ) {
@@ -143,14 +154,8 @@ export function useGridLayoutSync( { clientId: gridClientId } ) {
] );
}
-function getFirstEmptyCell(
- rects,
- columnCount,
- rowCount,
- columnSpan = 1,
- rowSpan = 1
-) {
- for ( let row = 1; row <= rowCount; row++ ) {
+function getFirstEmptyCell( rects, columnCount, columnSpan = 1, rowSpan = 1 ) {
+ for ( let row = 1; ; row++ ) {
for ( let column = 1; column <= columnCount; column++ ) {
const rect = new GridRect( {
columnStart: column,
@@ -163,5 +168,4 @@ function getFirstEmptyCell(
}
}
}
- return [ 1, 1 ];
}
diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js
index f5104213729366..3b77c10b2d18a0 100644
--- a/packages/block-editor/src/hooks/layout-child.js
+++ b/packages/block-editor/src/hooks/layout-child.js
@@ -59,6 +59,19 @@ function useBlockPropsChildLayoutStyles( { style } ) {
grid-column: span ${ columnSpan };
}`;
}
+ if ( rowStart && rowSpan ) {
+ css += `${ selector } {
+ grid-row: ${ rowStart } / span ${ rowSpan };
+ }`;
+ } else if ( rowStart ) {
+ css += `${ selector } {
+ grid-row: ${ rowStart };
+ }`;
+ } else if ( rowSpan ) {
+ css += `${ selector } {
+ grid-row: span ${ rowSpan };
+ }`;
+ }
/**
* If minimumColumnWidth is set on the parent, or if no
* columnCount is set, the grid is responsive so a
@@ -103,28 +116,24 @@ function useBlockPropsChildLayoutStyles( { style } ) {
const containerQueryValue =
highestNumber * parentColumnValue +
( highestNumber - 1 ) * defaultGapValue;
+ // For blocks that only span one column, we want to remove any rowStart values as
+ // the container reduces in size, so that blocks are still arranged in markup order.
+ const minimumContainerQueryValue =
+ parentColumnValue * 2 + defaultGapValue - 1;
// If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
- const gridColumnValue = columnSpan ? '1/-1' : 'auto';
+ const gridColumnValue =
+ columnSpan && columnSpan > 1 ? '1/-1' : 'auto';
- css += `@container (max-width: ${ containerQueryValue }${ parentColumnUnit }) {
+ css += `@container (max-width: ${ Math.max(
+ containerQueryValue,
+ minimumContainerQueryValue
+ ) }${ parentColumnUnit }) {
${ selector } {
grid-column: ${ gridColumnValue };
+ grid-row: auto;
}
}`;
}
- if ( rowStart && rowSpan ) {
- css += `${ selector } {
- grid-row: ${ rowStart } / span ${ rowSpan };
- }`;
- } else if ( rowStart ) {
- css += `${ selector } {
- grid-row: ${ rowStart };
- }`;
- } else if ( rowSpan ) {
- css += `${ selector } {
- grid-row: span ${ rowSpan };
- }`;
- }
}
useStyleOverride( { css } );
diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js
index 744441b22608b1..23b8bb5f3909fb 100644
--- a/packages/block-editor/src/layouts/grid.js
+++ b/packages/block-editor/src/layouts/grid.js
@@ -71,19 +71,28 @@ export default {
layoutBlockSupport = {},
} ) {
const { allowSizingOnChildren = false } = layoutBlockSupport;
+
+ // In the experiment we want to also show column control in Auto mode, and
+ // the minimum width control in Manual mode.
+ const showColumnsControl =
+ window.__experimentalEnableGridInteractivity || layout?.columnCount;
+ const showMinWidthControl =
+ window.__experimentalEnableGridInteractivity ||
+ ! layout?.columnCount;
return (
<>
- { layout?.columnCount ? (
+ { showColumnsControl && (
- ) : (
+ ) }
+ { showMinWidthControl && (
0 ) {
+ const maxValue = `max(${ minimumColumnWidth }, ( 100% - (${
+ blockGapValue || '1.2rem'
+ }*${ columnCount - 1 }) ) / ${ columnCount })`;
+ rules.push(
+ `grid-template-columns: repeat(auto-fill, minmax(${ maxValue }, 1fr))`,
+ `container-type: inline-size`
+ );
+ if ( rowCount ) {
+ rules.push(
+ `grid-template-rows: repeat(${ rowCount }, minmax(8px, auto))`
+ );
+ }
+ } else if ( columnCount ) {
rules.push(
`grid-template-columns: repeat(${ columnCount }, minmax(0, 1fr))`
);
if ( rowCount ) {
rules.push(
- `grid-template-rows: repeat(${ rowCount }, minmax(0, 1fr))`
+ `grid-template-rows: repeat(${ rowCount }, minmax(8px, auto))`
);
}
- } else if ( minimumColumnWidth ) {
+ } else {
rules.push(
- `grid-template-columns: repeat(auto-fill, minmax(min(${ minimumColumnWidth }, 100%), 1fr))`,
+ `grid-template-columns: repeat(auto-fill, minmax(min(${
+ minimumColumnWidth || '12rem'
+ }, 100%), 1fr))`,
'container-type: inline-size'
);
}
@@ -172,8 +196,11 @@ export default {
// Enables setting minimum width of grid items.
function GridLayoutMinimumWidthControl( { layout, onChange } ) {
- const { minimumColumnWidth: value = '12rem' } = layout;
- const [ quantity, unit ] = parseQuantityAndUnitFromRawValue( value );
+ const { minimumColumnWidth, columnCount, isManualPlacement } = layout;
+ const defaultValue = isManualPlacement || columnCount ? null : '12rem';
+ const value = minimumColumnWidth || defaultValue;
+ const [ quantity, unit = 'rem' ] =
+ parseQuantityAndUnitFromRawValue( value );
const handleSliderChange = ( next ) => {
onChange( {
@@ -228,7 +255,7 @@ function GridLayoutMinimumWidthControl( { layout, onChange } ) {