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 } ) {
+ { ( ! window.__experimentalEnableGridInteractivity || + ! isManualPlacement ) && ( + + { __( 'Columns' ) } + + ) } { window.__experimentalEnableGridInteractivity && - allowSizingOnChildren ? ( + allowSizingOnChildren && + isManualPlacement ? ( { onChange( { ...layout, - rowCount: value, + rowCount: parseInt( value, 10 ), } ); } } value={ rowCount } - min={ 1 } + min={ 0 } label={ __( 'Rows' ) } /> ) : ( @@ -297,7 +338,7 @@ function GridLayoutColumnsAndRowsControl( { columnCount: value, } ) } - min={ 1 } + min={ 0 } max={ 16 } withInputField={ false } label={ __( 'Columns' ) } @@ -313,7 +354,8 @@ function GridLayoutColumnsAndRowsControl( { // Enables switching between grid types function GridLayoutTypeControl( { layout, onChange } ) { - const { columnCount, minimumColumnWidth } = layout; + const { columnCount, rowCount, minimumColumnWidth, isManualPlacement } = + layout; /** * When switching, temporarily save any custom values set on the @@ -322,41 +364,62 @@ function GridLayoutTypeControl( { layout, onChange } ) { const [ tempColumnCount, setTempColumnCount ] = useState( columnCount || 3 ); + const [ tempRowCount, setTempRowCount ] = useState( rowCount ); const [ tempMinimumColumnWidth, setTempMinimumColumnWidth ] = useState( minimumColumnWidth || '12rem' ); - const isManual = !! columnCount ? 'manual' : 'auto'; + const gridPlacement = + isManualPlacement || + ( !! columnCount && ! window.__experimentalEnableGridInteractivity ) + ? 'manual' + : 'auto'; const onChangeType = ( value ) => { if ( value === 'manual' ) { setTempMinimumColumnWidth( minimumColumnWidth || '12rem' ); } else { setTempColumnCount( columnCount || 3 ); + setTempRowCount( rowCount ); } onChange( { ...layout, columnCount: value === 'manual' ? tempColumnCount : null, + rowCount: + value === 'manual' && + window.__experimentalEnableGridInteractivity + ? tempRowCount + : undefined, + isManualPlacement: + value === 'manual' && + window.__experimentalEnableGridInteractivity + ? true + : undefined, minimumColumnWidth: value === 'auto' ? tempMinimumColumnWidth : null, } ); }; + const helpText = + gridPlacement === 'manual' + ? __( + 'Grid items can be manually placed in any position on the grid.' + ) + : __( + 'Grid items are placed automatically depending on their order.' + ); + return (