@wordpress/block-editor 15.6.1-next.36001005c.0 → 15.6.1

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.
Files changed (163) hide show
  1. package/build/components/background-image-control/index.js +2 -2
  2. package/build/components/background-image-control/index.js.map +2 -2
  3. package/build/components/block-list/block.js +3 -3
  4. package/build/components/block-list/block.js.map +2 -2
  5. package/build/components/block-list/index.js +2 -2
  6. package/build/components/block-list/index.js.map +1 -1
  7. package/build/components/block-quick-navigation/index.js +0 -1
  8. package/build/components/block-quick-navigation/index.js.map +2 -2
  9. package/build/components/global-styles/border-panel.js +1 -2
  10. package/build/components/global-styles/border-panel.js.map +2 -2
  11. package/build/components/global-styles/color-panel.js +1 -2
  12. package/build/components/global-styles/color-panel.js.map +2 -2
  13. package/build/components/global-styles/dimensions-panel.js +2 -3
  14. package/build/components/global-styles/dimensions-panel.js.map +2 -2
  15. package/build/components/global-styles/filters-panel.js +1 -2
  16. package/build/components/global-styles/filters-panel.js.map +2 -2
  17. package/build/components/global-styles/get-block-css-selector.js +78 -0
  18. package/build/components/global-styles/get-block-css-selector.js.map +7 -0
  19. package/build/components/global-styles/hooks.js +95 -23
  20. package/build/components/global-styles/hooks.js.map +2 -2
  21. package/build/components/global-styles/index.js +14 -0
  22. package/build/components/global-styles/index.js.map +2 -2
  23. package/build/components/global-styles/typography-panel.js +19 -3
  24. package/build/components/global-styles/typography-panel.js.map +2 -2
  25. package/build/components/global-styles/typography-utils.js +49 -2
  26. package/build/components/global-styles/typography-utils.js.map +2 -2
  27. package/build/components/global-styles/use-global-styles-output.js +998 -0
  28. package/build/components/global-styles/use-global-styles-output.js.map +7 -0
  29. package/build/components/global-styles/utils.js +377 -0
  30. package/build/components/global-styles/utils.js.map +2 -2
  31. package/build/components/rich-text/index.js +8 -7
  32. package/build/components/rich-text/index.js.map +2 -2
  33. package/build/hooks/block-bindings.js +111 -170
  34. package/build/hooks/block-bindings.js.map +2 -2
  35. package/build/hooks/block-style-variation.js +10 -6
  36. package/build/hooks/block-style-variation.js.map +2 -2
  37. package/build/hooks/custom-class-name.js +1 -1
  38. package/build/hooks/custom-class-name.js.map +1 -1
  39. package/build/hooks/duotone.js +3 -3
  40. package/build/hooks/duotone.js.map +2 -2
  41. package/build/hooks/fit-text.js +31 -18
  42. package/build/hooks/fit-text.js.map +2 -2
  43. package/build/hooks/font-size.js +6 -5
  44. package/build/hooks/font-size.js.map +2 -2
  45. package/build/hooks/metadata.js +48 -2
  46. package/build/hooks/metadata.js.map +2 -2
  47. package/build/hooks/typography.js +11 -4
  48. package/build/hooks/typography.js.map +3 -3
  49. package/build/hooks/use-typography-props.js +2 -2
  50. package/build/hooks/use-typography-props.js.map +2 -2
  51. package/build/store/private-selectors.js +3 -3
  52. package/build/store/private-selectors.js.map +2 -2
  53. package/build/store/selectors.js +38 -13
  54. package/build/store/selectors.js.map +2 -2
  55. package/build/store/utils.js +2 -1
  56. package/build/store/utils.js.map +2 -2
  57. package/build/utils/fit-text-utils.js +4 -4
  58. package/build/utils/fit-text-utils.js.map +2 -2
  59. package/build-module/components/background-image-control/index.js +1 -1
  60. package/build-module/components/background-image-control/index.js.map +2 -2
  61. package/build-module/components/block-list/block.js +3 -3
  62. package/build-module/components/block-list/block.js.map +2 -2
  63. package/build-module/components/block-list/index.js +2 -2
  64. package/build-module/components/block-list/index.js.map +1 -1
  65. package/build-module/components/block-quick-navigation/index.js +0 -1
  66. package/build-module/components/block-quick-navigation/index.js.map +2 -2
  67. package/build-module/components/global-styles/border-panel.js +1 -2
  68. package/build-module/components/global-styles/border-panel.js.map +2 -2
  69. package/build-module/components/global-styles/color-panel.js +1 -2
  70. package/build-module/components/global-styles/color-panel.js.map +2 -2
  71. package/build-module/components/global-styles/dimensions-panel.js +1 -2
  72. package/build-module/components/global-styles/dimensions-panel.js.map +2 -2
  73. package/build-module/components/global-styles/filters-panel.js +1 -2
  74. package/build-module/components/global-styles/filters-panel.js.map +2 -2
  75. package/build-module/components/global-styles/get-block-css-selector.js +54 -0
  76. package/build-module/components/global-styles/get-block-css-selector.js.map +7 -0
  77. package/build-module/components/global-styles/hooks.js +95 -27
  78. package/build-module/components/global-styles/hooks.js.map +2 -2
  79. package/build-module/components/global-styles/index.js +14 -0
  80. package/build-module/components/global-styles/index.js.map +2 -2
  81. package/build-module/components/global-styles/typography-panel.js +19 -3
  82. package/build-module/components/global-styles/typography-panel.js.map +2 -2
  83. package/build-module/components/global-styles/typography-utils.js +49 -1
  84. package/build-module/components/global-styles/typography-utils.js.map +2 -2
  85. package/build-module/components/global-styles/use-global-styles-output.js +979 -0
  86. package/build-module/components/global-styles/use-global-styles-output.js.map +7 -0
  87. package/build-module/components/global-styles/utils.js +364 -0
  88. package/build-module/components/global-styles/utils.js.map +2 -2
  89. package/build-module/components/rich-text/index.js +8 -7
  90. package/build-module/components/rich-text/index.js.map +2 -2
  91. package/build-module/hooks/block-bindings.js +112 -172
  92. package/build-module/hooks/block-bindings.js.map +2 -2
  93. package/build-module/hooks/block-style-variation.js +12 -4
  94. package/build-module/hooks/block-style-variation.js.map +2 -2
  95. package/build-module/hooks/custom-class-name.js +1 -1
  96. package/build-module/hooks/custom-class-name.js.map +1 -1
  97. package/build-module/hooks/duotone.js +3 -3
  98. package/build-module/hooks/duotone.js.map +2 -2
  99. package/build-module/hooks/fit-text.js +32 -19
  100. package/build-module/hooks/fit-text.js.map +2 -2
  101. package/build-module/hooks/font-size.js +5 -4
  102. package/build-module/hooks/font-size.js.map +2 -2
  103. package/build-module/hooks/metadata.js +46 -1
  104. package/build-module/hooks/metadata.js.map +2 -2
  105. package/build-module/hooks/typography.js +11 -4
  106. package/build-module/hooks/typography.js.map +3 -3
  107. package/build-module/hooks/use-typography-props.js +1 -1
  108. package/build-module/hooks/use-typography-props.js.map +2 -2
  109. package/build-module/store/private-selectors.js +2 -2
  110. package/build-module/store/private-selectors.js.map +2 -2
  111. package/build-module/store/selectors.js +39 -14
  112. package/build-module/store/selectors.js.map +2 -2
  113. package/build-module/store/utils.js +3 -2
  114. package/build-module/store/utils.js.map +2 -2
  115. package/build-module/utils/fit-text-utils.js +4 -4
  116. package/build-module/utils/fit-text-utils.js.map +2 -2
  117. package/build-style/style-rtl.css +6 -10
  118. package/build-style/style.css +6 -10
  119. package/package.json +35 -36
  120. package/src/components/background-image-control/index.js +1 -1
  121. package/src/components/block-card/style.scss +1 -1
  122. package/src/components/block-list/block.js +1 -1
  123. package/src/components/block-list/index.js +2 -2
  124. package/src/components/block-navigation/style.scss +1 -1
  125. package/src/components/block-quick-navigation/index.js +0 -1
  126. package/src/components/block-switcher/style.scss +1 -1
  127. package/src/components/color-palette/test/__snapshots__/control.js.snap +1 -1
  128. package/src/components/global-styles/border-panel.js +1 -2
  129. package/src/components/global-styles/color-panel.js +1 -2
  130. package/src/components/global-styles/color-panel.native.js +1 -1
  131. package/src/components/global-styles/dimensions-panel.js +1 -2
  132. package/src/components/global-styles/filters-panel.js +1 -2
  133. package/src/components/global-styles/get-block-css-selector.js +114 -0
  134. package/src/components/global-styles/hooks.js +108 -29
  135. package/src/components/global-styles/index.js +8 -0
  136. package/src/components/global-styles/test/typography-utils.js +806 -0
  137. package/src/components/global-styles/test/use-global-styles-output.js +1131 -0
  138. package/src/components/global-styles/test/utils.js +442 -1
  139. package/src/components/global-styles/typography-panel.js +27 -3
  140. package/src/components/global-styles/typography-utils.js +133 -0
  141. package/src/components/global-styles/use-global-styles-output.js +1487 -0
  142. package/src/components/global-styles/utils.js +537 -0
  143. package/src/components/inserter/style.scss +2 -2
  144. package/src/components/multi-selection-inspector/style.scss +1 -1
  145. package/src/components/rich-text/index.js +8 -14
  146. package/src/hooks/block-bindings.js +79 -153
  147. package/src/hooks/block-style-variation.js +12 -4
  148. package/src/hooks/custom-class-name.js +1 -1
  149. package/src/hooks/duotone.js +3 -3
  150. package/src/hooks/fit-text.js +37 -28
  151. package/src/hooks/font-size.js +8 -4
  152. package/src/hooks/metadata.js +89 -0
  153. package/src/hooks/test/metadata.js +316 -0
  154. package/src/hooks/typography.js +15 -4
  155. package/src/hooks/use-typography-props.js +1 -1
  156. package/src/store/private-selectors.js +2 -2
  157. package/src/store/selectors.js +59 -21
  158. package/src/store/test/selectors.js +1 -1
  159. package/src/store/utils.js +2 -1
  160. package/src/style.scss +0 -1
  161. package/src/utils/fit-text-utils.js +4 -16
  162. package/tsconfig.json +0 -1
  163. package/src/components/block-quick-navigation/style.scss +0 -5
