Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dfaccb8
Fix canvas issues by removing VisualEditor’s height (#63724)
stokesman Jul 22, 2024
a5b0b61
Post Editor: Prevent popover from being hidden by metabox (#63939)
t-hamano Jul 27, 2024
5d72f9e
Global Styles: Fix block custom CSS pseudo element selectors (#63980)
aaronrobertshaw Jul 31, 2024
4f335ff
Avoid errors for post types without a 'menu_icon' (#64015)
Mamaduka Aug 1, 2024
87d361d
Data Views: Don't render action modal when there are no eligible item…
Mamaduka Aug 7, 2024
ba09a63
Post editor: apply space below content using a pseudo-element instead…
talldan Aug 22, 2024
0b33f8a
Featured Image Block: Reduce CSS specificity (#64463)
dsas Aug 23, 2024
9b9bbe8
Inserter: use lighter grammar parse to check allowed status (#64902)
ellatrix Aug 30, 2024
8bf8d2a
Prepare JSON schemas for Draft 7 update (#63582)
ajlende Jul 16, 2024
1b15571
Fix missing ref support for textAlign and textColumns in theme.json s…
ajlende Jul 23, 2024
583c332
Don't allow duplicating template parts in non-block-based themes (#64…
vcanales Sep 4, 2024
3b540b9
Fix bumped specificity for layout styles in non-iframed editor (#64076)
talldan Aug 13, 2024
53a370e
Update postcss-prefixwrap dependency to 1.51.0 to fix prefixing in `:…
talldan Aug 13, 2024
af472e8
Revert "Update postcss-prefixwrap dependency to 1.51.0 to fix prefixi…
vcanales Sep 4, 2024
8c9753b
Update postcss-prefixwrap dependency to 1.51.0 to fix prefixing in `:…
talldan Aug 13, 2024
2feb578
Post Editor: fix click space after post content to append (#64992)
stokesman Sep 4, 2024
4e5e520
re-build package lock to match format
vcanales Sep 4, 2024
7dcb38c
Revert "Inserter: use lighter grammar parse to check allowed status (…
vcanales Sep 4, 2024
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
Prev Previous commit
Next Next commit
Global Styles: Fix block custom CSS pseudo element selectors (#63980)
Unlinked contributors: harlet.

Co-authored-by: aaronrobertshaw <[email protected]>
Co-authored-by: andrewserong <[email protected]>
Co-authored-by: ramonjd <[email protected]>
Co-authored-by: dballari <[email protected]>
Co-authored-by: wongjn <[email protected]>
  • Loading branch information
6 people authored and vcanales committed Sep 4, 2024
commit 5d72f9e55a3c3db769fd8b55990305fa82328c61
3 changes: 3 additions & 0 deletions backport-changelog/6.6/7097.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/7097

* https://github.com/WordPress/gutenberg/pull/63980
26 changes: 23 additions & 3 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -1372,9 +1372,16 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets'
protected function process_blocks_custom_css( $css, $selector ) {
$processed_css = '';

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

// Split CSS nested rules.
$parts = explode( '&', $css );
foreach ( $parts as $part ) {
if ( empty( $part ) ) {
continue;
}
$is_root_css = ( ! str_contains( $part, '{' ) );
if ( $is_root_css ) {
// If the part doesn't contain braces, it applies to the root level.
Expand All @@ -1387,11 +1394,24 @@ protected function process_blocks_custom_css( $css, $selector ) {
}
$nested_selector = $part[0];
$css_value = $part[1];
$part_selector = str_starts_with( $nested_selector, ' ' )

/*
* Handle pseudo elements such as ::before, ::after etc. Regex will also
* capture any leading combinator such as >, +, or ~, as well as spaces.
* This allows pseudo elements as descendants e.g. `.parent ::before`.
*/
$matches = array();
$has_pseudo_element = preg_match( '/([>+~\s]*::[a-zA-Z-]+)/', $nested_selector, $matches );
$pseudo_part = $has_pseudo_element ? $matches[1] : '';
$nested_selector = $has_pseudo_element ? str_replace( $pseudo_part, '', $nested_selector ) : $nested_selector;

// Finalize selector and re-append pseudo element if required.
$part_selector = str_starts_with( $nested_selector, ' ' )
? static::scope_selector( $selector, $nested_selector )
: static::append_to_selector( $selector, $nested_selector );
$final_selector = ":root :where($part_selector)";
$processed_css .= $final_selector . '{' . trim( $css_value ) . '}';
$final_selector = ":root :where($part_selector)$pseudo_part";

$processed_css .= $final_selector . '{' . trim( $css_value ) . '}';
}
}
return $processed_css;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1031,11 +1031,19 @@ describe( 'global styles renderer', () => {
} );

describe( 'processCSSNesting', () => {
it( 'should return empty string when supplied css is empty', () => {
expect( processCSSNesting( '', '.foo' ) ).toEqual( '' );
} );
it( 'should return processed CSS without any nested selectors', () => {
expect(
processCSSNesting( 'color: red; margin: auto;', '.foo' )
).toEqual( ':root :where(.foo){color: red; margin: auto;}' );
} );
it( 'should return processed CSS when there are no root selectors', () => {
expect(
processCSSNesting( '&::before{color: red;}', '.foo' )
).toEqual( ':root :where(.foo)::before{color: red;}' );
} );
it( 'should return processed CSS with nested selectors', () => {
expect(
processCSSNesting(
Expand All @@ -1049,21 +1057,21 @@ describe( 'global styles renderer', () => {
it( 'should return processed CSS with pseudo elements', () => {
expect(
processCSSNesting(
'color: red; margin: auto; &::before{color: blue;} & ::before{color: green;} &.one::before{color: yellow;} & .two::before{color: purple;}',
'color: red; margin: auto; &::before{color: blue;} & ::before{color: green;} &.one::before{color: yellow;} & .two::before{color: purple;} & > ::before{color: darkseagreen;}',
'.foo'
)
).toEqual(
':root :where(.foo){color: red; margin: auto;}:root :where(.foo::before){color: blue;}:root :where(.foo ::before){color: green;}:root :where(.foo.one::before){color: yellow;}:root :where(.foo .two::before){color: purple;}'
':root :where(.foo){color: red; margin: auto;}:root :where(.foo)::before{color: blue;}:root :where(.foo) ::before{color: green;}:root :where(.foo.one)::before{color: yellow;}:root :where(.foo .two)::before{color: purple;}:root :where(.foo) > ::before{color: darkseagreen;}'
);
} );
it( 'should return processed CSS with multiple root selectors', () => {
expect(
processCSSNesting(
'color: red; margin: auto; &.one{color: blue;} & .two{color: green;} &::before{color: yellow;} & ::before{color: purple;} &.three::before{color: orange;} & .four::before{color: skyblue;}',
'color: red; margin: auto; &.one{color: blue;} & .two{color: green;} &::before{color: yellow;} & ::before{color: purple;} &.three::before{color: orange;} & .four::before{color: skyblue;} & > ::before{color: darkseagreen;}',
'.foo, .bar'
)
).toEqual(
':root :where(.foo, .bar){color: red; margin: auto;}:root :where(.foo.one, .bar.one){color: blue;}:root :where(.foo .two, .bar .two){color: green;}:root :where(.foo::before, .bar::before){color: yellow;}:root :where(.foo ::before, .bar ::before){color: purple;}:root :where(.foo.three::before, .bar.three::before){color: orange;}:root :where(.foo .four::before, .bar .four::before){color: skyblue;}'
':root :where(.foo, .bar){color: red; margin: auto;}:root :where(.foo.one, .bar.one){color: blue;}:root :where(.foo .two, .bar .two){color: green;}:root :where(.foo, .bar)::before{color: yellow;}:root :where(.foo, .bar) ::before{color: purple;}:root :where(.foo.three, .bar.three)::before{color: orange;}:root :where(.foo .four, .bar .four)::before{color: skyblue;}:root :where(.foo, .bar) > ::before{color: darkseagreen;}'
);
} );
} );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1312,9 +1312,17 @@ function updateConfigWithSeparator( config ) {
export function processCSSNesting( css, blockSelector ) {
let processedCSS = '';

if ( ! css || css.trim() === '' ) {
return processedCSS;
}

// Split CSS nested rules.
const parts = css.split( '&' );
parts.forEach( ( part ) => {
if ( ! part || part.trim() === '' ) {
return;
}

const isRootCss = ! part.includes( '{' );
if ( isRootCss ) {
// If the part doesn't contain braces, it applies to the root level.
Expand All @@ -1327,11 +1335,32 @@ export function processCSSNesting( css, blockSelector ) {
}

const [ nestedSelector, cssValue ] = splittedPart;
const combinedSelector = nestedSelector.startsWith( ' ' )
? scopeSelector( blockSelector, nestedSelector )
: appendToSelector( blockSelector, nestedSelector );

processedCSS += `:root :where(${ combinedSelector }){${ cssValue.trim() }}`;
// Handle pseudo elements such as ::before, ::after, etc. Regex will also
// capture any leading combinator such as >, +, or ~, as well as spaces.
// This allows pseudo elements as descendants e.g. `.parent ::before`.
const matches = nestedSelector.match( /([>+~\s]*::[a-zA-Z-]+)/ );
const pseudoPart = matches ? matches[ 1 ] : '';
const withoutPseudoElement = matches
? nestedSelector.replace( pseudoPart, '' ).trim()
: nestedSelector.trim();

let combinedSelector;
if ( withoutPseudoElement === '' ) {
// Only contained a pseudo element to use the block selector to form
// the final `:root :where()` selector.
combinedSelector = blockSelector;
} else {
// If the nested selector is a descendant of the block scope it with the
// block selector. Otherwise append it to the block selector.
combinedSelector = nestedSelector.startsWith( ' ' )
? scopeSelector( blockSelector, withoutPseudoElement )
: appendToSelector( blockSelector, withoutPseudoElement );
}

// Build final rule, re-adding any pseudo element outside the `:where()`
// to maintain valid CSS selector.
processedCSS += `:root :where(${ combinedSelector })${ pseudoPart }{${ cssValue.trim() }}`;
}
} );
return processedCSS;
Expand Down
18 changes: 16 additions & 2 deletions phpunit/class-wp-theme-json-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -4967,13 +4967,27 @@ public function test_process_blocks_custom_css( $input, $expected ) {
public function data_process_blocks_custom_css() {
return array(
// Simple CSS without any nested selectors.
'empty css' => array(
'input' => array(
'selector' => '.foo',
'css' => '',
),
'expected' => '',
),
'no nested selectors' => array(
'input' => array(
'selector' => '.foo',
'css' => 'color: red; margin: auto;',
),
'expected' => ':root :where(.foo){color: red; margin: auto;}',
),
'no root styles' => array(
'input' => array(
'selector' => '.foo',
'css' => '&::before{color: red;}',
),
'expected' => ':root :where(.foo)::before{color: red;}',
),
// CSS with nested selectors.
'with nested selector' => array(
'input' => array(
Expand All @@ -4988,15 +5002,15 @@ public function data_process_blocks_custom_css() {
'selector' => '.foo',
'css' => 'color: red; margin: auto; &::before{color: blue;} & ::before{color: green;} &.one::before{color: yellow;} & .two::before{color: purple;}',
),
'expected' => ':root :where(.foo){color: red; margin: auto;}:root :where(.foo::before){color: blue;}:root :where(.foo ::before){color: green;}:root :where(.foo.one::before){color: yellow;}:root :where(.foo .two::before){color: purple;}',
'expected' => ':root :where(.foo){color: red; margin: auto;}:root :where(.foo)::before{color: blue;}:root :where(.foo) ::before{color: green;}:root :where(.foo.one)::before{color: yellow;}:root :where(.foo .two)::before{color: purple;}',
),
// CSS with multiple root selectors.
'with multiple root selectors' => array(
'input' => array(
'selector' => '.foo, .bar',
'css' => 'color: red; margin: auto; &.one{color: blue;} & .two{color: green;} &::before{color: yellow;} & ::before{color: purple;} &.three::before{color: orange;} & .four::before{color: skyblue;}',
),
'expected' => ':root :where(.foo, .bar){color: red; margin: auto;}:root :where(.foo.one, .bar.one){color: blue;}:root :where(.foo .two, .bar .two){color: green;}:root :where(.foo::before, .bar::before){color: yellow;}:root :where(.foo ::before, .bar ::before){color: purple;}:root :where(.foo.three::before, .bar.three::before){color: orange;}:root :where(.foo .four::before, .bar .four::before){color: skyblue;}',
'expected' => ':root :where(.foo, .bar){color: red; margin: auto;}:root :where(.foo.one, .bar.one){color: blue;}:root :where(.foo .two, .bar .two){color: green;}:root :where(.foo, .bar)::before{color: yellow;}:root :where(.foo, .bar) ::before{color: purple;}:root :where(.foo.three, .bar.three)::before{color: orange;}:root :where(.foo .four, .bar .four)::before{color: skyblue;}',
),
);
}
Expand Down