diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 43f0e9beffba2..777417740b4b9 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -43,6 +43,16 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $style = ''; if ( 'default' === $layout_type ) { + if ( $has_block_gap_support ) { + if ( is_array( $gap_value ) ) { + $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null; + } + if ( $gap_value && ! $should_skip_gap_serialization ) { + $style .= "$selector > * { margin-block-start: 0; margin-block-end: 0; }"; + $style .= "$selector > * + * { margin-block-start: $gap_value; margin-block-end: 0; }"; + } + } + } elseif ( 'column' === $layout_type ) { $content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : ''; $wide_size = isset( $layout['wideSize'] ) ? $layout['wideSize'] : ''; @@ -181,20 +191,19 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false; $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; + $class_names = array(); + $layout_definitions = _wp_array_get( $global_layout_settings, array( 'definitions' ), array() ); + $block_classname = wp_get_block_default_classname( $block['blockName'] ); + $container_class = wp_unique_id( 'wp-container-' ); + $layout_classname = ''; + $use_global_padding = gutenberg_get_global_settings( array( 'useRootPaddingAwareAlignments' ) ) && ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ); + if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) { if ( ! $global_layout_settings ) { return $block_content; } - $used_layout = $global_layout_settings; } - $class_names = array(); - $layout_definitions = _wp_array_get( $global_layout_settings, array( 'definitions' ), array() ); - $block_classname = wp_get_block_default_classname( $block['blockName'] ); - $container_class = wp_unique_id( 'wp-container-' ); - $layout_classname = ''; - $use_global_padding = gutenberg_get_global_settings( array( 'useRootPaddingAwareAlignments' ) ) && ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ); - if ( $use_global_padding ) { $class_names[] = 'has-global-padding'; } diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index b7c24c3f9fe06..2313841c5394c 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -87,7 +87,7 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { * @var string[] */ const ELEMENTS = array( - 'link' => 'a:where(:not(.wp-element-button))', // The where is needed to lower the specificity. + 'link' => 'a:not(.wp-element-button)', 'heading' => 'h1, h2, h3, h4, h5, h6', 'h1' => 'h1', 'h2' => 'h2', @@ -736,16 +736,31 @@ function( $pseudo_selector ) use ( $selector ) { } } - /* - * Reset default browser margin on the root body element. - * This is set on the root selector **before** generating the ruleset - * from the `theme.json`. This is to ensure that if the `theme.json` declares - * `margin` in its `spacing` declaration for the `body` element then these - * user-generated values take precedence in the CSS cascade. - * @link https://github.com/WordPress/gutenberg/issues/36147. - */ if ( static::ROOT_BLOCK_SELECTOR === $selector ) { - $block_rules .= 'body { margin: 0; }'; + /* + * Reset default browser margin on the root body element. + * This is set on the root selector **before** generating the ruleset + * from the `theme.json`. This is to ensure that if the `theme.json` declares + * `margin` in its `spacing` declaration for the `body` element then these + * user-generated values take precedence in the CSS cascade. + * @link https://github.com/WordPress/gutenberg/issues/36147. + */ + $block_rules .= 'body { margin: 0;'; + + /* + * If there are content and wide widths in theme.json, output them + * as custom properties on the body element so all blocks can use them. + */ + if ( isset( $settings['layout']['contentSize'] ) && $settings['layout']['contentSize'] || isset( $settings['layout']['wideSize'] ) && $settings['layout']['wideSize'] ) { + $content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize']; + $content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial'; + $wide_size = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize']; + $wide_size = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial'; + $block_rules .= '--wp--style--global--content-size: ' . $content_size . ';'; + $block_rules .= '--wp--style--global--wide-size: ' . $wide_size . ';'; + } + + $block_rules .= '}'; } // 2. Generate and append the rules that use the general selector. @@ -1269,7 +1284,7 @@ protected function get_layout_styles( $block_metadata ) { $has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support. $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); $layout_definitions = _wp_array_get( $this->theme_json, array( 'settings', 'layout', 'definitions' ), array() ); - $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, and child combinator selectors. + $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. // Gap styles will only be output if the theme has block gap support, or supports a fallback gap. // Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value. @@ -1329,25 +1344,14 @@ protected function get_layout_styles( $block_metadata ) { } } - if ( ! $has_block_gap_support ) { - // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. - $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s%3$s)' : ':where(%1$s.%2$s%3$s)'; - $layout_selector = sprintf( - $format, - $selector, - $class_name, - $spacing_rule['selector'] - ); - } else { - $format = static::ROOT_BLOCK_SELECTOR === $selector ? '%s .%s%s' : '%s.%s%s'; - $layout_selector = sprintf( - $format, - $selector, - $class_name, - $spacing_rule['selector'] - ); - } - $block_rules .= static::to_ruleset( $layout_selector, $declarations ); + $format = static::ROOT_BLOCK_SELECTOR === $selector ? '%s .%s%s' : '%s.%s%s'; + $layout_selector = sprintf( + $format, + $selector, + $class_name, + $spacing_rule['selector'] + ); + $block_rules .= static::to_ruleset( $layout_selector, $declarations ); } } } diff --git a/lib/compat/wordpress-6.1/theme.json b/lib/compat/wordpress-6.1/theme.json index d817f33653612..c4d4660438ed5 100644 --- a/lib/compat/wordpress-6.1/theme.json +++ b/lib/compat/wordpress-6.1/theme.json @@ -234,6 +234,66 @@ } ] }, + "column": { + "name": "column", + "slug": "column", + "className": "is-layout-column", + "baseStyles": [ + { + "selector": " > .alignleft", + "rules": { + "float": "left", + "margin-inline-start": "0", + "margin-inline-end": "2em" + } + }, + { + "selector": " > .alignright", + "rules": { + "float": "right", + "margin-inline-start": "2em", + "margin-inline-end": "0" + } + }, + { + "selector": " > .aligncenter", + "rules": { + "margin-left": "auto !important", + "margin-right": "auto !important" + } + }, + { + "selector": " > :where(:not(.alignleft):not(.alignright):not(.alignfull))", + "rules": { + "max-width": "var(--wp--style--global--content-size)", + "margin-left": "auto !important", + "margin-right": "auto !important" + } + }, + { + "selector": " > .alignwide", + "rules": { + "max-width": "var(--wp--style--global--wide-size)" + } + } + ], + "spacingStyles": [ + { + "selector": " > *", + "rules": { + "margin-block-start": "0", + "margin-block-end": "0" + } + }, + { + "selector": " > * + *", + "rules": { + "margin-block-start": null, + "margin-block-end": "0" + } + } + ] + }, "flex": { "name": "flex", "slug": "flex", diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 69b5810b0f106..31daff7ba658d 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -110,11 +110,14 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { // Only show the inherit toggle if it's supported, // a default theme layout is set (e.g. one that provides `contentSize` and/or `wideSize` values), - // and that the default / flow layout type is in use, as this is the only one that supports inheritance. + // and either the default / flow or the column layout type is in use, as the toggle switches from one to the other. const showInheritToggle = !! ( allowInheriting && !! defaultThemeLayout && - ( ! layout?.type || layout?.type === 'default' || layout?.inherit ) + ( ! layout?.type || + layout?.type === 'default' || + layout?.type === 'column' || + layout?.inherit ) ); const usedLayout = layout || defaultBlockLayout || {}; @@ -141,12 +144,17 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { { showInheritToggle && ( <> setAttributes( { layout: { - inherit: ! inherit, + type: + layoutType?.name === 'column' + ? 'default' + : 'column', }, } ) } @@ -170,7 +178,7 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { /> ) } - { ! inherit && layoutType && ( + { layoutType && layoutType.name !== 'default' && ( +
+
+ { + nextWidth = + 0 > parseFloat( nextWidth ) + ? '0' + : nextWidth; + onChange( { + ...layout, + contentSize: nextWidth, + } ); + } } + units={ units } + /> + +
+
+ { + nextWidth = + 0 > parseFloat( nextWidth ) + ? '0' + : nextWidth; + onChange( { + ...layout, + wideSize: nextWidth, + } ); + } } + units={ units } + /> + +
+
+
+ +
+ +

