From e8d2de71b1f9cb44555c755355c55e810333db01 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 15 Jul 2022 07:51:13 +0100 Subject: [PATCH] Core CSS support for root padding and alignfull blocks (#42085) * 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 * 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. * Fix linting issue * Implement fallback behaviour in site editor where default flex gap is still rendered in themes without blockGap but with wp-block-styles * Remove connection to wp-block-styles so that fallback flex layout styles are always output * Update resolver class to add an empty blockGap placeholder for a block, if it provides a default blockGap value * 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 * Reuse most of the logic from #39926 * Don't accept string values * Apply root padding only on blocks with content width * Apply linting changes * Fix rebase error * Actually fix rebase * Add global padding toggle to layout-less blocks * Support custom block padding. * Output alignfull styles only when needed. * Toggle should only appear when needed. * Add context to comments about string value support * Interpret preset padding value for negative margin * Don't show toggle on blocks without content size. * Change setting name. * Improve preset processing logic * Reset padding when no content size applies. * Update style engine class to match trunk * Fix string check * Fix preset values in the editor. * Fix string check * Remove useGlobalPadding attribute * Replace padding with spacing in function parameter * Default root padding setting to false. * Dodgy fix for Cover block * Add test for get_styles_for_block function * Check for padding setting before adding classname. * Don't output vars if setting is off * Test default output without setting enabled. * Update lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php No humorous string values allowed Co-authored-by: Ramon * Remove empty line. Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Co-authored-by: Ramon --- lib/block-supports/layout.php | 32 +++- .../wordpress-6.1/class-wp-theme-json-6-1.php | 160 +++++++++++------- lib/compat/wordpress-6.1/theme.json | 11 +- packages/block-editor/src/hooks/layout.js | 17 +- packages/block-editor/src/hooks/style.js | 3 + packages/block-editor/src/layouts/flow.js | 24 ++- packages/block-library/src/cover/style.scss | 1 - packages/blocks/src/api/constants.js | 11 ++ .../global-styles/use-global-styles-output.js | 46 ++++- phpunit/class-wp-theme-json-test.php | 59 +++++++ 10 files changed, 289 insertions(+), 75 deletions(-) diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index fa63beef0710b3..b26ed9df659c28 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -34,10 +34,11 @@ function gutenberg_register_layout_support( $block_type ) { * @param string $gap_value The block gap value to apply. * @param boolean $should_skip_gap_serialization Whether to skip applying the user-defined value set in the editor. * @param string $fallback_gap_value The block gap value to apply. + * @param array $block_spacing Custom spacing set on the block. * * @return string CSS style. */ -function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false, $fallback_gap_value = '0.5em' ) { +function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false, $fallback_gap_value = '0.5em', $block_spacing = null ) { $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default'; $style = ''; @@ -54,7 +55,7 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] ); if ( $content_size || $wide_size ) { - $style = "$selector > :where(:not(.alignleft):not(.alignright)) {"; + $style = "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull)) {"; $style .= 'max-width: ' . esc_html( $all_max_width_value ) . ';'; $style .= 'margin-left: auto !important;'; $style .= 'margin-right: auto !important;'; @@ -62,6 +63,25 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $style .= "$selector > .alignwide { max-width: " . esc_html( $wide_max_width_value ) . ';}'; $style .= "$selector .alignfull { max-width: none; }"; + + if ( isset( $block_spacing ) ) { + $block_spacing_values = gutenberg_style_engine_get_block_supports_styles( + array( + 'spacing' => $block_spacing, + ) + ); + + // Handle negative margins for alignfull children of blocks with custom padding set. + // They're added separately because padding might only be set on one side. + if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) { + $padding_right = $block_spacing_values['declarations']['padding-right']; + $style .= "$selector > .alignfull { margin-right:calc($padding_right * -1); }"; + } + if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) { + $padding_left = $block_spacing_values['declarations']['padding-left']; + $style .= "$selector > .alignfull { margin-left: calc($padding_left * -1); }"; + } + } } if ( $has_block_gap_support ) { @@ -173,6 +193,11 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $block_classname = wp_get_block_default_classname( $block['blockName'] ); $container_class = wp_unique_id( 'wp-container-' ); $layout_classname = ''; + $use_global_padding = gutenberg_get_global_settings( array( 'useRootPaddingAwareAlignments' ) ) && ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ); + + if ( $use_global_padding ) { + $class_names[] = 'has-global-padding'; + } // 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 @@ -214,11 +239,12 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { } $fallback_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), '0.5em' ); + $block_spacing = _wp_array_get( $block, array( 'attrs', 'style', 'spacing' ), null ); // If a block's block.json skips serialization for spacing or spacing.blockGap, // don't apply the user-defined value to the styles. $should_skip_gap_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' ); - $style = gutenberg_get_layout_style( ".$block_classname.$container_class", $used_layout, $has_block_gap_support, $gap_value, $should_skip_gap_serialization, $fallback_gap_value ); + $style = gutenberg_get_layout_style( ".$block_classname.$container_class", $used_layout, $has_block_gap_support, $gap_value, $should_skip_gap_serialization, $fallback_gap_value, $block_spacing ); // Only add container class and enqueue block support styles if unique styles were generated. if ( ! empty( $style ) ) { 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 ba1aaddec8546d..5fc0dd1223ba62 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 @@ -32,48 +32,53 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { * 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' ), + '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' ), + '--wp--style--root--padding' => array( 'spacing', 'padding' ), + '--wp--style--root--padding-top' => array( 'spacing', 'padding', 'top' ), + '--wp--style--root--padding-right' => array( 'spacing', 'padding', 'right' ), + '--wp--style--root--padding-bottom' => array( 'spacing', 'padding', 'bottom' ), + '--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ), + 'text-decoration' => array( 'typography', 'textDecoration' ), + 'text-transform' => array( 'typography', 'textTransform' ), + 'filter' => array( 'filter', 'duotone' ), ); /** @@ -651,10 +656,10 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' * @return string Styles for the block. */ public function get_styles_for_block( $block_metadata ) { - $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); - - $selector = $block_metadata['selector']; - $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); + $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; + $selector = $block_metadata['selector']; + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); // Process style declarations for block support features the current // block contains selectors for. Values for a feature with a custom @@ -714,9 +719,9 @@ function( $pseudo_selector ) use ( $selector ) { // element then compute the style properties for it. // Otherwise just compute the styles for the default selector as normal. if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ) { - $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json ); + $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding ); } else { - $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json ); + $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding ); } $block_rules = ''; @@ -768,6 +773,13 @@ function( $pseudo_selector ) use ( $selector ) { if ( static::ROOT_BLOCK_SELECTOR === $selector ) { $block_gap_value = _wp_array_get( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ), '0.5em' ); + if ( $use_root_padding ) { + $block_rules .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }'; + $block_rules .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; + $block_rules .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; + $block_rules .= '.has-global-padding > .alignfull > :where([class*="wp-block-"]:not(.alignfull):not(.alignfull):not([class*="__"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; + } + $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; }'; @@ -824,13 +836,15 @@ protected function get_block_classes( $style_nodes ) { * ) * ``` * - * @param array $styles Styles to process. - * @param array $settings Theme settings. - * @param array $properties Properties metadata. - * @param array $theme_json Theme JSON array. - * @return array Returns the modified $declarations. + * @param array $styles Styles to process. + * @param array $settings Theme settings. + * @param array $properties Properties metadata. + * @param array $theme_json Theme JSON array. + * @param string $selector The style block selector. + * @param boolean $use_root_padding Whether to add custom properties at root level. + * @return array Returns the modified $declarations. */ - protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null ) { + protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) { if ( null === $properties ) { $properties = static::PROPERTIES_METADATA; } @@ -840,9 +854,24 @@ protected static function compute_style_properties( $styles, $settings = array() return $declarations; } + $root_variable_duplicates = array(); + foreach ( $properties as $css_property => $value_path ) { $value = static::get_property_value( $styles, $value_path, $theme_json ); + if ( strpos( $css_property, '--wp--style--root--' ) === 0 && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) { + continue; + } + // Root-level padding styles don't currently support strings with CSS shorthand values. + // This may change: https://github.com/WordPress/gutenberg/issues/40132. + if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) { + continue; + } + + if ( strpos( $css_property, '--wp--style--root--' ) === 0 && $use_root_padding ) { + $root_variable_duplicates[] = substr( $css_property, strlen( '--wp--style--root--' ) ); + } + // Look up protected properties, keyed by value path. // Skip protected properties that are explicitly set to `null`. if ( is_array( $value_path ) ) { @@ -867,6 +896,14 @@ protected static function compute_style_properties( $styles, $settings = array() ); } + // If a variable value is added to the root, the corresponding property should be removed. + foreach ( $root_variable_duplicates as $duplicate ) { + $discard = array_search( $duplicate, array_column( $declarations, 'name' ), true ); + if ( is_numeric( $discard ) ) { + array_splice( $declarations, $discard, 1 ); + } + } + return $declarations; } @@ -1054,14 +1091,15 @@ protected static function get_property_value( $styles, $path, $theme_json = null * @var array */ const VALID_SETTINGS = array( - 'appearanceTools' => null, - 'border' => array( + 'appearanceTools' => null, + 'useRootPaddingAwareAlignments' => null, + 'border' => array( 'color' => null, 'radius' => null, 'style' => null, 'width' => null, ), - 'color' => array( + 'color' => array( 'background' => null, 'custom' => null, 'customDuotone' => null, @@ -1075,13 +1113,13 @@ protected static function get_property_value( $styles, $path, $theme_json = null 'palette' => null, 'text' => null, ), - 'custom' => null, - 'layout' => array( + 'custom' => null, + 'layout' => array( 'contentSize' => null, 'definitions' => null, 'wideSize' => null, ), - 'spacing' => array( + 'spacing' => array( 'customSpacingSize' => null, 'spacingSizes' => null, 'spacingScale' => null, @@ -1090,7 +1128,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null 'padding' => null, 'units' => null, ), - 'typography' => array( + 'typography' => array( 'customFontSize' => null, 'dropCap' => null, 'fontFamilies' => null, diff --git a/lib/compat/wordpress-6.1/theme.json b/lib/compat/wordpress-6.1/theme.json index 426adc6195085d..b5358e56e24b91 100644 --- a/lib/compat/wordpress-6.1/theme.json +++ b/lib/compat/wordpress-6.1/theme.json @@ -2,6 +2,7 @@ "version": 2, "settings": { "appearanceTools": false, + "useRootPaddingAwareAlignments": false, "border": { "color": false, "radius": false, @@ -349,6 +350,14 @@ } } }, - "spacing": { "blockGap": "24px" } + "spacing": { + "blockGap": "24px", + "padding": { + "top": "0px", + "right": "0px", + "bottom": "0px", + "left": "0px" + } + } } } diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 65a44b6b8df546..4db7ac3b6ef05f 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -49,7 +49,12 @@ const layoutBlockSupportKey = '__experimentalLayout'; * * @return { Array } Array of CSS classname strings. */ -function getLayoutClasses( layout, layoutDefinitions ) { +function useLayoutClasses( layout, layoutDefinitions ) { + const rootPaddingAlignment = useSelect( ( select ) => { + const { getSettings } = select( blockEditorStore ); + return getSettings().__experimentalFeatures + ?.useRootPaddingAwareAlignments; + }, [] ); const layoutClassnames = []; if ( layoutDefinitions?.[ layout?.type || 'default' ]?.className ) { @@ -58,6 +63,10 @@ function getLayoutClasses( layout, layoutDefinitions ) { ); } + if ( ( layout?.inherit || layout?.contentSize ) && rootPaddingAlignment ) { + layoutClassnames.push( 'has-global-padding' ); + } + if ( layout?.orientation ) { layoutClassnames.push( `is-${ kebabCase( layout.orientation ) }` ); } @@ -136,7 +145,9 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { checked={ ! inherit } onChange={ () => setAttributes( { - layout: { inherit: ! inherit }, + layout: { + inherit: ! inherit, + }, } ) } /> @@ -267,7 +278,7 @@ export const withLayoutStyles = createHigherOrderComponent( ? defaultThemeLayout : layout || defaultBlockLayout || {}; const layoutClasses = shouldRenderLayoutStyles - ? getLayoutClasses( usedLayout, defaultThemeLayout?.definitions ) + ? useLayoutClasses( usedLayout, defaultThemeLayout?.definitions ) : null; const selector = `.${ getBlockDefaultClassName( name diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 7627cd545b6857..10ba33f002da0e 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -71,6 +71,9 @@ export function getInlineStyles( styles = {} ) { const ignoredStyles = [ 'spacing.blockGap' ]; const output = {}; Object.keys( STYLE_PROPERTY ).forEach( ( propKey ) => { + if ( STYLE_PROPERTY[ propKey ].rootOnly ) { + return; + } const path = STYLE_PROPERTY[ propKey ].value; const subPaths = STYLE_PROPERTY[ propKey ].properties; // Ignore styles on elements because they are handled on the server. diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index 1d8ea7d7a6aea2..f0e084d623b17b 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -8,6 +8,7 @@ import { } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { Icon, positionCenter, stretchWide } from '@wordpress/icons'; +import { getCSSRules } from '@wordpress/style-engine'; /** * Internal dependencies @@ -132,7 +133,7 @@ export default { ? ` ${ appendSelectors( selector, - '> :where(:not(.alignleft):not(.alignright))' + '> :where(:not(.alignleft):not(.alignright):not(.alignfull))' ) } { max-width: ${ contentSize ?? wideSize }; margin-left: auto !important; @@ -147,6 +148,27 @@ export default { ` : ''; + // If there is custom padding, add negative margins for alignfull blocks. + if ( style?.spacing?.padding ) { + // The style object might be storing a preset so we need to make sure we get a usable value. + const paddingValues = getCSSRules( style ); + paddingValues.forEach( ( rule ) => { + if ( rule.key === 'paddingRight' ) { + output += ` + ${ appendSelectors( selector, '> .alignfull' ) } { + margin-right: calc(${ rule.value } * -1); + } + `; + } else if ( rule.key === 'paddingLeft' ) { + output += ` + ${ appendSelectors( selector, '> .alignfull' ) } { + margin-left: calc(${ rule.value } * -1); + } + `; + } + } ); + } + // Output blockGap styles based on rules contained in layout definitions in theme.json. if ( hasBlockGapSupport && blockGapValue ) { output += getBlockGapCSS( diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss index 957cfc5d4e23a3..619cad46c1b5a3 100644 --- a/packages/block-library/src/cover/style.scss +++ b/packages/block-library/src/cover/style.scss @@ -4,7 +4,6 @@ background-size: cover; background-position: center center; min-height: 430px; - width: 100%; display: flex; justify-content: center; align-items: center; diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index fb8580ef30ef0a..b852fe3932fa05 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -199,6 +199,17 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { support: [ 'typography', '__experimentalLetterSpacing' ], useEngine: true, }, + '--wp--style--root--padding': { + value: [ 'spacing', 'padding' ], + support: [ 'spacing', 'padding' ], + properties: { + '--wp--style--root--padding-top': 'top', + '--wp--style--root--padding-right': 'right', + '--wp--style--root--padding-bottom': 'bottom', + '--wp--style--root--padding-left': 'left', + }, + rootOnly: true, + }, }; export const __EXPERIMENTAL_ELEMENTS = { 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 6e0d35bd6acd05..431e8a420f79b0 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 @@ -181,14 +181,26 @@ function flattenTree( input = {}, prefix, token ) { /** * Transform given style tree into a set of style declarations. * - * @param {Object} blockStyles Block styles. + * @param {Object} blockStyles Block styles. + * + * @param {string} selector The selector these declarations should attach to. + * + * @param {boolean} useRootPaddingAlign Whether to use CSS custom properties in root selector. * * @return {Array} An array of style declarations. */ -function getStylesDeclarations( blockStyles = {} ) { +function getStylesDeclarations( + blockStyles = {}, + selector = '', + useRootPaddingAlign +) { + const isRoot = ROOT_BLOCK_SELECTOR === selector; const output = reduce( STYLE_PROPERTY, - ( declarations, { value, properties, useEngine }, key ) => { + ( declarations, { value, properties, useEngine, rootOnly }, key ) => { + if ( rootOnly && ! isRoot ) { + return declarations; + } const pathToValue = value; if ( first( pathToValue ) === 'elements' || useEngine ) { return declarations; @@ -206,13 +218,22 @@ function getStylesDeclarations( blockStyles = {} ) { return; } - const cssProperty = kebabCase( name ); + const cssProperty = name.startsWith( '--' ) + ? name + : kebabCase( name ); declarations.push( `${ cssProperty }: ${ compileStyleValue( get( styleValue, [ prop ] ) ) }` ); } ); + } else if ( + key === '--wp--style--root--padding' && + typeof styleValue === 'string' + ) { + // Root-level padding styles don't currently support strings with CSS shorthand values. + // This may change: https://github.com/WordPress/gutenberg/issues/40132. + return declarations; } else if ( get( blockStyles, pathToValue, false ) ) { const cssProperty = key.startsWith( '--' ) ? key @@ -229,6 +250,10 @@ function getStylesDeclarations( blockStyles = {} ) { [] ); + if ( isRoot && useRootPaddingAlign ) { + return output; + } + // The goal is to move everything to server side generated engine styles // This is temporary as we absorb more and more styles into the engine. const extraRules = getCSSRules( blockStyles ); @@ -515,6 +540,7 @@ export const toStyles = ( ) => { const nodesWithStyles = getNodesWithStyles( tree, blockSelectors ); const nodesWithSettings = getNodesWithSettings( tree, blockSelectors ); + const useRootPaddingAlign = tree?.settings?.useRootPaddingAwareAlignments; /* * Reset default browser margin on the root body element. @@ -525,6 +551,12 @@ export const toStyles = ( * @link https://github.com/WordPress/gutenberg/issues/36147. */ let ruleset = 'body {margin: 0;}'; + + if ( useRootPaddingAlign ) { + ruleset = + 'body { margin: 0; padding-right: 0; padding-left: 0; padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom) } .has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); } .has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); } .has-global-padding > .alignfull > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; + } + nodesWithStyles.forEach( ( { selector, @@ -592,7 +624,11 @@ export const toStyles = ( } // Process the remaining block styles (they use either normal block class or __experimentalSelector). - const declarations = getStylesDeclarations( styles ); + const declarations = getStylesDeclarations( + styles, + selector, + useRootPaddingAlign + ); if ( declarations?.length ) { ruleset = ruleset + `${ selector }{${ declarations.join( ';' ) };}`; diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index dc095d610c0b93..655c5c5e5f8c92 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -974,4 +974,63 @@ function data_generate_spacing_scale_fixtures() { ), ); } + + function test_get_styles_for_block_with_padding_aware_alignments() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '10px', + 'right' => '12px', + 'bottom' => '10px', + 'left' => '12px', + ), + ), + ), + 'settings' => array( + 'useRootPaddingAwareAlignments' => true, + ), + ) + ); + + $metadata = array( + 'path' => array( + '0' => 'styles', + ), + 'selector' => 'body', + ); + + $expected = 'body { margin: 0; }body{--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding > .alignfull > :where([class*="wp-block-"]:not(.alignfull):not(.alignfull):not([class*="__"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.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; }'; + $this->assertEquals( $expected, $theme_json->get_styles_for_block( $metadata ) ); + } + + function test_get_styles_for_block_without_padding_aware_alignments() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '10px', + 'right' => '12px', + 'bottom' => '10px', + 'left' => '12px', + ), + ), + ), + ) + ); + + $metadata = array( + 'path' => array( + '0' => 'styles', + ), + 'selector' => 'body', + ); + + $expected = 'body { margin: 0; }body{padding-top: 10px;padding-right: 12px;padding-bottom: 10px;padding-left: 12px;}.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; }'; + $this->assertEquals( $expected, $theme_json->get_styles_for_block( $metadata ) ); + } }