@wordpress/global-styles-engine 1.0.1-next.36001005c.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.
Files changed (148) hide show
  1. package/LICENSE.md +788 -0
  2. package/README.md +133 -0
  3. package/build/core/merge.js +61 -0
  4. package/build/core/merge.js.map +7 -0
  5. package/build/core/render.js +997 -0
  6. package/build/core/render.js.map +7 -0
  7. package/build/core/selectors.js +82 -0
  8. package/build/core/selectors.js.map +7 -0
  9. package/build/index.js +77 -0
  10. package/build/index.js.map +7 -0
  11. package/build/settings/get-palette.js +163 -0
  12. package/build/settings/get-palette.js.map +7 -0
  13. package/build/settings/get-setting.js +112 -0
  14. package/build/settings/get-setting.js.map +7 -0
  15. package/build/settings/get-style.js +40 -0
  16. package/build/settings/get-style.js.map +7 -0
  17. package/build/settings/set-setting.js +39 -0
  18. package/build/settings/set-setting.js.map +7 -0
  19. package/build/settings/set-style.js +38 -0
  20. package/build/settings/set-style.js.map +7 -0
  21. package/build/types.js +17 -0
  22. package/build/types.js.map +7 -0
  23. package/build/utils/background.js +53 -0
  24. package/build/utils/background.js.map +7 -0
  25. package/build/utils/common.js +418 -0
  26. package/build/utils/common.js.map +7 -0
  27. package/build/utils/duotone.js +90 -0
  28. package/build/utils/duotone.js.map +7 -0
  29. package/build/utils/fluid.js +173 -0
  30. package/build/utils/fluid.js.map +7 -0
  31. package/build/utils/gap.js +50 -0
  32. package/build/utils/gap.js.map +7 -0
  33. package/build/utils/layout.js +199 -0
  34. package/build/utils/layout.js.map +7 -0
  35. package/build/utils/object.js +50 -0
  36. package/build/utils/object.js.map +7 -0
  37. package/build/utils/spacing.js +38 -0
  38. package/build/utils/spacing.js.map +7 -0
  39. package/build/utils/string.js +31 -0
  40. package/build/utils/string.js.map +7 -0
  41. package/build/utils/typography.js +72 -0
  42. package/build/utils/typography.js.map +7 -0
  43. package/build-module/core/merge.js +27 -0
  44. package/build-module/core/merge.js.map +7 -0
  45. package/build-module/core/render.js +979 -0
  46. package/build-module/core/render.js.map +7 -0
  47. package/build-module/core/selectors.js +58 -0
  48. package/build-module/core/selectors.js.map +7 -0
  49. package/build-module/index.js +37 -0
  50. package/build-module/index.js.map +7 -0
  51. package/build-module/settings/get-palette.js +143 -0
  52. package/build-module/settings/get-palette.js.map +7 -0
  53. package/build-module/settings/get-setting.js +88 -0
  54. package/build-module/settings/get-setting.js.map +7 -0
  55. package/build-module/settings/get-style.js +16 -0
  56. package/build-module/settings/get-style.js.map +7 -0
  57. package/build-module/settings/set-setting.js +15 -0
  58. package/build-module/settings/set-setting.js.map +7 -0
  59. package/build-module/settings/set-style.js +14 -0
  60. package/build-module/settings/set-style.js.map +7 -0
  61. package/build-module/types.js +1 -0
  62. package/build-module/types.js.map +7 -0
  63. package/build-module/utils/background.js +28 -0
  64. package/build-module/utils/background.js.map +7 -0
  65. package/build-module/utils/common.js +382 -0
  66. package/build-module/utils/common.js.map +7 -0
  67. package/build-module/utils/duotone.js +63 -0
  68. package/build-module/utils/duotone.js.map +7 -0
  69. package/build-module/utils/fluid.js +147 -0
  70. package/build-module/utils/fluid.js.map +7 -0
  71. package/build-module/utils/gap.js +25 -0
  72. package/build-module/utils/gap.js.map +7 -0
  73. package/build-module/utils/layout.js +175 -0
  74. package/build-module/utils/layout.js.map +7 -0
  75. package/build-module/utils/object.js +25 -0
  76. package/build-module/utils/object.js.map +7 -0
  77. package/build-module/utils/spacing.js +14 -0
  78. package/build-module/utils/spacing.js.map +7 -0
  79. package/build-module/utils/string.js +7 -0
  80. package/build-module/utils/string.js.map +7 -0
  81. package/build-module/utils/typography.js +50 -0
  82. package/build-module/utils/typography.js.map +7 -0
  83. package/build-types/core/merge.d.ts +13 -0
  84. package/build-types/core/merge.d.ts.map +1 -0
  85. package/build-types/core/render.d.ts +84 -0
  86. package/build-types/core/render.d.ts.map +1 -0
  87. package/build-types/core/selectors.d.ts +19 -0
  88. package/build-types/core/selectors.d.ts.map +1 -0
  89. package/build-types/index.d.ts +13 -0
  90. package/build-types/index.d.ts.map +1 -0
  91. package/build-types/settings/get-palette.d.ts +13 -0
  92. package/build-types/settings/get-palette.d.ts.map +1 -0
  93. package/build-types/settings/get-setting.d.ts +3 -0
  94. package/build-types/settings/get-setting.d.ts.map +1 -0
  95. package/build-types/settings/get-style.d.ts +3 -0
  96. package/build-types/settings/get-style.d.ts.map +1 -0
  97. package/build-types/settings/set-setting.d.ts +3 -0
  98. package/build-types/settings/set-setting.d.ts.map +1 -0
  99. package/build-types/settings/set-style.d.ts +3 -0
  100. package/build-types/settings/set-style.d.ts.map +1 -0
  101. package/build-types/types.d.ts +333 -0
  102. package/build-types/types.d.ts.map +1 -0
  103. package/build-types/utils/background.d.ts +16 -0
  104. package/build-types/utils/background.d.ts.map +1 -0
  105. package/build-types/utils/common.d.ts +169 -0
  106. package/build-types/utils/common.d.ts.map +1 -0
  107. package/build-types/utils/duotone.d.ts +40 -0
  108. package/build-types/utils/duotone.d.ts.map +1 -0
  109. package/build-types/utils/fluid.d.ts +68 -0
  110. package/build-types/utils/fluid.d.ts.map +1 -0
  111. package/build-types/utils/gap.d.ts +27 -0
  112. package/build-types/utils/gap.d.ts.map +1 -0
  113. package/build-types/utils/layout.d.ts +156 -0
  114. package/build-types/utils/layout.d.ts.map +1 -0
  115. package/build-types/utils/object.d.ts +27 -0
  116. package/build-types/utils/object.d.ts.map +1 -0
  117. package/build-types/utils/spacing.d.ts +2 -0
  118. package/build-types/utils/spacing.d.ts.map +1 -0
  119. package/build-types/utils/string.d.ts +9 -0
  120. package/build-types/utils/string.d.ts.map +1 -0
  121. package/build-types/utils/typography.d.ts +28 -0
  122. package/build-types/utils/typography.d.ts.map +1 -0
  123. package/package.json +50 -0
  124. package/src/core/merge.ts +43 -0
  125. package/src/core/render.tsx +1696 -0
  126. package/src/core/selectors.ts +121 -0
  127. package/src/index.ts +29 -0
  128. package/src/settings/get-palette.ts +187 -0
  129. package/src/settings/get-setting.ts +99 -0
  130. package/src/settings/get-style.ts +29 -0
  131. package/src/settings/set-setting.ts +22 -0
  132. package/src/settings/set-style.ts +23 -0
  133. package/src/test/render.test.ts +792 -0
  134. package/src/test/typography-utils.test.ts +354 -0
  135. package/src/test/utils.test.ts +451 -0
  136. package/src/types.ts +408 -0
  137. package/src/utils/background.ts +39 -0
  138. package/src/utils/common.ts +671 -0
  139. package/src/utils/duotone.ts +95 -0
  140. package/src/utils/fluid.ts +311 -0
  141. package/src/utils/gap.ts +56 -0
  142. package/src/utils/layout.ts +174 -0
  143. package/src/utils/object.ts +64 -0
  144. package/src/utils/spacing.ts +13 -0
  145. package/src/utils/string.ts +15 -0
  146. package/src/utils/typography.ts +142 -0
  147. package/tsconfig.json +18 -0
  148. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,671 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { getCSSValueFromRawStyle } from '@wordpress/style-engine';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import type {
10
+ GlobalStylesSettings,
11
+ ThemeFileLink,
12
+ TypographyPreset,
13
+ UnresolvedValue,
14
+ GlobalStylesConfig,
15
+ } from '../types';
16
+ import { getTypographyFontSizeValue } from './typography';
17
+ import { getValueFromObjectPath } from './object';
18
+
19
+ export const ROOT_BLOCK_SELECTOR = 'body';
20
+ export const ROOT_CSS_PROPERTIES_SELECTOR = ':root';
21
+
22
+ export const PRESET_METADATA = [
23
+ {
24
+ path: [ 'color', 'palette' ],
25
+ valueKey: 'color',
26
+ cssVarInfix: 'color',
27
+ classes: [
28
+ { classSuffix: 'color', propertyName: 'color' },
29
+ {
30
+ classSuffix: 'background-color',
31
+ propertyName: 'background-color',
32
+ },
33
+ {
34
+ classSuffix: 'border-color',
35
+ propertyName: 'border-color',
36
+ },
37
+ ],
38
+ },
39
+ {
40
+ path: [ 'color', 'gradients' ],
41
+ valueKey: 'gradient',
42
+ cssVarInfix: 'gradient',
43
+ classes: [
44
+ {
45
+ classSuffix: 'gradient-background',
46
+ propertyName: 'background',
47
+ },
48
+ ],
49
+ },
50
+ {
51
+ path: [ 'color', 'duotone' ],
52
+ valueKey: 'colors',
53
+ cssVarInfix: 'duotone',
54
+ valueFunc: ( { slug }: { slug: string } ) =>
55
+ `url( '#wp-duotone-${ slug }' )`,
56
+ classes: [],
57
+ },
58
+ {
59
+ path: [ 'shadow', 'presets' ],
60
+ valueKey: 'shadow',
61
+ cssVarInfix: 'shadow',
62
+ classes: [],
63
+ },
64
+ {
65
+ path: [ 'typography', 'fontSizes' ],
66
+ valueFunc: (
67
+ preset: TypographyPreset,
68
+ settings: GlobalStylesSettings
69
+ ) => getTypographyFontSizeValue( preset, settings ),
70
+ valueKey: 'size',
71
+ cssVarInfix: 'font-size',
72
+ classes: [ { classSuffix: 'font-size', propertyName: 'font-size' } ],
73
+ },
74
+ {
75
+ path: [ 'typography', 'fontFamilies' ],
76
+ valueKey: 'fontFamily',
77
+ cssVarInfix: 'font-family',
78
+ classes: [
79
+ { classSuffix: 'font-family', propertyName: 'font-family' },
80
+ ],
81
+ },
82
+ {
83
+ path: [ 'spacing', 'spacingSizes' ],
84
+ valueKey: 'size',
85
+ cssVarInfix: 'spacing',
86
+ valueFunc: ( { size }: { size: string } ) => size,
87
+ classes: [],
88
+ },
89
+ {
90
+ path: [ 'border', 'radiusSizes' ],
91
+ valueKey: 'size',
92
+ cssVarInfix: 'border-radius',
93
+ classes: [],
94
+ },
95
+ ];
96
+
97
+ export const STYLE_PATH_TO_CSS_VAR_INFIX: Record< string, string > = {
98
+ 'color.background': 'color',
99
+ 'color.text': 'color',
100
+ 'filter.duotone': 'duotone',
101
+ 'elements.link.color.text': 'color',
102
+ 'elements.link.:hover.color.text': 'color',
103
+ 'elements.link.typography.fontFamily': 'font-family',
104
+ 'elements.link.typography.fontSize': 'font-size',
105
+ 'elements.button.color.text': 'color',
106
+ 'elements.button.color.background': 'color',
107
+ 'elements.caption.color.text': 'color',
108
+ 'elements.button.typography.fontFamily': 'font-family',
109
+ 'elements.button.typography.fontSize': 'font-size',
110
+ 'elements.heading.color': 'color',
111
+ 'elements.heading.color.background': 'color',
112
+ 'elements.heading.typography.fontFamily': 'font-family',
113
+ 'elements.heading.gradient': 'gradient',
114
+ 'elements.heading.color.gradient': 'gradient',
115
+ 'elements.h1.color': 'color',
116
+ 'elements.h1.color.background': 'color',
117
+ 'elements.h1.typography.fontFamily': 'font-family',
118
+ 'elements.h1.color.gradient': 'gradient',
119
+ 'elements.h2.color': 'color',
120
+ 'elements.h2.color.background': 'color',
121
+ 'elements.h2.typography.fontFamily': 'font-family',
122
+ 'elements.h2.color.gradient': 'gradient',
123
+ 'elements.h3.color': 'color',
124
+ 'elements.h3.color.background': 'color',
125
+ 'elements.h3.typography.fontFamily': 'font-family',
126
+ 'elements.h3.color.gradient': 'gradient',
127
+ 'elements.h4.color': 'color',
128
+ 'elements.h4.color.background': 'color',
129
+ 'elements.h4.typography.fontFamily': 'font-family',
130
+ 'elements.h4.color.gradient': 'gradient',
131
+ 'elements.h5.color': 'color',
132
+ 'elements.h5.color.background': 'color',
133
+ 'elements.h5.typography.fontFamily': 'font-family',
134
+ 'elements.h5.color.gradient': 'gradient',
135
+ 'elements.h6.color': 'color',
136
+ 'elements.h6.color.background': 'color',
137
+ 'elements.h6.typography.fontFamily': 'font-family',
138
+ 'elements.h6.color.gradient': 'gradient',
139
+ 'color.gradient': 'gradient',
140
+ shadow: 'shadow',
141
+ 'typography.fontSize': 'font-size',
142
+ 'typography.fontFamily': 'font-family',
143
+ };
144
+
145
+ /**
146
+ * Function that scopes a selector with another one. This works a bit like
147
+ * SCSS nesting except the `&` operator isn't supported.
148
+ *
149
+ * @example
150
+ * ```js
151
+ * const scope = '.a, .b .c';
152
+ * const selector = '> .x, .y';
153
+ * const merged = scopeSelector( scope, selector );
154
+ * // merged is '.a > .x, .a .y, .b .c > .x, .b .c .y'
155
+ * ```
156
+ *
157
+ * @param scope Selector to scope to.
158
+ * @param selector Original selector.
159
+ *
160
+ * @return Scoped selector.
161
+ */
162
+ export function scopeSelector( scope: string | undefined, selector: string ) {
163
+ if ( ! scope || ! selector ) {
164
+ return selector;
165
+ }
166
+
167
+ const scopes = scope.split( ',' );
168
+ const selectors = selector.split( ',' );
169
+
170
+ const selectorsScoped: string[] = [];
171
+ scopes.forEach( ( outer ) => {
172
+ selectors.forEach( ( inner ) => {
173
+ selectorsScoped.push( `${ outer.trim() } ${ inner.trim() }` );
174
+ } );
175
+ } );
176
+
177
+ return selectorsScoped.join( ', ' );
178
+ }
179
+
180
+ /**
181
+ * Scopes a collection of selectors for features and subfeatures.
182
+ *
183
+ * @example
184
+ * ```js
185
+ * const scope = '.custom-scope';
186
+ * const selectors = {
187
+ * color: '.wp-my-block p',
188
+ * typography: { fontSize: '.wp-my-block caption' },
189
+ * };
190
+ * const result = scopeFeatureSelector( scope, selectors );
191
+ * // result is {
192
+ * // color: '.custom-scope .wp-my-block p',
193
+ * // typography: { fonSize: '.custom-scope .wp-my-block caption' },
194
+ * // }
195
+ * ```
196
+ *
197
+ * @param scope Selector to scope collection of selectors with.
198
+ * @param selectors Collection of feature selectors e.g.
199
+ *
200
+ * @return Scoped collection of feature selectors.
201
+ */
202
+ export function scopeFeatureSelectors(
203
+ scope: string | undefined,
204
+ selectors: string | Record< string, string | Record< string, string > >
205
+ ) {
206
+ if ( ! scope || ! selectors ) {
207
+ return;
208
+ }
209
+
210
+ const featureSelectors: Record<
211
+ string,
212
+ string | Record< string, string >
213
+ > = {};
214
+
215
+ Object.entries( selectors ).forEach( ( [ feature, selector ] ) => {
216
+ if ( typeof selector === 'string' ) {
217
+ featureSelectors[ feature ] = scopeSelector( scope, selector );
218
+ }
219
+
220
+ if ( typeof selector === 'object' ) {
221
+ featureSelectors[ feature ] = {};
222
+
223
+ Object.entries( selector ).forEach(
224
+ ( [ subfeature, subfeatureSelector ] ) => {
225
+ // @ts-expect-error
226
+ featureSelectors[ feature ][ subfeature ] = scopeSelector(
227
+ scope,
228
+ subfeatureSelector as string
229
+ );
230
+ }
231
+ );
232
+ }
233
+ } );
234
+
235
+ return featureSelectors;
236
+ }
237
+
238
+ /**
239
+ * Appends a sub-selector to an existing one.
240
+ *
241
+ * Given the compounded `selector` "h1, h2, h3"
242
+ * and the `toAppend` selector ".some-class" the result will be
243
+ * "h1.some-class, h2.some-class, h3.some-class".
244
+ *
245
+ * @param selector Original selector.
246
+ * @param toAppend Selector to append.
247
+ *
248
+ * @return The new selector.
249
+ */
250
+ export function appendToSelector( selector: string, toAppend: string ) {
251
+ if ( ! selector.includes( ',' ) ) {
252
+ return selector + toAppend;
253
+ }
254
+ const selectors = selector.split( ',' );
255
+ const newSelectors = selectors.map( ( sel ) => sel + toAppend );
256
+ return newSelectors.join( ',' );
257
+ }
258
+
259
+ /**
260
+ * Generates the selector for a block style variation by creating the
261
+ * appropriate CSS class and adding it to the ancestor portion of the block's
262
+ * selector.
263
+ *
264
+ * For example, take the Button block which has a compound selector:
265
+ * `.wp-block-button .wp-block-button__link`. With a variation named 'custom',
266
+ * the class `.is-style-custom` should be added to the `.wp-block-button`
267
+ * ancestor only.
268
+ *
269
+ * This function will take into account comma separated and complex selectors.
270
+ *
271
+ * @param variation Name for the variation.
272
+ * @param blockSelector CSS selector for the block.
273
+ *
274
+ * @return CSS selector for the block style variation.
275
+ */
276
+ export function getBlockStyleVariationSelector(
277
+ variation: string,
278
+ blockSelector: string
279
+ ) {
280
+ const variationClass = `.is-style-${ variation }`;
281
+
282
+ if ( ! blockSelector ) {
283
+ return variationClass;
284
+ }
285
+
286
+ const ancestorRegex = /((?::\([^)]+\))?\s*)([^\s:]+)/;
287
+ const addVariationClass = (
288
+ _match: string,
289
+ group1: string,
290
+ group2: string
291
+ ) => {
292
+ return group1 + group2 + variationClass;
293
+ };
294
+
295
+ const result = blockSelector
296
+ .split( ',' )
297
+ .map( ( part ) => part.replace( ancestorRegex, addVariationClass ) );
298
+
299
+ return result.join( ',' );
300
+ }
301
+
302
+ /**
303
+ * Resolves ref values in theme JSON.
304
+ *
305
+ * @param ruleValue A block style value that may contain a reference to a theme.json value.
306
+ * @param tree A theme.json object.
307
+ * @return The resolved value or incoming ruleValue.
308
+ */
309
+ export function getResolvedRefValue(
310
+ ruleValue: UnresolvedValue,
311
+ tree?: GlobalStylesConfig
312
+ ): UnresolvedValue {
313
+ if ( ! ruleValue || ! tree ) {
314
+ return ruleValue;
315
+ }
316
+
317
+ /*
318
+ * Where the rule value is an object with a 'ref' property pointing
319
+ * to a path, this converts that path into the value at that path.
320
+ * For example: { "ref": "style.color.background" } => "#fff".
321
+ */
322
+ if (
323
+ typeof ruleValue === 'object' &&
324
+ 'ref' in ruleValue &&
325
+ ruleValue?.ref
326
+ ) {
327
+ const resolvedRuleValue = getCSSValueFromRawStyle(
328
+ getValueFromObjectPath( tree, ruleValue.ref )
329
+ ) as UnresolvedValue;
330
+
331
+ /*
332
+ * Presence of another ref indicates a reference to another dynamic value.
333
+ * Pointing to another dynamic value is not supported.
334
+ */
335
+ if (
336
+ typeof resolvedRuleValue === 'object' &&
337
+ resolvedRuleValue !== null &&
338
+ 'ref' in resolvedRuleValue &&
339
+ resolvedRuleValue?.ref
340
+ ) {
341
+ return undefined;
342
+ }
343
+
344
+ if ( resolvedRuleValue === undefined ) {
345
+ return ruleValue;
346
+ }
347
+
348
+ return resolvedRuleValue;
349
+ }
350
+ return ruleValue;
351
+ }
352
+
353
+ /**
354
+ * Looks up a theme file URI based on a relative path.
355
+ *
356
+ * @param file A relative path.
357
+ * @param themeFileURIs A collection of absolute theme file URIs and their corresponding file paths.
358
+ * @return A resolved theme file URI, if one is found in the themeFileURIs collection.
359
+ */
360
+ export function getResolvedThemeFilePath(
361
+ file: string,
362
+ themeFileURIs?: ThemeFileLink[]
363
+ ) {
364
+ if ( ! file || ! themeFileURIs || ! Array.isArray( themeFileURIs ) ) {
365
+ return file;
366
+ }
367
+
368
+ const uri = themeFileURIs.find(
369
+ ( themeFileUri ) => themeFileUri?.name === file
370
+ );
371
+
372
+ if ( ! uri?.href ) {
373
+ return file;
374
+ }
375
+
376
+ return uri?.href;
377
+ }
378
+
379
+ /**
380
+ * Resolves ref and relative path values in theme JSON.
381
+ *
382
+ * @param ruleValue A block style value that may contain a reference to a theme.json value.
383
+ * @param tree A theme.json object.
384
+ * @return The resolved value or incoming ruleValue.
385
+ */
386
+ export function getResolvedValue(
387
+ ruleValue: UnresolvedValue,
388
+ tree: GlobalStylesConfig | undefined
389
+ ) {
390
+ if ( ! ruleValue || ! tree ) {
391
+ return ruleValue;
392
+ }
393
+
394
+ // Resolve ref values.
395
+ const resolvedValue = getResolvedRefValue( ruleValue, tree );
396
+
397
+ // Resolve relative paths.
398
+ if (
399
+ typeof resolvedValue === 'object' &&
400
+ resolvedValue !== null &&
401
+ 'url' in resolvedValue &&
402
+ resolvedValue?.url
403
+ ) {
404
+ resolvedValue.url = getResolvedThemeFilePath(
405
+ resolvedValue.url,
406
+ tree?._links?.[ 'wp:theme-file' ]
407
+ );
408
+ }
409
+
410
+ return resolvedValue;
411
+ }
412
+
413
+ function findInPresetsBy(
414
+ settings: GlobalStylesSettings,
415
+ blockName?: string,
416
+ presetPath: string[] = [],
417
+ presetProperty: string = 'slug',
418
+ presetValueValue?: string
419
+ ) {
420
+ // Block presets take priority above root level presets.
421
+ const orderedPresetsByOrigin = [
422
+ blockName
423
+ ? getValueFromObjectPath( settings, [
424
+ 'blocks',
425
+ blockName,
426
+ ...presetPath,
427
+ ] )
428
+ : undefined,
429
+ getValueFromObjectPath( settings, presetPath ),
430
+ ].filter( Boolean );
431
+
432
+ for ( const presetByOrigin of orderedPresetsByOrigin ) {
433
+ if ( presetByOrigin ) {
434
+ // Preset origins ordered by priority.
435
+ const origins = [ 'custom', 'theme', 'default' ];
436
+ for ( const origin of origins ) {
437
+ // @ts-expect-error
438
+ const presets = presetByOrigin[ origin ];
439
+ if ( presets ) {
440
+ const presetObject = presets.find(
441
+ ( preset: any ) =>
442
+ preset[ presetProperty ] === presetValueValue
443
+ );
444
+ if ( presetObject ) {
445
+ if ( presetProperty === 'slug' ) {
446
+ return presetObject;
447
+ }
448
+ // If there is a highest priority preset with the same slug but different value the preset we found was overwritten and should be ignored.
449
+ const highestPresetObjectWithSameSlug = findInPresetsBy(
450
+ settings,
451
+ blockName,
452
+ presetPath,
453
+ 'slug',
454
+ presetObject.slug
455
+ );
456
+ if (
457
+ highestPresetObjectWithSameSlug[
458
+ presetProperty
459
+ ] === presetObject[ presetProperty ]
460
+ ) {
461
+ return presetObject;
462
+ }
463
+ return undefined;
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ }
470
+
471
+ function getValueFromPresetVariable(
472
+ features: GlobalStylesConfig,
473
+ blockName?: string,
474
+ variable?: string,
475
+ [ presetType, slug ]: string[] = []
476
+ ) {
477
+ const metadata = PRESET_METADATA.find(
478
+ ( data ) => data.cssVarInfix === presetType
479
+ );
480
+ if ( ! metadata || ! features.settings ) {
481
+ return variable;
482
+ }
483
+
484
+ const presetObject = findInPresetsBy(
485
+ features.settings,
486
+ blockName,
487
+ metadata.path,
488
+ 'slug',
489
+ slug
490
+ );
491
+
492
+ if ( presetObject ) {
493
+ const { valueKey } = metadata;
494
+ const result = presetObject[ valueKey ];
495
+ return getValueFromVariable( features, blockName, result );
496
+ }
497
+
498
+ return variable;
499
+ }
500
+
501
+ function getValueFromCustomVariable(
502
+ features: GlobalStylesConfig,
503
+ blockName?: string,
504
+ variable?: string,
505
+ path: string[] = []
506
+ ): string | undefined {
507
+ const result =
508
+ ( blockName
509
+ ? getValueFromObjectPath( features?.settings ?? {}, [
510
+ 'blocks',
511
+ blockName,
512
+ 'custom',
513
+ ...path,
514
+ ] )
515
+ : undefined ) ??
516
+ getValueFromObjectPath( features?.settings ?? {}, [
517
+ 'custom',
518
+ ...path,
519
+ ] );
520
+ if ( ! result ) {
521
+ return variable;
522
+ }
523
+ // A variable may reference another variable so we need recursion until we find the value.
524
+ return getValueFromVariable( features, blockName, result as string );
525
+ }
526
+
527
+ /**
528
+ * Attempts to fetch the value of a theme.json CSS variable.
529
+ *
530
+ * This function resolves CSS variable references in two formats:
531
+ * - User format: `var:preset|color|red` or `var:custom|spacing|small`
532
+ * - Theme format: `var(--wp--preset--color--red)` or `var(--wp--custom--spacing--small)`
533
+ *
534
+ * It also handles ref-style variables in the format `{ ref: "path.to.value" }`.
535
+ *
536
+ * @param features GlobalStylesContext config (user, base, or merged). Represents the theme.json tree.
537
+ * @param blockName The name of a block as represented in the styles property. E.g., 'root' for root-level, and 'core/block-name' for blocks.
538
+ * @param variable An incoming style value. A CSS var value is expected, but it could be any value.
539
+ * @return The value of the CSS var, if found. If not found, returns the original variable argument.
540
+ */
541
+ export function getValueFromVariable(
542
+ features: GlobalStylesConfig,
543
+ blockName?: string,
544
+ variable?: string | UnresolvedValue
545
+ ): any {
546
+ if ( ! variable || typeof variable !== 'string' ) {
547
+ if (
548
+ typeof variable === 'object' &&
549
+ variable !== null &&
550
+ 'ref' in variable &&
551
+ typeof variable.ref === 'string'
552
+ ) {
553
+ const resolvedVariable = getValueFromObjectPath(
554
+ features,
555
+ variable.ref
556
+ );
557
+ // Presence of another ref indicates a reference to another dynamic value.
558
+ // Pointing to another dynamic value is not supported.
559
+ if (
560
+ ! resolvedVariable ||
561
+ ( typeof resolvedVariable === 'object' &&
562
+ 'ref' in resolvedVariable )
563
+ ) {
564
+ return resolvedVariable;
565
+ }
566
+ variable = resolvedVariable as string;
567
+ } else {
568
+ return variable;
569
+ }
570
+ }
571
+ const USER_VALUE_PREFIX = 'var:';
572
+ const THEME_VALUE_PREFIX = 'var(--wp--';
573
+ const THEME_VALUE_SUFFIX = ')';
574
+
575
+ let parsedVar;
576
+
577
+ if ( variable.startsWith( USER_VALUE_PREFIX ) ) {
578
+ parsedVar = variable.slice( USER_VALUE_PREFIX.length ).split( '|' );
579
+ } else if (
580
+ variable.startsWith( THEME_VALUE_PREFIX ) &&
581
+ variable.endsWith( THEME_VALUE_SUFFIX )
582
+ ) {
583
+ parsedVar = variable
584
+ .slice( THEME_VALUE_PREFIX.length, -THEME_VALUE_SUFFIX.length )
585
+ .split( '--' );
586
+ } else {
587
+ // We don't know how to parse the value: either is raw of uses complex CSS such as `calc(1px * var(--wp--variable) )`
588
+ return variable;
589
+ }
590
+
591
+ const [ type, ...path ] = parsedVar;
592
+ if ( type === 'preset' ) {
593
+ return getValueFromPresetVariable(
594
+ features,
595
+ blockName,
596
+ variable,
597
+ path
598
+ );
599
+ }
600
+ if ( type === 'custom' ) {
601
+ return getValueFromCustomVariable(
602
+ features,
603
+ blockName,
604
+ variable,
605
+ path
606
+ );
607
+ }
608
+ return variable;
609
+ }
610
+
611
+ /**
612
+ * Encodes a value to a preset variable format if it matches a preset.
613
+ * This is the inverse operation of getValueFromVariable().
614
+ *
615
+ * @example
616
+ * ```js
617
+ * const presetVar = getPresetVariableFromValue(
618
+ * globalStyles.settings,
619
+ * 'core/paragraph',
620
+ * 'color.text',
621
+ * '#ff0000'
622
+ * );
623
+ * // If #ff0000 is the 'red' preset color, returns 'var:preset|color|red'
624
+ * // Otherwise returns '#ff0000'
625
+ * ```
626
+ *
627
+ * @param features GlobalStylesContext settings object.
628
+ * @param blockName The name of a block (e.g., 'core/paragraph').
629
+ * @param variableStylePath The style path (e.g., 'color.text', 'typography.fontSize').
630
+ * @param presetPropertyValue The value to encode (e.g., '#ff0000').
631
+ * @return The preset variable if found, otherwise the original value.
632
+ */
633
+ export function getPresetVariableFromValue(
634
+ features: GlobalStylesSettings,
635
+ blockName: string | undefined,
636
+ variableStylePath: string,
637
+ presetPropertyValue: any
638
+ ): any {
639
+ if ( ! presetPropertyValue ) {
640
+ return presetPropertyValue;
641
+ }
642
+
643
+ const cssVarInfix = STYLE_PATH_TO_CSS_VAR_INFIX[ variableStylePath ];
644
+
645
+ const metadata = PRESET_METADATA.find(
646
+ ( data ) => data.cssVarInfix === cssVarInfix
647
+ );
648
+
649
+ if ( ! metadata ) {
650
+ // The property doesn't have preset data
651
+ // so the value should be returned as it is.
652
+ return presetPropertyValue;
653
+ }
654
+ const { valueKey, path } = metadata;
655
+
656
+ const presetObject = findInPresetsBy(
657
+ features,
658
+ blockName,
659
+ path,
660
+ valueKey,
661
+ presetPropertyValue
662
+ );
663
+
664
+ if ( ! presetObject ) {
665
+ // Value wasn't found in the presets,
666
+ // so it must be a custom value.
667
+ return presetPropertyValue;
668
+ }
669
+
670
+ return `var:preset|${ cssVarInfix }|${ presetObject.slug }`;
671
+ }