+ { __( + 'Customize the width for all elements that are assigned to the center or wide columns.' + ) } +

+ + ); + }, + toolBarControls: function DefaultLayoutToolbarControls() { + return null; + }, + getLayoutStyle: function getLayoutStyle( { + selector, + layout = {}, + style, + blockName, + hasBlockGapSupport, + layoutDefinitions, + } ) { + const { contentSize, wideSize } = layout; + const blockGapStyleValue = getGapBoxControlValueFromStyle( + style?.spacing?.blockGap + ); + // If a block's block.json skips serialization for spacing or + // spacing.blockGap, don't apply the user-defined value to the styles. + const blockGapValue = + blockGapStyleValue?.top && + ! shouldSkipSerialization( blockName, 'spacing', 'blockGap' ) + ? blockGapStyleValue?.top + : ''; + + let output = + !! contentSize || !! wideSize + ? ` + ${ appendSelectors( + selector, + '> :where(:not(.alignleft):not(.alignright):not(.alignfull))' + ) } { + max-width: ${ contentSize ?? wideSize }; + margin-left: auto !important; + margin-right: auto !important; + } + ${ appendSelectors( selector, '> .alignwide' ) } { + max-width: ${ wideSize ?? contentSize }; + } + ${ appendSelectors( selector, '> .alignfull' ) } { + max-width: none; + } + ` + : ''; + + // If there is custom padding, add negative margins for alignfull blocks. + if ( style?.spacing?.padding ) { + // The style object might be storing a preset so we need to make sure we get a usable value. + const paddingValues = getCSSRules( style ); + paddingValues.forEach( ( rule ) => { + if ( rule.key === 'paddingRight' ) { + output += ` + ${ appendSelectors( selector, '> .alignfull' ) } { + margin-right: calc(${ rule.value } * -1); + } + `; + } else if ( rule.key === 'paddingLeft' ) { + output += ` + ${ appendSelectors( selector, '> .alignfull' ) } { + margin-left: calc(${ rule.value } * -1); + } + `; + } + } ); + } + + // Output blockGap styles based on rules contained in layout definitions in theme.json. + if ( hasBlockGapSupport && blockGapValue ) { + output += getBlockGapCSS( + selector, + layoutDefinitions, + 'column', + blockGapValue + ); + } + return output; + }, + getOrientation() { + return 'vertical'; + }, + getAlignments( layout ) { + const alignmentInfo = getAlignmentsInfo( layout ); + if ( layout.alignments !== undefined ) { + if ( ! layout.alignments.includes( 'none' ) ) { + layout.alignments.unshift( 'none' ); + } + return layout.alignments.map( ( alignment ) => ( { + name: alignment, + info: alignmentInfo[ alignment ], + } ) ); + } + const { contentSize, wideSize } = layout; + + const alignments = [ + { name: 'left' }, + { name: 'center' }, + { name: 'right' }, + ]; + + if ( contentSize ) { + alignments.unshift( { name: 'full' } ); + } + + if ( wideSize ) { + alignments.unshift( { name: 'wide', info: alignmentInfo.wide } ); + } + + alignments.unshift( { name: 'none', info: alignmentInfo.none } ); + + return alignments; + }, +}; + +/** + * Helper method to assign contextual info to clarify + * alignment settings. + * + * Besides checking if `contentSize` and `wideSize` have a + * value, we now show this information only if their values + * are not a `css var`. This needs to change when parsing + * css variables land. + * + * @see https://github.com/WordPress/gutenberg/pull/34710#issuecomment-918000752 + * + * @param {Object} layout The layout object. + * @return {Object} An object with contextual info per alignment. + */ +function getAlignmentsInfo( layout ) { + const { contentSize, wideSize } = layout; + const alignmentInfo = {}; + const sizeRegex = /^(?!0)\d+(px|em|rem|vw|vh|%)?$/i; + if ( sizeRegex.test( contentSize ) ) { + // translators: %s: container size (i.e. 600px etc) + alignmentInfo.none = sprintf( __( 'Max %s wide' ), contentSize ); + } + if ( sizeRegex.test( wideSize ) ) { + // translators: %s: container size (i.e. 600px etc) + alignmentInfo.wide = sprintf( __( 'Max %s wide' ), wideSize ); + } + return alignmentInfo; +} diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index f0e084d623b17..6f6ab4d51db43 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -1,122 +1,32 @@ /** * WordPress dependencies */ -import { - Button, - __experimentalUseCustomUnits as useCustomUnits, - __experimentalUnitControl as UnitControl, -} from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; -import { Icon, positionCenter, stretchWide } from '@wordpress/icons'; -import { getCSSRules } from '@wordpress/style-engine'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import useSetting from '../components/use-setting'; -import { appendSelectors, getBlockGapCSS } from './utils'; + +import { getBlockGapCSS } from './utils'; import { getGapBoxControlValueFromStyle } from '../hooks/gap'; import { shouldSkipSerialization } from '../hooks/utils'; export default { name: 'default', label: __( 'Flow' ), - inspectorControls: function DefaultLayoutInspectorControls( { - layout, - onChange, - } ) { - const { wideSize, contentSize } = layout; - const units = useCustomUnits( { - availableUnits: useSetting( 'spacing.units' ) || [ - '%', - 'px', - 'em', - 'rem', - 'vw', - ], - } ); - - return ( - <> -
-
- { - nextWidth = - 0 > parseFloat( nextWidth ) - ? '0' - : nextWidth; - onChange( { - ...layout, - contentSize: nextWidth, - } ); - } } - units={ units } - /> - -
-
- { - nextWidth = - 0 > parseFloat( nextWidth ) - ? '0' - : nextWidth; - onChange( { - ...layout, - wideSize: nextWidth, - } ); - } } - units={ units } - /> - -
-
-
- -
- -

