Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -812,14 +812,18 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support
$should_output_grid_rows = ( null === $viewport_overrides || $has_viewport_property_override( 'rowCount' ) ) && ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['rowCount'] );
$grid_declarations = array();

// When enabled, columns stretch to fill the available space using
// `auto-fit`; otherwise empty tracks are preserved with `auto-fill`.
$auto_placement = ! empty( $layout_for_styles['autoFit'] ) ? 'auto-fit' : 'auto-fill';

if ( $should_output_grid_columns && ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {
$max_value = 'max(min(' . $layout_for_styles['minimumColumnWidth'] . ', 100%), (100% - (' . $responsive_gap_value . ' * (' . $layout_for_styles['columnCount'] . ' - 1))) /' . $layout_for_styles['columnCount'] . ')';
$grid_declarations['grid-template-columns'] = 'repeat(auto-fill, minmax(' . $max_value . ', 1fr))';
$grid_declarations['grid-template-columns'] = 'repeat(' . $auto_placement . ', minmax(' . $max_value . ', 1fr))';
} elseif ( $should_output_grid_columns && ! empty( $layout_for_styles['columnCount'] ) ) {
$grid_declarations['grid-template-columns'] = 'repeat(' . $layout_for_styles['columnCount'] . ', minmax(0, 1fr))';
} elseif ( $should_output_grid_columns ) {
$minimum_column_width = ! empty( $layout_for_styles['minimumColumnWidth'] ) ? $layout_for_styles['minimumColumnWidth'] : '12rem';
$grid_declarations['grid-template-columns'] = 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))';
$grid_declarations['grid-template-columns'] = 'repeat(' . $auto_placement . ', minmax(min(' . $minimum_column_width . ', 100%), 1fr))';
}

