@@ -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
856863function 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+
872976export 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 }
0 commit comments