diff --git a/docs/how-to-guides/themes/block-based-themes.md b/docs/how-to-guides/themes/block-based-themes.md index dc931cc203b11c..3368d80fcfff33 100644 --- a/docs/how-to-guides/themes/block-based-themes.md +++ b/docs/how-to-guides/themes/block-based-themes.md @@ -31,7 +31,7 @@ theme |__ ... ``` -The difference with existing WordPress themes is that the different templates in the template hierarchy, and template parts, are block templates instead of php files. In addition, this example includes an [`experimental-theme.json`](/docs/how-to-guides/themes/theme-json.md) file for some styles. +The difference with existing WordPress themes is that the different templates in the template hierarchy, and template parts, are block templates instead of php files. In addition, this example includes an [`experimental-theme.json`](/docs/how-to-guides/themes/theme-json.md) file for some styles. ## What is a block template? @@ -50,20 +50,16 @@ Here's an example of a block template:
-
- - -
+ +
-
- -

Footer

- -
+ +

Footer

+
``` @@ -72,23 +68,23 @@ Here's an example of a block template: Ultimately, any WordPress user with the correct capabilities (example: `administrator` WordPress role) will be able to access these templates in the WordPress admin, edit them in dedicated views and potentially export them as a theme. -As of Gutenberg 8.5, there are two ways to create and edit templates within Gutenberg. +As of Gutenberg 8.5, there are two ways to create and edit templates within Gutenberg. ### Edit templates within The "Appearance" section of WP-Admin You can navigate to the temporary "Templates" admin menu under "Appearance" `wp-admin/edit.php?post_type=wp_template` and use this as a playground to edit your templates. Add blocks here and switch to the code editor mode to grab the HTML of the template when you are done. Afterwards, you can paste that markup into a file in your theme directory. -Please note that the "Templates" admin menu under "Appearance" will _not_ list templates that are bundled with your theme. It only lists new templates created by the specific WordPress site you're working on. +Please note that the "Templates" admin menu under "Appearance" will _not_ list templates that are bundled with your theme. It only lists new templates created by the specific WordPress site you're working on. ### Edit Templates within the Full-site Editor -To begin, create a blank template file within your theme. For example: `mytheme/block-templates/index.html`. Afterwards, open the Full-site editor. Your new template should appear as the active template, and should be blank. Add blocks as you normally would using Gutenberg. You can add and create template parts directly using the "Template Parts" block. +To begin, create a blank template file within your theme. For example: `mytheme/block-templates/index.html`. Afterwards, open the Full-site editor. Your new template should appear as the active template, and should be blank. Add blocks as you normally would using Gutenberg. You can add and create template parts directly using the "Template Parts" block. -Repeat for any additional templates you'd like to bundle with your theme. +Repeat for any additional templates you'd like to bundle with your theme. -When you're done, click the "Export Theme" option in the "Tools" (ellipsis) menu of the site editor. This will provide you with a ZIP download of all the templates and template parts you've created in the site editor. These new HTML files can be placed directly into your theme. +When you're done, click the "Export Theme" option in the "Tools" (ellipsis) menu of the site editor. This will provide you with a ZIP download of all the templates and template parts you've created in the site editor. These new HTML files can be placed directly into your theme. -Note that when you export template parts this way, the template part block markup will include a `postID` attribute that can be safely removed when distributing your theme. +Note that when you export template parts this way, the template part block markup will include a `postID` attribute that can be safely removed when distributing your theme. ## Templates CPT @@ -102,33 +98,33 @@ Note that it won't take precedence over any of your theme's templates with highe Some blocks have been made specifically for block-based themes. For example, you'll most likely use the **Site Title** block in your site's header while your **single** block template will most likely include a **Post Title** and a **Post Content** block. -As we're still early in the process, the number of blocks specifically dedicated to these block templates is relatively small but more will be added as we move forward with the project. As of Gutenberg 8.5, the following blocks are currently available: - -- Site Title -- Template Part -- Query -- Query Loop -- Query Pagination -- Post Title -- Post Content -- Post Author -- Post Comment -- Post Comment Author -- Post Comment Date -- Post Comments -- Post Comments Count -- Post Comments Form -- Post Date -- Post Excerpt -- Post Featured Image -- Post Hierarchical Terms -- Post Tags +As we're still early in the process, the number of blocks specifically dedicated to these block templates is relatively small but more will be added as we move forward with the project. As of Gutenberg 8.5, the following blocks are currently available: + +- Site Title +- Template Part +- Query +- Query Loop +- Query Pagination +- Post Title +- Post Content +- Post Author +- Post Comment +- Post Comment Author +- Post Comment Date +- Post Comments +- Post Comments Count +- Post Comments Form +- Post Date +- Post Excerpt +- Post Featured Image +- Post Hierarchical Terms +- Post Tags ## Styling -One of the most important aspects of themes (if not the most important) is the styling. While initially you'll be able to provide styles and enqueue them using the same hooks themes have always used, the [Global Styles](/docs/how-to-guides/themes/theme-json.md) effort will provide a scaffolding for adding many theme styles in the future. +One of the most important aspects of themes (if not the most important) is the styling. While initially you'll be able to provide styles and enqueue them using the same hooks themes have always used, the [Global Styles](/docs/how-to-guides/themes/theme-json.md) effort will provide a scaffolding for adding many theme styles in the future. ## Resources -- [Full Site Editing](https://github.com/WordPress/gutenberg/labels/%5BFeature%5D%20Full%20Site%20Editing) label. -- [Theme Experiments](https://github.com/WordPress/theme-experiments) repository, full of block-based theme examples created by the WordPress community. +- [Full Site Editing](https://github.com/WordPress/gutenberg/labels/%5BFeature%5D%20Full%20Site%20Editing) label. +- [Theme Experiments](https://github.com/WordPress/theme-experiments) repository, full of block-based theme examples created by the WordPress community. diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index 74831234b7f547..42b8c950d736d2 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -25,9 +25,16 @@ The Block Editor API has evolved at different velocities and there are some grow This describes the current efforts to consolidate the various APIs related to styles into a single point – a `experimental-theme.json` file that should be located inside the root of the theme directory. +### Global settings for the block editor + +Instead of the proliferation of theme support flags or alternative methods, the `experimental-theme.json` files provides a canonical way to define the settings of the block editor. These settings includes things like: + + - What customization options should be made available or hidden from the user. + - What are the default colors, font sizes... available to the user. + - Defines the default layout of the editor. (widths and available alignments). ### Settings can be controlled per block -The Block Editor already allows the control of specific settings such as alignment, drop cap, presets available, etc. All of these work at the block level. By using the `experimental-theme.json` we aim to allow themes to control these at a block level. +For more granularity, these settings also work at the block level in `experimental-theme.json`. Examples of what can be achieved are: @@ -156,7 +163,11 @@ The settings section has the following structure and default values: ``` { "settings": { - "some/block": { + "defaults": { + "layout": { /* Default layout to be used in the post editor */ + "contentSize": "800px", + "wideSize": "1000px", + } "border": { "customRadius": false /* true to opt-in */ }, diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php new file mode 100644 index 00000000000000..1abc69c7362da5 --- /dev/null +++ b/lib/block-supports/layout.php @@ -0,0 +1,150 @@ +supports, array( '__experimentalLayout' ), false ); + } + if ( $support_layout ) { + if ( ! $block_type->attributes ) { + $block_type->attributes = array(); + } + + if ( ! array_key_exists( 'layout', $block_type->attributes ) ) { + $block_type->attributes['layout'] = array( + 'type' => 'object', + ); + } + } +} + +/** + * Renders the layout config to the block wrapper. + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function gutenberg_render_layout_support_flag( $block_content, $block ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $support_layout = false; + if ( $block_type && property_exists( $block_type, 'supports' ) ) { + $support_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout' ), false ); + } + if ( ! $support_layout || ! isset( $block['attrs']['layout'] ) ) { + return $block_content; + } + + $used_layout = $block['attrs']['layout']; + if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) { + $tree = WP_Theme_JSON_Resolver::get_merged_data( array(), 'theme' ); + $default_layout = _wp_array_get( $tree->get_settings(), array( 'defaults', 'layout' ) ); + if ( ! $default_layout ) { + return $block_content; + } + $used_layout = $default_layout; + } + + $id = uniqid(); + $content_size = isset( $used_layout['contentSize'] ) ? $used_layout['contentSize'] : null; + $wide_size = isset( $used_layout['wideSize'] ) ? $used_layout['wideSize'] : null; + + $style = ''; + if ( $content_size || $wide_size ) { + ob_start(); + ?> + > * { + max-width: ; + margin-left: auto; + margin-right: auto; + } + + > .alignwide { + max-width: ; + } + + .alignfull { + max-width: none; + } + + .alignleft { + float: left; + margin-right: 2em; + } + + .alignright { + float: right; + margin-left: 2em; + } + ' . $style . ''; +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'layout', + array( + 'register_attribute' => 'gutenberg_register_layout_support', + ) +); +add_filter( 'render_block', 'gutenberg_render_layout_support_flag', 10, 2 ); + +/** + * For themes without theme.json file, make sure + * to restore the inner div for the group block + * to avoid breaking styles relying on that div. + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function gutenberg_restore_group_inner_container( $block_content, $block ) { + $group_with_inner_container_regex = '/(^(\s|\S)*]*wp-block-group(\s|")[^>]*>)(([\s]|\S)*]*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', () => {
-
+
  • B
  • C

D

-
+
@@ -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": "
\n\t
\n\t\t\n\n\t\t
\n\t
" + "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\t
\n\t\t\n\n\t\t
\n\t
\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 @@ -
+

My paragraph

-
+
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": "
\n\t
\n\t\t\n\n\t\t\n\t
\n
" + } +] 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\t
\n\t\t\n\n\t\t\n\t
\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 @@ -
+

test

-
+
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`] = ` " -
+

p1

-
+
" `; 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`] = ` " -
+

Paragraph inside group

-
+
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`] = ` " -
+

just a paragraph


-
+
" `; 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, };