Skip to content

Commit

Permalink
Add style engine support for nested at-rules. (#58867)
Browse files Browse the repository at this point in the history
Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
  • Loading branch information
3 people authored Feb 9, 2024
1 parent 93d417b commit 0ba8692
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 12 deletions.
53 changes: 47 additions & 6 deletions packages/style-engine/class-wp-style-engine-css-rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,27 @@ class WP_Style_Engine_CSS_Rule {
*/
protected $declarations;

/**
* The CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
*
* @var string
*/
protected $at_rule;


/**
* Constructor
*
* @param string $selector The CSS selector.
* @param string[]|WP_Style_Engine_CSS_Declarations $declarations An associative array of CSS definitions, e.g., array( "$property" => "$value", "$property" => "$value" ),
* or a WP_Style_Engine_CSS_Declarations object.
* @param string $at_rule A CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
*
*/
public function __construct( $selector = '', $declarations = array() ) {
public function __construct( $selector = '', $declarations = array(), $at_rule = '' ) {
$this->set_selector( $selector );
$this->add_declarations( $declarations );
$this->set_at_rule( $at_rule );
}

/**
Expand Down Expand Up @@ -80,6 +91,18 @@ public function add_declarations( $declarations ) {
return $this;
}

/**
* Sets the at_rule.
*
* @param string $at_rule A CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
*
* @return WP_Style_Engine_CSS_Rule Returns the object to allow chaining of methods.
*/
public function set_at_rule( $at_rule ) {
$this->at_rule = $at_rule;
return $this;
}

/**
* Gets the declarations object.
*
Expand All @@ -98,6 +121,15 @@ public function get_selector() {
return $this->selector;
}

/**
* Gets the at_rule.
*
* @return string
*/
public function get_at_rule() {
return $this->at_rule;
}

/**
* Gets the CSS.
*
Expand All @@ -107,19 +139,28 @@ public function get_selector() {
* @return string
*/
public function get_css( $should_prettify = false, $indent_count = 0 ) {
$rule_indent = $should_prettify ? str_repeat( "\t", $indent_count ) : '';
$declarations_indent = $should_prettify ? $indent_count + 1 : 0;
$suffix = $should_prettify ? "\n" : '';
$spacer = $should_prettify ? ' ' : '';
$rule_indent = $should_prettify ? str_repeat( "\t", $indent_count ) : '';
$nested_rule_indent = $should_prettify ? str_repeat( "\t", $indent_count + 1 ) : '';
$declarations_indent = $should_prettify ? $indent_count + 1 : 0;
$nested_declarations_indent = $should_prettify ? $indent_count + 2 : 0;
$suffix = $should_prettify ? "\n" : '';
$spacer = $should_prettify ? ' ' : '';
// Trims any multiple selectors strings.
$selector = $should_prettify ? implode( ',', array_map( 'trim', explode( ',', $this->get_selector() ) ) ) : $this->get_selector();
$selector = $should_prettify ? str_replace( array( ',' ), ",\n", $selector ) : $selector;
$css_declarations = $this->declarations->get_declarations_string( $should_prettify, $declarations_indent );
$at_rule = $this->get_at_rule();
$has_at_rule = ! empty( $at_rule );
$css_declarations = $this->declarations->get_declarations_string( $should_prettify, $has_at_rule ? $nested_declarations_indent : $declarations_indent );

if ( empty( $css_declarations ) ) {
return '';
}

if ( $has_at_rule ) {
$selector = "{$rule_indent}{$at_rule}{$spacer}{{$suffix}{$nested_rule_indent}{$selector}{$spacer}{{$suffix}{$css_declarations}{$suffix}{$nested_rule_indent}}{$suffix}{$rule_indent}}";
return $selector;
}

return "{$rule_indent}{$selector}{$spacer}{{$suffix}{$css_declarations}{$suffix}{$rule_indent}}";
}
}
Expand Down
11 changes: 10 additions & 1 deletion packages/style-engine/class-wp-style-engine-css-rules-store.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,26 @@ public function get_all_rules() {
* If the rule does not exist, it will be created.
*
* @param string $selector The CSS selector.
* @param string $at_rule The CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
*
* @return WP_Style_Engine_CSS_Rule|void Returns a WP_Style_Engine_CSS_Rule object, or null if the selector is empty.
*/
public function add_rule( $selector ) {
public function add_rule( $selector, $at_rule = '' ) {
$selector = trim( $selector );
$at_rule = trim( $at_rule );

// Bail early if there is no selector.
if ( empty( $selector ) ) {
return;
}

if ( ! empty( $at_rule ) ) {
if ( empty( $this->rules[ "$at_rule $selector" ] ) ) {
$this->rules[ "$at_rule $selector" ] = new WP_Style_Engine_CSS_Rule( $selector, array(), $at_rule );
}
return $this->rules[ "$at_rule $selector" ];
}

// Create the rule if it doesn't exist.
if ( empty( $this->rules[ $selector ] ) ) {
$this->rules[ $selector ] = new WP_Style_Engine_CSS_Rule( $selector );
Expand Down
19 changes: 18 additions & 1 deletion packages/style-engine/class-wp-style-engine-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,29 @@ public function add_rules( $css_rules ) {

foreach ( $css_rules as $rule ) {
$selector = $rule->get_selector();
$at_rule = $rule->get_at_rule();

/**
* If there is an at_rule and it already exists in the css_rules array,
* add the rule to it.
* Otherwise, create a new entry for the at_rule
*/
if ( ! empty( $at_rule ) ) {
if ( isset( $this->css_rules[ "$at_rule $selector" ] ) ) {
$this->css_rules[ "$at_rule $selector" ]->add_declarations( $rule->get_declarations() );
continue;
}
$this->css_rules[ "$at_rule $selector" ] = $rule;
continue;
}

// If the selector already exists, add the declarations to it.
if ( isset( $this->css_rules[ $selector ] ) ) {
$this->css_rules[ $selector ]->add_declarations( $rule->get_declarations() );
continue;
}
$this->css_rules[ $rule->get_selector() ] = $rule;
}

return $this;
}

Expand Down Expand Up @@ -110,6 +126,7 @@ public function get_css( $options = array() ) {
// Build the CSS.
$css = '';
foreach ( $this->css_rules as $rule ) {
// See class WP_Style_Engine_CSS_Rule for the get_css method.
$css .= $rule->get_css( $options['prettify'] );
$css .= $options['prettify'] ? "\n" : '';
}
Expand Down
4 changes: 2 additions & 2 deletions packages/style-engine/class-wp-style-engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,11 @@ protected static function is_valid_style_value( $style_value ) {
*
* @return void.
*/
public static function store_css_rule( $store_name, $css_selector, $css_declarations ) {
public static function store_css_rule( $store_name, $css_selector, $css_declarations, $css_at_rule = '' ) {
if ( empty( $store_name ) || empty( $css_selector ) || empty( $css_declarations ) ) {
return;
}
static::get_store( $store_name )->add_rule( $css_selector )->add_declarations( $css_declarations );
static::get_store( $store_name )->add_rule( $css_selector, $css_at_rule )->add_declarations( $css_declarations );
}

/**
Expand Down
7 changes: 5 additions & 2 deletions packages/style-engine/style-engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function wp_style_engine_get_styles( $block_styles, $options = array() ) {
* Required. A collection of CSS rules.
*
* @type array ...$0 {
* @type string $at_rule A CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
* @type string $selector A CSS selector.
* @type string[] $declarations An associative array of CSS definitions, e.g., array( "$property" => "$value", "$property" => "$value" ).
* }
Expand Down Expand Up @@ -116,11 +117,13 @@ function wp_style_engine_get_stylesheet_from_css_rules( $css_rules, $options = a
continue;
}

$at_rule = ! empty( $css_rule['at_rule'] ) ? $css_rule['at_rule'] : '';

if ( ! empty( $options['context'] ) ) {
WP_Style_Engine::store_css_rule( $options['context'], $css_rule['selector'], $css_rule['declarations'] );
WP_Style_Engine::store_css_rule( $options['context'], $css_rule['selector'], $css_rule['declarations'], $at_rule );
}

$css_rule_objects[] = new WP_Style_Engine_CSS_Rule( $css_rule['selector'], $css_rule['declarations'] );
$css_rule_objects[] = new WP_Style_Engine_CSS_Rule( $css_rule['selector'], $css_rule['declarations'], $at_rule );
}

if ( empty( $css_rule_objects ) ) {
Expand Down
81 changes: 81 additions & 0 deletions phpunit/style-engine/class-wp-style-engine-processor-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,41 @@ public function test_should_return_rules_as_compiled_css() {
);
}

/**
* Tests adding nested rules with at-rules and returning compiled CSS rules.
*
* @covers ::add_rules
* @covers ::get_css
*/
public function test_should_return_nested_rules_as_compiled_css() {
$a_nice_css_rule = new WP_Style_Engine_CSS_Rule_Gutenberg( '.a-nice-rule' );
$a_nice_css_rule->add_declarations(
array(
'color' => 'var(--nice-color)',
'background-color' => 'purple',
)
);
$a_nice_css_rule->set_at_rule( '@media (min-width: 80rem)' );

$a_nicer_css_rule = new WP_Style_Engine_CSS_Rule_Gutenberg( '.a-nicer-rule' );
$a_nicer_css_rule->add_declarations(
array(
'font-family' => 'Nice sans',
'font-size' => '1em',
'background-color' => 'purple',
)
);
$a_nicer_css_rule->set_at_rule( '@layer nicety' );

$a_nice_processor = new WP_Style_Engine_Processor_Gutenberg();
$a_nice_processor->add_rules( array( $a_nice_css_rule, $a_nicer_css_rule ) );

$this->assertSame(
'@media (min-width: 80rem){.a-nice-rule{color:var(--nice-color);background-color:purple;}}@layer nicety{.a-nicer-rule{font-family:Nice sans;font-size:1em;background-color:purple;}}',
$a_nice_processor->get_css( array( 'prettify' => false ) )
);
}

/**
* Tests compiling CSS rules and formatting them with new lines and indents.
*
Expand Down Expand Up @@ -95,6 +130,52 @@ public function test_should_return_prettified_css_rules() {
);
}

/**
* Tests compiling nested CSS rules and formatting them with new lines and indents.
*
* @covers ::get_css
*/
public function test_should_return_prettified_nested_css_rules() {
$a_wonderful_css_rule = new WP_Style_Engine_CSS_Rule_Gutenberg( '.a-wonderful-rule' );
$a_wonderful_css_rule->add_declarations(
array(
'color' => 'var(--wonderful-color)',
'background-color' => 'orange',
)
);
$a_wonderful_css_rule->set_at_rule( '@media (min-width: 80rem)' );

$a_very_wonderful_css_rule = new WP_Style_Engine_CSS_Rule_Gutenberg( '.a-very_wonderful-rule' );
$a_very_wonderful_css_rule->add_declarations(
array(
'color' => 'var(--wonderful-color)',
'background-color' => 'orange',
)
);
$a_very_wonderful_css_rule->set_at_rule( '@layer wonderfulness' );

$a_wonderful_processor = new WP_Style_Engine_Processor_Gutenberg();
$a_wonderful_processor->add_rules( array( $a_wonderful_css_rule, $a_very_wonderful_css_rule ) );

$expected = '@media (min-width: 80rem) {
.a-wonderful-rule {
color: var(--wonderful-color);
background-color: orange;
}
}
@layer wonderfulness {
.a-very_wonderful-rule {
color: var(--wonderful-color);
background-color: orange;
}
}
';
$this->assertSame(
$expected,
$a_wonderful_processor->get_css( array( 'prettify' => true ) )
);
}

/**
* Tests adding a store and compiling CSS rules from that store.
*
Expand Down

0 comments on commit 0ba8692

Please sign in to comment.