@wordpress/global-styles-engine 1.11.0 → 1.11.1-next.v.202604201441.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -156,6 +156,13 @@ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
156
156
  typography: 'typography',
157
157
  };
158
158
 
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
+
159
166
  /**
160
167
  * Transform given preset tree into a set of preset class declarations.
161
168
  *
@@ -854,12 +861,23 @@ const STYLE_KEYS = [
854
861
  ];
855
862
 
856
863
  function pickStyleKeys( treeToPickFrom: any ): any {
864
+ return pickStyleAndPseudoKeys( treeToPickFrom );
865
+ }
866
+
867
+ function pickStyleAndPseudoKeys(
868
+ treeToPickFrom: any,
869
+ blockName?: string
870
+ ): any {
857
871
  if ( ! treeToPickFrom ) {
858
872
  return {};
859
873
  }
860
874
  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 )
863
881
  );
864
882
  // clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it
865
883
  const clonedEntries = pickedEntries.map( ( [ key, style ] ) => [
@@ -869,6 +887,92 @@ function pickStyleKeys( treeToPickFrom: any ): any {
869
887
  return Object.fromEntries( clonedEntries );
870
888
  }
871
889
 
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
+
872
976
  export const getNodesWithStyles = (
873
977
  tree: GlobalStylesConfig,
874
978
  blockSelectors: string | BlockSelectors
@@ -920,7 +1024,7 @@ export const getNodesWithStyles = (
920
1024
  // Iterate over blocks: they can have styles & elements.
921
1025
  Object.entries( tree.styles?.blocks ?? {} ).forEach(
922
1026
  ( [ blockName, node ] ) => {
923
- const blockStyles = pickStyleKeys( node );
1027
+ const blockStyles = pickStyleAndPseudoKeys( node, blockName );
924
1028
  const typedNode = node as BlockNode;
925
1029
 
926
1030
  // Store variation data for later processing, but don't add to nodes yet.
@@ -932,8 +1036,10 @@ export const getNodesWithStyles = (
932
1036
  Object.entries( typedNode.variations ).forEach(
933
1037
  ( [ variationName, variation ] ) => {
934
1038
  const typedVariation = variation as BlockVariation;
935
- variations[ variationName ] =
936
- pickStyleKeys( typedVariation );
1039
+ variations[ variationName ] = pickStyleAndPseudoKeys(
1040
+ typedVariation,
1041
+ blockName
1042
+ );
937
1043
  if ( typedVariation?.css ) {
938
1044
  variations[ variationName ].css =
939
1045
  typedVariation.css;
@@ -1002,7 +1108,10 @@ export const getNodesWithStyles = (
1002
1108
  : undefined;
1003
1109
 
1004
1110
  const variationBlockStyleNodes =
1005
- pickStyleKeys( variationBlockStyles );
1111
+ pickStyleAndPseudoKeys(
1112
+ variationBlockStyles,
1113
+ variationBlockName
1114
+ );
1006
1115
 
1007
1116
  if ( variationBlockStyles?.css ) {
1008
1117
  variationBlockStyleNodes.css =
@@ -1562,6 +1671,17 @@ export const transformToStyles = (
1562
1671
  `:root :where(${ styleVariationSelector })`
1563
1672
  );
1564
1673
  }
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
+
1565
1685
  // Generate layout styles for the variation if it supports layout and has blockGap defined.
1566
1686
  if (
1567
1687
  hasLayoutSupport &&
@@ -1583,45 +1703,14 @@ export const transformToStyles = (
1583
1703
  );
1584
1704
  }
1585
1705
 
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
1589
1713
  );
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
- }
1625
1714
  }
1626
1715
  );
1627
1716
  }
@@ -38,6 +38,7 @@ const VALID_SETTINGS = [
38
38
  'dimensions.aspectRatio',
39
39
  'dimensions.height',
40
40
  'dimensions.minHeight',
41
+ 'dimensions.minWidth',
41
42
  'dimensions.width',
42
43
  'dimensions.dimensionSizes',
43
44
  'layout.contentSize',
@@ -652,6 +652,106 @@ describe( 'global styles renderer', () => {
652
652
  );
653
653
  } );
654
654
 
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
+
655
755
  it( 'should handle duotone filter', () => {
656
756
  const tree = {
657
757
  styles: {
@@ -991,7 +1091,7 @@ describe( 'global styles renderer', () => {
991
1091
  } );
992
1092
 
993
1093
  it( 'should convert preset percentage width to calc() formula', () => {
994
- const tree: GlobalStylesConfig = {
1094
+ const tree = {
995
1095
  settings: {
996
1096
  blocks: {
997
1097
  'core/button': {
@@ -1018,7 +1118,7 @@ describe( 'global styles renderer', () => {
1018
1118
  },
1019
1119
  },
1020
1120
  },
1021
- };
1121
+ } as unknown as GlobalStylesConfig;
1022
1122
 
1023
1123
  const blockSelectors = {
1024
1124
  'core/button': {