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 all 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
45 changes: 25 additions & 20 deletions lib/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,30 +226,35 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$class_names[] = sanitize_title( $layout_classname );
}

$gap_value = _wp_array_get( $block, array( 'attrs', 'style', 'spacing', 'blockGap' ) );
// Skip if gap value contains unsupported characters.
// Regex for CSS value borrowed from `safecss_filter_attr`, and used here
// because we only want to match against the value, not the CSS attribute.
if ( is_array( $gap_value ) ) {
foreach ( $gap_value as $key => $value ) {
$gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
// Only generate Layout styles if the theme has not opted-out.
// Attribute-based Layout classnames are output in all cases.
if ( ! current_theme_supports( 'disable-layout-styles' ) ) {

$gap_value = _wp_array_get( $block, array( 'attrs', 'style', 'spacing', 'blockGap' ) );
// Skip if gap value contains unsupported characters.
// Regex for CSS value borrowed from `safecss_filter_attr`, and used here
// because we only want to match against the value, not the CSS attribute.
if ( is_array( $gap_value ) ) {
foreach ( $gap_value as $key => $value ) {
$gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
}
} else {
$gap_value = $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
}
} else {
$gap_value = $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
}

$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 );
$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 );
// 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 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
31 changes: 31 additions & 0 deletions lib/compat/wordpress-6.1/theme.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/**
* Temporary compatibility shims for features present in Gutenberg.
* This file should be removed when WordPress 6.1.0 becomes the lowest
* supported version by this plugin.
*
* @package gutenberg
*/

/**
* This function runs in addition to the core `create_initial_theme_features`.
* The 6.1 release needs to update the core function to include the body of this function.
*
* Creates the initial theme features when the 'setup_theme' action is fired.
*
* See {@see 'setup_theme'}.
*
* @since 5.5.0
* @since 6.0.1 The `block-templates` feature was added.
* @since 6.1.0 The `disable-layout-styles` feature was added.
*/
function gutenberg_create_initial_theme_features() {
register_theme_feature(
'disable-layout-styles',
array(
'description' => __( 'Whether the theme disables generated layout styles.', 'gutenberg' ),
'show_in_rest' => true,
)
);
}
add_action( 'setup_theme', 'gutenberg_create_initial_theme_features', 0 );
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
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.1/date-settings.php';
require __DIR__ . '/compat/wordpress-6.1/block-patterns.php';
require __DIR__ . '/compat/wordpress-6.1/edit-form-blocks.php';
require __DIR__ . '/compat/wordpress-6.1/theme.php';

// Experimental features.
remove_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); // Turns off WP 6.0's stopgap handler for Webfonts API.
Expand Down
10 changes: 8 additions & 2 deletions packages/block-editor/src/hooks/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,16 @@ export const withInspectorControls = createHigherOrderComponent(
export const withLayoutStyles = createHigherOrderComponent(
( BlockListBlock ) => ( props ) => {
const { name, attributes } = props;
const shouldRenderLayoutStyles = hasBlockSupport(
const hasLayoutBlockSupport = hasBlockSupport(
name,
layoutBlockSupportKey
);
const disableLayoutStyles = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
return !! getSettings().disableLayoutStyles;
} );
const shouldRenderLayoutStyles =
hasLayoutBlockSupport && ! disableLayoutStyles;
const id = useInstanceId( BlockListBlock );
const defaultThemeLayout = useSetting( 'layout' ) || {};
const element = useContext( BlockList.__unstableElementContext );
Expand All @@ -277,7 +283,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,
disableLayoutStyles = 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 (
! disableLayoutStyles &&
( ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport )
) {
ruleset += getLayoutStyles( {
tree,
style: styles,
Expand Down Expand Up @@ -769,6 +775,10 @@ 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 disableLayoutStyles = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
return !! getSettings().disableLayoutStyles;
} );

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

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