@@ -0,0 +1,1487 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY,
6
+ __EXPERIMENTAL_ELEMENTS as ELEMENTS,
7
+ getBlockSupport,
8
+ getBlockTypes,
9
+ store as blocksStore,
10
+ } from '@wordpress/blocks';
11
+ import { useSelect } from '@wordpress/data';
12
+ import { useContext, useMemo } from '@wordpress/element';
13
+ import { getCSSRules, getCSSValueFromRawStyle } from '@wordpress/style-engine';
14
+ import { privateApis as componentsPrivateApis } from '@wordpress/components';
15
+
16
+ /**
17
+ * Internal dependencies
18
+ */
19
+ import {
20
+ PRESET_METADATA,
21
+ ROOT_BLOCK_SELECTOR,
22
+ ROOT_CSS_PROPERTIES_SELECTOR,
23
+ scopeSelector,
24
+ scopeFeatureSelectors,
25
+ appendToSelector,
26
+ getBlockStyleVariationSelector,
27
+ getResolvedValue,
28
+ } from './utils';
29
+ import { getBlockCSSSelector } from './get-block-css-selector';
30
+ import { getTypographyFontSizeValue } from './typography-utils';
31
+ import { GlobalStylesContext } from './context';
32
+ import { useGlobalSetting } from './hooks';
33
+ import { getDuotoneFilter } from '../duotone/utils';
34
+ import { getGapCSSValue } from '../../hooks/gap';
35
+ import { setBackgroundStyleDefaults } from '../../hooks/background';
36
+ import { store as blockEditorStore } from '../../store';
37
+ import { LAYOUT_DEFINITIONS } from '../../layouts/definitions';
38
+ import { getValueFromObjectPath, setImmutably } from '../../utils/object';
39
+ import { unlock } from '../../lock-unlock';
40
+
41
+ // Elements that rely on class names in their selectors.
42
+ const ELEMENT_CLASS_NAMES = {
43
+ button: 'wp-element-button',
44
+ caption: 'wp-element-caption',
45
+ };
46
+
47
+ // List of block support features that can have their related styles
48
+ // generated under their own feature level selector rather than the block's.
49
+ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
50
+ __experimentalBorder: 'border',
51
+ color: 'color',
52
+ spacing: 'spacing',
53
+ typography: 'typography',
54
+ };
55
+ const { kebabCase } = unlock( componentsPrivateApis );
56
+
57
+ /**
58
+ * Transform given preset tree into a set of style declarations.
59
+ *
60
+ * @param {Object} blockPresets
61
+ * @param {Object} mergedSettings Merged theme.json settings.
62
+ *
63
+ * @return {Array<Object>} An array of style declarations.
64
+ */
65
+ function getPresetsDeclarations( blockPresets = {}, mergedSettings ) {
66
+ return PRESET_METADATA.reduce(
67
+ ( declarations, { path, valueKey, valueFunc, cssVarInfix } ) => {
68
+ const presetByOrigin = getValueFromObjectPath(
69
+ blockPresets,
70
+ path,
71
+ []
72
+ );
73
+ [ 'default', 'theme', 'custom' ].forEach( ( origin ) => {
74
+ if ( presetByOrigin[ origin ] ) {
75
+ presetByOrigin[ origin ].forEach( ( value ) => {
76
+ if ( valueKey && ! valueFunc ) {
77
+ declarations.push(
78
+ `--wp--preset--${ cssVarInfix }--${ kebabCase(
79
+ value.slug
80
+ ) }: ${ value[ valueKey ] }`
81
+ );
82
+ } else if (
83
+ valueFunc &&
84
+ typeof valueFunc === 'function'
85
+ ) {
86
+ declarations.push(
87
+ `--wp--preset--${ cssVarInfix }--${ kebabCase(
88
+ value.slug
89
+ ) }: ${ valueFunc( value, mergedSettings ) }`
90
+ );
91
+ }
92
+ } );
93
+ }
94
+ } );
95
+
96
+ return declarations;
97
+ },
98
+ []
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Transform given preset tree into a set of preset class declarations.
104
+ *
105
+ * @param {?string} blockSelector
106
+ * @param {Object} blockPresets
107
+ * @return {string} CSS declarations for the preset classes.
108
+ */
109
+ function getPresetsClasses( blockSelector = '*', blockPresets = {} ) {
110
+ return PRESET_METADATA.reduce(
111
+ ( declarations, { path, cssVarInfix, classes } ) => {
112
+ if ( ! classes ) {
113
+ return declarations;
114
+ }
115
+
116
+ const presetByOrigin = getValueFromObjectPath(
117
+ blockPresets,
118
+ path,
119
+ []
120
+ );
121
+ [ 'default', 'theme', 'custom' ].forEach( ( origin ) => {
122
+ if ( presetByOrigin[ origin ] ) {
123
+ presetByOrigin[ origin ].forEach( ( { slug } ) => {
124
+ classes.forEach( ( { classSuffix, propertyName } ) => {
125
+ const classSelectorToUse = `.has-${ kebabCase(
126
+ slug
127
+ ) }-${ classSuffix }`;
128
+ const selectorToUse = blockSelector
129
+ .split( ',' ) // Selector can be "h1, h2, h3"
130
+ .map(
131
+ ( selector ) =>
132
+ `${ selector }${ classSelectorToUse }`
133
+ )
134
+ .join( ',' );
135
+ const value = `var(--wp--preset--${ cssVarInfix }--${ kebabCase(
136
+ slug
137
+ ) })`;
138
+ declarations += `${ selectorToUse }{${ propertyName }: ${ value } !important;}`;
139
+ } );
140
+ } );
141
+ }
142
+ } );
143
+ return declarations;
144
+ },
145
+ ''
146
+ );
147
+ }
148
+
149
+ function getPresetsSvgFilters( blockPresets = {} ) {
150
+ return PRESET_METADATA.filter(
151
+ // Duotone are the only type of filters for now.
152
+ ( metadata ) => metadata.path.at( -1 ) === 'duotone'
153
+ ).flatMap( ( metadata ) => {
154
+ const presetByOrigin = getValueFromObjectPath(
155
+ blockPresets,
156
+ metadata.path,
157
+ {}
158
+ );
159
+ return [ 'default', 'theme' ]
160
+ .filter( ( origin ) => presetByOrigin[ origin ] )
161
+ .flatMap( ( origin ) =>
162
+ presetByOrigin[ origin ].map( ( preset ) =>
163
+ getDuotoneFilter(
164
+ `wp-duotone-${ preset.slug }`,
165
+ preset.colors
166
+ )
167
+ )
168
+ )
169
+ .join( '' );
170
+ } );
171
+ }
172
+
173
+ function flattenTree( input = {}, prefix, token ) {
174
+ let result = [];
175
+ Object.keys( input ).forEach( ( key ) => {
176
+ const newKey = prefix + kebabCase( key.replace( '/', '-' ) );
177
+ const newLeaf = input[ key ];
178
+
179
+ if ( newLeaf instanceof Object ) {
180
+ const newPrefix = newKey + token;
181
+ result = [ ...result, ...flattenTree( newLeaf, newPrefix, token ) ];
182
+ } else {
183
+ result.push( `${ newKey }: ${ newLeaf }` );
184
+ }
185
+ } );
186
+ return result;
187
+ }
188
+
189
+ /**
190
+ * Gets variation selector string from feature selector.
191
+ *
192
+ * @param {string} featureSelector The feature selector.
193
+ *
194
+ * @param {string} styleVariationSelector The style variation selector.
195
+ * @return {string} Combined selector string.
196
+ */
197
+ function concatFeatureVariationSelectorString(
198
+ featureSelector,
199
+ styleVariationSelector
200
+ ) {
201
+ const featureSelectors = featureSelector.split( ',' );
202
+ const combinedSelectors = [];
203
+ featureSelectors.forEach( ( selector ) => {
204
+ combinedSelectors.push(
205
+ `${ styleVariationSelector.trim() }${ selector.trim() }`
206
+ );
207
+ } );
208
+ return combinedSelectors.join( ', ' );
209
+ }
210
+
211
+ /**
212
+ * Generate style declarations for a block's custom feature and subfeature
213
+ * selectors.
214
+ *
215
+ * NOTE: The passed `styles` object will be mutated by this function.
216
+ *
217
+ * @param {Object} selectors Custom selectors object for a block.
218
+ * @param {Object} styles A block's styles object.
219
+ *
220
+ * @return {Object} Style declarations.
221
+ */
222
+ const getFeatureDeclarations = ( selectors, styles ) => {
223
+ const declarations = {};
224
+
225
+ Object.entries( selectors ).forEach( ( [ feature, selector ] ) => {
226
+ // We're only processing features/subfeatures that have styles.
227
+ if ( feature === 'root' || ! styles?.[ feature ] ) {
228
+ return;
229
+ }
230
+
231
+ const isShorthand = typeof selector === 'string';
232
+
233
+ // If we have a selector object instead of shorthand process it.
234
+ if ( ! isShorthand ) {
235
+ Object.entries( selector ).forEach(
236
+ ( [ subfeature, subfeatureSelector ] ) => {
237
+ // Don't process root feature selector yet or any
238
+ // subfeature that doesn't have a style.
239
+ if (
240
+ subfeature === 'root' ||
241
+ ! styles?.[ feature ][ subfeature ]
242
+ ) {
243
+ return;
244
+ }
245
+
246
+ // Create a temporary styles object and build
247
+ // declarations for subfeature.
248
+ const subfeatureStyles = {
249
+ [ feature ]: {
250
+ [ subfeature ]: styles[ feature ][ subfeature ],
251
+ },
252
+ };
253
+ const newDeclarations =
254
+ getStylesDeclarations( subfeatureStyles );
255
+
256
+ // Merge new declarations in with any others that
257
+ // share the same selector.
258
+ declarations[ subfeatureSelector ] = [
259
+ ...( declarations[ subfeatureSelector ] || [] ),
260
+ ...newDeclarations,
261
+ ];
262
+
263
+ // Remove the subfeature's style now it will be
264
+ // included under its own selector not the block's.
265
+ delete styles[ feature ][ subfeature ];
266
+ }
267
+ );
268
+ }
269
+
270
+ // Now subfeatures have been processed and removed, we can
271
+ // process root, or shorthand, feature selectors.
272
+ if ( isShorthand || selector.root ) {
273
+ const featureSelector = isShorthand ? selector : selector.root;
274
+
275
+ // Create temporary style object and build declarations for feature.
276
+ const featureStyles = { [ feature ]: styles[ feature ] };
277
+ const newDeclarations = getStylesDeclarations( featureStyles );
278
+
279
+ // Merge new declarations with any others that share the selector.
280
+ declarations[ featureSelector ] = [
281
+ ...( declarations[ featureSelector ] || [] ),
282
+ ...newDeclarations,
283
+ ];
284
+
285
+ // Remove the feature from the block's styles now as it will be
286
+ // included under its own selector not the block's.
287
+ delete styles[ feature ];
288
+ }
289
+ } );
290
+
291
+ return declarations;
292
+ };
293
+
294
+ /**
295
+ * Transform given style tree into a set of style declarations.
296
+ *
297
+ * @param {Object} blockStyles Block styles.
298
+ *
299
+ * @param {string} selector The selector these declarations should attach to.
300
+ *
301
+ * @param {boolean} useRootPaddingAlign Whether to use CSS custom properties in root selector.
302
+ *
303
+ * @param {Object} tree A theme.json tree containing layout definitions.
304
+ *
305
+ * @param {boolean} disableRootPadding Whether to force disable the root padding styles.
306
+ * @return {Array} An array of style declarations.
307
+ */
308
+ export function getStylesDeclarations(
309
+ blockStyles = {},
310
+ selector = '',
311
+ useRootPaddingAlign,
312
+ tree = {},
313
+ disableRootPadding = false
314
+ ) {
315
+ const isRoot = ROOT_BLOCK_SELECTOR === selector;
316
+ const output = Object.entries( STYLE_PROPERTY ).reduce(
317
+ (
318
+ declarations,
319
+ [ key, { value, properties, useEngine, rootOnly } ]
320
+ ) => {
321
+ if ( rootOnly && ! isRoot ) {
322
+ return declarations;
323
+ }
324
+ const pathToValue = value;
325
+ if ( pathToValue[ 0 ] === 'elements' || useEngine ) {
326
+ return declarations;
327
+ }
328
+
329
+ const styleValue = getValueFromObjectPath(
330
+ blockStyles,
331
+ pathToValue
332
+ );
333
+
334
+ // Root-level padding styles don't currently support strings with CSS shorthand values.
335
+ // This may change: https://github.com/WordPress/gutenberg/issues/40132.
336
+ if (
337
+ key === '--wp--style--root--padding' &&
338
+ ( typeof styleValue === 'string' || ! useRootPaddingAlign )
339
+ ) {
340
+ return declarations;
341
+ }
342
+
343
+ if ( properties && typeof styleValue !== 'string' ) {
344
+ Object.entries( properties ).forEach( ( entry ) => {
345
+ const [ name, prop ] = entry;
346
+
347
+ if (
348
+ ! getValueFromObjectPath( styleValue, [ prop ], false )
349
+ ) {
350
+ // Do not create a declaration
351
+ // for sub-properties that don't have any value.
352
+ return;
353
+ }
354
+
355
+ const cssProperty = name.startsWith( '--' )
356
+ ? name
357
+ : kebabCase( name );
358
+ declarations.push(
359
+ `${ cssProperty }: ${ getCSSValueFromRawStyle(
360
+ getValueFromObjectPath( styleValue, [ prop ] )
361
+ ) }`
362
+ );
363
+ } );
364
+ } else if (
365
+ getValueFromObjectPath( blockStyles, pathToValue, false )
366
+ ) {
367
+ const cssProperty = key.startsWith( '--' )
368
+ ? key
369
+ : kebabCase( key );
370
+ declarations.push(
371
+ `${ cssProperty }: ${ getCSSValueFromRawStyle(
372
+ getValueFromObjectPath( blockStyles, pathToValue )
373
+ ) }`
374
+ );
375
+ }
376
+
377
+ return declarations;
378
+ },
379
+ []
380
+ );
381
+
382
+ /*
383
+ * Preprocess background image values.
384
+ *
385
+ * Note: As we absorb more and more styles into the engine, we could simplify this function.
386
+ * A refactor is for the style engine to handle ref resolution (and possibly defaults)
387
+ * via a public util used internally and externally. Theme.json tree and defaults could be passed
388
+ * as options.
389
+ */
390
+ if ( !! blockStyles.background ) {
391
+ /*
392
+ * Resolve dynamic values before they are compiled by the style engine,
393
+ * which doesn't (yet) resolve dynamic values.
394
+ */
395
+ if ( blockStyles.background?.backgroundImage ) {
396
+ blockStyles.background.backgroundImage = getResolvedValue(
397
+ blockStyles.background.backgroundImage,
398
+ tree
399
+ );
400
+ }
401
+
402
+ /*
403
+ * Set default values for block background styles.
404
+ * Top-level styles are an exception as they are applied to the body.
405
+ */
406
+ if ( ! isRoot && !! blockStyles.background?.backgroundImage?.id ) {
407
+ blockStyles = {
408
+ ...blockStyles,
409
+ background: {
410
+ ...blockStyles.background,
411
+ ...setBackgroundStyleDefaults( blockStyles.background ),
412
+ },
413
+ };
414
+ }
415
+ }
416
+
417
+ const extraRules = getCSSRules( blockStyles );
418
+ extraRules.forEach( ( rule ) => {
419
+ // Don't output padding properties if padding variables are set or if we're not editing a full template.
420
+ if (
421
+ isRoot &&
422
+ ( useRootPaddingAlign || disableRootPadding ) &&
423
+ rule.key.startsWith( 'padding' )
424
+ ) {
425
+ return;
426
+ }
427
+ const cssProperty = rule.key.startsWith( '--' )
428
+ ? rule.key
429
+ : kebabCase( rule.key );
430
+
431
+ let ruleValue = getResolvedValue( rule.value, tree, null );
432
+
433
+ // Calculate fluid typography rules where available.
434
+ if ( cssProperty === 'font-size' ) {
435
+ /*
436
+ * getTypographyFontSizeValue() will check
437
+ * if fluid typography has been activated and also
438
+ * whether the incoming value can be converted to a fluid value.
439
+ * Values that already have a "clamp()" function will not pass the test,
440
+ * and therefore the original $value will be returned.
441
+ */
442
+ ruleValue = getTypographyFontSizeValue(
443
+ { size: ruleValue },
444
+ tree?.settings
445
+ );
446
+ }
447
+
448
+ // For aspect ratio to work, other dimensions rules (and Cover block defaults) must be unset.
449
+ // This ensures that a fixed height does not override the aspect ratio.
450
+ if ( cssProperty === 'aspect-ratio' ) {
451
+ output.push( 'min-height: unset' );
452
+ }
453
+
454
+ output.push( `${ cssProperty }: ${ ruleValue }` );
455
+ } );
456
+
457
+ return output;
458
+ }
459
+
460
+ /**
461
+ * Get generated CSS for layout styles by looking up layout definitions provided
462
+ * in theme.json, and outputting common layout styles, and specific blockGap values.
463
+ *
464
+ * @param {Object} props
465
+ * @param {Object} props.layoutDefinitions Layout definitions, keyed by layout type.
466
+ * @param {Object} props.style A style object containing spacing values.
467
+ * @param {string} props.selector Selector used to group together layout styling rules.
468
+ * @param {boolean} props.hasBlockGapSupport Whether or not the theme opts-in to blockGap support.
469
+ * @param {boolean} props.hasFallbackGapSupport Whether or not the theme allows fallback gap styles.
470
+ * @param {?string} props.fallbackGapValue An optional fallback gap value if no real gap value is available.
471
+ * @return {string} Generated CSS rules for the layout styles.
472
+ */
473
+ export function getLayoutStyles( {
474
+ layoutDefinitions = LAYOUT_DEFINITIONS,
475
+ style,
476
+ selector,
477
+ hasBlockGapSupport,
478
+ hasFallbackGapSupport,
479
+ fallbackGapValue,
480
+ } ) {
481
+ let ruleset = '';
482
+ let gapValue = hasBlockGapSupport
483
+ ? getGapCSSValue( style?.spacing?.blockGap )
484
+ : '';
485
+
486
+ // Ensure a fallback gap value for the root layout definitions,
487
+ // and use a fallback value if one is provided for the current block.
488
+ if ( hasFallbackGapSupport ) {
489
+ if ( selector === ROOT_BLOCK_SELECTOR ) {
490
+ gapValue = ! gapValue ? '0.5em' : gapValue;
491
+ } else if ( ! hasBlockGapSupport && fallbackGapValue ) {
492
+ gapValue = fallbackGapValue;
493
+ }
494
+ }
495
+
496
+ if ( gapValue && layoutDefinitions ) {
497
+ Object.values( layoutDefinitions ).forEach(
498
+ ( { className, name, spacingStyles } ) => {
499
+ // Allow outputting fallback gap styles for flex layout type when block gap support isn't available.
500
+ if (
501
+ ! hasBlockGapSupport &&
502
+ 'flex' !== name &&
503
+ 'grid' !== name
504
+ ) {
505
+ return;
506
+ }
507
+
508
+ if ( spacingStyles?.length ) {
509
+ spacingStyles.forEach( ( spacingStyle ) => {
510
+ const declarations = [];
511
+
512
+ if ( spacingStyle.rules ) {
513
+ Object.entries( spacingStyle.rules ).forEach(
514
+ ( [ cssProperty, cssValue ] ) => {
515
+ declarations.push(
516
+ `${ cssProperty }: ${
517
+ cssValue ? cssValue : gapValue
518
+ }`
519
+ );
520
+ }
521
+ );
522
+ }
523
+
524
+ if ( declarations.length ) {
525
+ let combinedSelector = '';
526
+
527
+ if ( ! hasBlockGapSupport ) {
528
+ // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles.
529
+ combinedSelector =
530
+ selector === ROOT_BLOCK_SELECTOR
531
+ ? `:where(.${ className }${
532
+ spacingStyle?.selector || ''
533
+ })`
534
+ : `:where(${ selector }.${ className }${
535
+ spacingStyle?.selector || ''
536
+ })`;
537
+ } else {
538
+ combinedSelector =
539
+ selector === ROOT_BLOCK_SELECTOR
540
+ ? `:root :where(.${ className })${
541
+ spacingStyle?.selector || ''
542
+ }`
543
+ : `:root :where(${ selector }-${ className })${
544
+ spacingStyle?.selector || ''
545
+ }`;
546
+ }
547
+ ruleset += `${ combinedSelector } { ${ declarations.join(
548
+ '; '
549
+ ) }; }`;
550
+ }
551
+ } );
552
+ }
553
+ }
554
+ );
555
+ // For backwards compatibility, ensure the legacy block gap CSS variable is still available.
556
+ if ( selector === ROOT_BLOCK_SELECTOR && hasBlockGapSupport ) {
557
+ ruleset += `${ ROOT_CSS_PROPERTIES_SELECTOR } { --wp--style--block-gap: ${ gapValue }; }`;
558
+ }
559
+ }
560
+
561
+ // Output base styles
562
+ if ( selector === ROOT_BLOCK_SELECTOR && layoutDefinitions ) {
563
+ const validDisplayModes = [ 'block', 'flex', 'grid' ];
564
+ Object.values( layoutDefinitions ).forEach(
565
+ ( { className, displayMode, baseStyles } ) => {
566
+ if (
567
+ displayMode &&
568
+ validDisplayModes.includes( displayMode )
569
+ ) {
570
+ ruleset += `${ selector } .${ className } { display:${ displayMode }; }`;
571
+ }
572
+
573
+ if ( baseStyles?.length ) {
574
+ baseStyles.forEach( ( baseStyle ) => {
575
+ const declarations = [];
576
+
577
+ if ( baseStyle.rules ) {
578
+ Object.entries( baseStyle.rules ).forEach(
579
+ ( [ cssProperty, cssValue ] ) => {
580
+ declarations.push(
581
+ `${ cssProperty }: ${ cssValue }`
582
+ );
583
+ }
584
+ );
585
+ }
586
+
587
+ if ( declarations.length ) {
588
+ const combinedSelector = `.${ className }${
589
+ baseStyle?.selector || ''
590
+ }`;
591
+ ruleset += `${ combinedSelector } { ${ declarations.join(
592
+ '; '
593
+ ) }; }`;
594
+ }
595
+ } );
596
+ }
597
+ }
598
+ );
599
+ }
600
+
601
+ return ruleset;
602
+ }
603
+
604
+ const STYLE_KEYS = [
605
+ 'border',
606
+ 'color',
607
+ 'dimensions',
608
+ 'spacing',
609
+ 'typography',
610
+ 'filter',
611
+ 'outline',
612
+ 'shadow',
613
+ 'background',
614
+ ];
615
+
616
+ function pickStyleKeys( treeToPickFrom ) {
617
+ if ( ! treeToPickFrom ) {
618
+ return {};
619
+ }
620
+ const entries = Object.entries( treeToPickFrom );
621
+ const pickedEntries = entries.filter( ( [ key ] ) =>
622
+ STYLE_KEYS.includes( key )
623
+ );
624
+ // clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it
625
+ const clonedEntries = pickedEntries.map( ( [ key, style ] ) => [
626
+ key,
627
+ JSON.parse( JSON.stringify( style ) ),
628
+ ] );
629
+ return Object.fromEntries( clonedEntries );
630
+ }
631
+
632
+ export const getNodesWithStyles = ( tree, blockSelectors ) => {
633
+ const nodes = [];
634
+
635
+ if ( ! tree?.styles ) {
636
+ return nodes;
637
+ }
638
+
639
+ // Top-level.
640
+ const styles = pickStyleKeys( tree.styles );
641
+ if ( styles ) {
642
+ nodes.push( {
643
+ styles,
644
+ selector: ROOT_BLOCK_SELECTOR,
645
+ // Root selector (body) styles should not be wrapped in `:root where()` to keep
646
+ // specificity at (0,0,1) and maintain backwards compatibility.
647
+ skipSelectorWrapper: true,
648
+ } );
649
+ }
650
+
651
+ Object.entries( ELEMENTS ).forEach( ( [ name, selector ] ) => {
652
+ if ( tree.styles?.elements?.[ name ] ) {
653
+ nodes.push( {
654
+ styles: tree.styles?.elements?.[ name ],
655
+ selector,
656
+ // Top level elements that don't use a class name should not receive the
657
+ // `:root :where()` wrapper to maintain backwards compatibility.
658
+ skipSelectorWrapper: ! ELEMENT_CLASS_NAMES[ name ],
659
+ } );
660
+ }
661
+ } );
662
+
663
+ // Iterate over blocks: they can have styles & elements.
664
+ Object.entries( tree.styles?.blocks ?? {} ).forEach(
665
+ ( [ blockName, node ] ) => {
666
+ const blockStyles = pickStyleKeys( node );
667
+
668
+ if ( node?.variations ) {
669
+ const variations = {};
670
+ Object.entries( node.variations ).forEach(
671
+ ( [ variationName, variation ] ) => {
672
+ variations[ variationName ] =
673
+ pickStyleKeys( variation );
674
+ if ( variation?.css ) {
675
+ variations[ variationName ].css = variation.css;
676
+ }
677
+ const variationSelector =
678
+ blockSelectors[ blockName ]
679
+ ?.styleVariationSelectors?.[ variationName ];
680
+
681
+ // Process the variation's inner element styles.
682
+ // This comes before the inner block styles so the
683
+ // element styles within the block type styles take
684
+ // precedence over these.
685
+ Object.entries( variation?.elements ?? {} ).forEach(
686
+ ( [ element, elementStyles ] ) => {
687
+ if ( elementStyles && ELEMENTS[ element ] ) {
688
+ nodes.push( {
689
+ styles: elementStyles,
690
+ selector: scopeSelector(
691
+ variationSelector,
692
+ ELEMENTS[ element ]
693
+ ),
694
+ } );
695
+ }
696
+ }
697
+ );
698
+
699
+ // Process the variations inner block type styles.
700
+ Object.entries( variation?.blocks ?? {} ).forEach(
701
+ ( [
702
+ variationBlockName,
703
+ variationBlockStyles,
704
+ ] ) => {
705
+ const variationBlockSelector = scopeSelector(
706
+ variationSelector,
707
+ blockSelectors[ variationBlockName ]
708
+ ?.selector
709
+ );
710
+ const variationDuotoneSelector = scopeSelector(
711
+ variationSelector,
712
+ blockSelectors[ variationBlockName ]
713
+ ?.duotoneSelector
714
+ );
715
+ const variationFeatureSelectors =
716
+ scopeFeatureSelectors(
717
+ variationSelector,
718
+ blockSelectors[ variationBlockName ]
719
+ ?.featureSelectors
720
+ );
721
+
722
+ const variationBlockStyleNodes =
723
+ pickStyleKeys( variationBlockStyles );
724
+
725
+ if ( variationBlockStyles?.css ) {
726
+ variationBlockStyleNodes.css =
727
+ variationBlockStyles.css;
728
+ }
729
+
730
+ nodes.push( {
731
+ selector: variationBlockSelector,
732
+ duotoneSelector: variationDuotoneSelector,
733
+ featureSelectors: variationFeatureSelectors,
734
+ fallbackGapValue:
735
+ blockSelectors[ variationBlockName ]
736
+ ?.fallbackGapValue,
737
+ hasLayoutSupport:
738
+ blockSelectors[ variationBlockName ]
739
+ ?.hasLayoutSupport,
740
+ styles: variationBlockStyleNodes,
741
+ } );
742
+
743
+ // Process element styles for the inner blocks
744
+ // of the variation.
745
+ Object.entries(
746
+ variationBlockStyles.elements ?? {}
747
+ ).forEach(
748
+ ( [
749
+ variationBlockElement,
750
+ variationBlockElementStyles,
751
+ ] ) => {
752
+ if (
753
+ variationBlockElementStyles &&
754
+ ELEMENTS[ variationBlockElement ]
755
+ ) {
756
+ nodes.push( {
757
+ styles: variationBlockElementStyles,
758
+ selector: scopeSelector(
759
+ variationBlockSelector,
760
+ ELEMENTS[
761
+ variationBlockElement
762
+ ]
763
+ ),
764
+ } );
765
+ }
766
+ }
767
+ );
768
+ }
769
+ );
770
+ }
771
+ );
772
+ blockStyles.variations = variations;
773
+ }
774
+
775
+ if ( blockSelectors?.[ blockName ]?.selector ) {
776
+ nodes.push( {
777
+ duotoneSelector:
778
+ blockSelectors[ blockName ].duotoneSelector,
779
+ fallbackGapValue:
780
+ blockSelectors[ blockName ].fallbackGapValue,
781
+ hasLayoutSupport:
782
+ blockSelectors[ blockName ].hasLayoutSupport,
783
+ selector: blockSelectors[ blockName ].selector,
784
+ styles: blockStyles,
785
+ featureSelectors:
786
+ blockSelectors[ blockName ].featureSelectors,
787
+ styleVariationSelectors:
788
+ blockSelectors[ blockName ].styleVariationSelectors,
789
+ } );
790
+ }
791
+
792
+ Object.entries( node?.elements ?? {} ).forEach(
793
+ ( [ elementName, value ] ) => {
794
+ if (
795
+ value &&
796
+ blockSelectors?.[ blockName ] &&
797
+ ELEMENTS[ elementName ]
798
+ ) {
799
+ nodes.push( {
800
+ styles: value,
801
+ selector: blockSelectors[ blockName ]?.selector
802
+ .split( ',' )
803
+ .map( ( sel ) => {
804
+ const elementSelectors =
805
+ ELEMENTS[ elementName ].split( ',' );
806
+ return elementSelectors.map(
807
+ ( elementSelector ) =>
808
+ sel + ' ' + elementSelector
809
+ );
810
+ } )
811
+ .join( ',' ),
812
+ } );
813
+ }
814
+ }
815
+ );
816
+ }
817
+ );
818
+
819
+ return nodes;
820
+ };
821
+
822
+ export const getNodesWithSettings = ( tree, blockSelectors ) => {
823
+ const nodes = [];
824
+
825
+ if ( ! tree?.settings ) {
826
+ return nodes;
827
+ }
828
+
829
+ const pickPresets = ( treeToPickFrom ) => {
830
+ let presets = {};
831
+ PRESET_METADATA.forEach( ( { path } ) => {
832
+ const value = getValueFromObjectPath( treeToPickFrom, path, false );
833
+ if ( value !== false ) {
834
+ presets = setImmutably( presets, path, value );
835
+ }
836
+ } );
837
+ return presets;
838
+ };
839
+
840
+ // Top-level.
841
+ const presets = pickPresets( tree.settings );
842
+ const custom = tree.settings?.custom;
843
+ if ( Object.keys( presets ).length > 0 || custom ) {
844
+ nodes.push( {
845
+ presets,
846
+ custom,
847
+ selector: ROOT_CSS_PROPERTIES_SELECTOR,
848
+ } );
849
+ }
850
+
851
+ // Blocks.
852
+ Object.entries( tree.settings?.blocks ?? {} ).forEach(
853
+ ( [ blockName, node ] ) => {
854
+ const blockPresets = pickPresets( node );
855
+ const blockCustom = node.custom;
856
+ if ( Object.keys( blockPresets ).length > 0 || blockCustom ) {
857
+ nodes.push( {
858
+ presets: blockPresets,
859
+ custom: blockCustom,
860
+ selector: blockSelectors[ blockName ]?.selector,
861
+ } );
862
+ }
863
+ }
864
+ );
865
+
866
+ return nodes;
867
+ };
868
+
869
+ export const toCustomProperties = ( tree, blockSelectors ) => {
870
+ const settings = getNodesWithSettings( tree, blockSelectors );
871
+ let ruleset = '';
872
+ settings.forEach( ( { presets, custom, selector } ) => {
873
+ const declarations = getPresetsDeclarations( presets, tree?.settings );
874
+ const customProps = flattenTree( custom, '--wp--custom--', '--' );
875
+ if ( customProps.length > 0 ) {
876
+ declarations.push( ...customProps );
877
+ }
878
+
879
+ if ( declarations.length > 0 ) {
880
+ ruleset += `${ selector }{${ declarations.join( ';' ) };}`;
881
+ }
882
+ } );
883
+
884
+ return ruleset;
885
+ };
886
+
887
+ export const toStyles = (
888
+ tree,
889
+ blockSelectors,
890
+ hasBlockGapSupport,
891
+ hasFallbackGapSupport,
892
+ disableLayoutStyles = false,
893
+ disableRootPadding = false,
894
+ styleOptions = undefined
895
+ ) => {
896
+ // These allow opting out of certain sets of styles.
897
+ const options = {
898
+ blockGap: true,
899
+ blockStyles: true,
900
+ layoutStyles: true,
901
+ marginReset: true,
902
+ presets: true,
903
+ rootPadding: true,
904
+ variationStyles: false,
905
+ ...styleOptions,
906
+ };
907
+ const nodesWithStyles = getNodesWithStyles( tree, blockSelectors );
908
+ const nodesWithSettings = getNodesWithSettings( tree, blockSelectors );
909
+ const useRootPaddingAlign = tree?.settings?.useRootPaddingAwareAlignments;
910
+ const { contentSize, wideSize } = tree?.settings?.layout || {};
911
+ const hasBodyStyles =
912
+ options.marginReset || options.rootPadding || options.layoutStyles;
913
+
914
+ let ruleset = '';
915
+
916
+ if ( options.presets && ( contentSize || wideSize ) ) {
917
+ ruleset += `${ ROOT_CSS_PROPERTIES_SELECTOR } {`;
918
+ ruleset = contentSize
919
+ ? ruleset + ` --wp--style--global--content-size: ${ contentSize };`
920
+ : ruleset;
921
+ ruleset = wideSize
922
+ ? ruleset + ` --wp--style--global--wide-size: ${ wideSize };`
923
+ : ruleset;
924
+ ruleset += '}';
925
+ }
926
+
927
+ if ( hasBodyStyles ) {
928
+ /*
929
+ * Reset default browser margin on the body element.
930
+ * This is set on the body selector **before** generating the ruleset
931
+ * from the `theme.json`. This is to ensure that if the `theme.json` declares
932
+ * `margin` in its `spacing` declaration for the `body` element then these
933
+ * user-generated values take precedence in the CSS cascade.
934
+ * @link https://github.com/WordPress/gutenberg/issues/36147.
935
+ */
936
+ ruleset += ':where(body) {margin: 0;';
937
+
938
+ // Root padding styles should be output for full templates, patterns and template parts.
939
+ if ( options.rootPadding && useRootPaddingAlign ) {
940
+ /*
941
+ * These rules reproduce the ones from https://github.com/WordPress/gutenberg/blob/79103f124925d1f457f627e154f52a56228ed5ad/lib/class-wp-theme-json-gutenberg.php#L2508
942
+ * almost exactly, but for the selectors that target block wrappers in the front end. This code only runs in the editor, so it doesn't need those selectors.
943
+ */
944
+ ruleset += `padding-right: 0; padding-left: 0; padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom) }
945
+ .has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }
946
+ .has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }
947
+ .has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }
948
+ .has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0;
949
+ `;
950
+ }
951
+
952
+ ruleset += '}';
953
+ }
954
+
955
+ if ( options.blockStyles ) {
956
+ nodesWithStyles.forEach(
957
+ ( {
958
+ selector,
959
+ duotoneSelector,
960
+ styles,
961
+ fallbackGapValue,
962
+ hasLayoutSupport,
963
+ featureSelectors,
964
+ styleVariationSelectors,
965
+ skipSelectorWrapper,
966
+ } ) => {
967
+ // Process styles for block support features with custom feature level
968
+ // CSS selectors set.
969
+ if ( featureSelectors ) {
970
+ const featureDeclarations = getFeatureDeclarations(
971
+ featureSelectors,
972
+ styles
973
+ );
974
+
975
+ Object.entries( featureDeclarations ).forEach(
976
+ ( [ cssSelector, declarations ] ) => {
977
+ if ( declarations.length ) {
978
+ const rules = declarations.join( ';' );
979
+ ruleset += `:root :where(${ cssSelector }){${ rules };}`;
980
+ }
981
+ }
982
+ );
983
+ }
984
+
985
+ // Process duotone styles.
986
+ if ( duotoneSelector ) {
987
+ const duotoneStyles = {};
988
+ if ( styles?.filter ) {
989
+ duotoneStyles.filter = styles.filter;
990
+ delete styles.filter;
991
+ }
992
+ const duotoneDeclarations =
993
+ getStylesDeclarations( duotoneStyles );
994
+ if ( duotoneDeclarations.length ) {
995
+ ruleset += `${ duotoneSelector }{${ duotoneDeclarations.join(
996
+ ';'
997
+ ) };}`;
998
+ }
999
+ }
1000
+
1001
+ // Process blockGap and layout styles.
1002
+ if (
1003
+ ! disableLayoutStyles &&
1004
+ ( ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport )
1005
+ ) {
1006
+ ruleset += getLayoutStyles( {
1007
+ style: styles,
1008
+ selector,
1009
+ hasBlockGapSupport,
1010
+ hasFallbackGapSupport,
1011
+ fallbackGapValue,
1012
+ } );
1013
+ }
1014
+
1015
+ // Process the remaining block styles (they use either normal block class or __experimentalSelector).
1016
+ const styleDeclarations = getStylesDeclarations(
1017
+ styles,
1018
+ selector,
1019
+ useRootPaddingAlign,
1020
+ tree,
1021
+ disableRootPadding
1022
+ );
1023
+ if ( styleDeclarations?.length ) {
1024
+ const generalSelector = skipSelectorWrapper
1025
+ ? selector
1026
+ : `:root :where(${ selector })`;
1027
+ ruleset += `${ generalSelector }{${ styleDeclarations.join(
1028
+ ';'
1029
+ ) };}`;
1030
+ }
1031
+ if ( styles?.css ) {
1032
+ ruleset += processCSSNesting(
1033
+ styles.css,
1034
+ `:root :where(${ selector })`
1035
+ );
1036
+ }
1037
+
1038
+ if ( options.variationStyles && styleVariationSelectors ) {
1039
+ Object.entries( styleVariationSelectors ).forEach(
1040
+ ( [ styleVariationName, styleVariationSelector ] ) => {
1041
+ const styleVariations =
1042
+ styles?.variations?.[ styleVariationName ];
1043
+ if ( styleVariations ) {
1044
+ // If the block uses any custom selectors for block support, add those first.
1045
+ if ( featureSelectors ) {
1046
+ const featureDeclarations =
1047
+ getFeatureDeclarations(
1048
+ featureSelectors,
1049
+ styleVariations
1050
+ );
1051
+
1052
+ Object.entries(
1053
+ featureDeclarations
1054
+ ).forEach(
1055
+ ( [ baseSelector, declarations ] ) => {
1056
+ if ( declarations.length ) {
1057
+ const cssSelector =
1058
+ concatFeatureVariationSelectorString(
1059
+ baseSelector,
1060
+ styleVariationSelector
1061
+ );
1062
+ const rules =
1063
+ declarations.join( ';' );
1064
+ ruleset += `:root :where(${ cssSelector }){${ rules };}`;
1065
+ }
1066
+ }
1067
+ );
1068
+ }
1069
+
1070
+ // Otherwise add regular selectors.
1071
+ const styleVariationDeclarations =
1072
+ getStylesDeclarations(
1073
+ styleVariations,
1074
+ styleVariationSelector,
1075
+ useRootPaddingAlign,
1076
+ tree
1077
+ );
1078
+ if ( styleVariationDeclarations.length ) {
1079
+ ruleset += `:root :where(${ styleVariationSelector }){${ styleVariationDeclarations.join(
1080
+ ';'
1081
+ ) };}`;
1082
+ }
1083
+ if ( styleVariations?.css ) {
1084
+ ruleset += processCSSNesting(
1085
+ styleVariations.css,
1086
+ `:root :where(${ styleVariationSelector })`
1087
+ );
1088
+ }
1089
+ }
1090
+ }
1091
+ );
1092
+ }
1093
+
1094
+ // Check for pseudo selector in `styles` and handle separately.
1095
+ const pseudoSelectorStyles = Object.entries( styles ).filter(
1096
+ ( [ key ] ) => key.startsWith( ':' )
1097
+ );
1098
+
1099
+ if ( pseudoSelectorStyles?.length ) {
1100
+ pseudoSelectorStyles.forEach(
1101
+ ( [ pseudoKey, pseudoStyle ] ) => {
1102
+ const pseudoDeclarations =
1103
+ getStylesDeclarations( pseudoStyle );
1104
+
1105
+ if ( ! pseudoDeclarations?.length ) {
1106
+ return;
1107
+ }
1108
+
1109
+ // `selector` may be provided in a form
1110
+ // where block level selectors have sub element
1111
+ // selectors appended to them as a comma separated
1112
+ // string.
1113
+ // e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`;
1114
+ // Split and append pseudo selector to create
1115
+ // the proper rules to target the elements.
1116
+ const _selector = selector
1117
+ .split( ',' )
1118
+ .map( ( sel ) => sel + pseudoKey )
1119
+ .join( ',' );
1120
+
1121
+ // As pseudo classes such as :hover, :focus etc. have class-level
1122
+ // specificity, they must use the `:root :where()` wrapper. This.
1123
+ // caps the specificity at `0-1-0` to allow proper nesting of variations
1124
+ // and block type element styles.
1125
+ const pseudoRule = `:root :where(${ _selector }){${ pseudoDeclarations.join(
1126
+ ';'
1127
+ ) };}`;
1128
+
1129
+ ruleset += pseudoRule;
1130
+ }
1131
+ );
1132
+ }
1133
+ }
1134
+ );
1135
+ }
1136
+
1137
+ if ( options.layoutStyles ) {
1138
+ /* Add alignment / layout styles */
1139
+ ruleset =
1140
+ ruleset +
1141
+ '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }';
1142
+ ruleset =
1143
+ ruleset +
1144
+ '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }';
1145
+ ruleset =
1146
+ ruleset +
1147
+ '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }';
1148
+ }
1149
+
1150
+ if ( options.blockGap && hasBlockGapSupport ) {
1151
+ // Use fallback of `0.5em` just in case, however if there is blockGap support, there should nearly always be a real value.
1152
+ const gapValue =
1153
+ getGapCSSValue( tree?.styles?.spacing?.blockGap ) || '0.5em';
1154
+ ruleset =
1155
+ ruleset +
1156
+ `:root :where(.wp-site-blocks) > * { margin-block-start: ${ gapValue }; margin-block-end: 0; }`;
1157
+ ruleset =
1158
+ ruleset +
1159
+ ':root :where(.wp-site-blocks) > :first-child { margin-block-start: 0; }';
1160
+ ruleset =
1161
+ ruleset +
1162
+ ':root :where(.wp-site-blocks) > :last-child { margin-block-end: 0; }';
1163
+ }
1164
+
1165
+ if ( options.presets ) {
1166
+ nodesWithSettings.forEach( ( { selector, presets } ) => {
1167
+ if (
1168
+ ROOT_BLOCK_SELECTOR === selector ||
1169
+ ROOT_CSS_PROPERTIES_SELECTOR === selector
1170
+ ) {
1171
+ // Do not add extra specificity for top-level classes.
1172
+ selector = '';
1173
+ }
1174
+
1175
+ const classes = getPresetsClasses( selector, presets );
1176
+ if ( classes.length > 0 ) {
1177
+ ruleset += classes;
1178
+ }
1179
+ } );
1180
+ }
1181
+
1182
+ return ruleset;
1183
+ };
1184
+
1185
+ export function toSvgFilters( tree, blockSelectors ) {
1186
+ const nodesWithSettings = getNodesWithSettings( tree, blockSelectors );
1187
+ return nodesWithSettings.flatMap( ( { presets } ) => {
1188
+ return getPresetsSvgFilters( presets );
1189
+ } );
1190
+ }
1191
+
1192
+ const getSelectorsConfig = ( blockType, rootSelector ) => {
1193
+ if (
1194
+ blockType?.selectors &&
1195
+ Object.keys( blockType.selectors ).length > 0
1196
+ ) {
1197
+ return blockType.selectors;
1198
+ }
1199
+
1200
+ const config = { root: rootSelector };
1201
+ Object.entries( BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS ).forEach(
1202
+ ( [ featureKey, featureName ] ) => {
1203
+ const featureSelector = getBlockCSSSelector(
1204
+ blockType,
1205
+ featureKey
1206
+ );
1207
+
1208
+ if ( featureSelector ) {
1209
+ config[ featureName ] = featureSelector;
1210
+ }
1211
+ }
1212
+ );
1213
+
1214
+ return config;
1215
+ };
1216
+
1217
+ export const getBlockSelectors = (
1218
+ blockTypes,
1219
+ getBlockStyles,
1220
+ variationInstanceId
1221
+ ) => {
1222
+ const result = {};
1223
+ blockTypes.forEach( ( blockType ) => {
1224
+ const name = blockType.name;
1225
+ const selector = getBlockCSSSelector( blockType );
1226
+ let duotoneSelector = getBlockCSSSelector(
1227
+ blockType,
1228
+ 'filter.duotone'
1229
+ );
1230
+
1231
+ // Keep backwards compatibility for support.color.__experimentalDuotone.
1232
+ if ( ! duotoneSelector ) {
1233
+ const rootSelector = getBlockCSSSelector( blockType );
1234
+ const duotoneSupport = getBlockSupport(
1235
+ blockType,
1236
+ 'color.__experimentalDuotone',
1237
+ false
1238
+ );
1239
+ duotoneSelector =
1240
+ duotoneSupport && scopeSelector( rootSelector, duotoneSupport );
1241
+ }
1242
+
1243
+ const hasLayoutSupport =
1244
+ !! blockType?.supports?.layout ||
1245
+ !! blockType?.supports?.__experimentalLayout;
1246
+ const fallbackGapValue =
1247
+ blockType?.supports?.spacing?.blockGap?.__experimentalDefault;
1248
+
1249
+ const blockStyleVariations = getBlockStyles( name );
1250
+ const styleVariationSelectors = {};
1251
+ blockStyleVariations?.forEach( ( variation ) => {
1252
+ const variationSuffix = variationInstanceId
1253
+ ? `-${ variationInstanceId }`
1254
+ : '';
1255
+ const variationName = `${ variation.name }${ variationSuffix }`;
1256
+ const styleVariationSelector = getBlockStyleVariationSelector(
1257
+ variationName,
1258
+ selector
1259
+ );
1260
+
1261
+ styleVariationSelectors[ variationName ] = styleVariationSelector;
1262
+ } );
1263
+
1264
+ // For each block support feature add any custom selectors.
1265
+ const featureSelectors = getSelectorsConfig( blockType, selector );
1266
+
1267
+ result[ name ] = {
1268
+ duotoneSelector,
1269
+ fallbackGapValue,
1270
+ featureSelectors: Object.keys( featureSelectors ).length
1271
+ ? featureSelectors
1272
+ : undefined,
1273
+ hasLayoutSupport,
1274
+ name,
1275
+ selector,
1276
+ styleVariationSelectors: blockStyleVariations?.length
1277
+ ? styleVariationSelectors
1278
+ : undefined,
1279
+ };
1280
+ } );
1281
+
1282
+ return result;
1283
+ };
1284
+
1285
+ /**
1286
+ * If there is a separator block whose color is defined in theme.json via background,
1287
+ * update the separator color to the same value by using border color.
1288
+ *
1289
+ * @param {Object} config Theme.json configuration file object.
1290
+ * @return {Object} configTheme.json configuration file object updated.
1291
+ */
1292
+ function updateConfigWithSeparator( config ) {
1293
+ const needsSeparatorStyleUpdate =
1294
+ config.styles?.blocks?.[ 'core/separator' ] &&
1295
+ config.styles?.blocks?.[ 'core/separator' ].color?.background &&
1296
+ ! config.styles?.blocks?.[ 'core/separator' ].color?.text &&
1297
+ ! config.styles?.blocks?.[ 'core/separator' ].border?.color;
1298
+ if ( needsSeparatorStyleUpdate ) {
1299
+ return {
1300
+ ...config,
1301
+ styles: {
1302
+ ...config.styles,
1303
+ blocks: {
1304
+ ...config.styles.blocks,
1305
+ 'core/separator': {
1306
+ ...config.styles.blocks[ 'core/separator' ],
1307
+ color: {
1308
+ ...config.styles.blocks[ 'core/separator' ].color,
1309
+ text: config.styles?.blocks[ 'core/separator' ]
1310
+ .color.background,
1311
+ },
1312
+ },
1313
+ },
1314
+ },
1315
+ };
1316
+ }
1317
+ return config;
1318
+ }
1319
+
1320
+ export function processCSSNesting( css, blockSelector ) {
1321
+ let processedCSS = '';
1322
+
1323
+ if ( ! css || css.trim() === '' ) {
1324
+ return processedCSS;
1325
+ }
1326
+
1327
+ // Split CSS nested rules.
1328
+ const parts = css.split( '&' );
1329
+ parts.forEach( ( part ) => {
1330
+ if ( ! part || part.trim() === '' ) {
1331
+ return;
1332
+ }
1333
+
1334
+ const isRootCss = ! part.includes( '{' );
1335
+ if ( isRootCss ) {
1336
+ // If the part doesn't contain braces, it applies to the root level.
1337
+ processedCSS += `:root :where(${ blockSelector }){${ part.trim() }}`;
1338
+ } else {
1339
+ // If the part contains braces, it's a nested CSS rule.
1340
+ const splitPart = part.replace( '}', '' ).split( '{' );
1341
+ if ( splitPart.length !== 2 ) {
1342
+ return;
1343
+ }
1344
+
1345
+ const [ nestedSelector, cssValue ] = splitPart;
1346
+
1347
+ // Handle pseudo elements such as ::before, ::after, etc. Regex will also
1348
+ // capture any leading combinator such as >, +, or ~, as well as spaces.
1349
+ // This allows pseudo elements as descendants e.g. `.parent ::before`.
1350
+ const matches = nestedSelector.match( /([>+~\s]*::[a-zA-Z-]+)/ );
1351
+ const pseudoPart = matches ? matches[ 1 ] : '';
1352
+ const withoutPseudoElement = matches
1353
+ ? nestedSelector.replace( pseudoPart, '' ).trim()
1354
+ : nestedSelector.trim();
1355
+
1356
+ let combinedSelector;
1357
+ if ( withoutPseudoElement === '' ) {
1358
+ // Only contained a pseudo element to use the block selector to form
1359
+ // the final `:root :where()` selector.
1360
+ combinedSelector = blockSelector;
1361
+ } else {
1362
+ // If the nested selector is a descendant of the block scope it with the
1363
+ // block selector. Otherwise append it to the block selector.
1364
+ combinedSelector = nestedSelector.startsWith( ' ' )
1365
+ ? scopeSelector( blockSelector, withoutPseudoElement )
1366
+ : appendToSelector( blockSelector, withoutPseudoElement );
1367
+ }
1368
+
1369
+ // Build final rule, re-adding any pseudo element outside the `:where()`
1370
+ // to maintain valid CSS selector.
1371
+ processedCSS += `:root :where(${ combinedSelector })${ pseudoPart }{${ cssValue.trim() }}`;
1372
+ }
1373
+ } );
1374
+ return processedCSS;
1375
+ }
1376
+
1377
+ /**
1378
+ * Returns the global styles output using a global styles configuration.
1379
+ * If wishing to generate global styles and settings based on the
1380
+ * global styles config loaded in the editor context, use `useGlobalStylesOutput()`.
1381
+ * The use case for a custom config is to generate bespoke styles
1382
+ * and settings for previews, or other out-of-editor experiences.
1383
+ *
1384
+ * @param {Object} mergedConfig Global styles configuration.
1385
+ * @param {boolean} disableRootPadding Disable root padding styles.
1386
+ *
1387
+ * @return {Array} Array of stylesheets and settings.
1388
+ */
1389
+ export function useGlobalStylesOutputWithConfig(
1390
+ mergedConfig = {},
1391
+ disableRootPadding
1392
+ ) {
1393
+ const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' );
1394
+ const hasBlockGapSupport = blockGap !== null;
1395
+ const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support.
1396
+ const disableLayoutStyles = useSelect( ( select ) => {
1397
+ const { getSettings } = select( blockEditorStore );
1398
+ return !! getSettings().disableLayoutStyles;
1399
+ } );
1400
+
1401
+ const { getBlockStyles } = useSelect( blocksStore );
1402
+
1403
+ return useMemo( () => {
1404
+ if ( ! mergedConfig?.styles || ! mergedConfig?.settings ) {
1405
+ return [];
1406
+ }
1407
+ const updatedConfig = updateConfigWithSeparator( mergedConfig );
1408
+
1409
+ const blockSelectors = getBlockSelectors(
1410
+ getBlockTypes(),
1411
+ getBlockStyles
1412
+ );
1413
+
1414
+ const customProperties = toCustomProperties(
1415
+ updatedConfig,
1416
+ blockSelectors
1417
+ );
1418
+
1419
+ const globalStyles = toStyles(
1420
+ updatedConfig,
1421
+ blockSelectors,
1422
+ hasBlockGapSupport,
1423
+ hasFallbackGapSupport,
1424
+ disableLayoutStyles,
1425
+ disableRootPadding
1426
+ );
1427
+ const svgs = toSvgFilters( updatedConfig, blockSelectors );
1428
+
1429
+ const styles = [
1430
+ {
1431
+ css: customProperties,
1432
+ isGlobalStyles: true,
1433
+ },
1434
+ {
1435
+ css: globalStyles,
1436
+ isGlobalStyles: true,
1437
+ },
1438
+ // Load custom CSS in own stylesheet so that any invalid CSS entered in the input won't break all the global styles in the editor.
1439
+ {
1440
+ css: updatedConfig.styles.css ?? '',
1441
+ isGlobalStyles: true,
1442
+ },
1443
+ {
1444
+ assets: svgs,
1445
+ __unstableType: 'svg',
1446
+ isGlobalStyles: true,
1447
+ },
1448
+ ];
1449
+
1450
+ // Loop through the blocks to check if there are custom CSS values.
1451
+ // If there are, get the block selector and push the selector together with
1452
+ // the CSS value to the 'stylesheets' array.
1453
+ getBlockTypes().forEach( ( blockType ) => {
1454
+ if ( updatedConfig.styles.blocks[ blockType.name ]?.css ) {
1455
+ const selector = blockSelectors[ blockType.name ].selector;
1456
+ styles.push( {
1457
+ css: processCSSNesting(
1458
+ updatedConfig.styles.blocks[ blockType.name ]?.css,
1459
+ selector
1460
+ ),
1461
+ isGlobalStyles: true,
1462
+ } );
1463
+ }
1464
+ } );
1465
+
1466
+ return [ styles, updatedConfig.settings ];
1467
+ }, [
1468
+ hasBlockGapSupport,
1469
+ hasFallbackGapSupport,
1470
+ mergedConfig,
1471
+ disableLayoutStyles,
1472
+ disableRootPadding,
1473
+ getBlockStyles,
1474
+ ] );
1475
+ }
1476
+
1477
+ /**
1478
+ * Returns the global styles output based on the current state of global styles config loaded in the editor context.
1479
+ *
1480
+ * @param {boolean} disableRootPadding Disable root padding styles.
1481
+ *
1482
+ * @return {Array} Array of stylesheets and settings.
1483
+ */
1484
+ export function useGlobalStylesOutput( disableRootPadding = false ) {
1485
+ const { merged: mergedConfig } = useContext( GlobalStylesContext );
1486
+ return useGlobalStylesOutputWithConfig( mergedConfig, disableRootPadding );
1487
+ }