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

Layout: Add a disable-layout-styles theme supports flag to opt out of all layout styles #42544

Merged
merged 5 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/how-to-guides/themes/theme-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,18 @@ add_theme_support( 'disable-custom-gradients' );

When set, users will be restricted to the default gradients provided in the block editor or the gradients provided via the `editor-gradient-presets` theme support setting.

### Disabling base layout styles

_**Note:** Since WordPress 6.1._

Themes can opt out of generated block layout styles that provide default structural styles for core blocks including Group, Columns, Buttons, and Social Icons. By using the following code, these themes commit to providing their own structural styling, as using this feature will result in core blocks displaying incorrectly in both the editor and site frontend:

```php
add_theme_support( 'disable-layout-styles' );
```

For themes looking to customize `blockGap` styles or block spacing, see [the developer docs on Global Settings & Styles](/docs/how-to-guides/themes/theme-json/#what-is-blockgap-and-how-can-i-use-it).

### Supporting custom line heights

Some blocks like paragraph and headings support customizing the line height. Themes can enable support for this feature with the following code:
Expand Down
21 changes: 12 additions & 9 deletions lib/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,18 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$fallback_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), '0.5em' );
$block_spacing = _wp_array_get( $block, array( 'attrs', 'style', 'spacing' ), null );

// If a block's block.json skips serialization for spacing or spacing.blockGap,
// don't apply the user-defined value to the styles.
$should_skip_gap_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' );
$style = gutenberg_get_layout_style( ".$block_classname.$container_class", $used_layout, $has_block_gap_support, $gap_value, $should_skip_gap_serialization, $fallback_gap_value, $block_spacing );

