@@ -159,6 +159,13 @@ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
159159 typography : 'typography' ,
160160} ;
161161
162+ // The valid pseudo-selectors that can be used for blocks.
163+ // Keep in sync with WP_Theme_JSON_Gutenberg::VALID_BLOCK_PSEUDO_SELECTORS.
164+ const VALID_BLOCK_PSEUDO_SELECTORS : Record < string , string [ ] > = {
165+ 'core/button' : [ ':hover' , ':focus' , ':focus-visible' , ':active' ] ,
166+ 'core/navigation-link' : [ ':hover' , ':focus' , ':focus-visible' , ':active' ] ,
167+ } ;
168+
162169/**
163170 * Transform given preset tree into a set of preset class declarations.
164171 *
@@ -857,12 +864,23 @@ const STYLE_KEYS = [
857864] ;
858865
859866function pickStyleKeys ( treeToPickFrom : any ) : any {
867+ return pickStyleAndPseudoKeys ( treeToPickFrom ) ;
868+ }
869+
870+ function pickStyleAndPseudoKeys (
871+ treeToPickFrom : any ,
872+ blockName ?: string
873+ ) : any {
860874 if ( ! treeToPickFrom ) {
861875 return { } ;
862876 }
863877 const entries = Object . entries ( treeToPickFrom ) ;
864- const pickedEntries = entries . filter ( ( [ key ] ) =>
865- STYLE_KEYS . includes ( key )
878+ const allowedPseudoSelectors = blockName
879+ ? VALID_BLOCK_PSEUDO_SELECTORS [ blockName ] ?? [ ]
880+ : [ ] ;
881+ const pickedEntries = entries . filter (
882+ ( [ key ] ) =>
883+ STYLE_KEYS . includes ( key ) || allowedPseudoSelectors . includes ( key )
866884 ) ;
867885 // clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it
868886 const clonedEntries = pickedEntries . map ( ( [ key , style ] ) => [
@@ -872,6 +890,92 @@ function pickStyleKeys( treeToPickFrom: any ): any {
872890 return Object . fromEntries ( clonedEntries ) ;
873891}
874892
893+ function appendPseudoSelectorStyles (
894+ styles : Record < string , any > ,
895+ selector : string ,
896+ ruleset : string ,
897+ featureSelectors :
898+ | string
899+ | Record < string , string | Record < string , string > >
900+ | undefined ,
901+ treeSettings : Record < string , any > | undefined ,
902+ blockName : string | undefined ,
903+ styleVariationSelector ?: string
904+ ) : string {
905+ const pseudoSelectorStyles = Object . entries ( styles ) . filter ( ( [ key ] ) =>
906+ key . startsWith ( ':' )
907+ ) ;
908+
909+ if ( ! pseudoSelectorStyles . length ) {
910+ return ruleset ;
911+ }
912+
913+ pseudoSelectorStyles . forEach ( ( [ pseudoKey , pseudoStyle ] ) => {
914+ if ( ! pseudoStyle || typeof pseudoStyle !== 'object' ) {
915+ return ;
916+ }
917+
918+ const remainingPseudoStyles = JSON . parse (
919+ JSON . stringify ( pseudoStyle )
920+ ) ;
921+
922+ if ( featureSelectors && typeof featureSelectors !== 'string' ) {
923+ let pseudoFeatureDeclarations = getFeatureDeclarations (
924+ featureSelectors ,
925+ remainingPseudoStyles
926+ ) ;
927+
928+ pseudoFeatureDeclarations = updateParagraphTextIndentSelector (
929+ pseudoFeatureDeclarations ,
930+ treeSettings ,
931+ blockName
932+ ) ;
933+
934+ pseudoFeatureDeclarations = updateButtonWidthDeclarations (
935+ pseudoFeatureDeclarations ,
936+ treeSettings
937+ ) ;
938+
939+ Object . entries ( pseudoFeatureDeclarations ) . forEach (
940+ ( [ baseSelector , declarations ] ) => {
941+ if ( ! declarations . length ) {
942+ return ;
943+ }
944+ const pseudoFeatureSelector = appendToSelector (
945+ baseSelector ,
946+ pseudoKey
947+ ) ;
948+ const cssSelector = styleVariationSelector
949+ ? concatFeatureVariationSelectorString (
950+ pseudoFeatureSelector ,
951+ styleVariationSelector
952+ )
953+ : pseudoFeatureSelector ;
954+ const rules = declarations . join ( ';' ) ;
955+ ruleset += `:root :where(${ cssSelector } ){${ rules } ;}` ;
956+ }
957+ ) ;
958+ }
959+
960+ const pseudoDeclarations = getStylesDeclarations (
961+ remainingPseudoStyles
962+ ) ;
963+
964+ if ( ! pseudoDeclarations . length ) {
965+ return ;
966+ }
967+
968+ const pseudoSelector = appendToSelector ( selector , pseudoKey ) ;
969+ const pseudoRule = `:root :where(${ pseudoSelector } ){${ pseudoDeclarations . join (
970+ ';'
971+ ) } ;}`;
972+
973+ ruleset += pseudoRule ;
974+ } ) ;
975+
976+ return ruleset ;
977+ }
978+
875979export const getNodesWithStyles = (
876980 tree : GlobalStylesConfig ,
877981 blockSelectors : string | BlockSelectors
@@ -923,7 +1027,7 @@ export const getNodesWithStyles = (
9231027 // Iterate over blocks: they can have styles & elements.
9241028 Object . entries ( tree . styles ?. blocks ?? { } ) . forEach (
9251029 ( [ blockName , node ] ) => {
926- const blockStyles = pickStyleKeys ( node ) ;
1030+ const blockStyles = pickStyleAndPseudoKeys ( node , blockName ) ;
9271031 const typedNode = node as BlockNode ;
9281032
9291033 // Store variation data for later processing, but don't add to nodes yet.
@@ -935,8 +1039,10 @@ export const getNodesWithStyles = (
9351039 Object . entries ( typedNode . variations ) . forEach (
9361040 ( [ variationName , variation ] ) => {
9371041 const typedVariation = variation as BlockVariation ;
938- variations [ variationName ] =
939- pickStyleKeys ( typedVariation ) ;
1042+ variations [ variationName ] = pickStyleAndPseudoKeys (
1043+ typedVariation ,
1044+ blockName
1045+ ) ;
9401046 if ( typedVariation ?. css ) {
9411047 variations [ variationName ] . css =
9421048 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 =
@@ -1558,6 +1667,17 @@ export const transformToStyles = (
15581667 `:root :where(${ styleVariationSelector } )`
15591668 ) ;
15601669 }
1670+
1671+ ruleset = appendPseudoSelectorStyles (
1672+ styleVariations ,
1673+ styleVariationSelector as string ,
1674+ ruleset ,
1675+ featureSelectors ,
1676+ tree . settings ,
1677+ name ,
1678+ styleVariationSelector as string
1679+ ) ;
1680+
15611681 // Generate layout styles for the variation if it supports layout and has blockGap defined.
15621682 if (
15631683 hasLayoutSupport &&
@@ -1579,45 +1699,14 @@ export const transformToStyles = (
15791699 ) ;
15801700 }
15811701
1582- // Check for pseudo selector in `styles` and handle separately.
1583- const pseudoSelectorStyles = Object . entries ( styles ) . filter (
1584- ( [ key ] ) => key . startsWith ( ':' )
1702+ ruleset = appendPseudoSelectorStyles (
1703+ styles ,
1704+ selector ,
1705+ ruleset ,
1706+ featureSelectors ,
1707+ tree . settings ,
1708+ name
15851709 ) ;
1586-
1587- if ( pseudoSelectorStyles ?. length ) {
1588- pseudoSelectorStyles . forEach (
1589- ( [ pseudoKey , pseudoStyle ] ) => {
1590- const pseudoDeclarations =
1591- getStylesDeclarations ( pseudoStyle ) ;
1592-
1593- if ( ! pseudoDeclarations ?. length ) {
1594- return ;
1595- }
1596-
1597- // `selector` may be provided in a form
1598- // where block level selectors have sub element
1599- // selectors appended to them as a comma separated
1600- // string.
1601- // e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`;
1602- // Split and append pseudo selector to create
1603- // the proper rules to target the elements.
1604- const _selector = selector
1605- . split ( ',' )
1606- . map ( ( sel : string ) => sel + pseudoKey )
1607- . join ( ',' ) ;
1608-
1609- // As pseudo classes such as :hover, :focus etc. have class-level
1610- // specificity, they must use the `:root :where()` wrapper. This.
1611- // caps the specificity at `0-1-0` to allow proper nesting of variations
1612- // and block type element styles.
1613- const pseudoRule = `:root :where(${ _selector } ){${ pseudoDeclarations . join (
1614- ';'
1615- ) } ;}`;
1616-
1617- ruleset += pseudoRule ;
1618- }
1619- ) ;
1620- }
16211710 }
16221711 ) ;
16231712 }
0 commit comments