if ( ! empty( $grid_declarations ) ) {
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Enhancements

- Grid: Add a "Fill available space" option to the grid layout that switches the auto-placement keyword from `auto-fill` to `auto-fit`, so columns stretch to fill the row instead of leaving empty tracks. ([#57186](https://github.com/WordPress/gutenberg/issues/57186))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should link to this PR, not the issue.

- List View: a block that supports `listView` is now excluded from the List View when it has no inner blocks and disallows insertion (`allowedBlocks` is `[]` or `false`), since there is nothing to show, rearrange, or add. ([#78932](https://github.com/WordPress/gutenberg/pull/78932))

## 15.21.1 (2026-06-16)
Expand Down
59 changes: 57 additions & 2 deletions packages/block-editor/src/layouts/grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Flex,
FlexItem,
RangeControl,
ToggleControl,
__experimentalNumberControl as NumberControl,
__experimentalToggleGroupControl as ToggleGroupControl,
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
Expand Down Expand Up @@ -81,6 +82,9 @@ export default {
const showMinWidthControl =
! layout?.isManualPlacement ||
window.__experimentalEnableGridInteractivity;
// Auto-fit/auto-fill only applies when grid items are placed
// automatically, so the control is irrelevant in manual placement mode.
const showFillControl = ! layout?.isManualPlacement;
const defaultColumnCount = layout.isManualPlacement ? 3 : undefined;
const hasLayoutValue = ( key, defaultValue ) =>
( layout?.[ key ] ?? defaultValue ) !==
Expand All @@ -92,6 +96,7 @@ export default {
hasLayoutValue( 'rowCount' );
const hasMinimumColumnWidthValue = () =>
hasLayoutValue( 'minimumColumnWidth' );
const hasFillValue = () => hasLayoutValue( 'autoFit', false );
const resetGridType = () =>
onChange(
cleanEmptyObject( {
Expand All @@ -116,6 +121,13 @@ export default {
minimumColumnWidth: resetLayout?.minimumColumnWidth,
} )
);
const resetFill = () =>
onChange(
cleanEmptyObject( {
...layout,
autoFit: resetLayout?.autoFit,
} )
);

return (
<>
Expand Down Expand Up @@ -162,6 +174,19 @@ export default {
/>
</ToolsPanelItem>
) }
{ showFillControl && (
<ToolsPanelItem
label={ __( 'Fill available space' ) }
hasValue={ hasFillValue }
onDeselect={ resetFill }
panelId={ clientId }
>
<GridLayoutFillControl
layout={ layout }
onChange={ onChange }
/>
</ToolsPanelItem>
) }
</>
);
},
Expand All @@ -188,8 +213,13 @@ export default {
minimumColumnWidth = null,
columnCount = null,
rowCount = null,
autoFit = false,
} = effectiveLayout;

// When enabled, columns stretch to fill the available space using
// `auto-fit`; otherwise empty tracks are preserved with `auto-fill`.
const autoPlacement = autoFit ? 'auto-fit' : 'auto-fill';

// Check that the grid layout attributes are of the correct type, so that we don't accidentally
// write code that stores a string attribute instead of a number.
if ( process.env.NODE_ENV === 'development' ) {
Expand All @@ -205,6 +235,9 @@ export default {
if ( rowCount && typeof rowCount !== 'number' ) {
throw new Error( 'rowCount must be a number' );
}
if ( autoFit && typeof autoFit !== 'boolean' ) {
throw new Error( 'autoFit must be a boolean' );
}
}

// Use the global blockGap value as fallback when available.
Expand Down Expand Up @@ -256,15 +289,15 @@ export default {
columnCount - 1
}) ) / ${ columnCount })`;
rules.push(
`grid-template-columns: repeat(auto-fill, minmax(${ maxValue }, 1fr))`
`grid-template-columns: repeat(${ autoPlacement }, minmax(${ maxValue }, 1fr))`
);
} else if ( shouldOutputGridColumns && columnCount ) {
rules.push(
`grid-template-columns: repeat(${ columnCount }, minmax(0, 1fr))`
);
} else if ( shouldOutputGridColumns ) {
rules.push(
`grid-template-columns: repeat(auto-fill, minmax(min(${
`grid-template-columns: repeat(${ autoPlacement }, minmax(min(${
minimumColumnWidth || '12rem'
}, 100%), 1fr))`
);
Expand Down Expand Up @@ -488,6 +521,28 @@ function GridLayoutColumnsAndRowsControl( {
);
}

// Enables stretching grid columns to fill the available space (auto-fit)
// instead of leaving empty tracks at the end of a row (auto-fill).
function GridLayoutFillControl( { layout, onChange } ) {
const { autoFit = false } = layout;

return (
<ToggleControl
label={ __( 'Fill available space' ) }
help={ __(
'Stretch columns to fill the available space, instead of leaving gaps when there are too few items to fill a row.'
) }
checked={ autoFit }
onChange={ ( value ) =>
onChange( {
...layout,
autoFit: value || undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to make sure that autoFit can explicitly be set to false, in case the default value is true and false is set as a viewport override (because we'll need a rule to be output in that case).

} )
}
/>
);
}

// Enables switching between grid types
function GridLayoutTypeControl( { layout, onChange } ) {
const { columnCount, rowCount, minimumColumnWidth, isManualPlacement } =
Expand Down
32 changes: 32 additions & 0 deletions packages/block-editor/src/layouts/test/grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,38 @@ describe( 'getLayoutStyle', () => {
layoutDefinitions: undefined,
} );

expect( result ).toBe( expected );
} );
it( 'should use `auto-fit` instead of `auto-fill` when autoFit is enabled', () => {
const expected = `.my-container { grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr)); container-type: inline-size; }`;

const result = grid.getLayoutStyle( {
selector: '.my-container',
layout: { autoFit: true },
style: {},
blockName: 'test-block',
hasBlockGapSupport: false,
layoutDefinitions: undefined,
} );

expect( result ).toBe( expected );
} );
it( 'should use `auto-fit` with max() function when autoFit is enabled and both minimumColumnWidth and columnCount are provided', () => {
const expected = `.my-container { grid-template-columns: repeat(auto-fit, minmax(max(min( 12rem, 100%), ( 100% - (1.2rem*2) ) / 3), 1fr)); container-type: inline-size; }`;

const result = grid.getLayoutStyle( {
selector: '.my-container',
layout: {
minimumColumnWidth: '12rem',
columnCount: 3,
autoFit: true,
},
style: {},
blockName: 'test-block',
hasBlockGapSupport: false,
layoutDefinitions: undefined,
} );

expect( result ).toBe( expected );
} );
} );
5 changes: 5 additions & 0 deletions schemas/json/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,11 @@
"columnCount": {
"description": "The column count value.",
"type": "number"
},
"autoFit": {
"description": "Whether grid columns stretch to fill the available space (auto-fit) instead of leaving empty tracks (auto-fill).",
"type": "boolean",
"default": false
}
}
},
Expand Down
Loading