// Only add container class and enqueue block support styles if unique styles were generated.
if ( ! empty( $style ) ) {
$class_names[] = $container_class;
wp_enqueue_block_support_styles( $style );
// Only generate Layout styles if the theme has not opted-out.
if ( ! current_theme_supports( 'disable-layout-styles' ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also a bunch of layout-related logic towards the top of this function. Would it make sense to return early instead of adding this condition here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for testing! In this case, I don't think we can return early, because we still want the Layout support's semantic classnames to be added to the output which happens at line 260. Part of the feedback that inspired this PR was that there are Classic themes that would like the semantic classnames like layout type and content justification to be available, but not the generated Layout styles themselves.

It does make it a bit of an awkward place for the check to live, though! I suppose we could move it up to line 228 so that this if block includes the $gap_value declaration and everything below that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh, of course, that makes sense 🤦 Yeah, I guess we could move it up a little bit, so there are no unnecessary calculations performed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved it up a little in d77120e, which feels a bit more logical to me now, thanks for the suggestion! Also added a comment about the classnames so it's clearer why we're not returning early.

Separately to this PR, now that we've got a fair few classes being generated it might be worth us re-exploring splitting out the classnames logic to a separate function to make it a bit easier to read. We had it in a separate function for a little while but reverted in #41885 so that we could give it some more thought before committing to a particular approach that we then need to continue to support. But 🤞 we can settle on an approach before 6.1.

// If a block's block.json skips serialization for spacing or spacing.blockGap,
// don't apply the user-defined value to the styles.
$should_skip_gap_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' );
$style = gutenberg_get_layout_style( ".$block_classname.$container_class", $used_layout, $has_block_gap_support, $gap_value, $should_skip_gap_serialization, $fallback_gap_value, $block_spacing );

// Only add container class and enqueue block support styles if unique styles were generated.
if ( ! empty( $style ) ) {
$class_names[] = $container_class;
wp_enqueue_block_support_styles( $style );
}
}

// This assumes the hook only applies to blocks with a single wrapper.
Expand Down
1 change: 1 addition & 0 deletions lib/compat/wordpress-6.1/block-editor-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ function gutenberg_get_block_editor_settings( $settings ) {
}

$settings['localAutosaveInterval'] = 15;
$settings['disableLayoutStyles'] = current_theme_supports( 'disable-layout-styles' );

return $settings;
}
Expand Down
5 changes: 5 additions & 0 deletions lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,11 @@ protected function get_layout_styles( $block_metadata ) {
$block_rules = '';
$block_type = null;

// Skip outputting layout styles if explicitly disabled.
if ( current_theme_supports( 'disable-layout-styles' ) ) {
return $block_rules;
}

if ( isset( $block_metadata['name'] ) ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] );
if ( ! block_has_support( $block_type, array( '__experimentalLayout' ), false ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ public function get_item_schema() {
'context' => array( 'post-editor', 'site-editor', 'widgets-editor' ),
),

'disableLayoutStyles' => array(
'description' => __( 'Disables output of layout styles.', 'gutenberg' ),
'type' => 'boolean',
'context' => array( 'post-editor', 'site-editor', 'widgets-editor' ),
),

'enableCustomLineHeight' => array(
'description' => __( 'Enables custom line height.', 'gutenberg' ),
'type' => 'boolean',
Expand Down
22 changes: 20 additions & 2 deletions packages/block-editor/src/hooks/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ function useLayoutClasses( layout, layoutDefinitions ) {
return layoutClassnames;
}

/**
* Determines whether or not the theme has disabled all layout styles output.
*
* This feature only disables the output of layout styles,
* the controls for adjusting layout will still be available in the editor.
* Themes that use this feature commit to providing their own styling for layout features.
*
* @return {boolean} Whether or not the theme opts-in to disable all layout styles.
*/
function useThemeHasDisabledLayoutStyles() {
return useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
return !! getSettings().disableLayoutStyles;
} );
}

function LayoutPanel( { setAttributes, attributes, name: blockName } ) {
const { layout } = attributes;
const defaultThemeLayout = useSetting( 'layout' );
Expand Down Expand Up @@ -264,10 +280,12 @@ export const withInspectorControls = createHigherOrderComponent(
export const withLayoutStyles = createHigherOrderComponent(
( BlockListBlock ) => ( props ) => {
const { name, attributes } = props;
const shouldRenderLayoutStyles = hasBlockSupport(
const hasLayoutBlockSupport = hasBlockSupport(
name,
layoutBlockSupportKey
);
const shouldRenderLayoutStyles =
hasLayoutBlockSupport && ! useThemeHasDisabledLayoutStyles();
const id = useInstanceId( BlockListBlock );
const defaultThemeLayout = useSetting( 'layout' ) || {};
const element = useContext( BlockList.__unstableElementContext );
Expand All @@ -277,7 +295,7 @@ export const withLayoutStyles = createHigherOrderComponent(
const usedLayout = layout?.inherit
? defaultThemeLayout
: layout || defaultBlockLayout || {};
const layoutClasses = shouldRenderLayoutStyles
const layoutClasses = hasLayoutBlockSupport
? useLayoutClasses( usedLayout, defaultThemeLayout?.definitions )
: null;
const selector = `.${ getBlockDefaultClassName(
Expand Down
34 changes: 20 additions & 14 deletions packages/edit-post/src/components/visual-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,15 @@ export default function VisualEditor( { styles } ) {
( select ) => select( editPostStore ).hasMetaBoxes(),
[]
);
const { themeSupportsLayout, assets } = useSelect( ( select ) => {
const _settings = select( blockEditorStore ).getSettings();
return {
themeSupportsLayout: _settings.supportsLayout,
assets: _settings.__unstableResolvedAssets,
};
}, [] );
const { themeHasDisabledLayoutStyles, themeSupportsLayout, assets } =
useSelect( ( select ) => {
const _settings = select( blockEditorStore ).getSettings();
return {
themeHasDisabledLayoutStyles: _settings.disableLayoutStyles,
themeSupportsLayout: _settings.supportsLayout,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a new key config when we already have settings.supportsLayout ? Are these two things different and why?

Copy link
Contributor Author

@andrewserong andrewserong Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The names are unfortunately similar, but disableLayoutStyles only disables the layout styles output, but not the controls or the classname output. The use case is for themes that wish to provide their own styling rules for layout, using the classnames output by the controls / layout support. It's been a while now since we implemented this feature, but from memory, this was one of those features that helped some theme developers to adopt layout controls when they weren't ready yet to adopt all the generated layout styles.

assets: _settings.__unstableResolvedAssets,
};
}, [] );
const { clearSelectedBlock } = useDispatch( blockEditorStore );
const { setIsEditingTemplate } = useDispatch( editPostStore );
const desktopCanvasStyles = {
Expand Down Expand Up @@ -241,13 +243,17 @@ export default function VisualEditor( { styles } ) {
assets={ assets }
style={ { paddingBottom } }
>
{ themeSupportsLayout && ! isTemplateMode && (
<LayoutStyle
selector=".edit-post-visual-editor__post-title-wrapper, .block-editor-block-list__layout.is-root-container"
layout={ defaultLayout }
layoutDefinitions={ defaultLayout?.definitions }
/>
) }
{ themeSupportsLayout &&
! themeHasDisabledLayoutStyles &&
! isTemplateMode && (
<LayoutStyle
selector=".edit-post-visual-editor__post-title-wrapper, .block-editor-block-list__layout.is-root-container"
layout={ defaultLayout }
layoutDefinitions={
defaultLayout?.definitions
}
/>
) }
{ ! isTemplateMode && (
<div
className="edit-post-visual-editor__post-title-wrapper"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import {
__EXPERIMENTAL_ELEMENTS as ELEMENTS,
getBlockTypes,
} from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { useEffect, useState, useContext } from '@wordpress/element';
import { getCSSRules } from '@wordpress/style-engine';
import {
__unstablePresetDuotoneFilter as PresetDuotoneFilter,
__experimentalGetGapCSSValue as getGapCSSValue,
store as blockEditorStore,
} from '@wordpress/block-editor';

/**
Expand Down Expand Up @@ -536,7 +538,8 @@ export const toStyles = (
tree,
blockSelectors,
hasBlockGapSupport,
hasFallbackGapSupport
hasFallbackGapSupport,
skipLayoutStyles = false
) => {
const nodesWithStyles = getNodesWithStyles( tree, blockSelectors );
const nodesWithSettings = getNodesWithSettings( tree, blockSelectors );
Expand Down Expand Up @@ -612,7 +615,10 @@ export const toStyles = (
}

// Process blockGap and layout styles.
if ( ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport ) {
if (
! skipLayoutStyles &&
( ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport )
) {
ruleset += getLayoutStyles( {
tree,
style: styles,
Expand Down Expand Up @@ -761,6 +767,22 @@ export const getBlockSelectors = ( blockTypes ) => {
return result;
};

/**
* Determines whether or not the theme has disabled all layout styles output.
*
* This feature only disables the output of layout styles,
* the controls for adjusting layout will still be available in the editor.
* Themes that use this feature commit to providing their own styling for layout features.
*
* @return {boolean} Whether or not the theme opts-in to disable all layout styles.
*/
function useThemeHasDisabledLayoutStyles() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the site editor package access the hook from the block editor package instead of duplicating it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question — I wasn't sure if we wanted to expose it for use anywhere else so left it as a duplication for now. But if it's an official theme support, then yes, it probably makes better sense to have it defined once in the block editor package, and then export it 👍

I'll have a play.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After taking another look, I think it makes the code a little clearer if we don't actually use the hook abstraction. It didn't add much, and meant that if you wanted to see what it was actually checking you'd have to look at the implementation of the hook. In 6b6dadb I've switched to inlining the settings checks, so that it's closer to the feature name, which I think on balance, is a little easier to read.

Happy to change it, though, if you think there's a better way to handle it!

return useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
return !! getSettings().disableLayoutStyles;
} );
}

export function useGlobalStylesOutput() {
const [ stylesheets, setStylesheets ] = useState( [] );
const [ settings, setSettings ] = useState( {} );
Expand All @@ -769,6 +791,7 @@ export function useGlobalStylesOutput() {
const [ blockGap ] = useSetting( 'spacing.blockGap' );
const hasBlockGapSupport = blockGap !== null;
const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support.
const skipLayoutStyles = useThemeHasDisabledLayoutStyles();

useEffect( () => {
if ( ! mergedConfig?.styles || ! mergedConfig?.settings ) {
Expand All @@ -784,7 +807,8 @@ export function useGlobalStylesOutput() {
mergedConfig,
blockSelectors,
hasBlockGapSupport,
hasFallbackGapSupport
hasFallbackGapSupport,
skipLayoutStyles
);
const filters = toSvgFilters( mergedConfig, blockSelectors );
setStylesheets( [
Expand All @@ -799,7 +823,12 @@ export function useGlobalStylesOutput() {
] );
setSettings( mergedConfig.settings );
setSvgFilters( filters );
}, [ hasBlockGapSupport, hasFallbackGapSupport, mergedConfig ] );
}, [
hasBlockGapSupport,
hasFallbackGapSupport,
mergedConfig,
skipLayoutStyles,
] );

return [ stylesheets, settings, svgFilters, hasBlockGapSupport ];
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ function useBlockEditorSettings( settings, hasTemplate ) {
'disableCustomColors',
'disableCustomFontSizes',
'disableCustomGradients',
'disableLayoutStyles',
'enableCustomLineHeight',
'enableCustomSpacing',
'enableCustomUnits',
Expand Down
31 changes: 31 additions & 0 deletions phpunit/class-wp-theme-json-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,37 @@ public function test_get_stylesheet_generates_base_fallback_gap_layout_styles( $
);
}

/**
* @dataProvider data_get_layout_definitions
*
* @param array $layout_definitions Layout definitions as stored in core theme.json.
*/
public function test_get_stylesheet_skips_layout_styles( $layout_definitions ) {
add_theme_support( 'disable-layout-styles' );
$theme_json = new WP_Theme_JSON_Gutenberg(
array(
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
'settings' => array(
'layout' => array(
'definitions' => $layout_definitions,
),
'spacing' => array(
'blockGap' => null,
),
),
),
'default'
);
$stylesheet = $theme_json->get_stylesheet( array( 'base-layout-styles' ) );
remove_theme_support( 'disable-layout-styles' );

// All Layout styles should be skipped.
$this->assertEquals(
'',
$stylesheet
);
}

/**
* Data provider.
*
Expand Down