]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/';
+
+ if (
+ 'core/group' !== $block['blockName'] ||
+ WP_Theme_JSON_Resolver::theme_has_support() ||
+ 1 === preg_match( $group_with_inner_container_regex, $block_content )
+ ) {
+ return $block_content;
+ }
+
+ $replace_regex = '/(^(\s|\S)*
]*wp-block-group[^>]*>)((.|\S|\s)*)(<\/div>(\s|\S)*$)/m';
+ $updated_content = preg_replace_callback(
+ $replace_regex,
+ function( $matches ) {
+ return $matches[1] . '
' . $matches[3] . '
' . $matches[5];
+ },
+ $block_content
+ );
+
+ return $updated_content;
+}
+
+add_filter( 'render_block', 'gutenberg_restore_group_inner_container', 10, 2 );
diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php
index d967e1681acf18..fa75edaaef0490 100644
--- a/lib/class-wp-theme-json.php
+++ b/lib/class-wp-theme-json.php
@@ -166,6 +166,7 @@ class WP_Theme_JSON {
'customTextTransforms' => null,
),
'custom' => null,
+ 'layout' => null,
),
);
diff --git a/lib/client-assets.php b/lib/client-assets.php
index ef74ce9e025126..efa5aa79814d97 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -688,6 +688,10 @@ function gutenberg_extend_block_editor_settings_with_default_editor_styles( $set
*/
function gutenberg_extend_block_editor_settings_with_fse_theme_flag( $settings ) {
$settings['isFSETheme'] = gutenberg_is_fse_theme();
+
+ // Enable the new layout options for themes with a theme.json file.
+ $settings['supportsLayout'] = WP_Theme_JSON_Resolver::theme_has_support();
+
return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_fse_theme_flag' );
diff --git a/lib/load.php b/lib/load.php
index 0ac067e9d8166f..86478a886327f6 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -120,3 +120,4 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/block-supports/typography.php';
require __DIR__ . '/block-supports/custom-classname.php';
require __DIR__ . '/block-supports/border.php';
+require __DIR__ . '/block-supports/layout.php';
diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss
index b38d37c505e48e..2b01b965b4d30f 100644
--- a/packages/base-styles/_mixins.scss
+++ b/packages/base-styles/_mixins.scss
@@ -694,21 +694,3 @@
}
/* stylelint-enable function-comma-space-after */
}
-
-/**
- * These are default block editor widths in case the theme doesn't provide them.
- */
-@mixin default-block-widths {
-
- .wp-block {
- max-width: $content-width;
-
- &[data-align="wide"] {
- max-width: $wide-content-width;
- }
-
- &[data-align="full"] {
- max-width: none;
- }
- }
-}
diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index 55653a083dd7c6..6d2dc9a0d94de5 100644
--- a/packages/block-editor/README.md
+++ b/packages/block-editor/README.md
@@ -490,6 +490,7 @@ _Type Definition_
_Properties_
- _alignWide_ `boolean`: Enable/Disable Wide/Full Alignments
+- _supportsLayout_ `boolean`: Enable/disable layouts support in container blocks.
- _availableLegacyWidgets_ `Array`: Array of objects representing the legacy widgets available.
- _imageEditing_ `boolean`: Image Editing settings set to false to disable.
- _imageSizes_ `Array`: Available image sizes
diff --git a/packages/block-editor/src/components/block-alignment-control/ui.js b/packages/block-editor/src/components/block-alignment-control/ui.js
index 0ac3f4345e8e7f..33682e7bfcee7f 100644
--- a/packages/block-editor/src/components/block-alignment-control/ui.js
+++ b/packages/block-editor/src/components/block-alignment-control/ui.js
@@ -15,7 +15,7 @@ import {
/**
* Internal dependencies
*/
-import { useLayout } from '../inner-blocks/layout';
+import { useLayout } from '../block-list/layout';
import { store as blockEditorStore } from '../../store';
const BLOCK_ALIGNMENTS_CONTROLS = {
@@ -70,11 +70,12 @@ function BlockAlignmentUI( {
if ( ! supportsAlignments ) {
return null;
}
-
const { alignments: availableAlignments = DEFAULT_CONTROLS } = layout;
const enabledControls = controls.filter(
( control ) =>
- ( wideControlsEnabled || ! WIDE_CONTROLS.includes( control ) ) &&
+ ( layout.alignments || // Ignore the global wideAlignment check if the layout explicitely defines alignments.
+ wideControlsEnabled ||
+ ! WIDE_CONTROLS.includes( control ) ) &&
availableAlignments.includes( control )
);
diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js
index f1da838a8c7526..7047e0163d5dc1 100644
--- a/packages/block-editor/src/components/block-list/index.js
+++ b/packages/block-editor/src/components/block-list/index.js
@@ -19,11 +19,12 @@ import useInsertionPoint from './insertion-point';
import BlockPopover from './block-popover';
import { store as blockEditorStore } from '../../store';
import { useScrollSelectionIntoView } from '../selection-scroll-into-view';
+import { LayoutProvider, defaultLayout } from './layout';
export const BlockNodes = createContext();
export const SetBlockNodes = createContext();
-export default function BlockList( { className } ) {
+export default function BlockList( { className, __experimentalLayout } ) {
const ref = useRef();
const [ blockNodes, setBlockNodes ] = useState( {} );
const insertionPoint = useInsertionPoint( ref );
@@ -41,7 +42,10 @@ export default function BlockList( { className } ) {
) }
>
-
+
@@ -53,6 +57,7 @@ function Items( {
rootClientId,
renderAppender,
__experimentalAppenderTagName,
+ __experimentalLayout: layout = defaultLayout,
wrapperRef,
} ) {
function selector( select ) {
@@ -88,7 +93,7 @@ function Items( {
const isAppenderDropTarget = dropTargetIndex === blockClientIds.length;
return (
- <>
+
{ blockClientIds.map( ( clientId, index ) => {
const isBlockInSelection = hasMultiSelection
? multiSelectedBlockClientIds.includes( clientId )
@@ -129,7 +134,7 @@ function Items( {
isAppenderDropTarget && orientation === 'horizontal',
} ) }
/>
- >
+
);
}
diff --git a/packages/block-editor/src/components/block-list/insertion-point.js b/packages/block-editor/src/components/block-list/insertion-point.js
index a4f33a80bd8dad..f2842aa5b11d50 100644
--- a/packages/block-editor/src/components/block-list/insertion-point.js
+++ b/packages/block-editor/src/components/block-list/insertion-point.js
@@ -306,9 +306,11 @@ export default function useInsertionPoint( ref ) {
const children = Array.from( event.target.children );
const nextElement = children.find( ( blockEl ) => {
return (
- ( orientation === 'vertical' &&
+ ( blockEl.classList.contains( 'wp-block' ) &&
+ orientation === 'vertical' &&
blockEl.offsetTop > offsetTop ) ||
- ( orientation === 'horizontal' &&
+ ( blockEl.classList.contains( 'wp-block' ) &&
+ orientation === 'horizontal' &&
blockEl.offsetLeft > offsetLeft )
);
} );
diff --git a/packages/block-editor/src/components/block-list/layout.js b/packages/block-editor/src/components/block-list/layout.js
new file mode 100644
index 00000000000000..88d35a59a0ebb5
--- /dev/null
+++ b/packages/block-editor/src/components/block-list/layout.js
@@ -0,0 +1,64 @@
+/**
+ * WordPress dependencies
+ */
+import { createContext, useContext } from '@wordpress/element';
+
+export const defaultLayout = { type: 'default' };
+
+const Layout = createContext( defaultLayout );
+
+function appendSelectors( selectors, append ) {
+ return selectors
+ .split( ',' )
+ .map( ( subselector ) => subselector + ' ' + append )
+ .join( ',' );
+}
+
+/**
+ * Allows to define the layout.
+ */
+export const LayoutProvider = Layout.Provider;
+
+/**
+ * React hook used to retrieve the layout config.
+ */
+export function useLayout() {
+ return useContext( Layout );
+}
+
+export function LayoutStyle( { selector, layout = {} } ) {
+ const { contentSize, wideSize } = layout;
+
+ let style =
+ !! contentSize || !! wideSize
+ ? `
+ ${ appendSelectors( selector, '> *' ) } {
+ max-width: ${ contentSize ?? wideSize };
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ ${ appendSelectors( selector, '> [data-align="wide"]' ) } {
+ max-width: ${ wideSize ?? contentSize };
+ }
+
+ ${ appendSelectors( selector, '> [data-align="full"]' ) } {
+ max-width: none;
+ }
+ `
+ : '';
+
+ style += `
+ ${ appendSelectors( selector, '> [data-align="left"]' ) } {
+ float: left;
+ margin-right: 2em;
+ }
+
+ ${ appendSelectors( selector, '> [data-align="right"]' ) } {
+ float: right;
+ margin-left: 2em;
+ }
+ `;
+
+ return ;
+}
diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss
index eff5c20457da5c..ee4ac11b1c7d11 100644
--- a/packages/block-editor/src/components/block-list/style.scss
+++ b/packages/block-editor/src/components/block-list/style.scss
@@ -341,56 +341,12 @@
}
}
-// Extra specificity needed to override default element margins like lists (ul).
-.block-editor-block-list__layout .wp-block {
- margin-left: auto;
- margin-right: auto;
+.wp-block[data-align="left"] > *,
+.wp-block[data-align="right"] > * {
+ // Without z-index, won't be clickable as "above" adjacent content.
+ z-index: z-index("{core/image aligned left or right} .wp-block");
}
-.wp-block {
- // Alignments.
- &[data-align="left"],
- &[data-align="right"] {
- width: 100%;
-
- // When images are floated, the block itself should collapse to zero height.
- height: 0;
-
- &::before {
- content: none;
- }
- }
-
- &[data-align="left"] > *,
- &[data-align="right"] > * {
- // Without z-index, won't be clickable as "above" adjacent content.
- z-index: z-index("{core/image aligned left or right} .wp-block");
- }
-
- // Left.
- &[data-align="left"] > * {
- // This is in the editor only; the image should be floated on the frontend.
- /*!rtl:begin:ignore*/
- float: left;
- margin-right: 2em;
- /*!rtl:end:ignore*/
- }
-
- // Right.
- &[data-align="right"] > * {
- // Right: This is in the editor only; the image should be floated on the frontend.
- /*!rtl:begin:ignore*/
- float: right;
- margin-left: 2em;
- /*!rtl:end:ignore*/
- }
-
- // Wide and full-wide.
- &[data-align="full"],
- &[data-align="wide"] {
- clear: both;
- }
-}
/**
* In-Canvas Inserter
diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js
index 16977e34af4976..a77da2f7523772 100644
--- a/packages/block-editor/src/components/index.js
+++ b/packages/block-editor/src/components/index.js
@@ -84,6 +84,7 @@ export { default as BlockInspector } from './block-inspector';
export { default as BlockList } from './block-list';
export { useBlockProps } from './block-list/use-block-props';
export { Block as __experimentalBlock } from './block-list/block-wrapper';
+export { LayoutStyle as __experimentalLayoutStyle } from './block-list/layout';
export { default as BlockMover } from './block-mover';
export { default as BlockPreview } from './block-preview';
export {
diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js
index 7d8db1f019e866..e826f3f9a1c52b 100644
--- a/packages/block-editor/src/components/inner-blocks/index.js
+++ b/packages/block-editor/src/components/inner-blocks/index.js
@@ -23,7 +23,6 @@ import { BlockListItems } from '../block-list';
import { BlockContextProvider } from '../block-context';
import { useBlockEditContext } from '../block-edit/context';
import useBlockSync from '../provider/use-block-sync';
-import { defaultLayout, LayoutProvider } from './layout';
import { store as blockEditorStore } from '../../store';
/**
@@ -47,7 +46,7 @@ function UncontrolledInnerBlocks( props ) {
renderAppender,
orientation,
placeholder,
- __experimentalLayout: layout = defaultLayout,
+ __experimentalLayout,
} = props;
useNestedSettingsUpdate(
@@ -82,19 +81,16 @@ function UncontrolledInnerBlocks( props ) {
// This component needs to always be synchronous as it's the one changing
// the async mode depending on the block selection.
return (
-
-
-
-
-
+
+
+
);
}
diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js
index 4681b5e6f9ff35..0b9401425e8640 100644
--- a/packages/block-editor/src/components/inner-blocks/index.native.js
+++ b/packages/block-editor/src/components/inner-blocks/index.native.js
@@ -20,7 +20,7 @@ import BlockList from '../block-list';
import { useBlockEditContext } from '../block-edit/context';
import useBlockSync from '../provider/use-block-sync';
import { BlockContextProvider } from '../block-context';
-import { defaultLayout, LayoutProvider } from './layout';
+import { defaultLayout, LayoutProvider } from '../block-list/layout';
import { store as blockEditorStore } from '../../store';
/**
diff --git a/packages/block-editor/src/components/inner-blocks/layout.js b/packages/block-editor/src/components/inner-blocks/layout.js
deleted file mode 100644
index 745b59945ea61d..00000000000000
--- a/packages/block-editor/src/components/inner-blocks/layout.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { createContext, useContext } from '@wordpress/element';
-
-export const defaultLayout = { type: 'default' };
-
-const Layout = createContext( defaultLayout );
-
-/**
- * Allows to define the layout.
- */
-export const LayoutProvider = Layout.Provider;
-
-/**
- * React hook used to retrieve the layout config.
- */
-export function useLayout() {
- return useContext( Layout );
-}
diff --git a/packages/block-editor/src/components/use-editor-feature/index.js b/packages/block-editor/src/components/use-editor-feature/index.js
index 68f6e9e0cd8f25..81ab094ec7f6c5 100644
--- a/packages/block-editor/src/components/use-editor-feature/index.js
+++ b/packages/block-editor/src/components/use-editor-feature/index.js
@@ -63,7 +63,7 @@ function blockAttributesMatch( blockAttributes, attributes ) {
* Hook that retrieves the setting for the given editor feature.
* It works with nested objects using by finding the value at path.
*
- * @param {string} featurePath The path to the feature.
+ * @param {string} featurePath The path to the feature.
*
* @return {any} Returns the value defined for the setting.
*
@@ -88,7 +88,7 @@ export default function useEditorFeature( featurePath ) {
'supports',
'__experimentalSelector',
] );
- if ( isObject( selectors ) ) {
+ if ( clientId && isObject( selectors ) ) {
const blockAttributes = getBlockAttributes( clientId ) || {};
for ( const contextSelector in selectors ) {
const { attributes } = selectors[ contextSelector ];
diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js
index 6aa2b76fc1d1f5..c087e994ba9eb9 100644
--- a/packages/block-editor/src/hooks/index.js
+++ b/packages/block-editor/src/hooks/index.js
@@ -8,3 +8,4 @@ import './generated-class-name';
import './style';
import './color';
import './font-size';
+import './layout';
diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js
new file mode 100644
index 00000000000000..c564f720e36790
--- /dev/null
+++ b/packages/block-editor/src/hooks/layout.js
@@ -0,0 +1,234 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+import { has } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { Platform } from '@wordpress/element';
+import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose';
+import { addFilter } from '@wordpress/hooks';
+import { hasBlockSupport } from '@wordpress/blocks';
+import { useSelect } from '@wordpress/data';
+import {
+ ToggleControl,
+ PanelBody,
+ __experimentalUnitControl as UnitControl,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { Icon, positionCenter, stretchWide } from '@wordpress/icons';
+
+/**
+ * Internal dependencies
+ */
+import { store as blockEditorStore } from '../store';
+import { InspectorControls } from '../components';
+import useEditorFeature from '../components/use-editor-feature';
+import { LayoutStyle } from '../components/block-list/layout';
+
+const isWeb = Platform.OS === 'web';
+const CSS_UNITS = [
+ {
+ value: '%',
+ label: isWeb ? '%' : __( 'Percentage (%)' ),
+ default: '',
+ },
+ {
+ value: 'px',
+ label: isWeb ? 'px' : __( 'Pixels (px)' ),
+ default: '',
+ },
+ {
+ value: 'em',
+ label: isWeb ? 'em' : __( 'Relative to parent font size (em)' ),
+ default: '',
+ },
+ {
+ value: 'rem',
+ label: isWeb ? 'rem' : __( 'Relative to root font size (rem)' ),
+ default: '',
+ },
+ {
+ value: 'vw',
+ label: isWeb ? 'vw' : __( 'Viewport width (vw)' ),
+ default: '',
+ },
+];
+
+function LayoutPanel( { setAttributes, attributes } ) {
+ const { layout = {} } = attributes;
+ const { wideSize, contentSize, inherit = false } = layout;
+ const defaultLayout = useEditorFeature( 'layout' );
+ const themeSupportsLayout = useSelect( ( select ) => {
+ const { getSettings } = select( blockEditorStore );
+ return getSettings().supportsLayout;
+ }, [] );
+
+ if ( ! themeSupportsLayout ) {
+ return null;
+ }
+ return (
+
+
+ { !! defaultLayout && (
+
+ setAttributes( { layout: { inherit: ! inherit } } )
+ }
+ />
+ ) }
+ { ! inherit && (
+
+
+ {
+ nextWidth =
+ 0 > parseFloat( nextWidth )
+ ? '0'
+ : nextWidth;
+ setAttributes( {
+ layout: {
+ ...layout,
+ contentSize: nextWidth,
+ },
+ } );
+ } }
+ units={ CSS_UNITS }
+ />
+
+
+
+ {
+ nextWidth =
+ 0 > parseFloat( nextWidth )
+ ? '0'
+ : nextWidth;
+ setAttributes( {
+ layout: {
+ ...layout,
+ wideSize: nextWidth,
+ },
+ } );
+ } }
+ units={ CSS_UNITS }
+ />
+
+
+
+ ) }
+
+ { __(
+ 'The content and wide sizes determine the width of centered and wide columns inside.'
+ ) }
+
+
+
+ );
+}
+
+/**
+ * Filters registered block settings, extending attributes to include `layout`.
+ *
+ * @param {Object} settings Original block settings
+ * @return {Object} Filtered block settings
+ */
+export function addAttribute( settings ) {
+ if ( has( settings.attributes, [ 'layout', 'type' ] ) ) {
+ return settings;
+ }
+ if ( hasBlockSupport( settings, '__experimentalLayout' ) ) {
+ settings.attributes = {
+ ...settings.attributes,
+ layout: {
+ type: 'object',
+ },
+ };
+ }
+
+ return settings;
+}
+
+/**
+ * Override the default edit UI to include layout controls
+ *
+ * @param {Function} BlockEdit Original component
+ * @return {Function} Wrapped component
+ */
+export const withInspectorControls = createHigherOrderComponent(
+ ( BlockEdit ) => ( props ) => {
+ const { name: blockName } = props;
+ const supportLayout = hasBlockSupport(
+ blockName,
+ '__experimentalLayout'
+ );
+
+ return [
+ supportLayout &&
,
+
,
+ ];
+ },
+ 'withInspectorControls'
+);
+
+/**
+ * Override the default block element to add the layout styles.
+ *
+ * @param {Function} BlockListBlock Original component
+ * @return {Function} Wrapped component
+ */
+export const withLayoutStyles = createHigherOrderComponent(
+ ( BlockListBlock ) => ( props ) => {
+ const { name, attributes } = props;
+ const supportLayout = hasBlockSupport( name, '__experimentalLayout' );
+ const id = useInstanceId( BlockListBlock );
+ const defaultLayout = useEditorFeature( 'layout' );
+ if ( ! supportLayout ) {
+ return
;
+ }
+ const { layout = {} } = attributes;
+ const usedLayout = !! layout && layout.inherit ? defaultLayout : layout;
+ const className = classnames(
+ props?.className,
+ `wp-container-${ id }`
+ );
+
+ return (
+ <>
+
+
+ >
+ );
+ }
+);
+
+addFilter(
+ 'blocks.registerBlockType',
+ 'core/layout/addAttribute',
+ addAttribute
+);
+addFilter(
+ 'editor.BlockListBlock',
+ 'core/editor/layout/with-layout-styles',
+ withLayoutStyles
+);
+addFilter(
+ 'editor.BlockEdit',
+ 'core/editor/layout/with-inspector-controls',
+ withInspectorControls
+);
diff --git a/packages/block-editor/src/hooks/layout.scss b/packages/block-editor/src/hooks/layout.scss
new file mode 100644
index 00000000000000..1c4773a8889ecf
--- /dev/null
+++ b/packages/block-editor/src/hooks/layout.scss
@@ -0,0 +1,17 @@
+.block-editor-hooks__layout-controls {
+ display: flex;
+ margin-bottom: $grid-unit-30;
+
+ .block-editor-hooks__layout-controls-unit {
+ display: flex;
+ margin-right: $grid-unit-30;
+
+ svg {
+ margin: auto 0 $grid-unit-05 $grid-unit-10;
+ }
+ }
+}
+
+.block-editor-hooks__layout-controls-helptext {
+ font-size: $helptext-font-size;
+}
diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js
index ba608d34a35f87..0056a89f2ef178 100644
--- a/packages/block-editor/src/store/defaults.js
+++ b/packages/block-editor/src/store/defaults.js
@@ -12,6 +12,7 @@ export const PREFERENCES_DEFAULTS = {
*
* @typedef {Object} SETTINGS_DEFAULT
* @property {boolean} alignWide Enable/Disable Wide/Full Alignments
+ * @property {boolean} supportsLayout Enable/disable layouts support in container blocks.
* @property {Array} availableLegacyWidgets Array of objects representing the legacy widgets available.
* @property {boolean} imageEditing Image Editing settings set to false to disable.
* @property {Array} imageSizes Available image sizes
@@ -31,6 +32,8 @@ export const PREFERENCES_DEFAULTS = {
*/
export const SETTINGS_DEFAULTS = {
alignWide: false,
+ supportsLayout: true,
+
// colors setting is not used anymore now defaults are passed from theme.json on the server and core has its own defaults.
// The setting is only kept for backward compatibility purposes.
colors: [
diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss
index 9469ed8cdf9096..fffa4c55e38671 100644
--- a/packages/block-editor/src/style.scss
+++ b/packages/block-editor/src/style.scss
@@ -54,6 +54,7 @@
@import "./components/url-popover/style.scss";
@import "./components/warning/style.scss";
@import "./hooks/anchor.scss";
+@import "./hooks/layout.scss";
// This tag marks the end of the styles that apply to editing canvas contents and need to be manipulated when we resize the editor.
#end-resizable-editor-section {
diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json
index 913194728559b7..ad4cc62fc68a73 100644
--- a/packages/block-library/src/group/block.json
+++ b/packages/block-library/src/group/block.json
@@ -12,10 +12,7 @@
}
},
"supports": {
- "align": [
- "wide",
- "full"
- ],
+ "align": [ "wide", "full" ],
"anchor": true,
"html": false,
"color": {
@@ -30,7 +27,8 @@
"radius": true,
"style": true,
"width": true
- }
+ },
+ "__experimentalLayout": true
},
"editorStyle": "wp-block-group-editor",
"style": "wp-block-group"
diff --git a/packages/block-library/src/group/deprecated.js b/packages/block-library/src/group/deprecated.js
index 3e6d394197ffd6..337e498b0bdedd 100644
--- a/packages/block-library/src/group/deprecated.js
+++ b/packages/block-library/src/group/deprecated.js
@@ -7,7 +7,11 @@ import { omit } from 'lodash';
/**
* WordPress dependencies
*/
-import { InnerBlocks, getColorClassName } from '@wordpress/block-editor';
+import {
+ InnerBlocks,
+ getColorClassName,
+ useBlockProps,
+} from '@wordpress/block-editor';
const migrateAttributes = ( attributes ) => {
if ( ! attributes.tagName ) {
@@ -34,6 +38,43 @@ const migrateAttributes = ( attributes ) => {
};
const deprecated = [
+ // Version of the block with the double div.
+ {
+ attributes: {
+ tagName: {
+ type: 'string',
+ default: 'div',
+ },
+ templateLock: {
+ type: 'string',
+ },
+ },
+ supports: {
+ align: [ 'wide', 'full' ],
+ anchor: true,
+ color: {
+ gradients: true,
+ link: true,
+ },
+ spacing: {
+ padding: true,
+ },
+ __experimentalBorder: {
+ radius: true,
+ },
+ },
+ save( { attributes } ) {
+ const { tagName: Tag } = attributes;
+
+ return (
+
+
+
+
+
+ );
+ },
+ },
// Version of the block without global styles support
{
attributes: {
diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js
index 55de289edbd42f..9a27a481dfb30a 100644
--- a/packages/block-library/src/group/edit.js
+++ b/packages/block-library/src/group/edit.js
@@ -7,37 +7,44 @@ import {
useBlockProps,
InspectorAdvancedControls,
__experimentalUseInnerBlocksProps as useInnerBlocksProps,
+ __experimentalUseEditorFeature as useEditorFeature,
store as blockEditorStore,
} from '@wordpress/block-editor';
-import {
- SelectControl,
- __experimentalBoxControl as BoxControl,
-} from '@wordpress/components';
+import { SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-const { __Visualizer: BoxControlVisualizer } = BoxControl;
function GroupEdit( { attributes, setAttributes, clientId } ) {
- const hasInnerBlocks = useSelect(
+ const { hasInnerBlocks, themeSupportsLayout } = useSelect(
( select ) => {
- const { getBlock } = select( blockEditorStore );
+ const { getBlock, getSettings } = select( blockEditorStore );
const block = getBlock( clientId );
- return !! ( block && block.innerBlocks.length );
+ return {
+ hasInnerBlocks: !! ( block && block.innerBlocks.length ),
+ themeSupportsLayout: getSettings()?.supportsLayout,
+ };
},
[ clientId ]
);
+ const defaultLayout = useEditorFeature( 'layout' );
+ const { tagName: TagName = 'div', templateLock, layout = {} } = attributes;
+ const usedLayout = !! layout && layout.inherit ? defaultLayout : layout;
+ const { contentSize, wideSize } = usedLayout;
+ const alignments =
+ contentSize || wideSize
+ ? [ 'wide', 'full' ]
+ : [ 'left', 'center', 'right' ];
const blockProps = useBlockProps();
- const { tagName: TagName = 'div', templateLock } = attributes;
- const innerBlocksProps = useInnerBlocksProps(
- {
- className: 'wp-block-group__inner-container',
+ const innerBlocksProps = useInnerBlocksProps( blockProps, {
+ templateLock,
+ renderAppender: hasInnerBlocks
+ ? undefined
+ : InnerBlocks.ButtonBlockAppender,
+ __experimentalLayout: {
+ type: 'default',
+ // Find a way to inject this in the support flag code (hooks).
+ alignments: themeSupportsLayout ? alignments : undefined,
},
- {
- templateLock,
- renderAppender: hasInnerBlocks
- ? undefined
- : InnerBlocks.ButtonBlockAppender,
- }
- );
+ } );
return (
<>
@@ -59,13 +66,7 @@ function GroupEdit( { attributes, setAttributes, clientId } ) {
}
/>
-
-
-
-
+
>
);
}
diff --git a/packages/block-library/src/group/editor.scss b/packages/block-library/src/group/editor.scss
index 080bc64f433c44..92103665aa8c8e 100644
--- a/packages/block-library/src/group/editor.scss
+++ b/packages/block-library/src/group/editor.scss
@@ -1,75 +1,17 @@
/**
* Group: All Alignment Settings
*/
-
.wp-block-group {
-
// Ensure not rendering outside the element
// as -1px causes overflow-x scrollbars
.block-editor-block-list__insertion-point {
left: 0;
right: 0;
}
-
- // Full Width Blocks
- // specificity required to only target immediate child Blocks of a Group
- > .wp-block-group__inner-container > [data-align="full"] {
- margin-left: auto;
- margin-right: auto;
- }
-
- // Full Width Blocks with a background (ie: has padding)
- &.has-background > .wp-block-group__inner-container > [data-align="full"] {
- // note: using position `left` causes hoz scrollbars so
- // we opt to use margin instead
- // the 30px matches the hoz padding applied in `theme.scss`
- // added when the Block has a background set
- margin-left: -30px;
-
- // 60px here is x2 the hoz padding from `theme.scss` added when
- // the Block has a background set
- // note: also duplicated below for full width Groups
- width: calc(100% + 60px);
- }
-}
-
-/**
- * Group: Full Width Alignment
- */
-[data-align="full"] .wp-block-group {
-
- // Non-full Width Blocks
- // specificity required to only target immediate child Blocks of Group
- > .wp-block-group__inner-container > .wp-block {
- padding-left: $block-padding;
- padding-right: $block-padding;
-
- @include break-small() {
- padding-left: 0;
- padding-right: 0;
- }
- }
-
- // Full Width Blocks
- // specificity required to only target immediate child Blocks of Group
- > .wp-block-group__inner-container > [data-align="full"] {
- padding-right: 0;
- padding-left: 0;
- left: 0;
- width: 100%;
- max-width: none;
- }
-
- // Full Width Blocks with a background (ie: has padding)
- // note: also duplicated above for all Group widths
- &.has-background > .wp-block-group__inner-container > [data-align="full"] {
- width: calc(100% + 60px);
- }
}
// Place block list appender in the same place content will appear.
[data-type="core/group"].is-selected {
-
.block-list-appender {
margin-left: 0;
margin-right: 0;
diff --git a/packages/block-library/src/group/save.js b/packages/block-library/src/group/save.js
index 0a5f4b87ec7085..60e4dbd30a8f5b 100644
--- a/packages/block-library/src/group/save.js
+++ b/packages/block-library/src/group/save.js
@@ -5,12 +5,9 @@ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const { tagName: Tag } = attributes;
-
return (
-
-
-
+
);
}
diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js
index add399395072bd..cba52a0f33d6aa 100644
--- a/packages/blocks/src/api/test/parser.js
+++ b/packages/blocks/src/api/test/parser.js
@@ -777,14 +777,14 @@ describe( 'block parser', () => {
@@ -839,14 +839,13 @@ describe( 'block parser', () => {
innerContent: [ '
D
' ],
},
],
- innerHTML:
- '
',
+ innerHTML: '
',
innerContent: [
- '
',
+ '
',
null,
'',
null,
- '
',
+ '
',
],
},
],
diff --git a/packages/e2e-test-utils/src/click-block-toolbar-button.js b/packages/e2e-test-utils/src/click-block-toolbar-button.js
index 3aa431bfe29ff9..e73888f0681385 100644
--- a/packages/e2e-test-utils/src/click-block-toolbar-button.js
+++ b/packages/e2e-test-utils/src/click-block-toolbar-button.js
@@ -22,7 +22,7 @@ export async function clickBlockToolbarButton( label, type = 'ariaLabel' ) {
if ( type === 'content' ) {
button = await page.waitForXPath(
- `//*[@class='${ BLOCK_TOOLBAR_SELECTOR }']//button[contains(text(), '${ label }')]`
+ `//*[contains(concat(' ', normalize-space(@class), ' '), ' ${ BLOCK_TOOLBAR_SELECTOR } ')]//button[contains(text(), '${ label }')]`
);
}
diff --git a/packages/e2e-tests/fixtures/blocks/core__group.html b/packages/e2e-tests/fixtures/blocks/core__group.html
index e5df0f2fce926a..df7eef39e38981 100644
--- a/packages/e2e-tests/fixtures/blocks/core__group.html
+++ b/packages/e2e-tests/fixtures/blocks/core__group.html
@@ -1,12 +1,9 @@
-
-
-
-
This is a group block.
-
+
+
This is a group block.
+
-
-
Group block content.
-
-
+
+
Group block content.
+
diff --git a/packages/e2e-tests/fixtures/blocks/core__group.json b/packages/e2e-tests/fixtures/blocks/core__group.json
index 526a8d70fbcdfa..7fd0b5d60ba113 100644
--- a/packages/e2e-tests/fixtures/blocks/core__group.json
+++ b/packages/e2e-tests/fixtures/blocks/core__group.json
@@ -32,6 +32,6 @@
"originalContent": "
Group block content.
"
}
],
- "originalContent": "
"
+ "originalContent": "
\n\n
"
}
]
diff --git a/packages/e2e-tests/fixtures/blocks/core__group.parsed.json b/packages/e2e-tests/fixtures/blocks/core__group.parsed.json
index ba86ac5a02d6bd..a82a138ba47415 100644
--- a/packages/e2e-tests/fixtures/blocks/core__group.parsed.json
+++ b/packages/e2e-tests/fixtures/blocks/core__group.parsed.json
@@ -10,28 +10,28 @@
"blockName": "core/paragraph",
"attrs": {},
"innerBlocks": [],
- "innerHTML": "\n\t\t
This is a group block.
\n\t\t",
+ "innerHTML": "\n
This is a group block.
\n",
"innerContent": [
- "\n\t\t
This is a group block.
\n\t\t"
+ "\n
This is a group block.
\n"
]
},
{
"blockName": "core/paragraph",
"attrs": {},
"innerBlocks": [],
- "innerHTML": "\n\t\t
Group block content.
\n\t\t",
+ "innerHTML": "\n
Group block content.
\n",
"innerContent": [
- "\n\t\t
Group block content.
\n\t\t"
+ "\n
Group block content.
\n"
]
}
],
- "innerHTML": "\n
\n",
+ "innerHTML": "\n
\n\n
\n",
"innerContent": [
- "\n
\n\t
\n\t\t",
+ "\n
",
null,
- "\n\n\t\t",
+ "\n\n",
null,
- "
\n\t
\n"
+ "
\n"
]
},
{
diff --git a/packages/e2e-tests/fixtures/blocks/core__group.serialized.html b/packages/e2e-tests/fixtures/blocks/core__group.serialized.html
index 8ac236255f9f61..df7eef39e38981 100644
--- a/packages/e2e-tests/fixtures/blocks/core__group.serialized.html
+++ b/packages/e2e-tests/fixtures/blocks/core__group.serialized.html
@@ -1,9 +1,9 @@
-
+
This is a group block.
Group block content.
-
+
diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-2.serialized.html
index cf7ebe4f5a05ec..1e25d657b8106f 100644
--- a/packages/e2e-tests/fixtures/blocks/core__group__deprecated-2.serialized.html
+++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-2.serialized.html
@@ -1,5 +1,5 @@
-
diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.html b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.html
new file mode 100644
index 00000000000000..9702e29ed52d6c
--- /dev/null
+++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.html
@@ -0,0 +1,13 @@
+
+
+
+
+
This is a group block.
+
+
+
+
Group block content.
+
+
+
+
diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.json b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.json
new file mode 100644
index 00000000000000..2ca0de07bbb39a
--- /dev/null
+++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.json
@@ -0,0 +1,37 @@
+[
+ {
+ "clientId": "_clientId_0",
+ "name": "core/group",
+ "isValid": true,
+ "attributes": {
+ "tagName": "div",
+ "align": "full",
+ "backgroundColor": "secondary"
+ },
+ "innerBlocks": [
+ {
+ "clientId": "_clientId_0",
+ "name": "core/paragraph",
+ "isValid": true,
+ "attributes": {
+ "content": "This is a group block.",
+ "dropCap": false
+ },
+ "innerBlocks": [],
+ "originalContent": "
This is a group block.
"
+ },
+ {
+ "clientId": "_clientId_1",
+ "name": "core/paragraph",
+ "isValid": true,
+ "attributes": {
+ "content": "Group block content.",
+ "dropCap": false
+ },
+ "innerBlocks": [],
+ "originalContent": "
Group block content.
"
+ }
+ ],
+ "originalContent": "
"
+ }
+]
diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.parsed.json b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.parsed.json
new file mode 100644
index 00000000000000..9da531aefe611e
--- /dev/null
+++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.parsed.json
@@ -0,0 +1,46 @@
+[
+ {
+ "blockName": "core/group",
+ "attrs": {
+ "align": "full",
+ "backgroundColor": "secondary"
+ },
+ "innerBlocks": [
+ {
+ "blockName": "core/paragraph",
+ "attrs": {},
+ "innerBlocks": [],
+ "innerHTML": "\n\t\t
This is a group block.
\n\t\t",
+ "innerContent": [
+ "\n\t\t
This is a group block.
\n\t\t"
+ ]
+ },
+ {
+ "blockName": "core/paragraph",
+ "attrs": {},
+ "innerBlocks": [],
+ "innerHTML": "\n\t\t
Group block content.
\n\t\t",
+ "innerContent": [
+ "\n\t\t
Group block content.
\n\t\t"
+ ]
+ }
+ ],
+ "innerHTML": "\n
\n",
+ "innerContent": [
+ "\n
\n\t
\n\t\t",
+ null,
+ "\n\n\t\t",
+ null,
+ "\n\t
\n
\n"
+ ]
+ },
+ {
+ "blockName": null,
+ "attrs": {},
+ "innerBlocks": [],
+ "innerHTML": "\n",
+ "innerContent": [
+ "\n"
+ ]
+ }
+]
diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.serialized.html b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.serialized.html
new file mode 100644
index 00000000000000..df7eef39e38981
--- /dev/null
+++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated-inner-container.serialized.html
@@ -0,0 +1,9 @@
+
+
+
This is a group block.
+
+
+
+
Group block content.
+
+
diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json
index 7f1f51c3644e40..c67c67fc51cd8e 100644
--- a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json
+++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json
@@ -4,10 +4,10 @@
"name": "core/group",
"isValid": true,
"attributes": {
- "backgroundColor": "lighter-blue",
+ "tagName": "div",
"align": "full",
"anchor": "test-id",
- "tagName": "div"
+ "backgroundColor": "lighter-blue"
},
"innerBlocks": [
{
diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html
index b9f1dc3ba37e12..b36427bb867648 100644
--- a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html
+++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html
@@ -1,5 +1,5 @@
-
diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/group.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/group.test.js.snap
index 49ddf4c41946de..df7b3f6419f155 100644
--- a/packages/e2e-tests/specs/editor/blocks/__snapshots__/group.test.js.snap
+++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/group.test.js.snap
@@ -2,20 +2,20 @@
exports[`Group can be created using the block inserter 1`] = `
"
-
+
"
`;
exports[`Group can be created using the slash inserter 1`] = `
"
-
+
"
`;
exports[`Group can have other blocks appended to it using the button appender 1`] = `
"
-
+
Group Block with a Paragraph
-
+
"
`;
diff --git a/packages/e2e-tests/specs/editor/blocks/paragraph.test.js b/packages/e2e-tests/specs/editor/blocks/paragraph.test.js
index d998ae32387c39..74f3f7f8cb98ff 100644
--- a/packages/e2e-tests/specs/editor/blocks/paragraph.test.js
+++ b/packages/e2e-tests/specs/editor/blocks/paragraph.test.js
@@ -13,8 +13,9 @@ describe( 'Paragraph', () => {
await page.keyboard.type( '1' );
const firstBlockTagName = await page.evaluate( () => {
- return document.querySelector( '.block-editor-block-list__layout' )
- .firstChild.tagName;
+ return document.querySelector(
+ '.block-editor-block-list__layout .wp-block'
+ ).tagName;
} );
// The outer element should be a paragraph. Blocks should never have any
diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap
index bec98aa0820550..a7d34618db0de4 100644
--- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap
+++ b/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap
@@ -38,21 +38,21 @@ exports[`cpt locking template_lock all should not error when deleting the cotent
exports[`cpt locking template_lock all unlocked group should allow blocks to be moved 1`] = `
"
-
"
`;
exports[`cpt locking template_lock all unlocked group should allow blocks to be removed 1`] = `
"
-
"
`;
diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/adding-blocks.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/adding-blocks.test.js.snap
index 9870b5be306fa7..b83df123f6a4d0 100644
--- a/packages/e2e-tests/specs/editor/various/__snapshots__/adding-blocks.test.js.snap
+++ b/packages/e2e-tests/specs/editor/various/__snapshots__/adding-blocks.test.js.snap
@@ -55,9 +55,9 @@ lines preserved[/myshortcode]
exports[`adding blocks inserts a block in proper place after having clicked \`Browse All\` from block appender 1`] = `
"
-
diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/block-grouping.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/block-grouping.test.js.snap
index 4b786e99fdb23d..f3c24c200b971a 100644
--- a/packages/e2e-tests/specs/editor/various/__snapshots__/block-grouping.test.js.snap
+++ b/packages/e2e-tests/specs/editor/various/__snapshots__/block-grouping.test.js.snap
@@ -2,7 +2,7 @@
exports[`Block Grouping Group creation creates a group from multiple blocks of different types via block transforms 1`] = `
"
-
+
Group Heading
@@ -12,13 +12,13 @@ exports[`Block Grouping Group creation creates a group from multiple blocks of d
Some paragraph
-
+
"
`;
exports[`Block Grouping Group creation creates a group from multiple blocks of the same type via block transforms 1`] = `
"
-
+
First Paragraph
@@ -28,13 +28,13 @@ exports[`Block Grouping Group creation creates a group from multiple blocks of t
Third Paragraph
-
+
"
`;
exports[`Block Grouping Group creation creates a group from multiple blocks of the same type via options toolbar 1`] = `
"
-
+
First Paragraph
@@ -44,13 +44,13 @@ exports[`Block Grouping Group creation creates a group from multiple blocks of t
Third Paragraph
-
+
"
`;
exports[`Block Grouping Group creation groups and ungroups multiple blocks of different types via options toolbar 1`] = `
"
-
+
Group Heading
@@ -60,7 +60,7 @@ exports[`Block Grouping Group creation groups and ungroups multiple blocks of di
Some paragraph
-
+
"
`;
@@ -80,7 +80,7 @@ exports[`Block Grouping Group creation groups and ungroups multiple blocks of di
exports[`Block Grouping Preserving selected blocks attributes preserves width alignment settings of selected blocks 1`] = `
"
-
+
Group Heading
@@ -94,7 +94,7 @@ exports[`Block Grouping Preserving selected blocks attributes preserves width al
Some paragraph
-
+
"
`;
diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/block-hierarchy-navigation.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/block-hierarchy-navigation.test.js.snap
index 7904000d8aad2c..406e1be99232c7 100644
--- a/packages/e2e-tests/specs/editor/various/__snapshots__/block-hierarchy-navigation.test.js.snap
+++ b/packages/e2e-tests/specs/editor/various/__snapshots__/block-hierarchy-navigation.test.js.snap
@@ -52,12 +52,12 @@ exports[`Navigating the block hierarchy should navigate using the block hierarch
exports[`Navigating the block hierarchy should select the wrapper div for a group 1`] = `
"
-
"
`;
diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js
index 029a224eaab2f4..8a595a436d9b2b 100644
--- a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js
+++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js
@@ -16,17 +16,17 @@ async function getSelectedFlatIndices() {
const indices = [];
let single;
- Array.from( document.querySelectorAll( '.wp-block' ) ).forEach(
- ( node, index ) => {
- if ( node.classList.contains( 'is-selected' ) ) {
- single = index;
- }
-
- if ( node.classList.contains( 'is-multi-selected' ) ) {
- indices.push( index );
- }
+ Array.from(
+ document.querySelectorAll( '.wp-block:not(.editor-post-title)' )
+ ).forEach( ( node, index ) => {
+ if ( node.classList.contains( 'is-selected' ) ) {
+ single = index + 1;
}
- );
+
+ if ( node.classList.contains( 'is-multi-selected' ) ) {
+ indices.push( index + 1 );
+ }
+ } );
return single !== undefined ? single : indices;
} );
@@ -489,7 +489,6 @@ describe( 'Multi-block selection', () => {
await page.keyboard.press( 'Enter' );
await page.keyboard.type( '2' );
await pressKeyWithModifier( 'shift', 'ArrowUp' );
-
await testNativeSelection();
expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2 ] );
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index f2acd3ac387350..0c9da0ae76a82e 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -94,7 +94,9 @@ function Layout( { styles } ) {
showIconLabels,
hasReducedUI,
showBlockBreadcrumbs,
+ supportsLayout,
} = useSelect( ( select ) => {
+ const editorSettings = select( 'core/editor' ).getEditorSettings();
return {
hasFixedToolbar: select( editPostStore ).isFeatureActive(
'fixedToolbar'
@@ -112,8 +114,8 @@ function Layout( { styles } ) {
),
isInserterOpened: select( editPostStore ).isInserterOpened(),
mode: select( editPostStore ).getEditorMode(),
- isRichEditingEnabled: select( 'core/editor' ).getEditorSettings()
- .richEditingEnabled,
+ isRichEditingEnabled: editorSettings.richEditingEnabled,
+ supportsLayout: editorSettings.supportsLayout,
hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(),
previousShortcut: select(
keyboardShortcutsStore
@@ -139,6 +141,7 @@ function Layout( { styles } ) {
'has-fixed-toolbar': hasFixedToolbar,
'has-metaboxes': hasActiveMetaboxes,
'show-icon-labels': showIconLabels,
+ 'supports-layout': supportsLayout,
} );
const openSidebarPanel = () =>
openGeneralSidebar(
diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss
index e2c3a144674b5d..9a9c033b55b0c2 100644
--- a/packages/edit-post/src/components/layout/style.scss
+++ b/packages/edit-post/src/components/layout/style.scss
@@ -115,3 +115,112 @@
height: 100%;
}
}
+
+// Depreacted style needed for the block widths and alignments.
+// for themes that don't support the new layout (theme.json)
+.edit-post-layout:not(.supports-layout) {
+ .wp-block {
+ max-width: $content-width;
+ margin-left: auto;
+ margin-right: auto;
+
+ &[data-align="wide"] {
+ max-width: $wide-content-width;
+ }
+
+ &[data-align="full"] {
+ max-width: none;
+ }
+
+ // Alignments.
+ &[data-align="left"],
+ &[data-align="right"] {
+ width: 100%;
+
+ // When images are floated, the block itself should collapse to zero height.
+ height: 0;
+
+ &::before {
+ content: none;
+ }
+ }
+
+ // Left.
+ &[data-align="left"] > * {
+ /*!rtl:begin:ignore*/
+ float: left;
+ margin-right: 2em;
+ /*!rtl:end:ignore*/
+ }
+
+ // Right.
+ &[data-align="right"] > * {
+ /*!rtl:begin:ignore*/
+ float: right;
+ margin-left: 2em;
+ /*!rtl:end:ignore*/
+ }
+
+ // Wide and full-wide.
+ &[data-align="full"],
+ &[data-align="wide"] {
+ clear: both;
+ }
+
+ }
+
+ // Full Width Blocks
+ // specificity required to only target immediate child Blocks of a Group
+ .wp-block-group > [data-align="full"] {
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ // Full Width Blocks with a background (ie: has padding)
+ .wp-block-group.has-background > [data-align="full"] {
+ // note: using position `left` causes hoz scrollbars so
+ // we opt to use margin instead
+ // the 30px matches the hoz padding applied in `theme.scss`
+ // added when the Block has a background set
+ margin-left: -30px;
+
+ // 60px here is x2 the hoz padding from `theme.scss` added when
+ // the Block has a background set
+ // note: also duplicated below for full width Groups
+ width: calc(100% + 60px);
+ }
+
+ /**
+ * Group: Full Width Alignment
+ */
+ [data-align="full"] .wp-block-group {
+
+ // Non-full Width Blocks
+ // specificity required to only target immediate child Blocks of Group
+ > .wp-block {
+ padding-left: $block-padding;
+ padding-right: $block-padding;
+
+ @include break-small() {
+ padding-left: 0;
+ padding-right: 0;
+ }
+ }
+
+ // Full Width Blocks
+ // specificity required to only target immediate child Blocks of Group
+ > [data-align="full"] {
+ padding-right: 0;
+ padding-left: 0;
+ left: 0;
+ width: 100%;
+ max-width: none;
+ }
+
+ // Full Width Blocks with a background (ie: has padding)
+ // note: also duplicated above for all Group widths
+ &.has-background > [data-align="full"] {
+ width: calc(100% + 60px);
+ }
+ }
+}
diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js
index 76891cd8449b92..5302be09db319f 100644
--- a/packages/edit-post/src/components/visual-editor/index.js
+++ b/packages/edit-post/src/components/visual-editor/index.js
@@ -8,6 +8,7 @@ import {
import {
WritingFlow,
BlockList,
+ store as blockEditorStore,
__unstableUseBlockSelectionClearer as useBlockSelectionClearer,
__unstableUseTypewriter as useTypewriter,
__unstableUseClipboardHandler as useClipboardHandler,
@@ -16,6 +17,8 @@ import {
__experimentalUseResizeCanvas as useResizeCanvas,
__unstableUseCanvasClickRedirect as useCanvasClickRedirect,
__unstableEditorStyles as EditorStyles,
+ __experimentalUseEditorFeature as useEditorFeature,
+ __experimentalLayoutStyle as LayoutStyle,
} from '@wordpress/block-editor';
import { Popover } from '@wordpress/components';
import { useRef } from '@wordpress/element';
@@ -44,6 +47,10 @@ export default function VisualEditor( { styles } ) {
( select ) => select( editPostStore ).hasMetaBoxes(),
[]
);
+ const themeSupportsLayout = useSelect( ( select ) => {
+ const { getSettings } = select( blockEditorStore );
+ return getSettings().supportsLayout;
+ }, [] );
const desktopCanvasStyles = {
height: '100%',
// Add a constant padding for the typewritter effect. When typing at the
@@ -51,6 +58,12 @@ export default function VisualEditor( { styles } ) {
paddingBottom: hasMetaBoxes ? null : '40vh',
};
const resizedCanvasStyles = useResizeCanvas( deviceType );
+ const defaultLayout = useEditorFeature( 'layout' );
+ const { contentSize, wideSize } = defaultLayout || {};
+ const alignments =
+ contentSize || wideSize
+ ? [ 'wide', 'full' ]
+ : [ 'left', 'center', 'right' ];
const mergedRefs = useMergeRefs( [
ref,
@@ -63,6 +76,12 @@ export default function VisualEditor( { styles } ) {
return (
+ { themeSupportsLayout && (
+
+ ) }
@@ -77,7 +96,19 @@ export default function VisualEditor( { styles } ) {
) }
-
+
<__experimentalBlockSettingsMenuFirstItem>
diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss
index 6c13a6a8d4ad24..22ed97d362d86c 100644
--- a/packages/edit-post/src/style.scss
+++ b/packages/edit-post/src/style.scss
@@ -100,5 +100,4 @@ body.block-editor-page {
}
}
-@include default-block-widths();
@include wordpress-admin-schemes();
diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss
index 9dff7eb78b9988..42894cd6e4ecf4 100644
--- a/packages/edit-site/src/style.scss
+++ b/packages/edit-site/src/style.scss
@@ -76,4 +76,3 @@ body.toplevel_page_gutenberg-edit-site {
}
@include wordpress-admin-schemes();
-@include default-block-widths();
diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js
index 67546ff6710cd3..4198af22591d37 100644
--- a/packages/editor/src/components/provider/use-block-editor-settings.js
+++ b/packages/editor/src/components/provider/use-block-editor-settings.js
@@ -186,6 +186,7 @@ function useBlockEditorSettings( settings, hasTemplate ) {
'template',
'templateLock',
'titlePlaceholder',
+ 'supportsLayout',
] ),
mediaUpload: hasUploadPermissions ? mediaUpload : undefined,
__experimentalReusableBlocks: reusableBlocks,
diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js
index 5399999001d42e..94125a58f1392f 100644
--- a/packages/editor/src/store/defaults.js
+++ b/packages/editor/src/store/defaults.js
@@ -20,6 +20,7 @@ export const PREFERENCES_DEFAULTS = {
* disablePostFormats boolean Whether or not the post formats are disabled
* allowedMimeTypes array? List of allowed mime types and file extensions
* maxUploadFileSize number Maximum upload file size
+ * supportsLayout boolean Whether the editor supports layouts.
*/
export const EDITOR_SETTINGS_DEFAULTS = {
...SETTINGS_DEFAULTS,
@@ -27,4 +28,5 @@ export const EDITOR_SETTINGS_DEFAULTS = {
richEditingEnabled: true,
codeEditingEnabled: true,
enableCustomFields: false,
+ supportsLayout: true,
};