Skip to content

Commit 5070468

Browse files
tellthemachinesMaggieCabrera
andauthored
Fix pseudo selector block style rendering in the editor (#76879)
Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org> Co-authored-by: MaggieCabrera <onemaggie@git.wordpress.org>
1 parent ad255e8 commit 5070468

2 files changed

Lines changed: 235 additions & 46 deletions

File tree

packages/global-styles-engine/src/core/render.tsx

Lines changed: 133 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
156156
typography: 'typography',
157157
};
158158

159+
// The valid pseudo-selectors that can be used for blocks.
160+
// Keep in sync with WP_Theme_JSON_Gutenberg::VALID_BLOCK_PSEUDO_SELECTORS.
161+
const VALID_BLOCK_PSEUDO_SELECTORS: Record< string, string[] > = {
162+
'core/button': [ ':hover', ':focus', ':focus-visible', ':active' ],
163+
'core/navigation-link': [ ':hover', ':focus', ':focus-visible', ':active' ],
164+
};
165+
159166
/**
160167
* Transform given preset tree into a set of preset class declarations.
161168
*
@@ -854,12 +861,23 @@ const STYLE_KEYS = [
854861
];
855862

856863
function pickStyleKeys( treeToPickFrom: any ): any {
864+
return pickStyleAndPseudoKeys( treeToPickFrom );
865+
}
866+
867+
function pickStyleAndPseudoKeys(
868+
treeToPickFrom: any,
869+
blockName?: string
870+
): any {
857871
if ( ! treeToPickFrom ) {
858872
return {};
859873
}
860874
const entries = Object.entries( treeToPickFrom );
861-
const pickedEntries = entries.filter( ( [ key ] ) =>
862-
STYLE_KEYS.includes( key )
875+
const allowedPseudoSelectors = blockName
876+
? VALID_BLOCK_PSEUDO_SELECTORS[ blockName ] ?? []
877+
: [];
878+
const pickedEntries = entries.filter(
879+
( [ key ] ) =>
880+
STYLE_KEYS.includes( key ) || allowedPseudoSelectors.includes( key )
863881
);
864882
// clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it
865883
const clonedEntries = pickedEntries.map( ( [ key, style ] ) => [
@@ -869,6 +887,92 @@ function pickStyleKeys( treeToPickFrom: any ): any {
869887
return Object.fromEntries( clonedEntries );
870888
}
871889

890+
function appendPseudoSelectorStyles(
891+
styles: Record< string, any >,
892+
selector: string,
893+
ruleset: string,
894+
featureSelectors:
895+
| string
896+
| Record< string, string | Record< string, string > >
897+
| undefined,
898+
treeSettings: Record< string, any > | undefined,
899+
blockName: string | undefined,
900+
styleVariationSelector?: string
901+
): string {
902+
const pseudoSelectorStyles = Object.entries( styles ).filter( ( [ key ] ) =>
903+
key.startsWith( ':' )
904+
);
905+
906+
if ( ! pseudoSelectorStyles.length ) {
907+
return ruleset;
908+
}
909+
910+
pseudoSelectorStyles.forEach( ( [ pseudoKey, pseudoStyle ] ) => {
911+
if ( ! pseudoStyle || typeof pseudoStyle !== 'object' ) {
912+
return;
913+
}
914+
915+
const remainingPseudoStyles = JSON.parse(
916+
JSON.stringify( pseudoStyle )
917+
);
918+
919+
if ( featureSelectors && typeof featureSelectors !== 'string' ) {
920+
let pseudoFeatureDeclarations = getFeatureDeclarations(
921+
featureSelectors,
922+
remainingPseudoStyles
923+
);
924+
925+
pseudoFeatureDeclarations = updateParagraphTextIndentSelector(
926+
pseudoFeatureDeclarations,
927+
treeSettings,
928+
blockName
929+
);
930+
931+
pseudoFeatureDeclarations = updateButtonWidthDeclarations(
932+
pseudoFeatureDeclarations,
933+
treeSettings
934+
);
935+
936+
Object.entries( pseudoFeatureDeclarations ).forEach(
937+
( [ baseSelector, declarations ] ) => {
938+
if ( ! declarations.length ) {
939+
return;
940+
}
941+
const pseudoFeatureSelector = appendToSelector(
942+
baseSelector,
943+
pseudoKey
944+
);
945+
const cssSelector = styleVariationSelector
946+
? concatFeatureVariationSelectorString(
947+
pseudoFeatureSelector,
948+
styleVariationSelector
949+
)
950+
: pseudoFeatureSelector;
951+
const rules = declarations.join( ';' );
952+
ruleset += `:root :where(${ cssSelector }){${ rules };}`;
953+
}
954+
);
955+
}
956+
957+
const pseudoDeclarations = getStylesDeclarations(
958+
remainingPseudoStyles
959+
);
960+
961+
if ( ! pseudoDeclarations.length ) {
962+
return;
963+
}
964+
965+
const pseudoSelector = appendToSelector( selector, pseudoKey );
966+
const pseudoRule = `:root :where(${ pseudoSelector }){${ pseudoDeclarations.join(
967+
';'
968+
) };}`;
969+
970+
ruleset += pseudoRule;
971+
} );
972+
973+
return ruleset;
974+
}
975+
872976
export const getNodesWithStyles = (
873977
tree: GlobalStylesConfig,
874978
blockSelectors: string | BlockSelectors
@@ -920,7 +1024,7 @@ export const getNodesWithStyles = (
9201024
// Iterate over blocks: they can have styles & elements.
9211025
Object.entries( tree.styles?.blocks ?? {} ).forEach(
9221026
( [ blockName, node ] ) => {
923-
const blockStyles = pickStyleKeys( node );
1027+
const blockStyles = pickStyleAndPseudoKeys( node, blockName );
9241028
const typedNode = node as BlockNode;
9251029

9261030
// Store variation data for later processing, but don't add to nodes yet.
@@ -932,8 +1036,10 @@ export const getNodesWithStyles = (
9321036
Object.entries( typedNode.variations ).forEach(
9331037
( [ variationName, variation ] ) => {
9341038
const typedVariation = variation as BlockVariation;
935-
variations[ variationName ] =
936-
pickStyleKeys( typedVariation );
1039+
variations[ variationName ] = pickStyleAndPseudoKeys(
1040+
typedVariation,
1041+
blockName
1042+
);
9371043
if ( typedVariation?.css ) {
9381044
variations[ variationName ].css =
9391045
typedVariation.css;
@@ -1002,7 +1108,10 @@ export const getNodesWithStyles = (
10021108
: undefined;
10031109

10041110
const variationBlockStyleNodes =
1005-
pickStyleKeys( variationBlockStyles );
1111+
pickStyleAndPseudoKeys(
1112+
variationBlockStyles,
1113+
variationBlockName
1114+
);
10061115

10071116
if ( variationBlockStyles?.css ) {
10081117
variationBlockStyleNodes.css =
@@ -1562,6 +1671,17 @@ export const transformToStyles = (
15621671
`:root :where(${ styleVariationSelector })`
15631672
);
15641673
}
1674+
1675+
ruleset = appendPseudoSelectorStyles(
1676+
styleVariations,
1677+
styleVariationSelector as string,
1678+
ruleset,
1679+
featureSelectors,
1680+
tree.settings,
1681+
name,
1682+
styleVariationSelector as string
1683+
);
1684+
15651685
// Generate layout styles for the variation if it supports layout and has blockGap defined.
15661686
if (
15671687
hasLayoutSupport &&
@@ -1583,45 +1703,14 @@ export const transformToStyles = (
15831703
);
15841704
}
15851705

1586-
// Check for pseudo selector in `styles` and handle separately.
1587-
const pseudoSelectorStyles = Object.entries( styles ).filter(
1588-
( [ key ] ) => key.startsWith( ':' )
1706+
ruleset = appendPseudoSelectorStyles(
1707+
styles,
1708+
selector,
1709+
ruleset,
1710+
featureSelectors,
1711+
tree.settings,
1712+
name
15891713
);
1590-
1591-
if ( pseudoSelectorStyles?.length ) {
1592-
pseudoSelectorStyles.forEach(
1593-
( [ pseudoKey, pseudoStyle ] ) => {
1594-
const pseudoDeclarations =
1595-
getStylesDeclarations( pseudoStyle );
1596-
1597-
if ( ! pseudoDeclarations?.length ) {
1598-
return;
1599-
}
1600-
1601-
// `selector` may be provided in a form
1602-
// where block level selectors have sub element
1603-
// selectors appended to them as a comma separated
1604-
// string.
1605-
// e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`;
1606-
// Split and append pseudo selector to create
1607-
// the proper rules to target the elements.
1608-
const _selector = selector
1609-
.split( ',' )
1610-
.map( ( sel: string ) => sel + pseudoKey )
1611-
.join( ',' );
1612-
1613-
// As pseudo classes such as :hover, :focus etc. have class-level
1614-
// specificity, they must use the `:root :where()` wrapper. This.
1615-
// caps the specificity at `0-1-0` to allow proper nesting of variations
1616-
// and block type element styles.
1617-
const pseudoRule = `:root :where(${ _selector }){${ pseudoDeclarations.join(
1618-
';'
1619-
) };}`;
1620-
1621-
ruleset += pseudoRule;
1622-
}
1623-
);
1624-
}
16251714
}
16261715
);
16271716
}

packages/global-styles-engine/src/test/render.test.ts

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,106 @@ describe( 'global styles renderer', () => {
652652
);
653653
} );
654654

655+
it( 'should handle block pseudo selectors', () => {
656+
const tree = {
657+
styles: {
658+
blocks: {
659+
'core/button': {
660+
color: {
661+
text: 'red',
662+
},
663+
':hover': {
664+
color: {
665+
text: 'blue',
666+
},
667+
},
668+
},
669+
},
670+
},
671+
} as unknown as GlobalStylesConfig;
672+
673+
const blockSelectors = {
674+
'core/button': {
675+
selector: '.wp-block-button',
676+
},
677+
};
678+
679+
const result = transformToStyles(
680+
Object.freeze( tree ),
681+
blockSelectors,
682+
false,
683+
false,
684+
true,
685+
true,
686+
{
687+
blockGap: false,
688+
blockStyles: true,
689+
layoutStyles: false,
690+
marginReset: false,
691+
presets: false,
692+
rootPadding: false,
693+
}
694+
);
695+
696+
expect( result ).toEqual(
697+
':root :where(.wp-block-button){color: red;}:root :where(.wp-block-button:hover){color: blue;}'
698+
);
699+
} );
700+
701+
it( 'should handle style variation pseudo selectors', () => {
702+
const tree = {
703+
styles: {
704+
blocks: {
705+
'core/button': {
706+
variations: {
707+
foo: {
708+
color: {
709+
text: 'green',
710+
},
711+
':hover': {
712+
color: {
713+
text: 'yellow',
714+
},
715+
},
716+
},
717+
},
718+
},
719+
},
720+
},
721+
} as unknown as GlobalStylesConfig;
722+
723+
const blockSelectors = {
724+
'core/button': {
725+
selector: '.wp-block-button',
726+
styleVariationSelectors: {
727+
foo: '.is-style-foo.wp-block-button',
728+
},
729+
},
730+
};
731+
732+
const result = transformToStyles(
733+
Object.freeze( tree ),
734+
blockSelectors,
735+
false,
736+
false,
737+
true,
738+
true,
739+
{
740+
blockGap: false,
741+
blockStyles: true,
742+
layoutStyles: false,
743+
marginReset: false,
744+
presets: false,
745+
rootPadding: false,
746+
variationStyles: true,
747+
}
748+
);
749+
750+
expect( result ).toEqual(
751+
':root :where(.is-style-foo.wp-block-button){color: green;}:root :where(.is-style-foo.wp-block-button:hover){color: yellow;}'
752+
);
753+
} );
754+
655755
it( 'should handle duotone filter', () => {
656756
const tree = {
657757
styles: {
@@ -991,7 +1091,7 @@ describe( 'global styles renderer', () => {
9911091
} );
9921092

9931093
it( 'should convert preset percentage width to calc() formula', () => {
994-
const tree: GlobalStylesConfig = {
1094+
const tree = {
9951095
settings: {
9961096
blocks: {
9971097
'core/button': {
@@ -1018,7 +1118,7 @@ describe( 'global styles renderer', () => {
10181118
},
10191119
},
10201120
},
1021-
};
1121+
} as unknown as GlobalStylesConfig;
10221122

10231123
const blockSelectors = {
10241124
'core/button': {

0 commit comments

Comments
 (0)