From 60a8018958b61461df894db52e06e5da78bbd6c0 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 6 May 2022 16:59:42 +1000 Subject: [PATCH 01/14] Layout blockGap: Try using classnames to support block-level gap in theme.json Try implementing partially in editor Try adding block classname to the container class to deal with specificity, remove fallback gap Add fallback gap styles rendered at root Move changes to theme.json class 'up' to 6-1 file Fix rendering block-level blockGap set in the block's attributes in the post editor Implement changes in site editor / global styles comparable to PHP changes Try moving some of the layout definitions to theme.json Move layout style generation to a separate function Implement theme.json definitions approach in the site editor, ensure styles load correctly in the post editor Remove class duplication, use classname stored in theme.json instead of hard-coded classname Support split row/column values at the block level, and include output of the legacy CSS variable for backwards compatibility Ensure CSS variable is only output if gap support is opted-in Tweak tests Whitespace fix Update test Remove duplication block class from server-rendered output, update snapshot Fix failing PHP test Attempt to fix PHP test again Manually fix snapshot Fix PHP linting issue Linting Reorganise rules in theme.json Remove dead code Render base styles and only output container classes and styles if unique values are generated Move blockGap styles in global styles to a separate getLayoutStyles function Move layout_definitions up so that it's always available to base styles Linting fixes Update test snapshot Add baseStyles output to global styles Remove test snapshot Update layout supports to return a CSS string instead of a component, add check that string is non-empty before outputting container classnames and style tags Update flex layout to only output styles if unique values are set Fix is-root-container styling in post editor Update flex/flow layouts to look up layout definitions to generate gap styling Move blockGap JS logic to a shared utility function, add tests Add test in case layoutDefinitions is undefined Add minimal tests that flex and flow layouts that don't contain non-default values return empty strings Fix rebase in 6-1.php file Consolidate JS layout classnames generation Further consolidate classname generation Implement outputting non-default layout gap for classic themes Update fallback gap logic so that block themes that opt-out of blockGap but opt-in to wp-block-styles still get flex layout gap styles Fix Columns fallback gap styles in classic themes Ensure base layout styles are available in the editor for classic themes Fix root gap value Fix linting issues Fix linting issue Add a phpunit test for outputting layout styles based on layout definitions in theme.json Add additional tests, ensure base styles are still output so that alignments continue to function Remove todo items Add layout selector regex, css declaration check Add additional logical margin properties to allow list Fix flex-wrap rule in JS version of flex layout Co-authored-by: Ramon Rename default_layout to global_layout_settings Rename blockGapStyles to spacingStyles Fix rebase issues Fix linting issues Fix linting again --- lib/block-supports/layout.php | 84 +++-- .../get-global-styles-and-settings.php | 69 ---- .../wordpress-6.1/block-editor-settings.php | 12 + lib/compat/wordpress-6.1/blocks.php | 21 ++ .../wordpress-6.1/class-wp-theme-json-6-1.php | 324 +++++++++++++++++- .../get-global-styles-and-settings.php | 74 ++++ lib/compat/wordpress-6.1/theme.json | 79 +++++ .../src/components/block-list/layout.js | 18 +- packages/block-editor/src/hooks/index.js | 1 + packages/block-editor/src/hooks/layout.js | 66 ++-- packages/block-editor/src/index.js | 1 + packages/block-editor/src/layouts/flex.js | 88 ++--- packages/block-editor/src/layouts/flow.js | 49 +-- .../block-editor/src/layouts/test/flex.js | 21 ++ .../block-editor/src/layouts/test/flow.js | 21 ++ .../block-editor/src/layouts/test/utils.js | 138 ++++++++ packages/block-editor/src/layouts/utils.js | 47 ++- packages/block-library/src/group/edit.js | 4 +- packages/blocks/src/api/constants.js | 4 - .../src/components/visual-editor/index.js | 3 +- .../global-styles/dimensions-panel.js | 9 +- .../src/components/global-styles/hooks.js | 7 + .../global-styles/use-global-styles-output.js | 117 ++++++- phpunit/class-wp-theme-json-test.php | 211 ++++++++++++ 24 files changed, 1239 insertions(+), 229 deletions(-) create mode 100644 packages/block-editor/src/layouts/test/flex.js create mode 100644 packages/block-editor/src/layouts/test/flow.js create mode 100644 packages/block-editor/src/layouts/test/utils.js diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index b1ba529baae75c..fa63beef0710b3 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -64,16 +64,14 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $style .= "$selector .alignfull { max-width: none; }"; } - $style .= "$selector > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }"; - $style .= "$selector > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }"; - $style .= "$selector > .aligncenter { margin-left: auto !important; margin-right: auto !important; }"; if ( $has_block_gap_support ) { if ( is_array( $gap_value ) ) { $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null; } - $gap_style = $gap_value && ! $should_skip_gap_serialization ? $gap_value : 'var( --wp--style--block-gap )'; - $style .= "$selector > * { margin-block-start: 0; margin-block-end: 0; }"; - $style .= "$selector > * + * { margin-block-start: $gap_style; margin-block-end: 0; }"; + if ( $gap_value && ! $should_skip_gap_serialization ) { + $style .= "$selector > * { margin-block-start: 0; margin-block-end: 0; }"; + $style .= "$selector > * + * { margin-block-start: $gap_value; margin-block-end: 0; }"; + } } } elseif ( 'flex' === $layout_type ) { $layout_orientation = isset( $layout['orientation'] ) ? $layout['orientation'] : 'horizontal'; @@ -94,26 +92,23 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $justify_content_options += array( 'space-between' => 'space-between' ); } - $flex_wrap_options = array( 'wrap', 'nowrap' ); - $flex_wrap = ! empty( $layout['flexWrap'] ) && in_array( $layout['flexWrap'], $flex_wrap_options, true ) ? - $layout['flexWrap'] : - 'wrap'; + if ( ! empty( $layout['flexWrap'] ) && 'nowrap' === $layout['flexWrap'] ) { + $style .= "$selector { flex-wrap: nowrap; }"; + } - $style = "$selector {"; - $style .= 'display: flex;'; if ( $has_block_gap_support ) { if ( is_array( $gap_value ) ) { $gap_row = isset( $gap_value['top'] ) ? $gap_value['top'] : $fallback_gap_value; $gap_column = isset( $gap_value['left'] ) ? $gap_value['left'] : $fallback_gap_value; $gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column; } - $gap_style = $gap_value && ! $should_skip_gap_serialization ? $gap_value : "var( --wp--style--block-gap, $fallback_gap_value )"; - $style .= "gap: $gap_style;"; - } else { - $style .= "gap: $fallback_gap_value;"; + if ( $gap_value && ! $should_skip_gap_serialization ) { + $style .= "$selector {"; + $style .= "gap: $gap_value;"; + $style .= '}'; + } } - $style .= "flex-wrap: $flex_wrap;"; if ( 'horizontal' === $layout_orientation ) { /** * Add this style only if is not empty for backwards compatibility, @@ -121,25 +116,26 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support * by custom css. */ if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { + $style .= "$selector {"; $style .= "justify-content: {$justify_content_options[ $layout['justifyContent'] ]};"; + $style .= '}'; } if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) { + $style .= "$selector {"; $style .= "align-items: {$vertical_alignment_options[ $layout['verticalAlignment'] ]};"; - } else { - $style .= 'align-items: center;'; + $style .= '}'; } } else { + $style .= "$selector {"; $style .= 'flex-direction: column;'; if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { $style .= "align-items: {$justify_content_options[ $layout['justifyContent'] ]};"; } else { $style .= 'align-items: flex-start;'; } + $style .= '}'; } - $style .= '}'; - - $style .= "$selector > * { margin: 0; }"; } return $style; @@ -160,21 +156,23 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { return $block_content; } - $block_gap = gutenberg_get_global_settings( array( 'spacing', 'blockGap' ) ); - $default_layout = gutenberg_get_global_settings( array( 'layout' ) ); - $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false; - $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); - $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; + $block_gap = gutenberg_get_global_settings( array( 'spacing', 'blockGap' ) ); + $global_layout_settings = gutenberg_get_global_settings( array( 'layout' ) ); + $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false; + $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); + $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) { - if ( ! $default_layout ) { + if ( ! $global_layout_settings ) { return $block_content; } - $used_layout = $default_layout; + $used_layout = $global_layout_settings; } - $class_names = array(); - $container_class = wp_unique_id( 'wp-container-' ); - $class_names[] = $container_class; + $class_names = array(); + $layout_definitions = _wp_array_get( $global_layout_settings, array( 'definitions' ), array() ); + $block_classname = wp_get_block_default_classname( $block['blockName'] ); + $container_class = wp_unique_id( 'wp-container-' ); + $layout_classname = ''; // The following section was added to reintroduce a small set of layout classnames that were // removed in the 5.9 release (https://github.com/WordPress/gutenberg/issues/38719). It is @@ -192,6 +190,17 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $class_names[] = 'is-nowrap'; } + // Get classname for layout type. + if ( isset( $used_layout['type'] ) ) { + $layout_classname = _wp_array_get( $layout_definitions, array( $used_layout['type'], 'className' ), '' ); + } else { + $layout_classname = _wp_array_get( $layout_definitions, array( 'default', 'className' ), '' ); + } + + if ( $layout_classname && is_string( $layout_classname ) ) { + $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 @@ -209,7 +218,14 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { // 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( ".$container_class", $used_layout, $has_block_gap_support, $gap_value, $should_skip_gap_serialization, $fallback_gap_value ); + $style = gutenberg_get_layout_style( ".$block_classname.$container_class", $used_layout, $has_block_gap_support, $gap_value, $should_skip_gap_serialization, $fallback_gap_value ); + + // 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. // I think this is a reasonable limitation for that particular hook. $content = preg_replace( @@ -219,8 +235,6 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { 1 ); - wp_enqueue_block_support_styles( $style ); - return $content; } diff --git a/lib/compat/wordpress-6.0/get-global-styles-and-settings.php b/lib/compat/wordpress-6.0/get-global-styles-and-settings.php index 5768b4cba80986..1dfafe9f7631d3 100644 --- a/lib/compat/wordpress-6.0/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.0/get-global-styles-and-settings.php @@ -63,75 +63,6 @@ function gutenberg_get_global_styles( $path = array(), $context = array() ) { return _wp_array_get( $styles, $path, $styles ); } -/** - * Returns the stylesheet resulting of merging core, theme, and user data. - * - * @param array $types Types of styles to load. Optional. - * It accepts 'variables', 'styles', 'presets' as values. - * If empty, it'll load all for themes with theme.json support - * and only [ 'variables', 'presets' ] for themes without theme.json support. - * - * @return string Stylesheet. - */ -function gutenberg_get_global_stylesheet( $types = array() ) { - // Return cached value if it can be used and exists. - // It's cached by theme to make sure that theme switching clears the cache. - $can_use_cached = ( - ( empty( $types ) ) && - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && - ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && - ! is_admin() - ); - $transient_name = 'gutenberg_global_styles_' . get_stylesheet(); - if ( $can_use_cached ) { - $cached = get_transient( $transient_name ); - if ( $cached ) { - return $cached; - } - } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support(); - if ( empty( $types ) ) { - $types = array( 'variables', 'styles', 'presets' ); - } - - /* - * If variables are part of the stylesheet, - * we add them for all origins (default, theme, user). - * This is so themes without a theme.json still work as before 5.9: - * they can override the default presets. - * See https://core.trac.wordpress.org/ticket/54782 - */ - $styles_variables = ''; - if ( in_array( 'variables', $types, true ) ) { - $styles_variables = $tree->get_stylesheet( array( 'variables' ) ); - $types = array_diff( $types, array( 'variables' ) ); - } - - /* - * For the remaining types (presets, styles), we do consider origins: - * - * - themes without theme.json: only the classes for the presets defined by core - * - themes with theme.json: the presets and styles classes, both from core and the theme - */ - $styles_rest = ''; - if ( ! empty( $types ) ) { - $origins = array( 'default', 'theme', 'custom' ); - if ( ! $supports_theme_json ) { - $origins = array( 'default' ); - } - $styles_rest = $tree->get_stylesheet( $types, $origins ); - } - $stylesheet = $styles_variables . $styles_rest; - if ( $can_use_cached ) { - // Cache for a minute. - // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. - set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); - } - return $stylesheet; -} - /** * Returns a string containing the SVGs to be referenced as filters (duotone). * diff --git a/lib/compat/wordpress-6.1/block-editor-settings.php b/lib/compat/wordpress-6.1/block-editor-settings.php index ed591cf60196ae..c24cf6980d3420 100644 --- a/lib/compat/wordpress-6.1/block-editor-settings.php +++ b/lib/compat/wordpress-6.1/block-editor-settings.php @@ -81,6 +81,18 @@ function gutenberg_get_block_editor_settings( $settings ) { $block_classes['css'] = $actual_css; $new_global_styles[] = $block_classes; } + } elseif ( current_theme_supports( 'wp-block-styles' ) ) { + // If there is no `theme.json` file, but the theme opts in to block styles, ensure base layout styles are available. + $block_classes = array( + 'css' => 'base-layout-styles', + '__unstableType' => 'theme', + 'isGlobalStyles' => true, + ); + $actual_css = gutenberg_get_global_stylesheet( array( $block_classes['css'] ) ); + if ( '' !== $actual_css ) { + $block_classes['css'] = $actual_css; + $new_global_styles[] = $block_classes; + } } $settings['styles'] = array_merge( $new_global_styles, $styles_without_existing_global_styles ); diff --git a/lib/compat/wordpress-6.1/blocks.php b/lib/compat/wordpress-6.1/blocks.php index 85e124306161a1..875b95e9a7992b 100644 --- a/lib/compat/wordpress-6.1/blocks.php +++ b/lib/compat/wordpress-6.1/blocks.php @@ -5,6 +5,27 @@ * @package gutenberg */ +/** + * Update allowed inline style attributes list. + * + * Note: This should be removed when the minimum required WP version is >= 6.1. + * + * @param string[] $attrs Array of allowed CSS attributes. + * @return string[] CSS attributes. + */ +function gutenberg_safe_style_attrs_6_1( $attrs ) { + $attrs[] = 'display'; + $attrs[] = 'flex-wrap'; + $attrs[] = 'gap'; + $attrs[] = 'margin-block-start'; + $attrs[] = 'margin-block-end'; + $attrs[] = 'margin-inline-start'; + $attrs[] = 'margin-inline-end'; + + return $attrs; +} +add_filter( 'safe_style_css', 'gutenberg_safe_style_attrs_6_1' ); + /** * Registers view scripts for core blocks if handling is missing in WordPress core. * diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 189e3694dd87f6..fed87e131d6659 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -25,7 +25,58 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { 'link' => array( ':hover', ':focus', ':active', ':visited' ), ); - /* + /** + * Metadata for style properties. + * + * Each element is a direct mapping from the CSS property name to the + * path to the value in theme.json & block attributes. + */ + const PROPERTIES_METADATA = array( + 'background' => array( 'color', 'gradient' ), + 'background-color' => array( 'color', 'background' ), + 'border-radius' => array( 'border', 'radius' ), + 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), + 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), + 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ), + 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ), + 'border-color' => array( 'border', 'color' ), + 'border-width' => array( 'border', 'width' ), + 'border-style' => array( 'border', 'style' ), + 'border-top-color' => array( 'border', 'top', 'color' ), + 'border-top-width' => array( 'border', 'top', 'width' ), + 'border-top-style' => array( 'border', 'top', 'style' ), + 'border-right-color' => array( 'border', 'right', 'color' ), + 'border-right-width' => array( 'border', 'right', 'width' ), + 'border-right-style' => array( 'border', 'right', 'style' ), + 'border-bottom-color' => array( 'border', 'bottom', 'color' ), + 'border-bottom-width' => array( 'border', 'bottom', 'width' ), + 'border-bottom-style' => array( 'border', 'bottom', 'style' ), + 'border-left-color' => array( 'border', 'left', 'color' ), + 'border-left-width' => array( 'border', 'left', 'width' ), + 'border-left-style' => array( 'border', 'left', 'style' ), + 'color' => array( 'color', 'text' ), + 'font-family' => array( 'typography', 'fontFamily' ), + 'font-size' => array( 'typography', 'fontSize' ), + 'font-style' => array( 'typography', 'fontStyle' ), + 'font-weight' => array( 'typography', 'fontWeight' ), + 'letter-spacing' => array( 'typography', 'letterSpacing' ), + 'line-height' => array( 'typography', 'lineHeight' ), + 'margin' => array( 'spacing', 'margin' ), + 'margin-top' => array( 'spacing', 'margin', 'top' ), + 'margin-right' => array( 'spacing', 'margin', 'right' ), + 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), + 'margin-left' => array( 'spacing', 'margin', 'left' ), + 'padding' => array( 'spacing', 'padding' ), + 'padding-top' => array( 'spacing', 'padding', 'top' ), + 'padding-right' => array( 'spacing', 'padding', 'right' ), + 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ), + 'padding-left' => array( 'spacing', 'padding', 'left' ), + 'text-decoration' => array( 'typography', 'textDecoration' ), + 'text-transform' => array( 'typography', 'textTransform' ), + 'filter' => array( 'filter', 'duotone' ), + ); + + /** * The valid elements that can be found under styles. * * @var string[] @@ -151,8 +202,6 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n return $output; } - - /** * Removes insecure data from theme.json. * @@ -230,6 +279,47 @@ public static function remove_insecure_properties( $theme_json ) { return $theme_json; } + /** + * The valid properties under the styles key. + * + * @var array + */ + const VALID_STYLES = array( + 'border' => array( + 'color' => null, + 'radius' => null, + 'style' => null, + 'width' => null, + 'top' => null, + 'right' => null, + 'bottom' => null, + 'left' => null, + ), + 'color' => array( + 'background' => null, + 'gradient' => null, + 'text' => null, + ), + 'filter' => array( + 'duotone' => null, + ), + 'spacing' => array( + 'margin' => null, + 'padding' => null, + 'blockGap' => null, + ), + 'typography' => array( + 'fontFamily' => null, + 'fontSize' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textDecoration' => null, + 'textTransform' => null, + ), + ); + /** * Returns the metadata for each block. * @@ -452,6 +542,73 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { return $nodes; } + /** + * Returns the stylesheet that results of processing + * the theme.json structure this object represents. + * + * @param array $types Types of styles to load. Will load all by default. It accepts: + * 'variables': only the CSS Custom Properties for presets & custom ones. + * 'styles': only the styles section in theme.json. + * 'presets': only the classes for the presets. + * @param array $origins A list of origins to include. By default it includes VALID_ORIGINS. + * @return string Stylesheet. + */ + public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null ) { + if ( null === $origins ) { + $origins = static::VALID_ORIGINS; + } + + if ( is_string( $types ) ) { + // Dispatch error and map old arguments to new ones. + _deprecated_argument( __FUNCTION__, '5.9' ); + if ( 'block_styles' === $types ) { + $types = array( 'styles', 'presets' ); + } elseif ( 'css_variables' === $types ) { + $types = array( 'variables' ); + } else { + $types = array( 'variables', 'styles', 'presets' ); + } + } + + $blocks_metadata = static::get_blocks_metadata(); + $style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata ); + $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); + + $stylesheet = ''; + + if ( in_array( 'variables', $types, true ) ) { + $stylesheet .= $this->get_css_variables( $setting_nodes, $origins ); + } + + if ( in_array( 'styles', $types, true ) ) { + $stylesheet .= $this->get_block_classes( $style_nodes ); + } elseif ( in_array( 'base-layout-styles', $types, true ) ) { + // Base layout styles are provided as part of `styles`, so only output separately if explicitly requested. + // For backwards compatibility, the Columns block is explicitly included, to support a different default gap value. + $base_styles_nodes = array( + array( + 'path' => array( 'styles' ), + 'selector' => static::ROOT_BLOCK_SELECTOR, + ), + array( + 'path' => array( 'styles', 'blocks', 'core/columns' ), + 'selector' => '.wp-block-columns', + 'name' => 'core/columns', + ), + ); + + foreach ( $base_styles_nodes as $base_style_node ) { + $stylesheet .= $this->get_layout_styles( $base_style_node ); + } + } + + if ( in_array( 'presets', $types, true ) ) { + $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); + } + + return $stylesheet; + } + /** * Gets the CSS rules for a particular block from theme.json. * @@ -530,16 +687,33 @@ function( $pseudo_selector ) use ( $selector ) { $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); } + // 4. Generate Layout block gap styles. + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; + $has_block_gap_value = _wp_array_get( $node, array( 'spacing', 'blockGap' ), false ); + + if ( + static::ROOT_BLOCK_SELECTOR !== $selector && + $has_block_gap_support && + $has_block_gap_value && + ! empty( $block_metadata['name'] ) + ) { + $block_rules .= $this->get_layout_styles( $block_metadata ); + } + if ( static::ROOT_BLOCK_SELECTOR === $selector ) { + $block_gap_value = _wp_array_get( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ), '0.5em' ); + $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; - $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; if ( $has_block_gap_support ) { $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; - $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; + $block_rules .= ".wp-site-blocks > * + * { margin-block-start: $block_gap_value; }"; + // For backwards compatibility, ensure the legacy block gap CSS variable is still available. + $block_rules .= "$selector { --wp--style--block-gap: $block_gap_value; }"; } + $block_rules .= $this->get_layout_styles( $block_metadata ); } return $block_rules; @@ -688,7 +862,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null return $value; } - /* + /** * Presets are a set of values that serve * to bootstrap some styles: colors, font sizes, etc. * @@ -826,6 +1000,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null 'custom' => null, 'customDuotone' => null, 'customGradient' => null, + 'defaultDuotone' => null, 'defaultGradients' => null, 'defaultPalette' => null, 'duotone' => null, @@ -837,6 +1012,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null 'custom' => null, 'layout' => array( 'contentSize' => null, + 'definitions' => null, 'wideSize' => null, ), 'spacing' => array( @@ -959,4 +1135,140 @@ public function set_spacing_sizes() { _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), array_merge( $below_sizes, $above_sizes ) ); } + + /** + * Get the CSS layout rules for a particular block from theme.json layout definitions. + * + * @param array $block_metadata Metadata about the block to get styles for. + * + * @return string Layout styles for the block. + */ + protected function get_layout_styles( $block_metadata ) { + $block_rules = ''; + $selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : ''; + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; + $has_block_styles_support = current_theme_supports( 'wp-block-styles' ); + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); + $layout_definitions = _wp_array_get( $this->theme_json, array( 'settings', 'layout', 'definitions' ), array() ); + $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, and child combinator selectors. + + // Gap styles will only be output if the theme has block gap support, or supports `wp-block-styles`. + // In this way, we tie the concept of gap styles to the styles that ship with core blocks. + // Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value. + if ( $has_block_gap_support || $has_block_styles_support ) { + $block_gap_value = null; + // Use a fallback gap value if block gap support is not available. + if ( ! $has_block_gap_support ) { + $block_gap_value = '0.5em'; + if ( isset( $block_metadata['name'] ) ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] ); + $block_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), '0.5em' ); + } + } else { + $block_gap_value = _wp_array_get( $node, array( 'spacing', 'blockGap' ), null ); + } + + // Support split row / column values and concatenate to a shorthand value. + if ( is_array( $block_gap_value ) ) { + if ( isset( $block_gap_value['top'] ) && isset( $block_gap_value['left'] ) ) { + $gap_row = $block_gap_value['top']; + $gap_column = $block_gap_value['left']; + $block_gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column; + } else { + // Skip outputting gap value if not all sides are provided. + $block_gap_value = null; + } + } + + if ( $block_gap_value ) { + foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { + // Allow skipping default layout, for example, so that classic themes can still output flex gap styles. + if ( ! $has_block_gap_support && 'default' === $layout_definition_key ) { + continue; + } + + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); + $spacing_rules = _wp_array_get( $layout_definition, array( 'spacingStyles' ), array() ); + + if ( + ! empty( $class_name ) && + ! empty( $spacing_rules ) + ) { + foreach ( $spacing_rules as $spacing_rule ) { + $declarations = array(); + if ( + isset( $spacing_rule['selector'] ) && + preg_match( $layout_selector_pattern, $spacing_rule['selector'] ) && + ! empty( $spacing_rule['rules'] ) + ) { + // Iterate over each of the styling rules and substitute non-string values such as `null` with the real `blockGap` value. + foreach ( $spacing_rule['rules'] as $css_property => $css_value ) { + $current_css_value = is_string( $css_value ) ? $css_value : $block_gap_value; + if ( static::is_safe_css_declaration( $css_property, $current_css_value ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $current_css_value, + ); + } + } + + $format = static::ROOT_BLOCK_SELECTOR === $selector ? '%s .%s%s' : '%s.%s%s'; + $layout_selector = sprintf( + $format, + $selector, + $class_name, + $spacing_rule['selector'] + ); + $block_rules .= static::to_ruleset( $layout_selector, $declarations ); + } + } + } + } + } + } + + // Output base styles. + if ( + static::ROOT_BLOCK_SELECTOR === $selector + ) { + foreach ( $layout_definitions as $layout_definition ) { + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); + $base_style_rules = _wp_array_get( $layout_definition, array( 'baseStyles' ), array() ); + + if ( + ! empty( $class_name ) && + ! empty( $base_style_rules ) + ) { + foreach ( $base_style_rules as $base_style_rule ) { + $declarations = array(); + + if ( + isset( $base_style_rule['selector'] ) && + preg_match( $layout_selector_pattern, $base_style_rule['selector'] ) && + ! empty( $base_style_rule['rules'] ) + ) { + foreach ( $base_style_rule['rules'] as $css_property => $css_value ) { + if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $css_value, + ); + } + } + + $format = static::ROOT_BLOCK_SELECTOR === $selector ? '%s .%s%s' : '%s.%s%s'; + $layout_selector = sprintf( + $format, + $selector, + $class_name, + $base_style_rule['selector'] + ); + $block_rules .= static::to_ruleset( $layout_selector, $declarations ); + } + } + } + } + } + return $block_rules; + } } diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php index dd699edc269b91..227305f127eed6 100644 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -47,3 +47,77 @@ function ( $item ) { } } } + +/** + * Returns the stylesheet resulting of merging core, theme, and user data. + * + * @param array $types Types of styles to load. Optional. + * It accepts 'variables', 'styles', 'presets' as values. + * If empty, it'll load all for themes with theme.json support + * and only [ 'variables', 'presets' ] for themes without theme.json support. + * + * @return string Stylesheet. + */ +function gutenberg_get_global_stylesheet( $types = array() ) { + // Return cached value if it can be used and exists. + // It's cached by theme to make sure that theme switching clears the cache. + $can_use_cached = ( + ( empty( $types ) ) && + ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && + ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && + ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && + ! is_admin() + ); + $transient_name = 'gutenberg_global_styles_' . get_stylesheet(); + if ( $can_use_cached ) { + $cached = get_transient( $transient_name ); + if ( $cached ) { + return $cached; + } + } + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support(); + if ( empty( $types ) && ! $supports_theme_json ) { + $types = array( 'variables', 'presets' ); + if ( current_theme_supports( 'wp-block-styles' ) ) { + $types[] = 'base-layout-styles'; + } + } elseif ( empty( $types ) ) { + $types = array( 'variables', 'styles', 'presets' ); + } + + /* + * If variables are part of the stylesheet, + * we add them for all origins (default, theme, user). + * This is so themes without a theme.json still work as before 5.9: + * they can override the default presets. + * See https://core.trac.wordpress.org/ticket/54782 + */ + $styles_variables = ''; + if ( in_array( 'variables', $types, true ) ) { + $styles_variables = $tree->get_stylesheet( array( 'variables' ) ); + $types = array_diff( $types, array( 'variables' ) ); + } + + /* + * For the remaining types (presets, styles), we do consider origins: + * + * - themes without theme.json: only the classes for the presets defined by core + * - themes with theme.json: the presets and styles classes, both from core and the theme + */ + $styles_rest = ''; + if ( ! empty( $types ) ) { + $origins = array( 'default', 'theme', 'custom' ); + if ( ! $supports_theme_json ) { + $origins = array( 'default' ); + } + $styles_rest = $tree->get_stylesheet( $types, $origins ); + } + $stylesheet = $styles_variables . $styles_rest; + if ( $can_use_cached ) { + // Cache for a minute. + // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. + set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); + } + return $stylesheet; +} diff --git a/lib/compat/wordpress-6.1/theme.json b/lib/compat/wordpress-6.1/theme.json index 928758be88e121..ac1ad1508c5d68 100644 --- a/lib/compat/wordpress-6.1/theme.json +++ b/lib/compat/wordpress-6.1/theme.json @@ -185,6 +185,85 @@ ], "text": true }, + "layout": { + "definitions": { + "default": { + "name": "default", + "slug": "flow", + "className": "is-layout-flow", + "baseStyles": [ + { + "selector": " > .alignleft", + "rules": { + "float": "left", + "margin-inline-start": "0", + "margin-inline-end": "2em" + } + }, + { + "selector": " > .alignright", + "rules": { + "float": "right", + "margin-inline-start": "2em", + "margin-inline-end": "0" + } + }, + { + "selector": " > .aligncenter", + "rules": { + "margin-left": "auto !important", + "margin-right": "auto !important" + } + } + ], + "spacingStyles": [ + { + "selector": " > *", + "rules": { + "margin-block-start": "0", + "margin-block-end": "0" + } + }, + { + "selector": " > * + *", + "rules": { + "margin-block-start": null, + "margin-block-end": "0" + } + } + ] + }, + "flex": { + "name": "flex", + "slug": "flex", + "className": "is-layout-flex", + "baseStyles": [ + { + "selector": "", + "rules": { + "display": "flex", + "flex-wrap": "wrap", + "align-items": "center" + } + }, + { + "selector": " > *", + "rules": { + "margin": "0" + } + } + ], + "spacingStyles": [ + { + "selector": "", + "rules": { + "gap": null + } + } + ] + } + } + }, "spacing": { "blockGap": null, "margin": false, diff --git a/packages/block-editor/src/components/block-list/layout.js b/packages/block-editor/src/components/block-list/layout.js index 79ff15811ab521..d7a9113ebfa0f3 100644 --- a/packages/block-editor/src/components/block-list/layout.js +++ b/packages/block-editor/src/components/block-list/layout.js @@ -7,6 +7,7 @@ import { createContext, useContext } from '@wordpress/element'; * Internal dependencies */ import { getLayoutType } from '../../layouts'; +import useSetting from '../use-setting'; export const defaultLayout = { type: 'default' }; @@ -24,12 +25,23 @@ export function useLayout() { return useContext( Layout ); } -export function LayoutStyle( { layout = {}, ...props } ) { +export function LayoutStyle( { layout = {}, css, ...props } ) { const layoutType = getLayoutType( layout.type ); + const blockGapSupport = useSetting( 'spacing.blockGap' ); + const hasBlockGapSupport = blockGapSupport !== null; if ( layoutType ) { - return ; + if ( css ) { + return ; + } + const layoutStyle = layoutType.getLayoutStyle?.( { + hasBlockGapSupport, + layout, + ...props, + } ); + if ( layoutStyle ) { + return ; + } } - return null; } diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 9548f5ea152ecb..272e79e78dbdd3 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -20,4 +20,5 @@ export { useCustomSides } from './dimensions'; export { getBorderClassesAndStyles, useBorderProps } from './use-border-props'; export { getColorClassesAndStyles, useColorProps } from './use-color-props'; export { getSpacingClassesAndStyles } from './use-spacing-props'; +export { getGapCSSValue } from './gap'; export { useCachedTruthy } from './use-cached-truthy'; diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index c5072200869ed2..65a44b6b8df546 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -9,7 +9,11 @@ import { has, kebabCase } from 'lodash'; */ import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; -import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; +import { + getBlockDefaultClassName, + getBlockSupport, + hasBlockSupport, +} from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { Button, @@ -40,35 +44,31 @@ const layoutBlockSupportKey = '__experimentalLayout'; * have the style engine generate a more extensive list of utility classnames which * will then replace this method. * - * @param { Array } attributes Array of block attributes. + * @param { Object } layout Layout object. + * @param { Object } layoutDefinitions An object containing layout definitions, stored in theme.json. * * @return { Array } Array of CSS classname strings. */ -function getLayoutClasses( attributes ) { +function getLayoutClasses( layout, layoutDefinitions ) { const layoutClassnames = []; - if ( ! attributes.layout ) { - return layoutClassnames; - } - - if ( attributes?.layout?.orientation ) { + if ( layoutDefinitions?.[ layout?.type || 'default' ]?.className ) { layoutClassnames.push( - `is-${ kebabCase( attributes.layout.orientation ) }` + layoutDefinitions?.[ layout?.type || 'default' ]?.className ); } - if ( attributes?.layout?.justifyContent ) { + if ( layout?.orientation ) { + layoutClassnames.push( `is-${ kebabCase( layout.orientation ) }` ); + } + + if ( layout?.justifyContent ) { layoutClassnames.push( - `is-content-justification-${ kebabCase( - attributes.layout.justifyContent - ) }` + `is-content-justification-${ kebabCase( layout.justifyContent ) }` ); } - if ( - attributes?.layout?.flexWrap && - attributes.layout.flexWrap === 'nowrap' - ) { + if ( layout?.flexWrap && layout.flexWrap === 'nowrap' ) { layoutClassnames.push( 'is-nowrap' ); } @@ -267,12 +267,36 @@ export const withLayoutStyles = createHigherOrderComponent( ? defaultThemeLayout : layout || defaultBlockLayout || {}; const layoutClasses = shouldRenderLayoutStyles - ? getLayoutClasses( attributes ) + ? getLayoutClasses( usedLayout, defaultThemeLayout?.definitions ) : null; + const selector = `.${ getBlockDefaultClassName( + name + ) }.wp-container-${ id }`; + const blockGapSupport = useSetting( 'spacing.blockGap' ); + const hasBlockGapSupport = blockGapSupport !== null; + + // Get CSS string for the current layout type. + // The CSS and `style` element is only output if it is not empty. + let css; + if ( shouldRenderLayoutStyles ) { + const fullLayoutType = getLayoutType( + usedLayout?.type || 'default' + ); + css = fullLayoutType?.getLayoutStyle?.( { + blockName: name, + selector, + layout: usedLayout, + layoutDefinitions: defaultThemeLayout?.definitions, + style: attributes?.style, + hasBlockGapSupport, + } ); + } + + // Attach a `wp-container-` id-based class name as well as a layout class name such as `is-layout-flex`. const className = classnames( props?.className, { - [ `wp-container-${ id }` ]: shouldRenderLayoutStyles, + [ `wp-container-${ id }` ]: shouldRenderLayoutStyles && !! css, // Only attach a container class if there is generated CSS to be attached. }, layoutClasses ); @@ -281,10 +305,12 @@ export const withLayoutStyles = createHigherOrderComponent( <> { shouldRenderLayoutStyles && element && + !! css && createPortal( , diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index b2fe8aec5e1ff0..c3d55ce8962c7a 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -9,6 +9,7 @@ export { useColorProps as __experimentalUseColorProps, useCustomSides as __experimentalUseCustomSides, getSpacingClassesAndStyles as __experimentalGetSpacingClassesAndStyles, + getGapCSSValue as __experimentalGetGapCSSValue, useCachedTruthy, } from './hooks'; export * from './components'; diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index 4be6528eed16c7..2e90f10f511f0b 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -11,14 +11,12 @@ import { arrowDown, } from '@wordpress/icons'; import { Button, ToggleControl, Flex, FlexItem } from '@wordpress/components'; -import { getBlockSupport } from '@wordpress/blocks'; /** * Internal dependencies */ -import { appendSelectors } from './utils'; +import { appendSelectors, getBlockGapCSS } from './utils'; import { getGapCSSValue } from '../hooks/gap'; -import useSetting from '../components/use-setting'; import { BlockControls, JustifyContentControl, @@ -107,59 +105,67 @@ export default { ); }, - save: function FlexLayoutStyle( { selector, layout, style, blockName } ) { + getLayoutStyle: function getLayoutStyle( { + selector, + layout, + style, + blockName, + hasBlockGapSupport, + layoutDefinitions, + } ) { const { orientation = 'horizontal' } = layout; - const blockGapSupport = useSetting( 'spacing.blockGap' ); - const fallbackValue = - getBlockSupport( blockName, [ - 'spacing', - 'blockGap', - '__experimentalDefault', - ] ) || '0.5em'; - const hasBlockGapStylesSupport = blockGapSupport !== null; // If a block's block.json skips serialization for spacing or spacing.blockGap, // don't apply the user-defined value to the styles. const blockGapValue = style?.spacing?.blockGap && ! shouldSkipSerialization( blockName, 'spacing', 'blockGap' ) - ? getGapCSSValue( style?.spacing?.blockGap, fallbackValue ) - : `var( --wp--style--block-gap, ${ fallbackValue } )`; - const justifyContent = - justifyContentMap[ layout.justifyContent ] || - justifyContentMap.left; + ? getGapCSSValue( style?.spacing?.blockGap ) + : undefined; + const justifyContent = justifyContentMap[ layout.justifyContent ]; const flexWrap = flexWrapOptions.includes( layout.flexWrap ) ? layout.flexWrap : 'wrap'; const verticalAlignment = - verticalAlignmentMap[ layout.verticalAlignment ] || - verticalAlignmentMap.center; - const rowOrientation = ` - flex-direction: row; - align-items: ${ verticalAlignment }; - justify-content: ${ justifyContent }; - `; + verticalAlignmentMap[ layout.verticalAlignment ]; const alignItems = alignItemsMap[ layout.justifyContent ] || alignItemsMap.left; - const columnOrientation = ` - flex-direction: column; - align-items: ${ alignItems }; - `; - return ( - - ); + if ( flexWrap && flexWrap !== 'wrap' ) { + rules.push( `flex-wrap: ${ flexWrap }` ); + } + + if ( orientation === 'horizontal' ) { + if ( verticalAlignment ) { + rules.push( `align-items: ${ verticalAlignment }` ); + } + if ( justifyContent ) { + rules.push( `justify-content: ${ justifyContent }` ); + } + } else { + rules.push( 'flex-direction: column' ); + rules.push( `align-items: ${ alignItems }` ); + } + + if ( rules.length ) { + output = `${ appendSelectors( selector ) } { + ${ rules.join( '; ' ) }; + }`; + } + + // Output blockGap styles based on rules contained in layout definitions in theme.json. + if ( hasBlockGapSupport && blockGapValue ) { + output += getBlockGapCSS( + selector, + layoutDefinitions, + 'flex', + blockGapValue + ); + } + return output; }, getOrientation( layout ) { const { orientation = 'horizontal' } = layout; diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index 82851cb0150756..1d8ea7d7a6aea2 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -13,7 +13,7 @@ import { Icon, positionCenter, stretchWide } from '@wordpress/icons'; * Internal dependencies */ import useSetting from '../components/use-setting'; -import { appendSelectors } from './utils'; +import { appendSelectors, getBlockGapCSS } from './utils'; import { getGapBoxControlValueFromStyle } from '../hooks/gap'; import { shouldSkipSerialization } from '../hooks/utils'; @@ -107,15 +107,15 @@ export default { toolBarControls: function DefaultLayoutToolbarControls() { return null; }, - save: function DefaultLayoutStyle( { + getLayoutStyle: function getLayoutStyle( { selector, layout = {}, style, blockName, + hasBlockGapSupport, + layoutDefinitions, } ) { const { contentSize, wideSize } = layout; - const blockGapSupport = useSetting( 'spacing.blockGap' ); - const hasBlockGapStylesSupport = blockGapSupport !== null; const blockGapStyleValue = getGapBoxControlValueFromStyle( style?.spacing?.blockGap ); @@ -125,7 +125,7 @@ export default { blockGapStyleValue?.top && ! shouldSkipSerialization( blockName, 'spacing', 'blockGap' ) ? blockGapStyleValue?.top - : 'var( --wp--style--block-gap )'; + : ''; let output = !! contentSize || !! wideSize @@ -147,37 +147,16 @@ export default { ` : ''; - output += ` - ${ appendSelectors( selector, '> .alignleft' ) } { - float: left; - margin-inline-start: 0; - margin-inline-end: 2em; - } - ${ appendSelectors( selector, '> .alignright' ) } { - float: right; - margin-inline-start: 2em; - margin-inline-end: 0; - } - - ${ appendSelectors( selector, '> .aligncenter' ) } { - margin-left: auto !important; - margin-right: auto !important; - } - `; - - if ( hasBlockGapStylesSupport ) { - output += ` - ${ appendSelectors( selector, '> *' ) } { - margin-block-start: 0; - margin-block-end: 0; - } - ${ appendSelectors( selector, '> * + *' ) } { - margin-block-start: ${ blockGapValue }; - } - `; + // Output blockGap styles based on rules contained in layout definitions in theme.json. + if ( hasBlockGapSupport && blockGapValue ) { + output += getBlockGapCSS( + selector, + layoutDefinitions, + 'default', + blockGapValue + ); } - - return ; + return output; }, getOrientation() { return 'vertical'; diff --git a/packages/block-editor/src/layouts/test/flex.js b/packages/block-editor/src/layouts/test/flex.js new file mode 100644 index 00000000000000..01bd8735dc782c --- /dev/null +++ b/packages/block-editor/src/layouts/test/flex.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import flex from '../flex'; + +describe( 'getLayoutStyle', () => { + it( 'should return an empty string if no non-default params are provided', () => { + const expected = ''; + + const result = flex.getLayoutStyle( { + selector: '.my-container', + layout: {}, + style: {}, + blockName: 'test-block', + hasBlockGapSupport: false, + layoutDefinitions: undefined, + } ); + + expect( result ).toBe( expected ); + } ); +} ); diff --git a/packages/block-editor/src/layouts/test/flow.js b/packages/block-editor/src/layouts/test/flow.js new file mode 100644 index 00000000000000..727ac214af6286 --- /dev/null +++ b/packages/block-editor/src/layouts/test/flow.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import flow from '../flow'; + +describe( 'getLayoutStyle', () => { + it( 'should return an empty string if no non-default params are provided', () => { + const expected = ''; + + const result = flow.getLayoutStyle( { + selector: '.my-container', + layout: {}, + style: {}, + blockName: 'test-block', + hasBlockGapSupport: false, + layoutDefinitions: undefined, + } ); + + expect( result ).toBe( expected ); + } ); +} ); diff --git a/packages/block-editor/src/layouts/test/utils.js b/packages/block-editor/src/layouts/test/utils.js new file mode 100644 index 00000000000000..529e1bf74e24c9 --- /dev/null +++ b/packages/block-editor/src/layouts/test/utils.js @@ -0,0 +1,138 @@ +/** + * Internal dependencies + */ +import { appendSelectors, getBlockGapCSS } from '../utils'; + +const layoutDefinitions = { + default: { + spacingStyles: [ + { + selector: ' > *', + rules: { + 'margin-block-start': '0', + 'margin-block-end': '0', + }, + }, + { + selector: ' > * + *', + rules: { + 'margin-block-start': null, + 'margin-block-end': '0', + }, + }, + ], + }, + flex: { + spacingStyles: [ + { + selector: '', + rules: { + gap: null, + }, + }, + ], + }, +}; + +describe( 'getBlockGapCSS', () => { + it( 'should output default blockGap rules', () => { + const expected = + '.editor-styles-wrapper .my-container > * { margin-block-start: 0; margin-block-end: 0; }.editor-styles-wrapper .my-container > * + * { margin-block-start: 3em; margin-block-end: 0; }'; + + const result = getBlockGapCSS( + '.my-container', + layoutDefinitions, + 'default', + '3em' + ); + + expect( result ).toBe( expected ); + } ); + + it( 'should output flex blockGap rules', () => { + const expected = '.editor-styles-wrapper .my-container { gap: 3em; }'; + + const result = getBlockGapCSS( + '.my-container', + layoutDefinitions, + 'flex', + '3em' + ); + + expect( result ).toBe( expected ); + } ); + + it( 'should return an empty string if layout type cannot be found', () => { + const expected = ''; + + const result = getBlockGapCSS( + '.my-container', + layoutDefinitions, + 'aTypeThatDoesNotExist', + '3em' + ); + + expect( result ).toBe( expected ); + } ); + + it( 'should return an empty string if layout definitions cannot be found', () => { + const expected = ''; + + const result = getBlockGapCSS( + '.my-container', + undefined, + 'flex', + '3em' + ); + + expect( result ).toBe( expected ); + } ); + + it( 'should return an empty string if blockGap is empty', () => { + const expected = ''; + + const result = getBlockGapCSS( + '.my-container', + layoutDefinitions, + 'flex', + null + ); + + expect( result ).toBe( expected ); + } ); + + it( 'should treat a blockGap string containing 0 as a valid value', () => { + const expected = '.editor-styles-wrapper .my-container { gap: 0; }'; + + const result = getBlockGapCSS( + '.my-container', + layoutDefinitions, + 'flex', + '0' + ); + + expect( result ).toBe( expected ); + } ); +} ); + +describe( 'appendSelectors', () => { + it( 'should append a subselector without an appended selector', () => { + expect( appendSelectors( '.original-selector' ) ).toBe( + '.editor-styles-wrapper .original-selector' + ); + } ); + + it( 'should append a subselector to a single selector', () => { + expect( appendSelectors( '.original-selector', '.appended' ) ).toBe( + '.editor-styles-wrapper .original-selector .appended' + ); + } ); + + it( 'should append a subselector to multiple selectors', () => { + expect( + appendSelectors( '.first-selector,.second-selector', '.appended' ) + ).toBe( + '.editor-styles-wrapper .first-selector .appended,.editor-styles-wrapper .second-selector .appended' + ); + } ); +} ); diff --git a/packages/block-editor/src/layouts/utils.js b/packages/block-editor/src/layouts/utils.js index 89e83fdbfb41e9..e2a27f7d7b121a 100644 --- a/packages/block-editor/src/layouts/utils.js +++ b/packages/block-editor/src/layouts/utils.js @@ -1,8 +1,8 @@ /** * Utility to generate the proper CSS selector for layout styles. * - * @param {string|string[]} selectors - CSS selectors - * @param {boolean} append - string to append. + * @param {string} selectors CSS selector, also supports multiple comma-separated selectors. + * @param {string} append The string to append. * * @return {string} - CSS selector. */ @@ -17,7 +17,48 @@ export function appendSelectors( selectors, append = '' ) { .split( ',' ) .map( ( subselector ) => - `.editor-styles-wrapper ${ subselector } ${ append }` + `.editor-styles-wrapper ${ subselector }${ + append ? ` ${ append }` : '' + }` ) .join( ',' ); } + +/** + * Get generated blockGap CSS rules based on layout definitions provided in theme.json + * Falsy values in the layout definition's spacingStyles rules will be swapped out + * with the provided `blockGapValue`. + * + * @param {string} selector The CSS selector to target for the generated rules. + * @param {Object} layoutDefinitions Layout definitions object from theme.json. + * @param {string} layoutType The layout type (e.g. `default` or `flex`). + * @param {string} blockGapValue The current blockGap value to be applied. + * @return {string} The generated CSS rules. + */ +export function getBlockGapCSS( + selector, + layoutDefinitions, + layoutType, + blockGapValue +) { + let output = ''; + if ( + layoutDefinitions?.[ layoutType ]?.spacingStyles?.length && + blockGapValue + ) { + layoutDefinitions[ layoutType ].spacingStyles.forEach( ( gapStyle ) => { + output += `${ appendSelectors( + selector, + gapStyle.selector.trim() + ) } { `; + output += Object.entries( gapStyle.rules ) + .map( + ( [ cssProperty, value ] ) => + `${ cssProperty }: ${ value ? value : blockGapValue }` + ) + .join( '; ' ); + output += '; }'; + } ); + } + return output; +} diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index ba41424b375efa..e45613bbba25d3 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -52,9 +52,7 @@ function GroupEdit( { attributes, setAttributes, clientId } ) { const { type = 'default' } = usedLayout; const layoutSupportEnabled = themeSupportsLayout || type !== 'default'; - const blockProps = useBlockProps( { - className: `is-layout-${ type }`, - } ); + const blockProps = useBlockProps(); const innerBlocksProps = useInnerBlocksProps( layoutSupportEnabled diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index b7e9d11524ac77..fb8580ef30ef0a 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -199,10 +199,6 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { support: [ 'typography', '__experimentalLetterSpacing' ], useEngine: true, }, - '--wp--style--block-gap': { - value: [ 'spacing', 'blockGap' ], - support: [ 'spacing', 'blockGap' ], - }, }; export const __EXPERIMENTAL_ELEMENTS = { diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index b5a2b0a4a4b971..77054884472a67 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -245,6 +245,7 @@ export default function VisualEditor( { styles } ) { ) } { ! isTemplateMode && ( @@ -260,7 +261,7 @@ export default function VisualEditor( { styles } ) { className={ isTemplateMode ? 'wp-site-blocks' - : undefined + : 'is-layout-flow' // Ensure root level blocks receive default/flow blockGap styling rules. } __experimentalLayout={ layout } /> diff --git a/packages/edit-site/src/components/global-styles/dimensions-panel.js b/packages/edit-site/src/components/global-styles/dimensions-panel.js index 80c6c25e1d64cb..0cd4571f1a3a41 100644 --- a/packages/edit-site/src/components/global-styles/dimensions-panel.js +++ b/packages/edit-site/src/components/global-styles/dimensions-panel.js @@ -43,13 +43,8 @@ function useHasMargin( name ) { function useHasGap( name ) { const supports = getSupportedGlobalStylesPanels( name ); const [ settings ] = useSetting( 'spacing.blockGap', name ); - // Do not show the gap control panel for block-level global styles - // as they do not work on the frontend. - // See: https://github.com/WordPress/gutenberg/pull/39845. - // We can revert this condition when they're working again. - return !! name - ? false - : settings && supports.includes( '--wp--style--block-gap' ); + + return settings && supports.includes( 'blockGap' ); } function filterValuesBySides( values, sides ) { diff --git a/packages/edit-site/src/components/global-styles/hooks.js b/packages/edit-site/src/components/global-styles/hooks.js index da7a9ccf6de234..ddfb478122e4ea 100644 --- a/packages/edit-site/src/components/global-styles/hooks.js +++ b/packages/edit-site/src/components/global-styles/hooks.js @@ -182,6 +182,13 @@ export function getSupportedGlobalStylesPanels( name ) { } const supportKeys = []; + + // TODO: In order to remove the block gap CSS variable from `STYLE_PROPERTY`, we need to update the logic here. + // Before landing this change, let's find a better place for this type of check so that it's a little more declarative? + if ( blockType?.supports?.spacing?.blockGap ) { + supportKeys.push( 'blockGap' ); + } + Object.keys( STYLE_PROPERTY ).forEach( ( styleName ) => { if ( ! STYLE_PROPERTY[ styleName ].support ) { return; diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index 9a8ad4f54282b7..4931a8257a0123 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -24,7 +24,10 @@ import { } from '@wordpress/blocks'; import { useEffect, useState, useContext } from '@wordpress/element'; import { getCSSRules } from '@wordpress/style-engine'; -import { __unstablePresetDuotoneFilter as PresetDuotoneFilter } from '@wordpress/block-editor'; +import { + __unstablePresetDuotoneFilter as PresetDuotoneFilter, + __experimentalGetGapCSSValue as getGapCSSValue, +} from '@wordpress/block-editor'; /** * Internal dependencies @@ -231,6 +234,103 @@ function getStylesDeclarations( blockStyles = {} ) { return output; } +/** + * Get generated CSS for layout styles by looking up layout definitions provided + * in theme.json, and outputting common layout styles, and specific blockGap values. + * + * @param {Object} tree A theme.json tree containing layout definitions. + * @param {Object} style A style object containing spacing values. + * @param {string} selector Selector used to group together layout styling rules. + * @param {boolean} hasBlockGapSupport Whether or not the theme opts-in to blockGap support. + * @return {string} Generated CSS rules for the layout styles. + */ +function getLayoutStyles( tree, style, selector, hasBlockGapSupport ) { + let ruleset = ''; + let gapValue = style?.spacing?.blockGap; + if ( + typeof gapValue !== undefined && + tree?.settings?.layout?.definitions + ) { + gapValue = getGapCSSValue( gapValue, '0.5em' ); + Object.values( tree.settings.layout.definitions ).forEach( + ( { className, spacingStyles } ) => { + if ( spacingStyles?.length ) { + spacingStyles.forEach( ( spacingStyle ) => { + const declarations = []; + + if ( spacingStyle.rules ) { + Object.entries( spacingStyle.rules ).forEach( + ( [ cssProperty, cssValue ] ) => { + declarations.push( + `${ cssProperty }: ${ + cssValue ? cssValue : gapValue + }` + ); + } + ); + } + + if ( declarations.length ) { + const combinedSelector = + selector === ROOT_BLOCK_SELECTOR + ? `${ selector } .${ className }${ + spacingStyle?.selector || '' + }` + : `${ selector }.${ className }${ + spacingStyle?.selector || '' + }`; + ruleset += `${ combinedSelector } { ${ declarations.join( + '; ' + ) } }`; + } + } ); + } + } + ); + // For backwards compatibility, ensure the legacy block gap CSS variable is still available. + if ( selector === ROOT_BLOCK_SELECTOR && hasBlockGapSupport ) { + ruleset += `${ selector } { --wp--style--block-gap: ${ gapValue }; }`; + } + } + + // Output base styles + if ( + selector === ROOT_BLOCK_SELECTOR && + tree?.settings?.layout?.definitions + ) { + Object.values( tree.settings.layout.definitions ).forEach( + ( { className, baseStyles } ) => { + if ( baseStyles?.length ) { + baseStyles.forEach( ( baseStyle ) => { + const declarations = []; + + if ( baseStyle.rules ) { + Object.entries( baseStyle.rules ).forEach( + ( [ cssProperty, cssValue ] ) => { + declarations.push( + `${ cssProperty }: ${ cssValue }` + ); + } + ); + } + + if ( declarations.length ) { + const combinedSelector = `${ selector } .${ className }${ + baseStyle?.selector || '' + }`; + ruleset += `${ combinedSelector } { ${ declarations.join( + '; ' + ) } }`; + } + } ); + } + } + ); + } + + return ruleset; +} + export const getNodesWithStyles = ( tree, blockSelectors ) => { const nodes = []; @@ -395,6 +495,14 @@ export const toStyles = ( tree, blockSelectors, hasBlockGapSupport ) => { `${ duotoneSelector }{${ duotoneDeclarations.join( ';' ) };}`; } + // Process blockGap and layout styles. + ruleset += getLayoutStyles( + tree, + styles, + selector, + hasBlockGapSupport + ); + // Process the remaning block styles (they use either normal block class or __experimentalSelector). const declarations = getStylesDeclarations( styles ); if ( declarations?.length ) { @@ -447,12 +555,17 @@ export const toStyles = ( tree, blockSelectors, hasBlockGapSupport ) => { '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; if ( hasBlockGapSupport ) { + // TODO: How do we correctly support different fallback values? + const gapValue = getGapCSSValue( + tree?.styles?.spacing?.blockGap, + '0.5em' + ); ruleset = ruleset + '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; ruleset = ruleset + - '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; + `.wp-site-blocks > * + * { margin-block-start: ${ gapValue }; }`; } nodesWithSettings.forEach( ( { selector, presets } ) => { diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index a662b8efb1d9e6..d22b3ade03699b 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -7,7 +7,218 @@ */ class WP_Theme_JSON_Gutenberg_Test extends WP_UnitTestCase { + /** + * @dataProvider data_get_layout_definitions + * + * @param array $layout_definitions Layout definitions as stored in core theme.json. + */ + public function test_get_stylesheet_generates_layout_styles( $layout_definitions ) { + $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' => true, + ), + ), + 'styles' => array( + 'spacing' => array( + 'blockGap' => '1em', + ), + ), + ), + 'default' + ); + + // Results also include root site blocks styles. + $this->assertEquals( + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }.wp-site-blocks > * + * { margin-block-start: 1em; }body { --wp--style--block-gap: 1em; }body .is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}body .is-layout-flow > * + *{margin-block-start: 1em;margin-block-end: 0;}body .is-layout-flex{gap: 1em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;flex-wrap: wrap;align-items: center;}', + $theme_json->get_stylesheet( array( 'styles' ) ) + ); + } + + /** + * @dataProvider data_get_layout_definitions + * + * @param array $layout_definitions Layout definitions as stored in core theme.json. + */ + public function test_get_stylesheet_generates_fallback_gap_layout_styles( $layout_definitions ) { + add_theme_support( 'wp-block-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, + ), + ), + 'styles' => array( + 'spacing' => array( + 'blockGap' => '1em', + ), + ), + ), + 'default' + ); + $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); + remove_theme_support( 'wp-block-styles' ); + + // Results also include root site blocks styles. + $this->assertEquals( + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }body .is-layout-flex{gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;flex-wrap: wrap;align-items: center;}', + $stylesheet + ); + } + + /** + * @dataProvider data_get_layout_definitions + * + * @param array $layout_definitions Layout definitions as stored in core theme.json. + */ + public function test_get_stylesheet_generates_base_fallback_gap_layout_styles( $layout_definitions ) { + add_theme_support( 'wp-block-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( 'wp-block-styles' ); + // Note the `base-layout-styles` includes a fallback gap for the Columns block for backwards compatibility. + $this->assertEquals( + 'body .is-layout-flex{gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;flex-wrap: wrap;align-items: center;}.wp-block-columns.is-layout-flex{gap: 2em;}', + $stylesheet + ); + } + + /** + * @dataProvider data_get_layout_definitions + * + * @param array $layout_definitions Layout definitions as stored in core theme.json. + */ + public function test_get_stylesheet_generates_base_layout_styles_and_skips_layout_gap_styles( $layout_definitions ) { + remove_theme_support( 'wp-block-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' + ); + + // Results only include base layout styles such as alignment, and does not include gap values. + $this->assertEquals( + 'body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;flex-wrap: wrap;align-items: center;}', + $theme_json->get_stylesheet( array( 'base-layout-styles' ) ) + ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_layout_definitions() { + return array( + 'layout definitions' => array( + array( + 'default' => array( + 'name' => 'default', + 'slug' => 'flow', + 'className' => 'is-layout-flow', + 'baseStyles' => array( + array( + 'selector' => ' > .alignleft', + 'rules' => array( + 'float' => 'left', + 'margin-inline-start' => '0', + 'margin-inline-end' => '2em', + ), + ), + array( + 'selector' => ' > .alignright', + 'rules' => array( + 'float' => 'right', + 'margin-inline-start' => '2em', + 'margin-inline-end' => '0', + ), + ), + array( + 'selector' => ' > .aligncenter', + 'rules' => array( + 'margin-left' => 'auto !important', + 'margin-right' => 'auto !important', + ), + ), + ), + 'spacingStyles' => array( + array( + 'selector' => ' > *', + 'rules' => array( + 'margin-block-start' => '0', + 'margin-block-end' => '0', + ), + ), + array( + 'selector' => ' > * + *', + 'rules' => array( + 'margin-block-start' => null, + 'margin-block-end' => '0', + ), + ), + ), + ), + 'flex' => array( + 'name' => 'flex', + 'slug' => 'flex', + 'className' => 'is-layout-flex', + 'baseStyles' => array( + array( + 'selector' => '', + 'rules' => array( + 'display' => 'flex', + 'flex-wrap' => 'wrap', + 'align-items' => 'center', + ), + ), + ), + 'spacingStyles' => array( + array( + 'selector' => '', + 'rules' => array( + 'gap' => null, + ), + ), + ), + ), + ), + ), + ); + } function test_get_stylesheet_handles_whitelisted_element_pseudo_selectors() { $theme_json = new WP_Theme_JSON_Gutenberg( array( From eee8666c230d3e6c727ee282b6b865413d385b8c Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:48:40 +1000 Subject: [PATCH 02/14] Fix gap styles for Gallery block --- packages/block-library/src/gallery/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/gallery/index.php b/packages/block-library/src/gallery/index.php index 81010c9064f830..e6eecb7dda4122 100644 --- a/packages/block-library/src/gallery/index.php +++ b/packages/block-library/src/gallery/index.php @@ -81,7 +81,7 @@ function block_core_gallery_render( $attributes, $content ) { } // Set the CSS variable to the column value, and the `gap` property to the combined gap value. - $style = '.' . $class . '{ --wp--style--unstable-gallery-gap: ' . $gap_column . '; gap: ' . $gap_value . '}'; + $style = '.wp-block-gallery.' . $class . '{ --wp--style--unstable-gallery-gap: ' . $gap_column . '; gap: ' . $gap_value . '}'; gutenberg_enqueue_block_support_styles( $style, 11 ); return $content; From fc311d6c1cc4b0e89bae9e6238f0622c9f6f66a5 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Mon, 27 Jun 2022 17:01:44 +1000 Subject: [PATCH 03/14] Ensure blockGap controls are not exposed in global styles when experimental skip serializiation is used. This ensures that the Gallery block is not exposed in the global styles UI, as its blockGap values are proceeded individually at the block level. Support for the Gallery block in global styles will be looked into in future follow-ups. --- .../src/components/global-styles/hooks.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/hooks.js b/packages/edit-site/src/components/global-styles/hooks.js index ddfb478122e4ea..af9fac61d30425 100644 --- a/packages/edit-site/src/components/global-styles/hooks.js +++ b/packages/edit-site/src/components/global-styles/hooks.js @@ -27,10 +27,9 @@ export const useGlobalStylesReset = () => { const canReset = !! config && ! isEqual( config, EMPTY_CONFIG ); return [ canReset, - useCallback( - () => setUserConfig( () => EMPTY_CONFIG ), - [ setUserConfig ] - ), + useCallback( () => setUserConfig( () => EMPTY_CONFIG ), [ + setUserConfig, + ] ), ]; }; @@ -183,9 +182,17 @@ export function getSupportedGlobalStylesPanels( name ) { const supportKeys = []; - // TODO: In order to remove the block gap CSS variable from `STYLE_PROPERTY`, we need to update the logic here. - // Before landing this change, let's find a better place for this type of check so that it's a little more declarative? - if ( blockType?.supports?.spacing?.blockGap ) { + // Check for blockGap support. + // Block spacing support doesn't map directly to a single style property, so needs to be handled separately. + // Also, only allow `blockGap` support if serialization has not been skipped, to be sure global spacing can be applied. + if ( + blockType?.supports?.spacing?.blockGap && + blockType?.supports?.spacing?.__experimentalSkipSerialization !== + true && + ! blockType?.supports?.spacing?.__experimentalSkipSerialization?.some?.( + ( spacingType ) => spacingType === 'blockGap' + ) + ) { supportKeys.push( 'blockGap' ); } From 52aa4ca013eb11b3be53b179c748d1d4f7fc9016 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 28 Jun 2022 10:31:09 +1000 Subject: [PATCH 04/14] Fix linting issue --- packages/edit-site/src/components/global-styles/hooks.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/hooks.js b/packages/edit-site/src/components/global-styles/hooks.js index af9fac61d30425..69bc0762f3a43c 100644 --- a/packages/edit-site/src/components/global-styles/hooks.js +++ b/packages/edit-site/src/components/global-styles/hooks.js @@ -27,9 +27,10 @@ export const useGlobalStylesReset = () => { const canReset = !! config && ! isEqual( config, EMPTY_CONFIG ); return [ canReset, - useCallback( () => setUserConfig( () => EMPTY_CONFIG ), [ - setUserConfig, - ] ), + useCallback( + () => setUserConfig( () => EMPTY_CONFIG ), + [ setUserConfig ] + ), ]; }; From 428b9962b2b1c6d763703ebfe7aa91a40c0d6797 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 28 Jun 2022 15:16:46 +1000 Subject: [PATCH 05/14] Implement fallback behaviour in site editor where default flex gap is still rendered in themes without blockGap but with wp-block-styles --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 2 +- .../global-styles/use-global-styles-output.js | 53 ++++++++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index fed87e131d6659..bd30463e284e7e 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -1182,7 +1182,7 @@ protected function get_layout_styles( $block_metadata ) { if ( $block_gap_value ) { foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { - // Allow skipping default layout, for example, so that classic themes can still output flex gap styles. + // Allow skipping default layout for themes that opt-in to block styles, but opt-out of blockGap. if ( ! $has_block_gap_support && 'default' === $layout_definition_key ) { continue; } diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index 4931a8257a0123..2c16597414c9b8 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -28,6 +28,8 @@ import { __unstablePresetDuotoneFilter as PresetDuotoneFilter, __experimentalGetGapCSSValue as getGapCSSValue, } from '@wordpress/block-editor'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -238,22 +240,36 @@ function getStylesDeclarations( blockStyles = {} ) { * Get generated CSS for layout styles by looking up layout definitions provided * in theme.json, and outputting common layout styles, and specific blockGap values. * - * @param {Object} tree A theme.json tree containing layout definitions. - * @param {Object} style A style object containing spacing values. - * @param {string} selector Selector used to group together layout styling rules. - * @param {boolean} hasBlockGapSupport Whether or not the theme opts-in to blockGap support. + * @param {Object} tree A theme.json tree containing layout definitions. + * @param {Object} style A style object containing spacing values. + * @param {string} selector Selector used to group together layout styling rules. + * @param {boolean} hasBlockGapSupport Whether or not the theme opts-in to blockGap support. + * @param {boolean} hasBlockStylesSupport Whether or not the theme opts-in to `wp-block-styles` support. * @return {string} Generated CSS rules for the layout styles. */ -function getLayoutStyles( tree, style, selector, hasBlockGapSupport ) { +function getLayoutStyles( + tree, + style, + selector, + hasBlockGapSupport, + hasBlockStylesSupport +) { let ruleset = ''; let gapValue = style?.spacing?.blockGap; if ( - typeof gapValue !== undefined && + ( gapValue !== undefined || hasBlockStylesSupport ) && tree?.settings?.layout?.definitions ) { - gapValue = getGapCSSValue( gapValue, '0.5em' ); + gapValue = hasBlockGapSupport + ? getGapCSSValue( gapValue, '0.5em' ) + : '0.5em'; Object.values( tree.settings.layout.definitions ).forEach( - ( { className, spacingStyles } ) => { + ( { className, name, spacingStyles } ) => { + // Allow skipping default layout for themes that opt-in to block styles, but opt-out of blockGap. + if ( ! hasBlockGapSupport && 'default' === name ) { + return; + } + if ( spacingStyles?.length ) { spacingStyles.forEach( ( spacingStyle ) => { const declarations = []; @@ -464,7 +480,12 @@ export const toCustomProperties = ( tree, blockSelectors ) => { return ruleset; }; -export const toStyles = ( tree, blockSelectors, hasBlockGapSupport ) => { +export const toStyles = ( + tree, + blockSelectors, + hasBlockGapSupport, + hasBlockStylesSupport +) => { const nodesWithStyles = getNodesWithStyles( tree, blockSelectors ); const nodesWithSettings = getNodesWithSettings( tree, blockSelectors ); @@ -500,7 +521,8 @@ export const toStyles = ( tree, blockSelectors, hasBlockGapSupport ) => { tree, styles, selector, - hasBlockGapSupport + hasBlockGapSupport, + hasBlockStylesSupport ); // Process the remaning block styles (they use either normal block class or __experimentalSelector). @@ -555,7 +577,7 @@ export const toStyles = ( tree, blockSelectors, hasBlockGapSupport ) => { '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; if ( hasBlockGapSupport ) { - // TODO: How do we correctly support different fallback values? + // Use fallback of `0.5em` just in case, however if there is blockGap support, there should nearly always be a real value. const gapValue = getGapCSSValue( tree?.styles?.spacing?.blockGap, '0.5em' @@ -616,6 +638,10 @@ export function useGlobalStylesOutput() { const { merged: mergedConfig } = useContext( GlobalStylesContext ); const [ blockGap ] = useSetting( 'spacing.blockGap' ); const hasBlockGapSupport = blockGap !== null; + const hasBlockStylesSupport = useSelect( + ( select ) => + select( coreStore ).getThemeSupports()[ 'wp-block-styles' ] + ); useEffect( () => { if ( ! mergedConfig?.styles || ! mergedConfig?.settings ) { @@ -630,7 +656,8 @@ export function useGlobalStylesOutput() { const globalStyles = toStyles( mergedConfig, blockSelectors, - hasBlockGapSupport + hasBlockGapSupport, + hasBlockStylesSupport ); const filters = toSvgFilters( mergedConfig, blockSelectors ); setStylesheets( [ @@ -645,7 +672,7 @@ export function useGlobalStylesOutput() { ] ); setSettings( mergedConfig.settings ); setSvgFilters( filters ); - }, [ mergedConfig ] ); + }, [ hasBlockGapSupport, hasBlockStylesSupport, mergedConfig ] ); return [ stylesheets, settings, svgFilters, hasBlockGapSupport ]; } From 0b1846039ff3f712a8b0866a12ade7909a21bf0e Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 29 Jun 2022 16:39:11 +1000 Subject: [PATCH 06/14] Add tests for global styles --- .../test/use-global-styles-output.js | 141 ++++++++++++++++++ .../global-styles/use-global-styles-output.js | 6 +- 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js index 10e1d533e025ba..3fd03fa1831d23 100644 --- a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js @@ -7,6 +7,7 @@ import { __EXPERIMENTAL_ELEMENTS as ELEMENTS } from '@wordpress/blocks'; * Internal dependencies */ import { + getLayoutStyles, getNodesWithSettings, getNodesWithStyles, toCustomProperties, @@ -450,4 +451,144 @@ describe( 'global styles renderer', () => { ); } ); } ); + + describe( 'getLayoutStyles', () => { + const layoutDefinitionsTree = { + settings: { + layout: { + definitions: { + default: { + name: 'default', + slug: 'flow', + className: 'is-layout-flow', + baseStyles: [ + { + selector: ' > .alignleft', + rules: { + float: 'left', + 'margin-inline-start': '0', + 'margin-inline-end': '2em', + }, + }, + { + selector: ' > .alignright', + rules: { + float: 'right', + 'margin-inline-start': '2em', + 'margin-inline-end': '0', + }, + }, + { + selector: ' > .aligncenter', + rules: { + 'margin-left': 'auto !important', + 'margin-right': 'auto !important', + }, + }, + ], + spacingStyles: [ + { + selector: ' > *', + rules: { + 'margin-block-start': '0', + 'margin-block-end': '0', + }, + }, + { + selector: ' > * + *', + rules: { + 'margin-block-start': null, + 'margin-block-end': '0', + }, + }, + ], + }, + flex: { + name: 'flex', + slug: 'flex', + className: 'is-layout-flex', + baseStyles: [ + { + selector: '', + rules: { + display: 'flex', + 'flex-wrap': 'wrap', + 'align-items': 'center', + }, + }, + { + selector: ' > *', + rules: { + margin: '0', + }, + }, + ], + spacingStyles: [ + { + selector: '', + rules: { + gap: null, + }, + }, + ], + }, + }, + }, + }, + }; + + it( 'should return fallback gap flex layout style if block styles are enabled and blockGap is disabled', () => { + const hasBlockGapSupport = false; + const hasBlockStylesSupport = true; + const style = { spacing: { blockGap: '12px' } }; + + const layoutStyles = getLayoutStyles( + layoutDefinitionsTree, + style, + 'wp-block-test', + hasBlockGapSupport, + hasBlockStylesSupport + ); + + expect( layoutStyles ).toEqual( + 'wp-block-test.is-layout-flex { gap: 0.5em; }' + ); + } ); + + it( 'should return fallback gap layout styles if blockGap is enabled, but there is no blockGap value', () => { + const hasBlockGapSupport = true; + const hasBlockStylesSupport = true; + const style = {}; + + const layoutStyles = getLayoutStyles( + layoutDefinitionsTree, + style, + 'body', + hasBlockGapSupport, + hasBlockStylesSupport + ); + + expect( layoutStyles ).toEqual( + 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 0.5em; margin-block-end: 0; }body .is-layout-flex { gap: 0.5em; }body { --wp--style--block-gap: 0.5em; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0 }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center }body .is-layout-flex > * { margin: 0 }' + ); + } ); + + it( 'should return real gap layout style if blockGap is enabled, and base styles', () => { + const hasBlockGapSupport = true; + const hasBlockStylesSupport = true; + const style = { spacing: { blockGap: '12px' } }; + + const layoutStyles = getLayoutStyles( + layoutDefinitionsTree, + style, + 'body', + hasBlockGapSupport, + hasBlockStylesSupport + ); + + expect( layoutStyles ).toEqual( + 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }body .is-layout-flex { gap: 12px; }body { --wp--style--block-gap: 12px; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0 }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center }body .is-layout-flex > * { margin: 0 }' + ); + } ); + } ); } ); diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index 2c16597414c9b8..08be7bafd64950 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -247,7 +247,7 @@ function getStylesDeclarations( blockStyles = {} ) { * @param {boolean} hasBlockStylesSupport Whether or not the theme opts-in to `wp-block-styles` support. * @return {string} Generated CSS rules for the layout styles. */ -function getLayoutStyles( +export function getLayoutStyles( tree, style, selector, @@ -261,7 +261,7 @@ function getLayoutStyles( tree?.settings?.layout?.definitions ) { gapValue = hasBlockGapSupport - ? getGapCSSValue( gapValue, '0.5em' ) + ? getGapCSSValue( gapValue, '0.5em' ) || '0.5em' : '0.5em'; Object.values( tree.settings.layout.definitions ).forEach( ( { className, name, spacingStyles } ) => { @@ -297,7 +297,7 @@ function getLayoutStyles( }`; ruleset += `${ combinedSelector } { ${ declarations.join( '; ' - ) } }`; + ) }; }`; } } ); } From d6ea6dc1461f175fe61a6eed19b1e55995be012f Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 30 Jun 2022 11:26:21 +1000 Subject: [PATCH 07/14] In global styles, only output layout gap rules at the block level for blocks that opt-in to the layout support --- .../test/use-global-styles-output.js | 66 ++++--- .../global-styles/use-global-styles-output.js | 176 ++++++++++-------- 2 files changed, 135 insertions(+), 107 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js index 3fd03fa1831d23..3730868d5ae872 100644 --- a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js @@ -537,57 +537,67 @@ describe( 'global styles renderer', () => { }, }; - it( 'should return fallback gap flex layout style if block styles are enabled and blockGap is disabled', () => { - const hasBlockGapSupport = false; - const hasBlockStylesSupport = true; + it( 'should return fallback gap flex layout style, and all base styles, if block styles are enabled and blockGap is disabled', () => { const style = { spacing: { blockGap: '12px' } }; - const layoutStyles = getLayoutStyles( - layoutDefinitionsTree, + const layoutStyles = getLayoutStyles( { + tree: layoutDefinitionsTree, style, - 'wp-block-test', - hasBlockGapSupport, - hasBlockStylesSupport - ); + selector: 'body', + hasBlockGapSupport: false, + hasBlockStylesSupport: true, + } ); expect( layoutStyles ).toEqual( - 'wp-block-test.is-layout-flex { gap: 0.5em; }' + 'body .is-layout-flex { gap: 0.5em; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' ); } ); - it( 'should return fallback gap layout styles if blockGap is enabled, but there is no blockGap value', () => { - const hasBlockGapSupport = true; - const hasBlockStylesSupport = true; + it( 'should return fallback gap layout styles, and base styles, if blockGap is enabled, but there is no blockGap value', () => { const style = {}; - const layoutStyles = getLayoutStyles( - layoutDefinitionsTree, + const layoutStyles = getLayoutStyles( { + tree: layoutDefinitionsTree, style, - 'body', - hasBlockGapSupport, - hasBlockStylesSupport - ); + selector: 'body', + hasBlockGapSupport: true, + hasBlockStylesSupport: true, + } ); expect( layoutStyles ).toEqual( - 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 0.5em; margin-block-end: 0; }body .is-layout-flex { gap: 0.5em; }body { --wp--style--block-gap: 0.5em; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0 }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center }body .is-layout-flex > * { margin: 0 }' + 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 0.5em; margin-block-end: 0; }body .is-layout-flex { gap: 0.5em; }body { --wp--style--block-gap: 0.5em; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' ); } ); it( 'should return real gap layout style if blockGap is enabled, and base styles', () => { - const hasBlockGapSupport = true; - const hasBlockStylesSupport = true; const style = { spacing: { blockGap: '12px' } }; - const layoutStyles = getLayoutStyles( - layoutDefinitionsTree, + const layoutStyles = getLayoutStyles( { + tree: layoutDefinitionsTree, style, - 'body', - hasBlockGapSupport, - hasBlockStylesSupport + selector: 'body', + hasBlockGapSupport: true, + hasBlockStylesSupport: true, + } ); + + expect( layoutStyles ).toEqual( + 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }body .is-layout-flex { gap: 12px; }body { --wp--style--block-gap: 12px; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' ); + } ); + + it( 'should return real gap layout style if blockGap is enabled', () => { + const style = { spacing: { blockGap: '12px' } }; + + const layoutStyles = getLayoutStyles( { + tree: layoutDefinitionsTree, + style, + selector: '.wp-block-group', + hasBlockGapSupport: true, + hasBlockStylesSupport: true, + } ); expect( layoutStyles ).toEqual( - 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }body .is-layout-flex { gap: 12px; }body { --wp--style--block-gap: 12px; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0 }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center }body .is-layout-flex > * { margin: 0 }' + '.wp-block-group.is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.wp-block-group.is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }.wp-block-group.is-layout-flex { gap: 12px; }' ); } ); } ); diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index 08be7bafd64950..cb3454f51e2e00 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -240,29 +240,37 @@ function getStylesDeclarations( blockStyles = {} ) { * Get generated CSS for layout styles by looking up layout definitions provided * in theme.json, and outputting common layout styles, and specific blockGap values. * - * @param {Object} tree A theme.json tree containing layout definitions. - * @param {Object} style A style object containing spacing values. - * @param {string} selector Selector used to group together layout styling rules. - * @param {boolean} hasBlockGapSupport Whether or not the theme opts-in to blockGap support. - * @param {boolean} hasBlockStylesSupport Whether or not the theme opts-in to `wp-block-styles` support. + * @param {Object} props + * @param {Object} props.tree A theme.json tree containing layout definitions. + * @param {Object} props.style A style object containing spacing values. + * @param {string} props.selector Selector used to group together layout styling rules. + * @param {boolean} props.hasBlockGapSupport Whether or not the theme opts-in to blockGap support. + * @param {boolean} props.hasBlockStylesSupport Whether or not the theme opts-in to `wp-block-styles` support. * @return {string} Generated CSS rules for the layout styles. */ -export function getLayoutStyles( +export function getLayoutStyles( { tree, style, selector, hasBlockGapSupport, - hasBlockStylesSupport -) { + hasBlockStylesSupport, +} ) { let ruleset = ''; - let gapValue = style?.spacing?.blockGap; + let gapValue = hasBlockGapSupport + ? getGapCSSValue( style?.spacing?.blockGap ) + : ''; + + // Ensure a fallback gap value for the root layout definitions if no gap value exists, + // or if there is block styles support and blockGap has been disabled. if ( - ( gapValue !== undefined || hasBlockStylesSupport ) && - tree?.settings?.layout?.definitions + ( ! gapValue || ! hasBlockGapSupport ) && + selector === ROOT_BLOCK_SELECTOR && + hasBlockStylesSupport ) { - gapValue = hasBlockGapSupport - ? getGapCSSValue( gapValue, '0.5em' ) || '0.5em' - : '0.5em'; + gapValue = '0.5em'; + } + + if ( gapValue && tree?.settings?.layout?.definitions ) { Object.values( tree.settings.layout.definitions ).forEach( ( { className, name, spacingStyles } ) => { // Allow skipping default layout for themes that opt-in to block styles, but opt-out of blockGap. @@ -336,7 +344,7 @@ export function getLayoutStyles( }`; ruleset += `${ combinedSelector } { ${ declarations.join( '; ' - ) } }`; + ) }; }`; } } ); } @@ -383,9 +391,10 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => { const blockStyles = pickStyleKeys( node ); if ( !! blockStyles && !! blockSelectors?.[ blockName ]?.selector ) { nodes.push( { - styles: blockStyles, - selector: blockSelectors[ blockName ].selector, duotoneSelector: blockSelectors[ blockName ].duotoneSelector, + hasLayoutSupport: blockSelectors[ blockName ].hasLayoutSupport, + selector: blockSelectors[ blockName ].selector, + styles: blockStyles, } ); } @@ -498,72 +507,81 @@ export const toStyles = ( * @link https://github.com/WordPress/gutenberg/issues/36147. */ let ruleset = 'body {margin: 0;}'; - nodesWithStyles.forEach( ( { selector, duotoneSelector, styles } ) => { - const duotoneStyles = {}; - if ( styles?.filter ) { - duotoneStyles.filter = styles.filter; - delete styles.filter; - } + nodesWithStyles.forEach( + ( { selector, duotoneSelector, styles, hasLayoutSupport } ) => { + const duotoneStyles = {}; + if ( styles?.filter ) { + duotoneStyles.filter = styles.filter; + delete styles.filter; + } - // Process duotone styles (they use color.__experimentalDuotone selector). - if ( duotoneSelector ) { - const duotoneDeclarations = getStylesDeclarations( duotoneStyles ); - if ( duotoneDeclarations.length === 0 ) { - return; + // Process duotone styles (they use color.__experimentalDuotone selector). + if ( duotoneSelector ) { + const duotoneDeclarations = + getStylesDeclarations( duotoneStyles ); + if ( duotoneDeclarations.length === 0 ) { + return; + } + ruleset = + ruleset + + `${ duotoneSelector }{${ duotoneDeclarations.join( + ';' + ) };}`; } - ruleset = - ruleset + - `${ duotoneSelector }{${ duotoneDeclarations.join( ';' ) };}`; - } - // Process blockGap and layout styles. - ruleset += getLayoutStyles( - tree, - styles, - selector, - hasBlockGapSupport, - hasBlockStylesSupport - ); + // Process blockGap and layout styles. + if ( ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport ) { + ruleset += getLayoutStyles( { + tree, + style: styles, + selector, + hasBlockGapSupport, + hasBlockStylesSupport, + } ); + } - // Process the remaning block styles (they use either normal block class or __experimentalSelector). - const declarations = getStylesDeclarations( styles ); - if ( declarations?.length ) { - ruleset = ruleset + `${ selector }{${ declarations.join( ';' ) };}`; - } + // Process the remaning block styles (they use either normal block class or __experimentalSelector). + const declarations = getStylesDeclarations( styles ); + if ( declarations?.length ) { + ruleset = + ruleset + `${ selector }{${ declarations.join( ';' ) };}`; + } - // Check for pseudo selector in `styles` and handle separately. - const psuedoSelectorStyles = Object.entries( styles ).filter( - ( [ key ] ) => key.startsWith( ':' ) - ); + // Check for pseudo selector in `styles` and handle separately. + const psuedoSelectorStyles = Object.entries( styles ).filter( + ( [ key ] ) => key.startsWith( ':' ) + ); - if ( psuedoSelectorStyles?.length ) { - psuedoSelectorStyles.forEach( ( [ pseudoKey, pseudoRule ] ) => { - const pseudoDeclarations = getStylesDeclarations( pseudoRule ); + if ( psuedoSelectorStyles?.length ) { + psuedoSelectorStyles.forEach( ( [ pseudoKey, pseudoRule ] ) => { + const pseudoDeclarations = + getStylesDeclarations( pseudoRule ); - if ( ! pseudoDeclarations?.length ) { - return; - } + if ( ! pseudoDeclarations?.length ) { + return; + } - // `selector` maybe provided in a form - // where block level selectors have sub element - // selectors appended to them as a comma seperated - // string. - // e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`; - // Split and append pseudo selector to create - // the proper rules to target the elements. - const _selector = selector - .split( ',' ) - .map( ( sel ) => sel + pseudoKey ) - .join( ',' ); - - const psuedoRule = `${ _selector }{${ pseudoDeclarations.join( - ';' - ) };}`; - - ruleset = ruleset + psuedoRule; - } ); + // `selector` maybe provided in a form + // where block level selectors have sub element + // selectors appended to them as a comma seperated + // string. + // e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`; + // Split and append pseudo selector to create + // the proper rules to target the elements. + const _selector = selector + .split( ',' ) + .map( ( sel ) => sel + pseudoKey ) + .join( ',' ); + + const psuedoRule = `${ _selector }{${ pseudoDeclarations.join( + ';' + ) };}`; + + ruleset = ruleset + psuedoRule; + } ); + } } - } ); + ); /* Add alignment / layout styles */ ruleset = @@ -578,10 +596,8 @@ export const toStyles = ( if ( hasBlockGapSupport ) { // Use fallback of `0.5em` just in case, however if there is blockGap support, there should nearly always be a real value. - const gapValue = getGapCSSValue( - tree?.styles?.spacing?.blockGap, - '0.5em' - ); + const gapValue = + getGapCSSValue( tree?.styles?.spacing?.blockGap ) || '0.5em'; ruleset = ruleset + '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; @@ -621,10 +637,12 @@ const getBlockSelectors = ( blockTypes ) => { '.wp-block-' + name.replace( 'core/', '' ).replace( '/', '-' ); const duotoneSelector = blockType?.supports?.color?.__experimentalDuotone ?? null; + const hasLayoutSupport = !! blockType?.supports?.__experimentalLayout; result[ name ] = { + duotoneSelector, + hasLayoutSupport, name, selector, - duotoneSelector, }; } ); From 3df57a0ac4c780b3a720b65337e3ae6454a34b04 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 30 Jun 2022 11:58:25 +1000 Subject: [PATCH 08/14] Update PHP implementation to check for layout support when rendering at the block level --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index bd30463e284e7e..14ffddfa54f028 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -1144,7 +1144,16 @@ public function set_spacing_sizes() { * @return string Layout styles for the block. */ protected function get_layout_styles( $block_metadata ) { - $block_rules = ''; + $block_rules = ''; + $block_type = null; + + 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 ) ) { + return $block_rules; + } + } + $selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : ''; $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; $has_block_styles_support = current_theme_supports( 'wp-block-styles' ); @@ -1160,8 +1169,7 @@ protected function get_layout_styles( $block_metadata ) { // Use a fallback gap value if block gap support is not available. if ( ! $has_block_gap_support ) { $block_gap_value = '0.5em'; - if ( isset( $block_metadata['name'] ) ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] ); + if ( ! empty( $block_type ) ) { $block_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), '0.5em' ); } } else { From 043846172c85e2775e2c3a2ac32d2c53a5f220d5 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 6 Jul 2022 13:01:47 +1000 Subject: [PATCH 09/14] Move display property output to special handling from an allow list --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 26 +++++++++++++++++-- lib/compat/wordpress-6.1/theme.json | 2 +- .../test/use-global-styles-output.js | 8 +++--- .../global-styles/use-global-styles-output.js | 10 ++++++- phpunit/class-wp-theme-json-test.php | 10 +++---- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 14ffddfa54f028..f8b82319d1bb04 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -1239,6 +1239,7 @@ protected function get_layout_styles( $block_metadata ) { if ( static::ROOT_BLOCK_SELECTOR === $selector ) { + $valid_display_modes = array( 'block', 'flex', 'grid' ); foreach ( $layout_definitions as $layout_definition ) { $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); $base_style_rules = _wp_array_get( $layout_definition, array( 'baseStyles' ), array() ); @@ -1247,6 +1248,28 @@ protected function get_layout_styles( $block_metadata ) { ! empty( $class_name ) && ! empty( $base_style_rules ) ) { + // Output display mode. This requires special handling as `display` is not exposed in `safe_style_css_filter`. + if ( + ! empty( $layout_definition['displayMode'] ) && + is_string( $layout_definition['displayMode'] ) && + in_array( $layout_definition['displayMode'], $valid_display_modes, true ) + ) { + $layout_selector = sprintf( + '%s .%s', + $selector, + $class_name + ); + $block_rules .= static::to_ruleset( + $layout_selector, + array( + array( + 'name' => 'display', + 'value' => $layout_definition['displayMode'], + ), + ) + ); + } + foreach ( $base_style_rules as $base_style_rule ) { $declarations = array(); @@ -1264,9 +1287,8 @@ protected function get_layout_styles( $block_metadata ) { } } - $format = static::ROOT_BLOCK_SELECTOR === $selector ? '%s .%s%s' : '%s.%s%s'; $layout_selector = sprintf( - $format, + '%s .%s%s', $selector, $class_name, $base_style_rule['selector'] diff --git a/lib/compat/wordpress-6.1/theme.json b/lib/compat/wordpress-6.1/theme.json index ac1ad1508c5d68..426adc6195085d 100644 --- a/lib/compat/wordpress-6.1/theme.json +++ b/lib/compat/wordpress-6.1/theme.json @@ -237,11 +237,11 @@ "name": "flex", "slug": "flex", "className": "is-layout-flex", + "displayMode": "flex", "baseStyles": [ { "selector": "", "rules": { - "display": "flex", "flex-wrap": "wrap", "align-items": "center" } diff --git a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js index 3730868d5ae872..3b4d527ce0b746 100644 --- a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js @@ -507,11 +507,11 @@ describe( 'global styles renderer', () => { name: 'flex', slug: 'flex', className: 'is-layout-flex', + displayMode: 'flex', baseStyles: [ { selector: '', rules: { - display: 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', }, @@ -549,7 +549,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - 'body .is-layout-flex { gap: 0.5em; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' + 'body .is-layout-flex { gap: 0.5em; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }body .is-layout-flex { flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' ); } ); @@ -565,7 +565,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 0.5em; margin-block-end: 0; }body .is-layout-flex { gap: 0.5em; }body { --wp--style--block-gap: 0.5em; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' + 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 0.5em; margin-block-end: 0; }body .is-layout-flex { gap: 0.5em; }body { --wp--style--block-gap: 0.5em; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }body .is-layout-flex { flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' ); } ); @@ -581,7 +581,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }body .is-layout-flex { gap: 12px; }body { --wp--style--block-gap: 12px; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display: flex; flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' + 'body .is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }body .is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }body .is-layout-flex { gap: 12px; }body { --wp--style--block-gap: 12px; }body .is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }body .is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }body .is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }body .is-layout-flex { flex-wrap: wrap; align-items: center; }body .is-layout-flex > * { margin: 0; }' ); } ); diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index cb3454f51e2e00..792f6bfad41f33 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -322,8 +322,16 @@ export function getLayoutStyles( { selector === ROOT_BLOCK_SELECTOR && tree?.settings?.layout?.definitions ) { + const validDisplayModes = [ 'block', 'flex', 'grid' ]; Object.values( tree.settings.layout.definitions ).forEach( - ( { className, baseStyles } ) => { + ( { className, displayMode, baseStyles } ) => { + if ( + displayMode && + validDisplayModes.includes( displayMode ) + ) { + ruleset += `${ selector } .${ className } { display:${ displayMode }; }`; + } + if ( baseStyles?.length ) { baseStyles.forEach( ( baseStyle ) => { const declarations = []; diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index d22b3ade03699b..a86c346e8e0aa1 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -35,7 +35,7 @@ public function test_get_stylesheet_generates_layout_styles( $layout_definitions // Results also include root site blocks styles. $this->assertEquals( - 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }.wp-site-blocks > * + * { margin-block-start: 1em; }body { --wp--style--block-gap: 1em; }body .is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}body .is-layout-flow > * + *{margin-block-start: 1em;margin-block-end: 0;}body .is-layout-flex{gap: 1em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;flex-wrap: wrap;align-items: center;}', + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }.wp-site-blocks > * + * { margin-block-start: 1em; }body { --wp--style--block-gap: 1em; }body .is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}body .is-layout-flow > * + *{margin-block-start: 1em;margin-block-end: 0;}body .is-layout-flex{gap: 1em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}', $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -71,7 +71,7 @@ public function test_get_stylesheet_generates_fallback_gap_layout_styles( $layou // Results also include root site blocks styles. $this->assertEquals( - 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }body .is-layout-flex{gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;flex-wrap: wrap;align-items: center;}', + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }body .is-layout-flex{gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}', $stylesheet ); } @@ -102,7 +102,7 @@ public function test_get_stylesheet_generates_base_fallback_gap_layout_styles( $ // Note the `base-layout-styles` includes a fallback gap for the Columns block for backwards compatibility. $this->assertEquals( - 'body .is-layout-flex{gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;flex-wrap: wrap;align-items: center;}.wp-block-columns.is-layout-flex{gap: 2em;}', + 'body .is-layout-flex{gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}.wp-block-columns.is-layout-flex{gap: 2em;}', $stylesheet ); } @@ -132,7 +132,7 @@ public function test_get_stylesheet_generates_base_layout_styles_and_skips_layou // Results only include base layout styles such as alignment, and does not include gap values. $this->assertEquals( - 'body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;flex-wrap: wrap;align-items: center;}', + 'body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}', $theme_json->get_stylesheet( array( 'base-layout-styles' ) ) ); } @@ -196,11 +196,11 @@ public function data_get_layout_definitions() { 'name' => 'flex', 'slug' => 'flex', 'className' => 'is-layout-flex', + 'displayMode' => 'flex', 'baseStyles' => array( array( 'selector' => '', 'rules' => array( - 'display' => 'flex', 'flex-wrap' => 'wrap', 'align-items' => 'center', ), From 0d540f43b057cf09a8c4f86a765fa449bf241f43 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 6 Jul 2022 13:21:35 +1000 Subject: [PATCH 10/14] Remove display from allow list of CSS properties --- lib/compat/wordpress-6.1/blocks.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/compat/wordpress-6.1/blocks.php b/lib/compat/wordpress-6.1/blocks.php index 875b95e9a7992b..ed41c5aa8cd098 100644 --- a/lib/compat/wordpress-6.1/blocks.php +++ b/lib/compat/wordpress-6.1/blocks.php @@ -14,7 +14,6 @@ * @return string[] CSS attributes. */ function gutenberg_safe_style_attrs_6_1( $attrs ) { - $attrs[] = 'display'; $attrs[] = 'flex-wrap'; $attrs[] = 'gap'; $attrs[] = 'margin-block-start'; From 56dc49353f1d78a3b8ef0d50186a7eb76c8faf87 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:42:45 +1000 Subject: [PATCH 11/14] Fix pseudo typo --- .../global-styles/use-global-styles-output.js | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index 792f6bfad41f33..7a377432482157 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -548,7 +548,7 @@ export const toStyles = ( } ); } - // Process the remaning block styles (they use either normal block class or __experimentalSelector). + // Process the remaining block styles (they use either normal block class or __experimentalSelector). const declarations = getStylesDeclarations( styles ); if ( declarations?.length ) { ruleset = @@ -556,37 +556,39 @@ export const toStyles = ( } // Check for pseudo selector in `styles` and handle separately. - const psuedoSelectorStyles = Object.entries( styles ).filter( + const pseudoSelectorStyles = Object.entries( styles ).filter( ( [ key ] ) => key.startsWith( ':' ) ); - if ( psuedoSelectorStyles?.length ) { - psuedoSelectorStyles.forEach( ( [ pseudoKey, pseudoRule ] ) => { - const pseudoDeclarations = - getStylesDeclarations( pseudoRule ); + if ( pseudoSelectorStyles?.length ) { + pseudoSelectorStyles.forEach( + ( [ pseudoKey, pseudoStyle ] ) => { + const pseudoDeclarations = + getStylesDeclarations( pseudoStyle ); - if ( ! pseudoDeclarations?.length ) { - return; - } - - // `selector` maybe provided in a form - // where block level selectors have sub element - // selectors appended to them as a comma seperated - // string. - // e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`; - // Split and append pseudo selector to create - // the proper rules to target the elements. - const _selector = selector - .split( ',' ) - .map( ( sel ) => sel + pseudoKey ) - .join( ',' ); - - const psuedoRule = `${ _selector }{${ pseudoDeclarations.join( - ';' - ) };}`; + if ( ! pseudoDeclarations?.length ) { + return; + } - ruleset = ruleset + psuedoRule; - } ); + // `selector` maybe provided in a form + // where block level selectors have sub element + // selectors appended to them as a comma seperated + // string. + // e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`; + // Split and append pseudo selector to create + // the proper rules to target the elements. + const _selector = selector + .split( ',' ) + .map( ( sel ) => sel + pseudoKey ) + .join( ',' ); + + const pseudoRule = `${ _selector }{${ pseudoDeclarations.join( + ';' + ) };}`; + + ruleset = ruleset + pseudoRule; + } + ); } } ); From 78f5c48f9bb939c6e1e64b0a7e368214b3571fe1 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 7 Jul 2022 12:19:44 +1000 Subject: [PATCH 12/14] Remove connection to wp-block-styles so that fallback flex layout styles are always output --- .../wordpress-6.1/block-editor-settings.php | 4 +-- .../wordpress-6.1/class-wp-theme-json-6-1.php | 7 ++-- .../get-global-styles-and-settings.php | 5 +-- .../test/use-global-styles-output.js | 8 ++--- .../global-styles/use-global-styles-output.js | 21 +++++------- phpunit/class-wp-theme-json-test.php | 34 ------------------- 6 files changed, 18 insertions(+), 61 deletions(-) diff --git a/lib/compat/wordpress-6.1/block-editor-settings.php b/lib/compat/wordpress-6.1/block-editor-settings.php index c24cf6980d3420..de61109ed3ac73 100644 --- a/lib/compat/wordpress-6.1/block-editor-settings.php +++ b/lib/compat/wordpress-6.1/block-editor-settings.php @@ -81,8 +81,8 @@ function gutenberg_get_block_editor_settings( $settings ) { $block_classes['css'] = $actual_css; $new_global_styles[] = $block_classes; } - } elseif ( current_theme_supports( 'wp-block-styles' ) ) { - // If there is no `theme.json` file, but the theme opts in to block styles, ensure base layout styles are available. + } else { + // If there is no `theme.json` file, ensure base layout styles are still available. $block_classes = array( 'css' => 'base-layout-styles', '__unstableType' => 'theme', diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index f8b82319d1bb04..91e366717632e5 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -1156,15 +1156,14 @@ protected function get_layout_styles( $block_metadata ) { $selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : ''; $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; - $has_block_styles_support = current_theme_supports( 'wp-block-styles' ); + $has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support. $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); $layout_definitions = _wp_array_get( $this->theme_json, array( 'settings', 'layout', 'definitions' ), array() ); $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, and child combinator selectors. - // Gap styles will only be output if the theme has block gap support, or supports `wp-block-styles`. - // In this way, we tie the concept of gap styles to the styles that ship with core blocks. + // Gap styles will only be output if the theme has block gap support, or supports a fallback gap. // Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value. - if ( $has_block_gap_support || $has_block_styles_support ) { + if ( $has_block_gap_support || $has_fallback_gap_support ) { $block_gap_value = null; // Use a fallback gap value if block gap support is not available. if ( ! $has_block_gap_support ) { diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php index 227305f127eed6..5dd950c627a641 100644 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -78,10 +78,7 @@ function gutenberg_get_global_stylesheet( $types = array() ) { $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); $supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support(); if ( empty( $types ) && ! $supports_theme_json ) { - $types = array( 'variables', 'presets' ); - if ( current_theme_supports( 'wp-block-styles' ) ) { - $types[] = 'base-layout-styles'; - } + $types = array( 'variables', 'presets', 'base-layout-styles' ); } elseif ( empty( $types ) ) { $types = array( 'variables', 'styles', 'presets' ); } diff --git a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js index 3b4d527ce0b746..8014c015dc44c9 100644 --- a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js @@ -545,7 +545,7 @@ describe( 'global styles renderer', () => { style, selector: 'body', hasBlockGapSupport: false, - hasBlockStylesSupport: true, + hasFallbackGapSupport: true, } ); expect( layoutStyles ).toEqual( @@ -561,7 +561,7 @@ describe( 'global styles renderer', () => { style, selector: 'body', hasBlockGapSupport: true, - hasBlockStylesSupport: true, + hasFallbackGapSupport: true, } ); expect( layoutStyles ).toEqual( @@ -577,7 +577,7 @@ describe( 'global styles renderer', () => { style, selector: 'body', hasBlockGapSupport: true, - hasBlockStylesSupport: true, + hasFallbackGapSupport: true, } ); expect( layoutStyles ).toEqual( @@ -593,7 +593,7 @@ describe( 'global styles renderer', () => { style, selector: '.wp-block-group', hasBlockGapSupport: true, - hasBlockStylesSupport: true, + hasFallbackGapSupport: true, } ); expect( layoutStyles ).toEqual( diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index 7a377432482157..4012c4a71363a4 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -28,8 +28,6 @@ import { __unstablePresetDuotoneFilter as PresetDuotoneFilter, __experimentalGetGapCSSValue as getGapCSSValue, } from '@wordpress/block-editor'; -import { useSelect } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -245,7 +243,7 @@ function getStylesDeclarations( blockStyles = {} ) { * @param {Object} props.style A style object containing spacing values. * @param {string} props.selector Selector used to group together layout styling rules. * @param {boolean} props.hasBlockGapSupport Whether or not the theme opts-in to blockGap support. - * @param {boolean} props.hasBlockStylesSupport Whether or not the theme opts-in to `wp-block-styles` support. + * @param {boolean} props.hasFallbackGapSupport Whether or not the theme allows fallback gap styles. * @return {string} Generated CSS rules for the layout styles. */ export function getLayoutStyles( { @@ -253,7 +251,7 @@ export function getLayoutStyles( { style, selector, hasBlockGapSupport, - hasBlockStylesSupport, + hasFallbackGapSupport, } ) { let ruleset = ''; let gapValue = hasBlockGapSupport @@ -265,7 +263,7 @@ export function getLayoutStyles( { if ( ( ! gapValue || ! hasBlockGapSupport ) && selector === ROOT_BLOCK_SELECTOR && - hasBlockStylesSupport + hasFallbackGapSupport ) { gapValue = '0.5em'; } @@ -501,7 +499,7 @@ export const toStyles = ( tree, blockSelectors, hasBlockGapSupport, - hasBlockStylesSupport + hasFallbackGapSupport ) => { const nodesWithStyles = getNodesWithStyles( tree, blockSelectors ); const nodesWithSettings = getNodesWithSettings( tree, blockSelectors ); @@ -544,7 +542,7 @@ export const toStyles = ( style: styles, selector, hasBlockGapSupport, - hasBlockStylesSupport, + hasFallbackGapSupport, } ); } @@ -666,10 +664,7 @@ export function useGlobalStylesOutput() { const { merged: mergedConfig } = useContext( GlobalStylesContext ); const [ blockGap ] = useSetting( 'spacing.blockGap' ); const hasBlockGapSupport = blockGap !== null; - const hasBlockStylesSupport = useSelect( - ( select ) => - select( coreStore ).getThemeSupports()[ 'wp-block-styles' ] - ); + const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support. useEffect( () => { if ( ! mergedConfig?.styles || ! mergedConfig?.settings ) { @@ -685,7 +680,7 @@ export function useGlobalStylesOutput() { mergedConfig, blockSelectors, hasBlockGapSupport, - hasBlockStylesSupport + hasFallbackGapSupport ); const filters = toSvgFilters( mergedConfig, blockSelectors ); setStylesheets( [ @@ -700,7 +695,7 @@ export function useGlobalStylesOutput() { ] ); setSettings( mergedConfig.settings ); setSvgFilters( filters ); - }, [ hasBlockGapSupport, hasBlockStylesSupport, mergedConfig ] ); + }, [ hasBlockGapSupport, hasFallbackGapSupport, mergedConfig ] ); return [ stylesheets, settings, svgFilters, hasBlockGapSupport ]; } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index a86c346e8e0aa1..0c830d8de9a9c9 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -46,7 +46,6 @@ public function test_get_stylesheet_generates_layout_styles( $layout_definitions * @param array $layout_definitions Layout definitions as stored in core theme.json. */ public function test_get_stylesheet_generates_fallback_gap_layout_styles( $layout_definitions ) { - add_theme_support( 'wp-block-styles' ); $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, @@ -67,7 +66,6 @@ public function test_get_stylesheet_generates_fallback_gap_layout_styles( $layou 'default' ); $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); - remove_theme_support( 'wp-block-styles' ); // Results also include root site blocks styles. $this->assertEquals( @@ -82,7 +80,6 @@ public function test_get_stylesheet_generates_fallback_gap_layout_styles( $layou * @param array $layout_definitions Layout definitions as stored in core theme.json. */ public function test_get_stylesheet_generates_base_fallback_gap_layout_styles( $layout_definitions ) { - add_theme_support( 'wp-block-styles' ); $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, @@ -98,7 +95,6 @@ public function test_get_stylesheet_generates_base_fallback_gap_layout_styles( $ 'default' ); $stylesheet = $theme_json->get_stylesheet( array( 'base-layout-styles' ) ); - remove_theme_support( 'wp-block-styles' ); // Note the `base-layout-styles` includes a fallback gap for the Columns block for backwards compatibility. $this->assertEquals( @@ -107,36 +103,6 @@ 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_generates_base_layout_styles_and_skips_layout_gap_styles( $layout_definitions ) { - remove_theme_support( 'wp-block-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' - ); - - // Results only include base layout styles such as alignment, and does not include gap values. - $this->assertEquals( - 'body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}', - $theme_json->get_stylesheet( array( 'base-layout-styles' ) ) - ); - } - /** * Data provider. * From 422f6654393f0e6085a9e0655eb35c1c6cc22238 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 8 Jul 2022 12:55:54 +1000 Subject: [PATCH 13/14] Update resolver class to add an empty blockGap placeholder for a block, if it provides a default blockGap value --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 10 ++----- ...class-wp-theme-json-resolver-gutenberg.php | 9 ++++++ .../global-styles/use-global-styles-output.js | 28 +++++++++++++------ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 91e366717632e5..5bb0c4fa944231 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -688,13 +688,8 @@ function( $pseudo_selector ) use ( $selector ) { } // 4. Generate Layout block gap styles. - $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; - $has_block_gap_value = _wp_array_get( $node, array( 'spacing', 'blockGap' ), false ); - if ( static::ROOT_BLOCK_SELECTOR !== $selector && - $has_block_gap_support && - $has_block_gap_value && ! empty( $block_metadata['name'] ) ) { $block_rules .= $this->get_layout_styles( $block_metadata ); @@ -707,6 +702,7 @@ function( $pseudo_selector ) use ( $selector ) { $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; if ( $has_block_gap_support ) { $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; $block_rules .= ".wp-site-blocks > * + * { margin-block-start: $block_gap_value; }"; @@ -1167,9 +1163,9 @@ protected function get_layout_styles( $block_metadata ) { $block_gap_value = null; // Use a fallback gap value if block gap support is not available. if ( ! $has_block_gap_support ) { - $block_gap_value = '0.5em'; + $block_gap_value = static::ROOT_BLOCK_SELECTOR === $selector ? '0.5em' : null; if ( ! empty( $block_type ) ) { - $block_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), '0.5em' ); + $block_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), null ); } } else { $block_gap_value = _wp_array_get( $node, array( 'spacing', 'blockGap' ), null ); diff --git a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php index 2007893afea7fc..adf5803de561aa 100644 --- a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php @@ -111,6 +111,15 @@ public static function get_block_data() { if ( isset( $block_type->supports['__experimentalStyle'] ) ) { $config['styles']['blocks'][ $block_name ] = static::remove_JSON_comments( $block_type->supports['__experimentalStyle'] ); } + + if ( + isset( $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ) && + null === _wp_array_get( $config, array( 'styles', 'blocks', $block_name, 'spacing', 'blockGap' ), null ) + ) { + // Ensure an empty placeholder value exists for the block, if it provides a default blockGap value. + // The real blockGap value to be used will be determined when the styles are rendered for output. + $config['styles']['blocks'][ $block_name ]['spacing']['blockGap'] = null; + } } // Core here means it's the lower level part of the styles chain. diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index 4012c4a71363a4..3f533c49f82e34 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -244,6 +244,7 @@ function getStylesDeclarations( blockStyles = {} ) { * @param {string} props.selector Selector used to group together layout styling rules. * @param {boolean} props.hasBlockGapSupport Whether or not the theme opts-in to blockGap support. * @param {boolean} props.hasFallbackGapSupport Whether or not the theme allows fallback gap styles. + * @param {?string} props.fallbackGapValue An optional fallback gap value if no real gap value is available. * @return {string} Generated CSS rules for the layout styles. */ export function getLayoutStyles( { @@ -252,20 +253,18 @@ export function getLayoutStyles( { selector, hasBlockGapSupport, hasFallbackGapSupport, + fallbackGapValue, } ) { let ruleset = ''; let gapValue = hasBlockGapSupport ? getGapCSSValue( style?.spacing?.blockGap ) : ''; - // Ensure a fallback gap value for the root layout definitions if no gap value exists, - // or if there is block styles support and blockGap has been disabled. - if ( - ( ! gapValue || ! hasBlockGapSupport ) && - selector === ROOT_BLOCK_SELECTOR && - hasFallbackGapSupport - ) { - gapValue = '0.5em'; + // Ensure a fallback gap value for the root layout definitions, and otherwise + // use a fallback value if one is provided for the current block. + if ( ! hasBlockGapSupport && hasFallbackGapSupport ) { + gapValue = + selector === ROOT_BLOCK_SELECTOR ? '0.5em' : fallbackGapValue; } if ( gapValue && tree?.settings?.layout?.definitions ) { @@ -398,6 +397,7 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => { if ( !! blockStyles && !! blockSelectors?.[ blockName ]?.selector ) { nodes.push( { duotoneSelector: blockSelectors[ blockName ].duotoneSelector, + fallbackGapValue: blockSelectors[ blockName ].fallbackGapValue, hasLayoutSupport: blockSelectors[ blockName ].hasLayoutSupport, selector: blockSelectors[ blockName ].selector, styles: blockStyles, @@ -514,7 +514,13 @@ export const toStyles = ( */ let ruleset = 'body {margin: 0;}'; nodesWithStyles.forEach( - ( { selector, duotoneSelector, styles, hasLayoutSupport } ) => { + ( { + selector, + duotoneSelector, + styles, + fallbackGapValue, + hasLayoutSupport, + } ) => { const duotoneStyles = {}; if ( styles?.filter ) { duotoneStyles.filter = styles.filter; @@ -543,6 +549,7 @@ export const toStyles = ( selector, hasBlockGapSupport, hasFallbackGapSupport, + fallbackGapValue, } ); } @@ -646,8 +653,11 @@ const getBlockSelectors = ( blockTypes ) => { const duotoneSelector = blockType?.supports?.color?.__experimentalDuotone ?? null; const hasLayoutSupport = !! blockType?.supports?.__experimentalLayout; + const fallbackGapValue = + blockType?.supports?.spacing?.blockGap?.__experimentalDefault; result[ name ] = { duotoneSelector, + fallbackGapValue, hasLayoutSupport, name, selector, From b3f8ba82435879fb87ca38a6ccb21a9f04ab44c4 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 8 Jul 2022 14:43:53 +1000 Subject: [PATCH 14/14] Fix global styles output logic, add an additional test for block-level fallback gap --- .../test/use-global-styles-output.js | 17 +++++++++++++++++ .../global-styles/use-global-styles-output.js | 13 ++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js index 8014c015dc44c9..b165d5bb917420 100644 --- a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js @@ -600,5 +600,22 @@ describe( 'global styles renderer', () => { '.wp-block-group.is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.wp-block-group.is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }.wp-block-group.is-layout-flex { gap: 12px; }' ); } ); + + it( 'should return fallback gap flex layout style for a block if blockGap is disabled, and a fallback value is provided', () => { + const style = { spacing: { blockGap: '12px' } }; + + const layoutStyles = getLayoutStyles( { + tree: layoutDefinitionsTree, + style, + selector: '.wp-block-group', + hasBlockGapSupport: false, // This means that the fallback value will be used instead of the "real" one. + hasFallbackGapSupport: true, + fallbackGapValue: '2em', + } ); + + expect( layoutStyles ).toEqual( + '.wp-block-group.is-layout-flex { gap: 2em; }' + ); + } ); } ); } ); diff --git a/packages/edit-site/src/components/global-styles/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/use-global-styles-output.js index 3f533c49f82e34..9db98c46a75ac6 100644 --- a/packages/edit-site/src/components/global-styles/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/use-global-styles-output.js @@ -260,11 +260,14 @@ export function getLayoutStyles( { ? getGapCSSValue( style?.spacing?.blockGap ) : ''; - // Ensure a fallback gap value for the root layout definitions, and otherwise - // use a fallback value if one is provided for the current block. - if ( ! hasBlockGapSupport && hasFallbackGapSupport ) { - gapValue = - selector === ROOT_BLOCK_SELECTOR ? '0.5em' : fallbackGapValue; + // Ensure a fallback gap value for the root layout definitions, + // and use a fallback value if one is provided for the current block. + if ( hasFallbackGapSupport ) { + if ( selector === ROOT_BLOCK_SELECTOR ) { + gapValue = ! gapValue ? '0.5em' : gapValue; + } else if ( ! hasBlockGapSupport && fallbackGapValue ) { + gapValue = fallbackGapValue; + } } if ( gapValue && tree?.settings?.layout?.definitions ) {