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