Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add style engine support for nested at-rules. #58867

Merged
merged 2 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 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 );
$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 );
$nested_css_declarations = $this->declarations->get_declarations_string( $should_prettify, $nested_declarations_indent );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit, and doesn't affect the API changes here, so not a blocker: Why is get_declarations_string called twice? Is there ever a time we'd need to have access to both $css_declarations and $nested_css_declarations?

Just wondering if something like the following could avoid the extra call:

$at_rule                 = $this->get_at_rule();
$has_at_rule             = ! empty( $at_rule );

$nested_css_declarations = $this->declarations->get_declarations_string(
    $should_prettify,
    $has_at_rule ? $nested_declarations_indent : $declarations_indent
);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh yeah that's a good idea! There's no need to access both at the same time.

$at_rule = $this->get_at_rule();

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

if ( ! empty( $at_rule ) ) {
$selector = "{$rule_indent}{$at_rule}{$spacer}{{$suffix}{$nested_rule_indent}{$selector}{$spacer}{{$suffix}{$nested_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
Loading