Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flex layout support to the block editor #33359

Merged
merged 11 commits into from
Jul 26, 2021
100 changes: 62 additions & 38 deletions lib/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,56 @@ function gutenberg_register_layout_support( $block_type ) {
}
}

/**
* Generates the CSS corresponding to the provided layout.
*
* @param string $selector CSS selector.
* @param array $layout Layout object.
*
* @return string CSS style.
*/
function gutenberg_get_layout_style( $selector, $layout ) {
$layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default';

$style = '';
if ( 'default' === $layout_type ) {
$content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : null;
$wide_size = isset( $layout['wideSize'] ) ? $layout['wideSize'] : null;

$all_max_width_value = $content_size ? $content_size : $wide_size;
$wide_max_width_value = $wide_size ? $wide_size : $content_size;

// Make sure there is a single CSS rule, and all tags are stripped for security.
// TODO: Use `safecss_filter_attr` instead - once https://core.trac.wordpress.org/ticket/46197 is patched.
$all_max_width_value = wp_strip_all_tags( explode( ';', $all_max_width_value )[0] );
$wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] );

$style = '';
if ( $content_size || $wide_size ) {
$style = "$selector > * {";
$style .= 'max-width: ' . esc_html( $all_max_width_value ) . ';';
$style .= 'margin-left: auto !important;';
$style .= 'margin-right: auto !important;';
$style .= '}';

$style .= "$selector > .alignwide { max-width: " . esc_html( $wide_max_width_value ) . ';}';

$style .= "$selector .alignfull { max-width: none; }";
}

$style .= "$selector .alignleft { float: left; margin-right: 2em; }";
$style .= "$selector .alignright { float: right; margin-left: 2em; }";
} elseif ( 'flex' === $layout_type ) {
$style = "$selector {";
$style .= 'display: flex;';
$style .= 'column-gap: 0.5em;';
$style .= 'align-items: center;';
$style .= '}';
}

return $style;
}

/**
* Renders the layout config to the block wrapper.
*
Expand All @@ -49,34 +99,8 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$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;

$all_max_width_value = $content_size ? $content_size : $wide_size;
$wide_max_width_value = $wide_size ? $wide_size : $content_size;

// Make sure there is a single CSS rule, and all tags are stripped for security.
// TODO: Use `safecss_filter_attr` instead - once https://core.trac.wordpress.org/ticket/46197 is patched.
$all_max_width_value = wp_strip_all_tags( explode( ';', $all_max_width_value )[0] );
$wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] );

$style = '';
if ( $content_size || $wide_size ) {
$style = ".wp-container-$id > * {";
$style .= 'max-width: ' . esc_html( $all_max_width_value ) . ';';
$style .= 'margin-left: auto !important;';
$style .= 'margin-right: auto !important;';
$style .= '}';

$style .= ".wp-container-$id > .alignwide { max-width: " . esc_html( $wide_max_width_value ) . ';}';

$style .= ".wp-container-$id .alignfull { max-width: none; }";
}

$style .= ".wp-container-$id .alignleft { float: left; margin-right: 2em; }";
$style .= ".wp-container-$id .alignright { float: right; margin-left: 2em; }";

$id = uniqid();
$style = gutenberg_get_layout_style( ".wp-container-$id", $used_layout );
// This assumes the hook only applies to blocks with a single wrapper.
// I think this is a reasonable limitation for that particular hook.
$content = preg_replace(
Expand All @@ -99,17 +123,17 @@ function () use ( $style ) {
return $content;
}

// This can be removed when plugin support requires WordPress 5.8.0+.
if ( ! function_exists( 'wp_render_layout_support_flag' ) ) {
// 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 );
// Register the block support. (overrides core one).
WP_Block_Supports::get_instance()->register(
'layout',
array(
'register_attribute' => 'gutenberg_register_layout_support',
)
);
if ( function_exists( 'wp_render_layout_support_flag' ) ) {
remove_filter( 'render_block', 'wp_render_layout_support_flag' );
}
add_filter( 'render_block', 'gutenberg_render_layout_support_flag', 10, 2 );

/**
* For themes without theme.json file, make sure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,35 @@ import { useSelect } from '@wordpress/data';
*/
import { useLayout } from '../block-list/layout';
import { store as blockEditorStore } from '../../store';
import { getLayoutType } from '../../layouts';

const DEFAULT_CONTROLS = [ 'left', 'center', 'right', 'wide', 'full' ];
const WIDE_CONTROLS = [ 'wide', 'full' ];

export default function useAvailableAlignments( controls = DEFAULT_CONTROLS ) {
const { wideControlsEnabled = false } = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
const settings = getSettings();
return {
wideControlsEnabled: settings.alignWide,
};
}, [] );
const { wideControlsEnabled = false, themeSupportsLayout } = useSelect(
( select ) => {
const { getSettings } = select( blockEditorStore );
const settings = getSettings();
return {
wideControlsEnabled: settings.alignWide,
themeSupportsLayout: settings.supportsLayout,
};
},
[]
);
const layout = useLayout();
const supportsAlignments = layout.type === 'default';
const layoutType = getLayoutType( layout?.type );
const layoutAlignments = layoutType.getAlignments( layout );

