From 0aa1a2e04ed7a441882252ad0e81693c56c34452 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 25 Jan 2023 15:49:38 +1100 Subject: [PATCH 01/13] Layout: Add layout aware margin styles output for blocks in global styles --- lib/class-wp-theme-json-gutenberg.php | 34 ++++++++++++++++++- lib/theme.json | 6 ++-- .../test/use-global-styles-output.js | 20 +++++++++++ .../global-styles/use-global-styles-output.js | 31 +++++++++++++++-- phpunit/class-wp-theme-json-test.php | 23 +++++++++---- 5 files changed, 103 insertions(+), 11 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index f3ed36dce18e57..b6d0823b89e085 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1215,7 +1215,10 @@ protected function get_layout_styles( $block_metadata ) { 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 ) ) { + if ( + ! block_has_support( $block_type, array( '__experimentalLayout' ), false ) && + ! block_has_support( $block_type, array( 'spacing', 'margin' ), false ) + ) { return $block_rules; } } @@ -1253,6 +1256,35 @@ protected function get_layout_styles( $block_metadata ) { } } + // If the theme has block support support, and the block has margin values, add layout-aware margin styles. + $margin_value = static::get_property_value( $node, array( 'spacing', 'margin' ) ); + if ( + $has_block_gap_support && + isset( $margin_value ) && '' !== $margin_value + ) { + $margin_styles = gutenberg_style_engine_get_styles( + array( 'spacing' => array( 'margin' => $margin_value ) ) + ); + + if ( ! empty( $margin_styles['css'] ) ) { + foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), '' ) ); + if ( + isset( $layout_definition['marginSelector'] ) && + preg_match( $layout_selector_pattern, $layout_definition['marginSelector'] ) + ) { + $block_rules .= sprintf( + '.%s%s%s {%s}', + $class_name, + $layout_definition['marginSelector'], + $selector, + $margin_styles['css'] + ); + } + } + } + } + // If the block should have custom gap, add the gap styles. if ( null !== $block_gap_value && false !== $block_gap_value && '' !== $block_gap_value ) { foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { diff --git a/lib/theme.json b/lib/theme.json index 8e60c945456b1b..50ff51d83efd71 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -262,7 +262,8 @@ "margin-block-end": "0" } } - ] + ], + "marginSelector": " > " }, "constrained": { "name": "constrained", @@ -322,7 +323,8 @@ "margin-block-end": "0" } } - ] + ], + "marginSelector": " > " }, "flex": { "name": "flex", diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index 2edef1b38b11b6..ed745b86f69551 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -549,6 +549,7 @@ describe( 'global styles renderer', () => { }, }, ], + marginSelector: ' > ', }, flex: { name: 'flex', @@ -664,6 +665,24 @@ describe( 'global styles renderer', () => { ':where(.wp-block-group.is-layout-flex) { gap: 2em; }' ); } ); + + it( 'should return layout aware margin styles', () => { + const style = { + spacing: { margin: { top: '25px', bottom: '50px' } }, + }; + + const layoutStyles = getLayoutStyles( { + tree: layoutDefinitionsTree, + style, + selector: '.wp-block-cover', + hasBlockGapSupport: true, + hasFallbackGapSupport: true, + } ); + + expect( layoutStyles ).toEqual( + '.is-layout-flow > .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' + ); + } ); } ); describe( 'getBlockSelectors', () => { @@ -691,6 +710,7 @@ describe( 'global styles renderer', () => { border: '.my-image img, .my-image .crop-area', }, hasLayoutSupport: false, + hasMarginSupport: false, }, } ); } ); diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index d4a55121672609..f5c97abf6c774c 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -14,7 +14,7 @@ import { } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { useContext, useMemo } from '@wordpress/element'; -import { getCSSRules } from '@wordpress/style-engine'; +import { compileCSS, getCSSRules } from '@wordpress/style-engine'; /** * Internal dependencies @@ -356,6 +356,26 @@ export function getLayoutStyles( { } } + // If the theme has block support support, and the block has margin values, add layout-aware margin styles. + if ( + hasBlockGapSupport && + style?.spacing?.margin && + tree?.settings?.layout?.definitions + ) { + const marginRules = compileCSS( { + spacing: { margin: style.spacing.margin }, + } ); + if ( marginRules ) { + Object.values( tree.settings.layout.definitions ).forEach( + ( { className, marginSelector } ) => { + if ( marginSelector ) { + ruleset += `.${ className }${ marginSelector }${ selector } { ${ marginRules } }`; + } + } + ); + } + } + if ( gapValue && tree?.settings?.layout?.definitions ) { Object.values( tree.settings.layout.definitions ).forEach( ( { className, name, spacingStyles } ) => { @@ -529,6 +549,8 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => { blockSelectors[ blockName ].fallbackGapValue, hasLayoutSupport: blockSelectors[ blockName ].hasLayoutSupport, + hasMarginSupport: + blockSelectors[ blockName ].hasMarginSupport, selector: blockSelectors[ blockName ].selector, styles: blockStyles, featureSelectors: @@ -682,6 +704,7 @@ export const toStyles = ( styles, fallbackGapValue, hasLayoutSupport, + hasMarginSupport, featureSelectors, styleVariationSelectors, } ) => { @@ -795,7 +818,9 @@ export const toStyles = ( // Process blockGap and layout styles. if ( ! disableLayoutStyles && - ( ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport ) + ( ROOT_BLOCK_SELECTOR === selector || + hasLayoutSupport || + hasMarginSupport ) ) { ruleset += getLayoutStyles( { tree, @@ -912,6 +937,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const duotoneSelector = blockType?.supports?.color?.__experimentalDuotone ?? null; const hasLayoutSupport = !! blockType?.supports?.__experimentalLayout; + const hasMarginSupport = !! blockType?.supports?.spacing?.margin; const fallbackGapValue = blockType?.supports?.spacing?.blockGap?.__experimentalDefault; @@ -947,6 +973,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { ? featureSelectors : undefined, hasLayoutSupport, + hasMarginSupport, name, selector, styleVariationSelectors: Object.keys( styleVariationSelectors ) diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 4aca6ad5c3bf1f..a11da8af881172 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -55,6 +55,16 @@ public function test_get_stylesheet_generates_layout_styles( $layout_definitions ), ), 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'margin' => array( + 'top' => '25px', + 'bottom' => '50px', + ), + ), + ), + ), 'spacing' => array( 'blockGap' => '1em', ), @@ -65,7 +75,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;}body .is-layout-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;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > .wp-block-group {margin-top:25px;margin-bottom:50px;}', $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -261,10 +271,10 @@ public function data_get_layout_definitions() { 'layout definitions' => array( array( 'default' => array( - 'name' => 'default', - 'slug' => 'flow', - 'className' => 'is-layout-flow', - 'baseStyles' => array( + 'name' => 'default', + 'slug' => 'flow', + 'className' => 'is-layout-flow', + 'baseStyles' => array( array( 'selector' => ' > .alignleft', 'rules' => array( @@ -289,7 +299,7 @@ public function data_get_layout_definitions() { ), ), ), - 'spacingStyles' => array( + 'spacingStyles' => array( array( 'selector' => ' > *', 'rules' => array( @@ -305,6 +315,7 @@ public function data_get_layout_definitions() { ), ), ), + 'marginSelector' => ' > ', ), 'flex' => array( 'name' => 'flex', From f921dc3779caa8a1f25b36e0814bfb3e6fd79ca7 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 27 Jan 2023 16:30:13 +1100 Subject: [PATCH 02/13] Add comment clarifying early return --- lib/class-wp-theme-json-gutenberg.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index b6d0823b89e085..e2ffab9a17a112 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1213,6 +1213,8 @@ protected function get_layout_styles( $block_metadata ) { return $block_rules; } + // Skip outputting layout styles if the block does not support layout or margin. + // For blocks that have margin but not layout support, only layout aware margin styles are output. if ( isset( $block_metadata['name'] ) ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] ); if ( From 97c048096756af0466de3f5e1e9cc8881f48518c Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 27 Jan 2023 16:53:27 +1100 Subject: [PATCH 03/13] Add layout aware rules for children of the root .wp-site-blocks class --- lib/class-wp-theme-json-gutenberg.php | 7 +++++++ .../global-styles/test/use-global-styles-output.js | 2 +- .../components/global-styles/use-global-styles-output.js | 3 +++ phpunit/class-wp-theme-json-test.php | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index e2ffab9a17a112..627006b0f07c71 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1269,6 +1269,7 @@ protected function get_layout_styles( $block_metadata ) { ); if ( ! empty( $margin_styles['css'] ) ) { + // Add layout aware margin rules for each supported layout type. foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), '' ) ); if ( @@ -1284,6 +1285,12 @@ protected function get_layout_styles( $block_metadata ) { ); } } + // Add layout aware margin rule for children of the root site blocks class. + $block_rules .= sprintf( + '.wp-site-blocks > %s {%s}', + $selector, + $margin_styles['css'] + ); } } diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index ed745b86f69551..271b3726e04aaf 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -680,7 +680,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - '.is-layout-flow > .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' + '.is-layout-flow > .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }.wp-site-blocks > .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' ); } ); } ); diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index f5c97abf6c774c..00c3b17fb54618 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -366,6 +366,7 @@ export function getLayoutStyles( { spacing: { margin: style.spacing.margin }, } ); if ( marginRules ) { + // Add layout aware margin rules for each supported layout type. Object.values( tree.settings.layout.definitions ).forEach( ( { className, marginSelector } ) => { if ( marginSelector ) { @@ -373,6 +374,8 @@ export function getLayoutStyles( { } } ); + // Add layout aware margin rule for children of the root site blocks class. + ruleset += `.wp-site-blocks > ${ selector } { ${ marginRules } }`; } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index a11da8af881172..52bf8eb99ade7f 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -75,7 +75,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;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > .wp-block-group {margin-top:25px;margin-bottom:50px;}', + '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;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > .wp-block-group {margin-top:25px;margin-bottom:50px;}.wp-site-blocks > .wp-block-group {margin-top:25px;margin-bottom:50px;}', $theme_json->get_stylesheet( array( 'styles' ) ) ); } From c2cad93ec4500c918b1a9914b5d86478844f2a72 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 31 Jan 2023 09:39:10 +1100 Subject: [PATCH 04/13] Update code comment Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> --- lib/class-wp-theme-json-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 627006b0f07c71..0d9a63496455b4 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1258,7 +1258,7 @@ protected function get_layout_styles( $block_metadata ) { } } - // If the theme has block support support, and the block has margin values, add layout-aware margin styles. + // If the theme has block gap support, and the block has margin values, add layout-aware margin styles. $margin_value = static::get_property_value( $node, array( 'spacing', 'margin' ) ); if ( $has_block_gap_support && From 166f2bd642aa51ffb72aede5bb34eb10eb2a59d9 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:47:05 +1100 Subject: [PATCH 05/13] Try out a different selector for the margin rules --- lib/class-wp-theme-json-gutenberg.php | 2 +- lib/theme.json | 4 ++-- .../components/global-styles/test/use-global-styles-output.js | 4 ++-- .../src/components/global-styles/use-global-styles-output.js | 2 +- phpunit/class-wp-theme-json-test.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 0d9a63496455b4..1c2617561082e0 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1287,7 +1287,7 @@ protected function get_layout_styles( $block_metadata ) { } // Add layout aware margin rule for children of the root site blocks class. $block_rules .= sprintf( - '.wp-site-blocks > %s {%s}', + '.wp-site-blocks > * + %s {%s}', $selector, $margin_styles['css'] ); diff --git a/lib/theme.json b/lib/theme.json index 50ff51d83efd71..f098a24f59b213 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -263,7 +263,7 @@ } } ], - "marginSelector": " > " + "marginSelector": " > * + " }, "constrained": { "name": "constrained", @@ -324,7 +324,7 @@ } } ], - "marginSelector": " > " + "marginSelector": " > * + " }, "flex": { "name": "flex", diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index 271b3726e04aaf..3c0e15ce951799 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -549,7 +549,7 @@ describe( 'global styles renderer', () => { }, }, ], - marginSelector: ' > ', + marginSelector: ' > * + ', }, flex: { name: 'flex', @@ -680,7 +680,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - '.is-layout-flow > .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }.wp-site-blocks > .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' + '.is-layout-flow > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }.wp-site-blocks > .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' ); } ); } ); diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 00c3b17fb54618..8f0fcaf32b4cf5 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -375,7 +375,7 @@ export function getLayoutStyles( { } ); // Add layout aware margin rule for children of the root site blocks class. - ruleset += `.wp-site-blocks > ${ selector } { ${ marginRules } }`; + ruleset += `.wp-site-blocks > * + ${ selector } { ${ marginRules } }`; } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 52bf8eb99ade7f..966e82f925235e 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -75,7 +75,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;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > .wp-block-group {margin-top:25px;margin-bottom:50px;}.wp-site-blocks > .wp-block-group {margin-top:25px;margin-bottom:50px;}', + '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;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > * + .wp-block-group {margin-top:25px;margin-bottom:50px;}.wp-site-blocks > * + .wp-block-group {margin-top:25px;margin-bottom:50px;}', $theme_json->get_stylesheet( array( 'styles' ) ) ); } From 3604c0fd412e4365fca1b9255ea7328e4b3b0192 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 3 Feb 2023 16:55:55 +1100 Subject: [PATCH 06/13] Try to fix tests --- .../components/global-styles/test/use-global-styles-output.js | 2 +- phpunit/class-wp-theme-json-test.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index 3c0e15ce951799..ee053fa60fe1ba 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -680,7 +680,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - '.is-layout-flow > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }.wp-site-blocks > .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' + '.is-layout-flow > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }.wp-site-blocks > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' ); } ); } ); diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 966e82f925235e..4af46892956cc6 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -315,7 +315,7 @@ public function data_get_layout_definitions() { ), ), ), - 'marginSelector' => ' > ', + 'marginSelector' => ' > * + ', ), 'flex' => array( 'name' => 'flex', From b5364189b2c2f4484bf962cd3fe50f5a68751b01 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 7 Feb 2023 16:28:05 +1100 Subject: [PATCH 07/13] Try more verbose marginStyles that accounts for first and last blocks within the layout --- lib/class-wp-theme-json-gutenberg.php | 49 +++++++++++++++---- lib/theme.json | 48 +++++++++++++++++- .../test/use-global-styles-output.js | 26 +++++++++- .../global-styles/use-global-styles-output.js | 42 ++++++++++++++-- phpunit/class-wp-theme-json-test.php | 26 +++++++++- 5 files changed, 170 insertions(+), 21 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 1c2617561082e0..3b05af650a9e38 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1271,18 +1271,47 @@ protected function get_layout_styles( $block_metadata ) { if ( ! empty( $margin_styles['css'] ) ) { // Add layout aware margin rules for each supported layout type. foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { - $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), '' ) ); + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), '' ) ); + $margin_rules = _wp_array_get( $layout_definition, array( 'marginStyles' ), array() ); + if ( - isset( $layout_definition['marginSelector'] ) && - preg_match( $layout_selector_pattern, $layout_definition['marginSelector'] ) + ! empty( $class_name ) && + ! empty( $margin_rules ) ) { - $block_rules .= sprintf( - '.%s%s%s {%s}', - $class_name, - $layout_definition['marginSelector'], - $selector, - $margin_styles['css'] - ); + foreach ( $margin_rules as $margin_rule ) { + if ( + isset( $margin_rule['selector'] ) && + preg_match( $layout_selector_pattern, $margin_rule['selector'] ) && + ! empty( $margin_rule['rules'] ) + ) { + $declarations = array(); + foreach ( $margin_rule['rules'] as $css_property => $css_value ) { + if ( is_string( $css_value ) ) { + if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $css_value, + ); + } + } elseif ( isset( $margin_styles['declarations'][ $css_property ] ) ) { + if ( static::is_safe_css_declaration( $css_property, $margin_styles['declarations'][ $css_property ] ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $margin_styles['declarations'][ $css_property ], + ); + } + } + } + $layout_selector = sprintf( + '.%s%s%s', + $class_name, + $margin_rule['selector'], + $selector + ); + + $block_rules .= static::to_ruleset( $layout_selector, $declarations ); + } + } } } // Add layout aware margin rule for children of the root site blocks class. diff --git a/lib/theme.json b/lib/theme.json index f098a24f59b213..5f681d52786032 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -263,7 +263,29 @@ } } ], - "marginSelector": " > * + " + "marginStyles": [ + { + "selector": " > ", + "rules": { + "margin-block-start": "0", + "margin-bottom": null + } + }, + { + "selector": " > * + ", + "rules": { + "margin-top": null, + "margin-bottom": null + } + }, + { + "selector": " > *:last-child", + "rules": { + "margin-top": null, + "margin-block-end": "0" + } + } + ] }, "constrained": { "name": "constrained", @@ -324,7 +346,29 @@ } } ], - "marginSelector": " > * + " + "marginStyles": [ + { + "selector": " > ", + "rules": { + "margin-block-start": "0", + "margin-bottom": null + } + }, + { + "selector": " > * + ", + "rules": { + "margin-top": null, + "margin-bottom": null + } + }, + { + "selector": " > *:last-child", + "rules": { + "margin-top": null, + "margin-block-end": "0" + } + } + ] }, "flex": { "name": "flex", diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index ee053fa60fe1ba..f4b517775fda79 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -549,7 +549,29 @@ describe( 'global styles renderer', () => { }, }, ], - marginSelector: ' > * + ', + marginStyles: [ + { + selector: ' > ', + rules: { + 'margin-block-start': '0', + 'margin-bottom': null, + }, + }, + { + selector: ' > * + ', + rules: { + 'margin-top': null, + 'margin-bottom': null, + }, + }, + { + selector: ' > *:last-child', + rules: { + 'margin-top': null, + 'margin-block-end': '0', + }, + }, + ], }, flex: { name: 'flex', @@ -680,7 +702,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - '.is-layout-flow > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }.wp-site-blocks > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' + '.is-layout-flow > .wp-block-cover { margin-block-start: 0; margin-bottom: 50px }.is-layout-flow > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px }.is-layout-flow > *:last-child.wp-block-cover { margin-top: 25px; margin-block-end: 0 }.wp-site-blocks > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' ); } ); } ); diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 8f0fcaf32b4cf5..c50be305f2f9da 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -362,20 +362,52 @@ export function getLayoutStyles( { style?.spacing?.margin && tree?.settings?.layout?.definitions ) { - const marginRules = compileCSS( { + const marginString = compileCSS( { spacing: { margin: style.spacing.margin }, } ); + + // Get margin rules keyed by CSS class name. + const marginRules = getCSSRules( { + spacing: { margin: style.spacing.margin }, + } ).reduce( + ( acc, rule ) => ( { + ...acc, + [ kebabCase( rule.key ) ]: rule.value, + } ), + {} + ); + if ( marginRules ) { // Add layout aware margin rules for each supported layout type. Object.values( tree.settings.layout.definitions ).forEach( - ( { className, marginSelector } ) => { - if ( marginSelector ) { - ruleset += `.${ className }${ marginSelector }${ selector } { ${ marginRules } }`; + ( { className, marginStyles } ) => { + if ( marginStyles?.length ) { + marginStyles.forEach( ( marginStyle ) => { + const declarations = []; + + Object.entries( marginStyle.rules ).forEach( + ( [ cssProperty, cssValue ] ) => { + if ( cssValue ) { + declarations.push( + `${ cssProperty }: ${ cssValue }` + ); + } else if ( marginRules[ cssProperty ] ) { + declarations.push( + `${ cssProperty }: ${ marginRules[ cssProperty ] }` + ); + } + } + ); + + ruleset += `.${ className }${ + marginStyle?.selector + }${ selector } { ${ declarations.join( '; ' ) } }`; + } ); } } ); // Add layout aware margin rule for children of the root site blocks class. - ruleset += `.wp-site-blocks > * + ${ selector } { ${ marginRules } }`; + ruleset += `.wp-site-blocks > * + ${ selector } { ${ marginString } }`; } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 4af46892956cc6..97d4f706e93fa2 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -75,7 +75,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;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > * + .wp-block-group {margin-top:25px;margin-bottom:50px;}.wp-site-blocks > * + .wp-block-group {margin-top:25px;margin-bottom:50px;}', + '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;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > .wp-block-group{margin-block-start: 0;margin-bottom: 50px;}.is-layout-flow > * + .wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > *:last-child.wp-block-group{margin-top: 25px;margin-block-end: 0;}.wp-site-blocks > * + .wp-block-group {margin-top:25px;margin-bottom:50px;}', $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -315,7 +315,29 @@ public function data_get_layout_definitions() { ), ), ), - 'marginSelector' => ' > * + ', + 'marginStyles' => array( + array( + 'selector' => ' > ', + 'rules' => array( + 'margin-block-start' => '0', + 'margin-bottom' => null, + ), + ), + array( + 'selector' => ' > * + ', + 'rules' => array( + 'margin-top' => null, + 'margin-bottom' => null, + ), + ), + array( + 'selector' => ' > *:last-child', + 'rules' => array( + 'margin-top' => null, + 'margin-block-end' => '0', + ), + ), + ), ), 'flex' => array( 'name' => 'flex', From f498b45d0f4689828f89f13585cea70c79cae353 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 7 Feb 2023 16:31:29 +1100 Subject: [PATCH 08/13] Please the linter --- lib/class-wp-theme-json-gutenberg.php | 4 ++-- phpunit/class-wp-theme-json-test.php | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 3b05af650a9e38..8b6edb9989f84c 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1289,14 +1289,14 @@ protected function get_layout_styles( $block_metadata ) { if ( is_string( $css_value ) ) { if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { $declarations[] = array( - 'name' => $css_property, + 'name' => $css_property, 'value' => $css_value, ); } } elseif ( isset( $margin_styles['declarations'][ $css_property ] ) ) { if ( static::is_safe_css_declaration( $css_property, $margin_styles['declarations'][ $css_property ] ) ) { $declarations[] = array( - 'name' => $css_property, + 'name' => $css_property, 'value' => $margin_styles['declarations'][ $css_property ], ); } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 97d4f706e93fa2..bc3dcfe2d5df33 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -271,10 +271,10 @@ public function data_get_layout_definitions() { 'layout definitions' => array( array( 'default' => array( - 'name' => 'default', - 'slug' => 'flow', - 'className' => 'is-layout-flow', - 'baseStyles' => array( + 'name' => 'default', + 'slug' => 'flow', + 'className' => 'is-layout-flow', + 'baseStyles' => array( array( 'selector' => ' > .alignleft', 'rules' => array( @@ -299,7 +299,7 @@ public function data_get_layout_definitions() { ), ), ), - 'spacingStyles' => array( + 'spacingStyles' => array( array( 'selector' => ' > *', 'rules' => array( @@ -315,7 +315,7 @@ public function data_get_layout_definitions() { ), ), ), - 'marginStyles' => array( + 'marginStyles' => array( array( 'selector' => ' > ', 'rules' => array( From 50e3aa510e92c1d3fb19ab3a6f745482a0615599 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:42:12 +1100 Subject: [PATCH 09/13] Try combining margin and spacing styles in PHP output, roll out logic to children of site blocks --- lib/class-wp-theme-json-gutenberg.php | 181 +++++++++++++------------- phpunit/class-wp-theme-json-test.php | 2 +- 2 files changed, 92 insertions(+), 91 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 8b6edb9989f84c..b0c4af2af14ae9 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1196,6 +1196,69 @@ protected function get_block_classes( $style_nodes ) { return $block_rules; } + /** + * Gets the CSS layout rules for a particular block from theme.json layout definitions. + * + * @since 6.3.0 + * + * @param string $selector_format The selector to use for the layout rules with `%s` as a placeholder for the layout rule's selector. + * @param array $layout_definition The layout definition to get styles for. + * @param array $rules_type The key for the set of layout rules to use (e.g. 'marginStyles' or 'spacingStyles'). + * @return string CSS string containing the layout rules. + */ + protected function construct_layout_rules( $selector_format, $layout_definition, $rules_type, $replacement_value ) { + $layout_rules = _wp_array_get( $layout_definition, array( $rules_type ), array() ); + $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. + + $block_rules = ''; + + if ( + ! empty( $selector_format ) && + ! empty( $layout_rules ) + ) { + foreach ( $layout_rules as $layout_rule ) { + if ( + isset( $layout_rule['selector'] ) && + preg_match( $layout_selector_pattern, $layout_rule['selector'] ) && + ! empty( $layout_rule['rules'] ) + ) { + $declarations = array(); + foreach ( $layout_rule['rules'] as $css_property => $css_value ) { + if ( is_string( $css_value ) ) { + if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $css_value, + ); + } + } elseif ( isset( $replacement_value ) && ! is_array( $replacement_value ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $replacement_value, + ); + } elseif ( isset( $replacement_value[ $css_property ] ) ) { + if ( static::is_safe_css_declaration( $css_property, $replacement_value[ $css_property ] ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $replacement_value[ $css_property ], + ); + } + } + } + + $layout_selector = sprintf( + $selector_format, + $layout_rule['selector'], + ); + + $block_rules .= static::to_ruleset( $layout_selector, $declarations ); + } + } + } + + return $block_rules; + } + /** * Gets the CSS layout rules for a particular block from theme.json layout definitions. * @@ -1272,54 +1335,23 @@ protected function get_layout_styles( $block_metadata ) { // Add layout aware margin rules for each supported layout type. foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), '' ) ); - $margin_rules = _wp_array_get( $layout_definition, array( 'marginStyles' ), array() ); - - if ( - ! empty( $class_name ) && - ! empty( $margin_rules ) - ) { - foreach ( $margin_rules as $margin_rule ) { - if ( - isset( $margin_rule['selector'] ) && - preg_match( $layout_selector_pattern, $margin_rule['selector'] ) && - ! empty( $margin_rule['rules'] ) - ) { - $declarations = array(); - foreach ( $margin_rule['rules'] as $css_property => $css_value ) { - if ( is_string( $css_value ) ) { - if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { - $declarations[] = array( - 'name' => $css_property, - 'value' => $css_value, - ); - } - } elseif ( isset( $margin_styles['declarations'][ $css_property ] ) ) { - if ( static::is_safe_css_declaration( $css_property, $margin_styles['declarations'][ $css_property ] ) ) { - $declarations[] = array( - 'name' => $css_property, - 'value' => $margin_styles['declarations'][ $css_property ], - ); - } - } - } - $layout_selector = sprintf( - '.%s%s%s', - $class_name, - $margin_rule['selector'], - $selector - ); + $block_rules .= $this->construct_layout_rules( + '.' . $class_name . '%s' . $selector, + $layout_definition, + 'marginStyles', + $margin_styles['declarations'] + ); - $block_rules .= static::to_ruleset( $layout_selector, $declarations ); - } - } + // Add layout aware margin rule for children of the root site blocks class. + if ( 'default' === $layout_definition_key ) { + $block_rules .= $this->construct_layout_rules( + '.wp-site-blocks%s' . $selector, + $layout_definition, + 'marginStyles', + $margin_styles['declarations'] + ); } } - // Add layout aware margin rule for children of the root site blocks class. - $block_rules .= sprintf( - '.wp-site-blocks > * + %s {%s}', - $selector, - $margin_styles['css'] - ); } } @@ -1331,53 +1363,22 @@ protected function get_layout_styles( $block_metadata ) { continue; } - $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); - $spacing_rules = _wp_array_get( $layout_definition, array( 'spacingStyles' ), array() ); + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); + $layout_selector_format = ''; - 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, - ); - } - } - - if ( ! $has_block_gap_support ) { - // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. - $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s%3$s)' : ':where(%1$s.%2$s%3$s)'; - $layout_selector = sprintf( - $format, - $selector, - $class_name, - $spacing_rule['selector'] - ); - } else { - $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 ); - } - } + if ( ! $has_block_gap_support ) { + // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. + $layout_selector_format = static::ROOT_BLOCK_SELECTOR === $selector ? ":where(.$class_name%s)" : ":where($selector.$class_name%s)"; + } else { + $layout_selector_format = static::ROOT_BLOCK_SELECTOR === $selector ? "$selector .$class_name%s" : "$selector.$class_name%s"; } + + $block_rules .= $this->construct_layout_rules( + $layout_selector_format, + $layout_definition, + 'spacingStyles', + $block_gap_value + ); } } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index bc3dcfe2d5df33..fe090e4a0b116b 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -75,7 +75,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;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > .wp-block-group{margin-block-start: 0;margin-bottom: 50px;}.is-layout-flow > * + .wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > *:last-child.wp-block-group{margin-top: 25px;margin-block-end: 0;}.wp-site-blocks > * + .wp-block-group {margin-top:25px;margin-bottom:50px;}', + '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;}.wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > .wp-block-group{margin-block-start: 0;margin-bottom: 50px;}.is-layout-flow > * + .wp-block-group{margin-top: 25px;margin-bottom: 50px;}.is-layout-flow > *:last-child.wp-block-group{margin-top: 25px;margin-block-end: 0;}.wp-site-blocks > .wp-block-group{margin-block-start: 0;margin-bottom: 50px;}.wp-site-blocks > * + .wp-block-group{margin-top: 25px;margin-bottom: 50px;}.wp-site-blocks > *:last-child.wp-block-group{margin-top: 25px;margin-block-end: 0;}', $theme_json->get_stylesheet( array( 'styles' ) ) ); } From 8c0a67ac69c00fd84f96b9a93c4d9e14ea5a7d34 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:43:56 +1100 Subject: [PATCH 10/13] Fix formatting --- lib/class-wp-theme-json-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index b0c4af2af14ae9..e3f30d8e299148 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1203,7 +1203,7 @@ protected function get_block_classes( $style_nodes ) { * * @param string $selector_format The selector to use for the layout rules with `%s` as a placeholder for the layout rule's selector. * @param array $layout_definition The layout definition to get styles for. - * @param array $rules_type The key for the set of layout rules to use (e.g. 'marginStyles' or 'spacingStyles'). + * @param string $rules_type The key for the set of layout rules to use (e.g. 'marginStyles' or 'spacingStyles'). * @return string CSS string containing the layout rules. */ protected function construct_layout_rules( $selector_format, $layout_definition, $rules_type, $replacement_value ) { From 5da1f26295ddab50c3c8be414b2a54aca4b13b83 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:55:38 +1100 Subject: [PATCH 11/13] Fix linting issues --- lib/class-wp-theme-json-gutenberg.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index e3f30d8e299148..216cc3b94c0f05 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1201,14 +1201,15 @@ protected function get_block_classes( $style_nodes ) { * * @since 6.3.0 * - * @param string $selector_format The selector to use for the layout rules with `%s` as a placeholder for the layout rule's selector. - * @param array $layout_definition The layout definition to get styles for. - * @param string $rules_type The key for the set of layout rules to use (e.g. 'marginStyles' or 'spacingStyles'). + * @param string $selector_format The selector to use for the layout rules with `%s` as a placeholder for the layout rule's selector. + * @param array $layout_definition The layout definition to get styles for. + * @param string $rules_type The key for the set of layout rules to use (e.g. 'marginStyles' or 'spacingStyles'). + * @param array|string $replacement_value The value to be output where layout rules define a null placeholder value. * @return string CSS string containing the layout rules. */ protected function construct_layout_rules( $selector_format, $layout_definition, $rules_type, $replacement_value ) { - $layout_rules = _wp_array_get( $layout_definition, array( $rules_type ), array() ); - $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. + $layout_rules = _wp_array_get( $layout_definition, array( $rules_type ), array() ); + $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. $block_rules = ''; @@ -1227,19 +1228,19 @@ protected function construct_layout_rules( $selector_format, $layout_definition, if ( is_string( $css_value ) ) { if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { $declarations[] = array( - 'name' => $css_property, + 'name' => $css_property, 'value' => $css_value, ); } } elseif ( isset( $replacement_value ) && ! is_array( $replacement_value ) ) { $declarations[] = array( - 'name' => $css_property, + 'name' => $css_property, 'value' => $replacement_value, ); } elseif ( isset( $replacement_value[ $css_property ] ) ) { if ( static::is_safe_css_declaration( $css_property, $replacement_value[ $css_property ] ) ) { $declarations[] = array( - 'name' => $css_property, + 'name' => $css_property, 'value' => $replacement_value[ $css_property ], ); } @@ -1248,7 +1249,7 @@ protected function construct_layout_rules( $selector_format, $layout_definition, $layout_selector = sprintf( $selector_format, - $layout_rule['selector'], + $layout_rule['selector'] ); $block_rules .= static::to_ruleset( $layout_selector, $declarations ); From db1ca3c67e970c4c0cc6149d00e71db5fffed1d0 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 22 Feb 2023 17:22:13 +1100 Subject: [PATCH 12/13] Consolidate JS logic for marginStyles and spacingStyles to follow PHP consolidation --- .../test/use-global-styles-output.js | 2 +- .../global-styles/use-global-styles-output.js | 181 ++++++++++-------- 2 files changed, 106 insertions(+), 77 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index f4b517775fda79..d484ddea3d8e6d 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -702,7 +702,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - '.is-layout-flow > .wp-block-cover { margin-block-start: 0; margin-bottom: 50px }.is-layout-flow > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px }.is-layout-flow > *:last-child.wp-block-cover { margin-top: 25px; margin-block-end: 0 }.wp-site-blocks > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }' + '.is-layout-flow > .wp-block-cover { margin-block-start: 0; margin-bottom: 50px; }.is-layout-flow > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }.is-layout-flow > *:last-child.wp-block-cover { margin-top: 25px; margin-block-end: 0; }.wp-site-blocks > .wp-block-cover { margin-block-start: 0; margin-bottom: 50px; }.wp-site-blocks > * + .wp-block-cover { margin-top: 25px; margin-bottom: 50px; }.wp-site-blocks > *:last-child.wp-block-cover { margin-top: 25px; margin-block-end: 0; }' ); } ); } ); diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index c50be305f2f9da..c0ef1f5f575bb2 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -14,7 +14,7 @@ import { } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { useContext, useMemo } from '@wordpress/element'; -import { compileCSS, getCSSRules } from '@wordpress/style-engine'; +import { getCSSRules } from '@wordpress/style-engine'; /** * Internal dependencies @@ -320,6 +320,70 @@ export function getStylesDeclarations( return output; } +/** + * Gets the CSS rules for a given layout definition. + * + * @param {string} selectorFormat The string for a selector where `%s` is replaced by the layout rules' selector. + * @param {Object} layoutDefinition The layout definition object. + * @param {string} rulesType The type of rules to get (e.g. 'marginStyles' or 'spacingStyles'). + * @param {string|number|Object} replacementValue The value to use for any undefined rules. + * @return {string} Generated CSS rules for the layout styles. + */ +function getLayoutDefinitionRules( + selectorFormat, + layoutDefinition, + rulesType, + replacementValue +) { + const layoutRules = layoutDefinition?.[ rulesType ]; + let ruleset = ''; + + if ( layoutRules?.length ) { + layoutRules.forEach( ( layoutRule ) => { + if ( + layoutRule?.selector !== undefined && + layoutRule?.rules && + selectorFormat + ) { + const declarations = []; + Object.entries( layoutRule.rules ).forEach( + ( [ cssProperty, cssValue ] ) => { + if ( cssValue ) { + declarations.push( + `${ cssProperty }: ${ cssValue }` + ); + } else if ( + typeof replacementValue === 'string' || + typeof replacementValue === 'number' + ) { + declarations.push( + `${ cssProperty }: ${ replacementValue }` + ); + } else if ( + replacementValue?.[ cssProperty ] !== undefined + ) { + declarations.push( + `${ cssProperty }: ${ replacementValue[ cssProperty ] }` + ); + } + } + ); + const selector = selectorFormat.replace( + '%s', + layoutRule.selector + ); + if ( declarations.length ) { + ruleset += `${ selector } { ${ declarations.join( + '; ' + ) }; }`; + } + } + } ); + } + + return ruleset; +} + /** * Get generated CSS for layout styles by looking up layout definitions provided * in theme.json, and outputting common layout styles, and specific blockGap values. @@ -362,10 +426,6 @@ export function getLayoutStyles( { style?.spacing?.margin && tree?.settings?.layout?.definitions ) { - const marginString = compileCSS( { - spacing: { margin: style.spacing.margin }, - } ); - // Get margin rules keyed by CSS class name. const marginRules = getCSSRules( { spacing: { margin: style.spacing.margin }, @@ -380,90 +440,59 @@ export function getLayoutStyles( { if ( marginRules ) { // Add layout aware margin rules for each supported layout type. Object.values( tree.settings.layout.definitions ).forEach( - ( { className, marginStyles } ) => { - if ( marginStyles?.length ) { - marginStyles.forEach( ( marginStyle ) => { - const declarations = []; - - Object.entries( marginStyle.rules ).forEach( - ( [ cssProperty, cssValue ] ) => { - if ( cssValue ) { - declarations.push( - `${ cssProperty }: ${ cssValue }` - ); - } else if ( marginRules[ cssProperty ] ) { - declarations.push( - `${ cssProperty }: ${ marginRules[ cssProperty ] }` - ); - } - } - ); - - ruleset += `.${ className }${ - marginStyle?.selector - }${ selector } { ${ declarations.join( '; ' ) } }`; - } ); + ( layoutDefinition ) => { + ruleset += getLayoutDefinitionRules( + `.${ layoutDefinition.className }%s${ selector }`, + layoutDefinition, + 'marginStyles', + marginRules + ); + // Add layout aware margin rule for children of the root site blocks class. + if ( layoutDefinition.name === 'default' ) { + ruleset += getLayoutDefinitionRules( + `.wp-site-blocks%s${ selector }`, + layoutDefinition, + 'marginStyles', + marginRules + ); } } ); - // Add layout aware margin rule for children of the root site blocks class. - ruleset += `.wp-site-blocks > * + ${ selector } { ${ marginString } }`; } } if ( gapValue && tree?.settings?.layout?.definitions ) { Object.values( tree.settings.layout.definitions ).forEach( - ( { className, name, spacingStyles } ) => { + ( layoutDefinition ) => { // Allow outputting fallback gap styles for flex layout type when block gap support isn't available. - if ( ! hasBlockGapSupport && 'flex' !== name ) { + if ( + ! hasBlockGapSupport && + 'flex' !== layoutDefinition?.name + ) { return; } - 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 ) { - let combinedSelector = ''; - - if ( ! hasBlockGapSupport ) { - // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. - combinedSelector = - selector === ROOT_BLOCK_SELECTOR - ? `:where(.${ className }${ - spacingStyle?.selector || '' - })` - : `:where(${ selector }.${ className }${ - spacingStyle?.selector || '' - })`; - } else { - combinedSelector = - selector === ROOT_BLOCK_SELECTOR - ? `${ selector } .${ className }${ - spacingStyle?.selector || '' - }` - : `${ selector }.${ className }${ - spacingStyle?.selector || '' - }`; - } - ruleset += `${ combinedSelector } { ${ declarations.join( - '; ' - ) }; }`; - } - } ); + let combinedSelector = ''; + + if ( ! hasBlockGapSupport ) { + // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. + combinedSelector = + selector === ROOT_BLOCK_SELECTOR + ? `:where(.${ layoutDefinition.className }%s)` + : `:where(${ selector }.${ layoutDefinition.className }%s)`; + } else { + combinedSelector = + selector === ROOT_BLOCK_SELECTOR + ? `${ selector } .${ layoutDefinition.className }%s` + : `${ selector }.${ layoutDefinition.className }%s`; } + + ruleset += getLayoutDefinitionRules( + combinedSelector, + layoutDefinition, + 'spacingStyles', + gapValue + ); } ); // For backwards compatibility, ensure the legacy block gap CSS variable is still available. From 1fa9c3b6f5cfb5c076cc19cdda18467883ddbf24 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:17:41 +1100 Subject: [PATCH 13/13] Rename function to add clarity --- lib/class-wp-theme-json-gutenberg.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 216cc3b94c0f05..87503a8b2caf6f 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1207,7 +1207,7 @@ protected function get_block_classes( $style_nodes ) { * @param array|string $replacement_value The value to be output where layout rules define a null placeholder value. * @return string CSS string containing the layout rules. */ - protected function construct_layout_rules( $selector_format, $layout_definition, $rules_type, $replacement_value ) { + protected function get_layout_definition_rules( $selector_format, $layout_definition, $rules_type, $replacement_value ) { $layout_rules = _wp_array_get( $layout_definition, array( $rules_type ), array() ); $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. @@ -1336,7 +1336,7 @@ protected function get_layout_styles( $block_metadata ) { // Add layout aware margin rules for each supported layout type. foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), '' ) ); - $block_rules .= $this->construct_layout_rules( + $block_rules .= $this->get_layout_definition_rules( '.' . $class_name . '%s' . $selector, $layout_definition, 'marginStyles', @@ -1345,7 +1345,7 @@ protected function get_layout_styles( $block_metadata ) { // Add layout aware margin rule for children of the root site blocks class. if ( 'default' === $layout_definition_key ) { - $block_rules .= $this->construct_layout_rules( + $block_rules .= $this->get_layout_definition_rules( '.wp-site-blocks%s' . $selector, $layout_definition, 'marginStyles', @@ -1374,7 +1374,7 @@ protected function get_layout_styles( $block_metadata ) { $layout_selector_format = static::ROOT_BLOCK_SELECTOR === $selector ? "$selector .$class_name%s" : "$selector.$class_name%s"; } - $block_rules .= $this->construct_layout_rules( + $block_rules .= $this->get_layout_definition_rules( $layout_selector_format, $layout_definition, 'spacingStyles',