From 5f4cddbcac90e5fe98fa6bfe9f93e5ff08b08b7a Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Wed, 8 May 2024 15:51:08 +1000 Subject: [PATCH] Reduce specificity of global styles and layout selectors. --- src/wp-includes/block-supports/layout.php | 18 +- src/wp-includes/class-wp-theme-json.php | 20 +- tests/phpunit/tests/block-supports/layout.php | 284 ++++++++++++++++++ 3 files changed, 303 insertions(+), 19 deletions(-) diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php index f5acd75a72d7c..9eb0be3f3ccc7 100644 --- a/src/wp-includes/block-supports/layout.php +++ b/src/wp-includes/block-supports/layout.php @@ -51,13 +51,13 @@ function wp_get_layout_definitions() { ), 'spacingStyles' => array( array( - 'selector' => ' > :first-child:first-child', + 'selector' => ' > :first-child', 'rules' => array( 'margin-block-start' => '0', ), ), array( - 'selector' => ' > :last-child:last-child', + 'selector' => ' > :last-child', 'rules' => array( 'margin-block-end' => '0', ), @@ -116,13 +116,13 @@ function wp_get_layout_definitions() { ), 'spacingStyles' => array( array( - 'selector' => ' > :first-child:first-child', + 'selector' => ' > :first-child', 'rules' => array( 'margin-block-start' => '0', ), ), array( - 'selector' => ' > :last-child:last-child', + 'selector' => ' > :last-child', 'rules' => array( 'margin-block-end' => '0', ), @@ -150,7 +150,7 @@ function wp_get_layout_definitions() { ), ), array( - 'selector' => ' > *', + 'selector' => ' > :is(*, div)', // :is(*, div) instead of just * increases the specificity by 001. 'rules' => array( 'margin' => '0', ), @@ -172,7 +172,7 @@ function wp_get_layout_definitions() { 'displayMode' => 'grid', 'baseStyles' => array( array( - 'selector' => ' > *', + 'selector' => ' > :is(*, div)', // :is(*, div) instead of just * increases the specificity by 001. 'rules' => array( 'margin' => '0', ), @@ -261,7 +261,7 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false ), ), array( - 'selector' => "$selector$selector > * + *", + 'selector' => "$selector > * + *", 'declarations' => array( 'margin-block-start' => $gap_value, 'margin-block-end' => '0', @@ -370,7 +370,7 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false ), ), array( - 'selector' => "$selector$selector > * + *", + 'selector' => "$selector > * + *", 'declarations' => array( 'margin-block-start' => $gap_value, 'margin-block-end' => '0', @@ -735,7 +735,7 @@ function wp_render_layout_support_flag( $block_content, $block ) { $has_block_gap_support = isset( $block_gap ); $style = wp_get_layout_style( - ".$container_class.$container_class", + ".$container_class", $used_layout, $has_block_gap_support, $gap_value, diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index e039fdbb9a925..9a2b109fd420d 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -1424,7 +1424,7 @@ protected function get_layout_styles( $block_metadata, $types = array() ) { $has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support. $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); $layout_definitions = wp_get_layout_definitions(); - $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. + $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\,\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. /* * Gap styles will only be output if the theme has block gap support, or supports a fallback gap. @@ -1499,7 +1499,7 @@ protected function get_layout_styles( $block_metadata, $types = array() ) { $spacing_rule['selector'] ); } else { - $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(%s .%s) %s' : '%s-%s%s'; + $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s) %3$s' : ':where(%1$s-%2$s) %3$s'; $layout_selector = sprintf( $format, $selector, @@ -1583,7 +1583,7 @@ protected function get_layout_styles( $block_metadata, $types = array() ) { } $layout_selector = sprintf( - '%s .%s%s', + '.%s%s', $selector, $class_name, $base_style_rule['selector'] @@ -2591,7 +2591,7 @@ static function ( $pseudo_selector ) use ( $selector ) { } // 2. Generate and append the rules that use the general selector. - $block_rules .= static::to_ruleset( $selector, $declarations ); + $block_rules .= static::to_ruleset( ":where($selector)", $declarations ); // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { @@ -2608,7 +2608,7 @@ static function ( $pseudo_selector ) use ( $selector ) { // 5. Generate and append the feature level rulesets. foreach ( $feature_declarations as $feature_selector => $individual_feature_declarations ) { - $block_rules .= static::to_ruleset( $feature_selector, $individual_feature_declarations ); + $block_rules .= static::to_ruleset( ":where($feature_selector)", $individual_feature_declarations ); } // 6. Generate and append the style variation rulesets. @@ -2643,8 +2643,8 @@ public function get_root_layout_rules( $selector, $block_metadata ) { $content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial'; $wide_size = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize']; $wide_size = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial'; - $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . ' { --wp--style--global--content-size: ' . $content_size . ';'; - $css .= '--wp--style--global--wide-size: ' . $wide_size . '; }'; + $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . ' { --wp--style--global--content-size: ' . $content_size . ';'; + $css .= '--wp--style--global--wide-size: ' . $wide_size . '; }'; } /* @@ -2655,7 +2655,7 @@ public function get_root_layout_rules( $selector, $block_metadata ) { * user-generated values take precedence in the CSS cascade. * @link https://github.com/WordPress/gutenberg/issues/36147. */ - $css .= 'body { margin: 0; }'; + $css .= ':where(body) { margin: 0; }'; if ( $use_root_padding ) { // Top and bottom padding are applied to the outer block container. @@ -2682,8 +2682,8 @@ public function get_root_layout_rules( $selector, $block_metadata ) { if ( isset( $this->theme_json['settings']['spacing']['blockGap'] ) ) { $block_gap_value = static::get_property_value( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ) ); $css .= ":where(.wp-site-blocks) > * { margin-block-start: $block_gap_value; margin-block-end: 0; }"; - $css .= ':where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }'; - $css .= ':where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }'; + $css .= ':where(.wp-site-blocks) > :first-child { margin-block-start: 0; }'; + $css .= ':where(.wp-site-blocks) > :last-child { margin-block-end: 0; }'; // For backwards compatibility, ensure the legacy block gap CSS variable is still available. $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . " { --wp--style--block-gap: $block_gap_value; }"; diff --git a/tests/phpunit/tests/block-supports/layout.php b/tests/phpunit/tests/block-supports/layout.php index a5f67f50f9b11..d67e015f0ea22 100644 --- a/tests/phpunit/tests/block-supports/layout.php +++ b/tests/phpunit/tests/block-supports/layout.php @@ -362,4 +362,288 @@ public function data_restore_group_inner_container() { ), ); } + + const ARGS_DEFAULTS = array( + 'selector' => null, + 'layout' => null, + 'has_block_gap_support' => false, + 'gap_value' => null, + 'should_skip_gap_serialization' => false, + 'fallback_gap_value' => '0.5em', + 'block_spacing' => null, + ); + + /** + * Generates the CSS corresponding to the provided layout. + * + * @ticket 61165 + * + * @dataProvider data_wp_get_layout_style + * + * @covers ::wp_get_layout_style + * + * @param array $args Dataset to test. + * @param string $expected_output The expected output. + */ + public function test_wp_get_layout_style( $args, $expected_output ) { + $args = array_merge( static::ARGS_DEFAULTS, $args ); + $layout_styles = wp_get_layout_style( + $args['selector'], + $args['layout'], + $args['has_block_gap_support'], + $args['gap_value'], + $args['should_skip_gap_serialization'], + $args['fallback_gap_value'], + $args['block_spacing'] + ); + + $this->assertSame( $expected_output, $layout_styles ); + } + + /** + * Data provider for test_wp_get_layout_style(). + * + * @return array + */ + public function data_wp_get_layout_style() { + return array( + 'no args should return empty value' => array( + 'args' => array(), + 'expected_output' => '', + ), + 'nulled args should return empty value' => array( + 'args' => array( + 'selector' => null, + 'layout' => null, + 'has_block_gap_support' => null, + 'gap_value' => null, + 'should_skip_gap_serialization' => null, + 'fallback_gap_value' => null, + 'block_spacing' => null, + ), + 'expected_output' => '', + ), + 'only selector should return empty value' => array( + 'args' => array( + 'selector' => '.wp-layout', + ), + 'expected_output' => '', + ), + 'default layout and block gap support' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'has_block_gap_support' => true, + 'gap_value' => '1em', + ), + 'expected_output' => '.wp-layout > *{margin-block-start:0;margin-block-end:0;}.wp-layout > * + *{margin-block-start:1em;margin-block-end:0;}', + ), + 'skip serialization should return empty value' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'has_block_gap_support' => true, + 'gap_value' => '1em', + 'should_skip_gap_serialization' => true, + ), + 'expected_output' => '', + ), + 'default layout and axial block gap support' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'has_block_gap_support' => true, + 'gap_value' => array( 'top' => '1em' ), + ), + 'expected_output' => '.wp-layout > *{margin-block-start:0;margin-block-end:0;}.wp-layout > * + *{margin-block-start:1em;margin-block-end:0;}', + ), + 'constrained layout with sizes' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'constrained', + 'contentSize' => '800px', + 'wideSize' => '1200px', + ), + ), + 'expected_output' => '.wp-layout > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width:800px;margin-left:auto !important;margin-right:auto !important;}.wp-layout > .alignwide{max-width:1200px;}.wp-layout .alignfull{max-width:none;}', + ), + 'constrained layout with sizes and block spacing' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'constrained', + 'contentSize' => '800px', + 'wideSize' => '1200px', + ), + 'block_spacing' => array( + 'padding' => array( + 'left' => '20px', + 'right' => '10px', + ), + ), + ), + 'expected_output' => '.wp-layout > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width:800px;margin-left:auto !important;margin-right:auto !important;}.wp-layout > .alignwide{max-width:1200px;}.wp-layout .alignfull{max-width:none;}.wp-layout > .alignfull{margin-right:calc(10px * -1);margin-left:calc(20px * -1);}', + ), + 'constrained layout with block gap support' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'constrained', + ), + 'has_block_gap_support' => true, + 'gap_value' => '2.5rem', + ), + 'expected_output' => '.wp-layout > *{margin-block-start:0;margin-block-end:0;}.wp-layout > * + *{margin-block-start:2.5rem;margin-block-end:0;}', + ), + 'constrained layout with axial block gap support' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'constrained', + ), + 'has_block_gap_support' => true, + 'gap_value' => array( 'top' => '2.5rem' ), + ), + 'expected_output' => '.wp-layout > *{margin-block-start:0;margin-block-end:0;}.wp-layout > * + *{margin-block-start:2.5rem;margin-block-end:0;}', + ), + 'constrained layout with block gap support and spacing preset' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'constrained', + ), + 'has_block_gap_support' => true, + 'gap_value' => 'var:preset|spacing|50', + ), + 'expected_output' => '.wp-layout > *{margin-block-start:0;margin-block-end:0;}.wp-layout > * + *{margin-block-start:var(--wp--preset--spacing--50);margin-block-end:0;}', + ), + 'flex layout with no args should return empty value' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'flex', + ), + ), + 'expected_output' => '', + ), + 'horizontal flex layout should return empty value' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'horizontal', + ), + ), + 'expected_output' => '', + ), + 'flex layout with properties' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'horizontal', + 'flexWrap' => 'nowrap', + 'justifyContent' => 'left', + 'verticalAlignment' => 'bottom', + ), + ), + 'expected_output' => '.wp-layout{flex-wrap:nowrap;justify-content:flex-start;align-items:flex-end;}', + ), + 'flex layout with properties and block gap' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'horizontal', + 'flexWrap' => 'nowrap', + 'justifyContent' => 'left', + 'verticalAlignment' => 'bottom', + ), + 'has_block_gap_support' => true, + 'gap_value' => '29px', + ), + 'expected_output' => '.wp-layout{flex-wrap:nowrap;gap:29px;justify-content:flex-start;align-items:flex-end;}', + ), + 'flex layout with properties and axial block gap' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'horizontal', + 'flexWrap' => 'nowrap', + 'justifyContent' => 'left', + 'verticalAlignment' => 'bottom', + ), + 'has_block_gap_support' => true, + 'gap_value' => array( + 'top' => '1px', + 'left' => '2px', + ), + ), + 'expected_output' => '.wp-layout{flex-wrap:nowrap;gap:1px 2px;justify-content:flex-start;align-items:flex-end;}', + ), + 'flex layout with properties and axial block gap using spacing preset' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'horizontal', + 'flexWrap' => 'nowrap', + 'justifyContent' => 'left', + 'verticalAlignment' => 'bottom', + ), + 'has_block_gap_support' => true, + 'gap_value' => array( + 'left' => 'var:preset|spacing|40', + ), + 'fallback_gap_value' => '11px', + ), + 'expected_output' => '.wp-layout{flex-wrap:nowrap;gap:11px var(--wp--preset--spacing--40);justify-content:flex-start;align-items:flex-end;}', + ), + 'vertical flex layout with properties' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'vertical', + 'flexWrap' => 'nowrap', + 'justifyContent' => 'left', + 'verticalAlignment' => 'bottom', + ), + ), + 'expected_output' => '.wp-layout{flex-wrap:nowrap;flex-direction:column;align-items:flex-start;justify-content:flex-end;}', + ), + 'default grid layout' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'grid', + ), + ), + 'expected_output' => '.wp-layout{grid-template-columns:repeat(auto-fill, minmax(min(12rem, 100%), 1fr));}', + ), + 'grid layout with columnCount' => array( + 'args' => array( + 'selector' => '.wp-layout', + 'layout' => array( + 'type' => 'grid', + 'columnCount' => 3, + ), + ), + 'expected_output' => '.wp-layout{grid-template-columns:repeat(3, minmax(0, 1fr));}', + ), + 'default layout with blockGap to verify converting gap value into valid CSS' => array( + 'args' => array( + 'selector' => '.wp-block-group.wp-container-6', + 'layout' => array( + 'type' => 'default', + ), + 'has_block_gap_support' => true, + 'gap_value' => 'var:preset|spacing|70', + 'block_spacing' => array( + 'blockGap' => 'var(--wp--preset--spacing--70)', + ), + ), + 'expected_output' => '.wp-block-group.wp-container-6 > *{margin-block-start:0;margin-block-end:0;}.wp-block-group.wp-container-6 > * + *{margin-block-start:var(--wp--preset--spacing--70);margin-block-end:0;}', + ), + ); + } }