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

Style engine: add support for nested CSS rules to wp_style_engine_get_stylesheet_from_css_rule() #58918

Draft
wants to merge 8 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ function gutenberg_is_experiment_enabled( $name ) {
if ( is_dir( __DIR__ . '/../build/style-engine' ) ) {
require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-declarations-gutenberg.php';
require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rule-gutenberg.php';
require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-group-gutenberg.php';
require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-store-gutenberg.php';
require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-processor-gutenberg.php';
require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-gutenberg.php';
Expand Down
3 changes: 3 additions & 0 deletions packages/style-engine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### Enhancement
- Style engine: add support for nested CSS rules to wp_style_engine_get_stylesheet_from_css_rule() ([#58918](https://github.com/WordPress/gutenberg/pull/58918)).

## 1.34.0 (2024-02-09)

## 1.33.0 (2024-01-24)
Expand Down
25 changes: 23 additions & 2 deletions packages/style-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,31 @@ $styles = array(
'selector' => '.wp-tomato',
'declarations' => array( 'padding' => '100px' )
),
);

$stylesheet = wp_style_engine_get_stylesheet_from_css_rules(
$styles,
array(
'selector' => '.wp-kumquat',
'context' => 'block-supports', // Indicates that these styles should be stored with block supports CSS.
)
);
print_r( $stylesheet ); // .wp-pumpkin{color:orange}.wp-tomato{color:red;padding:100px}
```

It's also possible to build simple, nested CSS rules using the `rules_group` key.

```php
$styles = array(
array(
'rules_group' => '@media (min-width: 80rem)',
'selector' => '.wp-carrot',
'declarations' => array( 'color' => 'orange' )
),
array(
'rules_group' => '@media (min-width: 80rem)',
'selector' => '.wp-tomato',
'declarations' => array( 'color' => 'red' )
),
);

$stylesheet = wp_style_engine_get_stylesheet_from_css_rules(
Expand All @@ -138,7 +159,7 @@ $stylesheet = wp_style_engine_get_stylesheet_from_css_rules(
'context' => 'block-supports', // Indicates that these styles should be stored with block supports CSS.
)
);
print_r( $stylesheet ); // .wp-pumpkin,.wp-kumquat{color:orange}.wp-tomato{color:red;padding:100px}
print_r( $stylesheet ); // @media (min-width: 80rem){.wp-carrot{color:orange}.wp-tomato{color:red;}}
```

### wp_style_engine_get_stylesheet_from_context()
Expand Down
56 changes: 23 additions & 33 deletions packages/style-engine/class-wp-style-engine-css-rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,25 @@ class WP_Style_Engine_CSS_Rule {
protected $declarations;

/**
* The CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
* A parent CSS selector in the case of nested CSS, or a CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`..
Copy link
Member Author

@ramonjd ramonjd Feb 12, 2024

Choose a reason for hiding this comment

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

Most of the changes in this PR are renaming $at_rule to $rules_group

*
* @var string
*/
protected $at_rule;

protected $rules_group;

/**
* 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`.
* @param string $rules_group A parent CSS selector in the case of nested CSS, or a CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
*
*/
public function __construct( $selector = '', $declarations = array(), $at_rule = '' ) {
public function __construct( $selector = '', $declarations = array(), $rules_group = '' ) {
$this->set_selector( $selector );
$this->add_declarations( $declarations );
$this->set_at_rule( $at_rule );
$this->set_rules_group( $rules_group );
}

/**
Expand Down Expand Up @@ -92,17 +91,26 @@ public function add_declarations( $declarations ) {
}

/**
* Sets the at_rule.
* Sets the rules group.
*
* @param string $at_rule A CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
* @param string $rules_group A parent CSS selector in the case of nested CSS, or 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;
public function set_rules_group( $rules_group ) {
$this->rules_group = $rules_group;
return $this;
}

/**
* Gets the rules group.
*
* @return string
*/
public function get_rules_group() {
return $this->rules_group ?? null;
}

/**
* Gets the declarations object.
*
Expand All @@ -121,15 +129,6 @@ 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 @@ -139,28 +138,19 @@ public function get_at_rule() {
* @return string
*/
public function get_css( $should_prettify = false, $indent_count = 0 ) {
$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 ? ' ' : '';
$rule_indent = $should_prettify ? str_repeat( "\t", $indent_count ) : '';
Copy link
Member Author

Choose a reason for hiding this comment

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

Just reverting to what is was before #58867

$declarations_indent = $should_prettify ? $indent_count + 1 : 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;
$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 );
$css_declarations = ! empty( $this->declarations ) ? $this->declarations->get_declarations_string( $should_prettify, $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
153 changes: 153 additions & 0 deletions packages/style-engine/class-wp-style-engine-css-rules-group.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php
/**
* WP_Style_Engine_CSS_Rules_Group
*
* A container for WP_Style_Engine_CSS_Rule objects.
*
* @package Gutenberg
*/

if ( ! class_exists( 'WP_Style_Engine_CSS_Rules_Group' ) ) {
/**
* Holds, sanitizes, processes and prints nested CSS rules for the Style Engine.
*
* @access private
*/
class WP_Style_Engine_CSS_Rules_Group {
/**
* The group's CSS selector in the case of nested CSS, or a CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
*
* @var string
*/
protected $rules_group;

/**
* The container declarations.
*
* Contains a WP_Style_Engine_CSS_Rule object.
*
* @var WP_Style_Engine_CSS_Rule[]
*/
protected $rules = array();

/**
* Constructor
*
* @param string $rules_group A parent CSS selector in the case of nested CSS, or a CSS nested @rule,
* such as `@media (min-width: 80rem)` or `@layer module`.
* @param WP_Style_Engine_CSS_Rule[]|WP_Style_Engine_CSS_Rule $rules Optional. A WP_Style_Engine_CSS_Rule object.
*/
public function __construct( $rules_group, $rules = array() ) {
$this->set_rules_group( $rules_group );
$this->add_rules( $rules );
}

/**
* Sets the rules group.
*
* @param string $rules_group The group's CSS selector in the case of nested CSS, or 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_rules_group( $rules_group ) {
$this->rules_group = $rules_group;
return $this;
}

/**
* Gets the rules group.
*
* @return string
*/
public function get_rules_group() {
return $this->rules_group;
}

/**
* Gets all nested rules.
*
* @return WP_Style_Engine_CSS_Rule[]
*/
public function get_rules() {
return $this->rules;
}

/**
* Gets a stored nested rules.
*
* @return WP_Style_Engine_CSS_Rule
*/
public function get_rule( $selector ) {
return $this->rules[ $selector ] ?? null;
}

/**
* Adds the rules.
*
* @param WP_Style_Engine_CSS_Rule|WP_Style_Engine_CSS_Rule[] $container_rules An array of declarations (property => value pairs),
* or a WP_Style_Engine_CSS_Declarations object.
*
* @return WP_Style_Engine_CSS_Rules_Group Returns the object to allow chaining of methods.
*/
public function add_rules( $rules ) {
if ( empty( $rules ) ) {
return $this;
}

if ( ! is_array( $rules ) ) {
$rules = array( $rules );
}

foreach ( $rules as $rule ) {
if ( ! $rule instanceof WP_Style_Engine_CSS_Rule ) {
_doing_it_wrong(
__METHOD__,
__( 'Rules passed to WP_Style_Engine_CSS_Rules_Container must be an instance of WP_Style_Engine_CSS_Rule', 'default' ),
'6.6.0'
);
continue;
}

if ( $this->rules_group !== $rule->get_rules_group() ) {
continue;
}

$selector = $rule->get_selector();

if ( isset( $this->rules[ $selector ] ) ) {
$this->rules[ $selector ]->add_declarations( $rule->get_declarations() );
} else {
$this->rules[ $selector ] = $rule;
}
}

return $this;
}

/**
* Gets the nested CSS.
*
* @param bool $should_prettify Whether to add spacing, new lines and indents.
* @param number $indent_count The number of tab indents to apply to the rule. Applies if `prettify` is `true`.
*
* @return string
*/
public function get_css( $should_prettify = false, $indent_count = 0 ) {
$css = '';
$indent_count = $should_prettify ? $indent_count + 1 : $indent_count;
$new_line = $should_prettify ? "\n" : '';
$spacer = $should_prettify ? ' ' : '';

foreach ( $this->rules as $rule ) {
$css .= $rule->get_css( $should_prettify, $indent_count );
$css .= $should_prettify ? "\n" : '';
}

if ( empty( $css ) ) {
return $css;
}

return "{$this->rules_group}{$spacer}{{$new_line}{$css}}";
}
}
}
18 changes: 9 additions & 9 deletions packages/style-engine/class-wp-style-engine-css-rules-store.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,25 +109,25 @@ public function get_all_rules() {
* Gets a WP_Style_Engine_CSS_Rule object by its selector.
* 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`.
* @param string $selector The CSS selector.
* @param string $rules_group A parent CSS selector in the case of nested CSS, or a 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, $at_rule = '' ) {
$selector = trim( $selector );
$at_rule = trim( $at_rule );
public function add_rule( $selector, $rules_group = '' ) {
$selector = $selector ? trim( $selector ) : '';
$rules_group = $rules_group ? trim( $rules_group ) : '';

// 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 );
if ( ! empty( $rules_group ) ) {
if ( empty( $this->rules[ "$rules_group $selector" ] ) ) {
$this->rules[ "$rules_group $selector" ] = new WP_Style_Engine_CSS_Rule( $selector, array(), $rules_group );
}
return $this->rules[ "$at_rule $selector" ];
return $this->rules[ "$rules_group $selector" ];
}

// Create the rule if it doesn't exist.
Expand Down
Loading
Loading