if ( themeSupportsLayout ) {
return layoutAlignments.filter( ( control ) =>
controls.includes( control )
);
}

if ( ! supportsAlignments ) {
// Starting here, it's the fallback for themes not supporting the layout config.
if ( layoutType.name !== 'default' ) {
return [];
}
const { alignments: availableAlignments = DEFAULT_CONTROLS } = layout;
Expand Down
60 changes: 11 additions & 49 deletions packages/block-editor/src/components/block-list/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,15 @@
*/
import { createContext, useContext } from '@wordpress/element';

/**
* Internal dependencies
*/
import { getLayoutType } from '../../layouts';

export const defaultLayout = { type: 'default' };

const Layout = createContext( defaultLayout );

function appendSelectors( selectors, append ) {
// Ideally we shouldn't need the `.editor-styles-wrapper` increased specificity here
// The problem though is that we have a `.editor-styles-wrapper p { margin: reset; }` style
// it's used to reset the default margin added by wp-admin to paragraphs
// so we need this to be higher speficity otherwise, it won't be applied to paragraphs inside containers
// When the post editor is fully iframed, this extra classname could be removed.

return selectors
.split( ',' )
.map(
( subselector ) =>
`.editor-styles-wrapper ${ subselector } ${ append }`
)
.join( ',' );
}

/**
* Allows to define the layout.
*/
Expand All @@ -35,39 +24,12 @@ 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 !important;
margin-right: auto !important;
}

${ 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;
}
export function LayoutStyle( { layout = {}, ...props } ) {
const layoutType = getLayoutType( layout.type );

${ appendSelectors( selector, '> [data-align="right"]' ) } {
float: right;
margin-left: 2em;
}
`;
if ( layoutType ) {
return <layoutType.save layout={ layout } { ...props } />;
}

return <style>{ style }</style>;
return null;
}
9 changes: 0 additions & 9 deletions packages/block-editor/src/components/block-list/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -251,15 +251,6 @@
&[data-clear="true"] {
float: none;
}

// This essentially duplicates the mobile styles for the appender component.
// It would be nice to be able to use element queries in that component instead https://github.com/tomhodgins/element-queries-spec
.block-editor-block-list__layout {
.block-editor-default-block-appender .block-editor-inserter {
left: auto;
right: $grid-unit-10;
}
}
}

.is-outline-mode .block-editor-block-list__block:not(.remove-outline) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -37,7 +42,9 @@ export function DefaultBlockAppender( {
return (
<div
data-root-client-id={ rootClientId || '' }
className="block-editor-default-block-appender"
className={ classnames( 'block-editor-default-block-appender', {
'has-visible-prompt': showPrompt,
} ) }
>
<p
tabIndex="0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
// Left side.
.block-editor-block-list__empty-block-inserter,
.block-editor-default-block-appender .block-editor-inserter {
right: $grid-unit-10; // Show to the right on mobile.

@include break-small {
display: flex;
height: 100%;
Expand All @@ -49,3 +47,8 @@
display: none;
}
}

.block-editor-default-block-appender.has-visible-prompt .block-editor-inserter,
.block-editor-block-list__empty-block-inserter {
right: 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`DefaultBlockAppender should append a default block when input focused 1`] = `
<div
className="block-editor-default-block-appender"
className="block-editor-default-block-appender has-visible-prompt"
data-root-client-id=""
>
<p
Expand Down Expand Up @@ -38,7 +38,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1

exports[`DefaultBlockAppender should match snapshot 1`] = `
<div
className="block-editor-default-block-appender"
className="block-editor-default-block-appender has-visible-prompt"
data-root-client-id=""
>
<p
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ function UncontrolledInnerBlocks( props ) {
allowedBlocks,
templateLock,
captureToolbars,
orientation
orientation,
__experimentalLayout
);

useInnerBlockTemplateSync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import isShallowEqual from '@wordpress/is-shallow-equal';
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { getLayoutType } from '../../layouts';

/**
* This hook is a side effect which updates the block-editor store when changes
Expand All @@ -27,13 +28,15 @@ import { store as blockEditorStore } from '../../store';
* the child block.
* @param {string} orientation The direction in which the block
* should face.
* @param {Object} layout The layout object for the block container.
*/
export default function useNestedSettingsUpdate(
clientId,
allowedBlocks,
templateLock,
captureToolbars,
orientation
orientation,
layout
) {
const { updateBlockListSettings } = useDispatch( blockEditorStore );

Expand Down Expand Up @@ -71,8 +74,13 @@ export default function useNestedSettingsUpdate(
newSettings.__experimentalCaptureToolbars = captureToolbars;
}

// Orientation depends on layout,
// ideally the separate orientation prop should be deprecated.
if ( orientation !== undefined ) {
newSettings.orientation = orientation;
} else {
const layoutType = getLayoutType( layout?.type );
newSettings.orientation = layoutType.getOrientation( layout );
}

if ( ! isShallowEqual( blockListSettings, newSettings ) ) {
Expand All @@ -87,5 +95,6 @@ export default function useNestedSettingsUpdate(
captureToolbars,
orientation,
updateBlockListSettings,
layout,
] );
}
Loading