- { __( - 'Customize the width for all elements that are assigned to the center or wide columns.' - ) } -

- - ); + inspectorControls: function DefaultLayoutInspectorControls() { + return null; }, toolBarControls: function DefaultLayoutToolbarControls() { return null; }, getLayoutStyle: function getLayoutStyle( { selector, - layout = {}, style, blockName, hasBlockGapSupport, layoutDefinitions, } ) { - const { contentSize, wideSize } = layout; const blockGapStyleValue = getGapBoxControlValueFromStyle( style?.spacing?.blockGap ); @@ -128,46 +38,7 @@ export default { ? blockGapStyleValue?.top : ''; - let output = - !! contentSize || !! wideSize - ? ` - ${ appendSelectors( - selector, - '> :where(:not(.alignleft):not(.alignright):not(.alignfull))' - ) } { - max-width: ${ contentSize ?? wideSize }; - margin-left: auto !important; - margin-right: auto !important; - } - ${ appendSelectors( selector, '> .alignwide' ) } { - max-width: ${ wideSize ?? contentSize }; - } - ${ appendSelectors( selector, '> .alignfull' ) } { - max-width: none; - } - ` - : ''; - - // If there is custom padding, add negative margins for alignfull blocks. - if ( style?.spacing?.padding ) { - // The style object might be storing a preset so we need to make sure we get a usable value. - const paddingValues = getCSSRules( style ); - paddingValues.forEach( ( rule ) => { - if ( rule.key === 'paddingRight' ) { - output += ` - ${ appendSelectors( selector, '> .alignfull' ) } { - margin-right: calc(${ rule.value } * -1); - } - `; - } else if ( rule.key === 'paddingLeft' ) { - output += ` - ${ appendSelectors( selector, '> .alignfull' ) } { - margin-left: calc(${ rule.value } * -1); - } - `; - } - } ); - } + let output; // Output blockGap styles based on rules contained in layout definitions in theme.json. if ( hasBlockGapSupport && blockGapValue ) { @@ -183,64 +54,7 @@ export default { getOrientation() { return 'vertical'; }, - getAlignments( layout ) { - const alignmentInfo = getAlignmentsInfo( layout ); - if ( layout.alignments !== undefined ) { - if ( ! layout.alignments.includes( 'none' ) ) { - layout.alignments.unshift( 'none' ); - } - return layout.alignments.map( ( alignment ) => ( { - name: alignment, - info: alignmentInfo[ alignment ], - } ) ); - } - const { contentSize, wideSize } = layout; - - const alignments = [ - { name: 'left' }, - { name: 'center' }, - { name: 'right' }, - ]; - - if ( contentSize ) { - alignments.unshift( { name: 'full' } ); - } - - if ( wideSize ) { - alignments.unshift( { name: 'wide', info: alignmentInfo.wide } ); - } - - alignments.unshift( { name: 'none', info: alignmentInfo.none } ); - - return alignments; + getAlignments() { + return []; }, }; - -/** - * Helper method to assign contextual info to clarify - * alignment settings. - * - * Besides checking if `contentSize` and `wideSize` have a - * value, we now show this information only if their values - * are not a `css var`. This needs to change when parsing - * css variables land. - * - * @see https://github.com/WordPress/gutenberg/pull/34710#issuecomment-918000752 - * - * @param {Object} layout The layout object. - * @return {Object} An object with contextual info per alignment. - */ -function getAlignmentsInfo( layout ) { - const { contentSize, wideSize } = layout; - const alignmentInfo = {}; - const sizeRegex = /^(?!0)\d+(px|em|rem|vw|vh|%)?$/i; - if ( sizeRegex.test( contentSize ) ) { - // translators: %s: container size (i.e. 600px etc) - alignmentInfo.none = sprintf( __( 'Max %s wide' ), contentSize ); - } - if ( sizeRegex.test( wideSize ) ) { - // translators: %s: container size (i.e. 600px etc) - alignmentInfo.wide = sprintf( __( 'Max %s wide' ), wideSize ); - } - return alignmentInfo; -} diff --git a/packages/block-editor/src/layouts/index.js b/packages/block-editor/src/layouts/index.js index 928876c1365f7..4e5a9f6acc4ef 100644 --- a/packages/block-editor/src/layouts/index.js +++ b/packages/block-editor/src/layouts/index.js @@ -3,8 +3,9 @@ */ import flex from './flex'; import flow from './flow'; +import column from './column'; -const layoutTypes = [ flow, flex ]; +const layoutTypes = [ flow, flex, column ]; /** * Retrieves a layout type by name. diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index 99cbbdf22b6d1..cef2e700a3fba 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -64,7 +64,11 @@ "fontSize": true } }, - "__experimentalLayout": true + "__experimentalLayout": { + "default": { + "type": "column" + } + } }, "editorStyle": "wp-block-group-editor", "style": "wp-block-group" diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index e45613bbba25d..d57f6d91277a2 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -48,7 +48,10 @@ function GroupEdit( { attributes, setAttributes, clientId } ) { ); const defaultLayout = useSetting( 'layout' ) || {}; const { tagName: TagName = 'div', templateLock, layout = {} } = attributes; - const usedLayout = !! layout && layout.inherit ? defaultLayout : layout; + const usedLayout = + ! layout?.type || layout?.type === 'column' + ? { ...defaultLayout, ...layout, type: 'column' } + : layout; const { type = 'default' } = usedLayout; const layoutSupportEnabled = themeSupportsLayout || type !== 'default';