From 0e1b1958fca7246ab55770e2d13015840c2614af Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:41:22 +1000 Subject: [PATCH] Editor: Migrate the Column block to the width block support Handle the Column block's width through the `dimensions.width` block support so it aligns with Global Styles and can be set from theme.json. Columns size themselves with `flex-basis` rather than `width` because they live in a flex container, so `WP_Theme_JSON` converts the computed `width` declaration and pins `flex-grow` to `0`. This runs at the block level, on block style variations, and on variation styles set within a responsive breakpoint. The block.json and theme.json changes arrive separately with the next Gutenberg package update, following the Button block migration. Developed in: https://github.com/WordPress/gutenberg/pull/76684 See #66055. --- src/wp-includes/class-wp-theme-json.php | 51 +++++ tests/phpunit/tests/theme/wpThemeJson.php | 246 ++++++++++++++++++++++ 2 files changed, 297 insertions(+) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index e600160539588..ebdb81c9195cc 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -3497,6 +3497,44 @@ private static function update_button_width_declarations( $feature_declarations, return $feature_declarations; } + /** + * Converts `width` declarations to `flex-basis` for column blocks. + * + * The column block sizes itself with `flex-basis` rather than `width` + * because it lives in a flex container. This post-processes the computed + * style declarations so the correct CSS property is output. + * + * @since 7.2.0 + * + * @param array $declarations An array of CSS declarations. + * @return array The updated declarations. + */ + private static function update_column_width_declarations( $declarations ) { + $has_width = false; + + foreach ( $declarations as &$declaration ) { + if ( 'width' === $declaration['name'] ) { + $declaration['name'] = 'flex-basis'; + $has_width = true; + } + } + unset( $declaration ); + + /* + * Columns without a width divide the remaining space between them via + * `flex-grow`. A column given a width should keep it instead, matching + * the behaviour of a width set on the block itself. + */ + if ( $has_width ) { + $declarations[] = array( + 'name' => 'flex-grow', + 'value' => '0', + ); + } + + return $declarations; + } + /** * An internal method to get the block nodes from a theme.json file. * @@ -3860,6 +3898,10 @@ public function get_styles_for_block( $block_metadata ) { // Compute declarations for remaining styles not covered by feature level selectors. $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); + if ( 'core/column' === ( $block_metadata['name'] ?? null ) ) { + $style_variation_declarations[ $style_variation['selector'] ] = self::update_column_width_declarations( $style_variation_declarations[ $style_variation['selector'] ] ); + } + // Process pseudo-selectors for this variation (e.g., :hover, :focus) if ( isset( $block_metadata['name'] ) ) { $block_name = $block_metadata['name']; @@ -3913,6 +3955,11 @@ public function get_styles_for_block( $block_metadata ) { // Process base properties for this breakpoint. $breakpoint_declarations = static::compute_style_properties( $breakpoint_node, $settings, null, $this->theme_json ); + + if ( 'core/column' === $block_name ) { + $breakpoint_declarations = self::update_column_width_declarations( $breakpoint_declarations ); + } + if ( ! empty( $breakpoint_declarations ) ) { $base_ruleset = static::to_ruleset( ':root :where(' . $style_variation['selector'] . ')', $breakpoint_declarations ); $variation_responsive_css .= $breakpoint_media . '{' . $base_ruleset . '}'; @@ -4081,6 +4128,10 @@ static function ( $pseudo_selector ) use ( $selector ) { } } + if ( 'core/column' === $block_name ) { + $declarations = self::update_column_width_declarations( $declarations ); + } + /* * If root styles has a background-image or a background (gradient) set, * set the min-height to '100%'. Minus `--wp-admin--admin-bar--height` for logged-in view. diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index c2cda7bb158d0..77691a981a919 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -6872,6 +6872,252 @@ public function data_update_button_width_declarations() { ); } + /** + * Tests that column block width declarations are output as `flex-basis`. + * + * @ticket 66055 + * + * @dataProvider data_update_column_width_declarations + * + * @param array $theme_json_args Theme JSON arguments including styles and optional settings. + * @param string $expected_output Expected CSS output. + */ + public function test_update_column_width_declarations( $theme_json_args, $expected_output ) { + $theme_json = new WP_Theme_JSON( + array_merge( + array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ), + $theme_json_args + ), + 'default' + ); + + $column_node = array( + 'name' => 'core/column', + 'path' => array( 'styles', 'blocks', 'core/column' ), + 'selector' => '.wp-block-column', + 'selectors' => array( + 'root' => '.wp-block-column', + ), + 'duotone' => null, + 'variations' => array(), + 'css' => '.wp-block-column', + ); + + $this->assertSame( $expected_output, $theme_json->get_styles_for_block( $column_node ) ); + } + + /** + * Data provider for column width declaration tests. + * + * @return array + */ + public function data_update_column_width_declarations() { + return array( + 'percentage width' => array( + array( + 'styles' => array( + 'blocks' => array( + 'core/column' => array( + 'dimensions' => array( + 'width' => '25%', + ), + ), + ), + ), + ), + 'expected_output' => ':root :where(.wp-block-column){flex-basis: 25%;flex-grow: 0;}', + ), + 'fixed width' => array( + array( + 'styles' => array( + 'blocks' => array( + 'core/column' => array( + 'dimensions' => array( + 'width' => '200px', + ), + ), + ), + ), + ), + 'expected_output' => ':root :where(.wp-block-column){flex-basis: 200px;flex-grow: 0;}', + ), + 'preset width' => array( + array( + 'settings' => array( + 'dimensions' => array( + 'dimensionSizes' => array( + array( + 'slug' => '50', + 'name' => '50%', + 'size' => '50%', + ), + ), + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/column' => array( + 'dimensions' => array( + 'width' => 'var:preset|dimension|50', + ), + ), + ), + ), + ), + 'expected_output' => ':root :where(.wp-block-column){flex-basis: var(--wp--preset--dimension--50);flex-grow: 0;}', + ), + 'width alongside other styles' => array( + array( + 'styles' => array( + 'blocks' => array( + 'core/column' => array( + 'color' => array( + 'text' => 'red', + ), + 'dimensions' => array( + 'width' => '25%', + ), + ), + ), + ), + ), + 'expected_output' => ':root :where(.wp-block-column){color: red;flex-basis: 25%;flex-grow: 0;}', + ), + 'no width leaves flex untouched' => array( + array( + 'styles' => array( + 'blocks' => array( + 'core/column' => array( + 'color' => array( + 'text' => 'red', + ), + ), + ), + ), + ), + 'expected_output' => ':root :where(.wp-block-column){color: red;}', + ), + ); + } + + /** + * Tests that a column width set within a responsive breakpoint is output as + * `flex-basis` within the matching media query. + * + * @ticket 66055 + */ + public function test_update_column_width_declarations_for_breakpoints() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/column' => array( + '@mobile' => array( + 'dimensions' => array( + 'width' => '100%', + ), + ), + ), + ), + ), + ), + 'default' + ); + + // Mirrors the responsive block node built by get_block_nodes(). + $column_breakpoint_node = array( + 'name' => 'core/column', + 'path' => array( 'styles', 'blocks', 'core/column', '@mobile' ), + 'media_query' => '@media (width <= 480px)', + 'selector' => '.wp-block-column', + 'selectors' => array( + 'root' => '.wp-block-column', + ), + 'variations' => array(), + 'css' => '.wp-block-column', + ); + + $this->assertSame( + '@media (width <= 480px){:root :where(.wp-block-column){flex-basis: 100%;flex-grow: 0;}}', + $theme_json->get_styles_for_block( $column_breakpoint_node ) + ); + } + + /** + * Tests that a column width set on a block style variation is output as + * `flex-basis`, including when set within a responsive breakpoint. + * + * @ticket 66055 + */ + public function test_update_column_width_declarations_for_style_variations() { + register_block_style( + 'core/column', + array( + 'name' => 'foo', + 'label' => 'Foo', + ) + ); + + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/column' => array( + 'variations' => array( + 'foo' => array( + 'dimensions' => array( + 'width' => '25%', + ), + '@mobile' => array( + 'dimensions' => array( + 'width' => '100%', + ), + ), + ), + ), + ), + ), + ), + ), + 'default' + ); + + $column_node = array( + 'name' => 'core/column', + 'path' => array( 'styles', 'blocks', 'core/column' ), + 'selector' => '.wp-block-column', + 'selectors' => array( + 'root' => '.wp-block-column', + ), + 'duotone' => null, + 'variations' => array( + array( + 'name' => 'foo', + 'path' => array( 'styles', 'blocks', 'core/column', 'variations', 'foo' ), + 'selector' => '.is-style-foo', + ), + ), + 'css' => '.wp-block-column', + ); + + $styles = $theme_json->get_styles_for_block( $column_node ); + + unregister_block_style( 'core/column', 'foo' ); + + $this->assertStringContainsString( + ':root :where(.is-style-foo){flex-basis: 25%;flex-grow: 0;}', + $styles, + 'The variation column width should be output as flex-basis.' + ); + $this->assertStringContainsString( + '@media (width <= 480px){:root :where(.is-style-foo){flex-basis: 100%;flex-grow: 0;}}', + $styles, + 'The responsive variation column width should be output as flex-basis.' + ); + } + /** * @ticket 57559 */