@teleporthq/teleport-plugin-css 0.43.0-alpha.0 → 0.43.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/__tests__/component-scoped.ts +4 -2
- package/__tests__/style-sheet.ts +3 -3
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +488 -43
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/style-sheet.d.ts +1 -0
- package/dist/cjs/style-sheet.d.ts.map +1 -1
- package/dist/cjs/style-sheet.js +41 -10
- package/dist/cjs/style-sheet.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +3 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +29 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +489 -44
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/style-sheet.d.ts +1 -0
- package/dist/esm/style-sheet.d.ts.map +1 -1
- package/dist/esm/style-sheet.js +41 -10
- package/dist/esm/style-sheet.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +25 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +5 -5
- package/src/index.ts +626 -58
- package/src/style-sheet.ts +58 -12
- package/src/utils.ts +36 -0
package/src/index.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { join, relative } from 'path'
|
|
1
2
|
import { StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
StyleUtils,
|
|
5
|
+
StyleBuilders,
|
|
6
|
+
HASTUtils,
|
|
7
|
+
HASTBuilders,
|
|
8
|
+
ASTUtils,
|
|
9
|
+
createBinaryExpression,
|
|
10
|
+
} from '@teleporthq/teleport-plugin-common'
|
|
3
11
|
import * as types from '@babel/types'
|
|
4
12
|
import {
|
|
5
13
|
ComponentPluginFactory,
|
|
@@ -12,8 +20,17 @@ import {
|
|
|
12
20
|
UIDLElementNodeReferenceStyles,
|
|
13
21
|
UIDLStyleMediaQueryScreenSizeCondition,
|
|
14
22
|
PluginCSS,
|
|
23
|
+
UIDLElement,
|
|
24
|
+
UIDLElementNode,
|
|
25
|
+
UIDLExpressionValue,
|
|
26
|
+
StateDefaultValueTypes,
|
|
27
|
+
PropDefaultValueTypes,
|
|
28
|
+
UIDLStyleInlineAsset,
|
|
29
|
+
UIDLFontAsset,
|
|
30
|
+
UIDLGlobalStateDefinition,
|
|
15
31
|
} from '@teleporthq/teleport-types'
|
|
16
32
|
import { createStyleSheetPlugin } from './style-sheet'
|
|
33
|
+
import { createConditionalStatement } from './utils'
|
|
17
34
|
|
|
18
35
|
interface CSSPluginConfig {
|
|
19
36
|
chunkName: string
|
|
@@ -21,11 +38,30 @@ interface CSSPluginConfig {
|
|
|
21
38
|
componentDecoratorChunkName: string
|
|
22
39
|
inlineStyleAttributeKey: string // style vs :style vs ...
|
|
23
40
|
classAttributeName: string // class vs className
|
|
24
|
-
forceScoping: boolean // class names get the component name prefix
|
|
25
41
|
templateStyle: 'html' | 'jsx'
|
|
26
42
|
declareDependency: 'import' | 'decorator' | 'none'
|
|
27
43
|
dynamicVariantPrefix?: string
|
|
28
44
|
staticPropReferences?: boolean
|
|
45
|
+
standaloneHtmlComponents?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const prefixUrlPathsInCss = (css: string, folderPath: string[]): string => {
|
|
49
|
+
const relativePrefix = relative(join(...folderPath), './')
|
|
50
|
+
if (!relativePrefix) {
|
|
51
|
+
return css
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return css.replace(/url\(["']?(.*?)["']?\)/g, (match, url) => {
|
|
55
|
+
if (
|
|
56
|
+
url.startsWith('http') ||
|
|
57
|
+
url.startsWith('data:') ||
|
|
58
|
+
url.startsWith('../') ||
|
|
59
|
+
url.startsWith('#')
|
|
60
|
+
) {
|
|
61
|
+
return match
|
|
62
|
+
}
|
|
63
|
+
return `url("${join(relativePrefix, url)}")`
|
|
64
|
+
})
|
|
29
65
|
}
|
|
30
66
|
|
|
31
67
|
const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
@@ -37,15 +73,25 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
37
73
|
classAttributeName = 'class',
|
|
38
74
|
templateStyle = 'html',
|
|
39
75
|
declareDependency = 'none',
|
|
40
|
-
forceScoping = false,
|
|
41
76
|
dynamicVariantPrefix,
|
|
42
77
|
staticPropReferences = false,
|
|
78
|
+
standaloneHtmlComponents = false,
|
|
43
79
|
} = config || {}
|
|
44
80
|
|
|
45
81
|
const cssPlugin: ComponentPlugin = async (structure) => {
|
|
46
82
|
const { uidl, chunks, dependencies, options } = structure
|
|
47
|
-
const {
|
|
48
|
-
|
|
83
|
+
const {
|
|
84
|
+
node,
|
|
85
|
+
styleSetDefinitions: componentStyleSet = {},
|
|
86
|
+
propDefinitions = {},
|
|
87
|
+
stateDefinitions = {},
|
|
88
|
+
} = uidl
|
|
89
|
+
const {
|
|
90
|
+
projectStyleSet,
|
|
91
|
+
designLanguage: { tokens = {} } = {},
|
|
92
|
+
isRootComponent,
|
|
93
|
+
globalStateDefinitions = {},
|
|
94
|
+
} = options || {}
|
|
49
95
|
const {
|
|
50
96
|
styleSetDefinitions = {},
|
|
51
97
|
fileName: projectStyleSheetName,
|
|
@@ -71,7 +117,7 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
71
117
|
(chunk) => chunk.name === componentDecoratorChunkName
|
|
72
118
|
)
|
|
73
119
|
|
|
74
|
-
const
|
|
120
|
+
const jsxNodesLookup = templateChunk.meta.nodesLookup as Record<
|
|
75
121
|
string,
|
|
76
122
|
HastNode | types.JSXElement
|
|
77
123
|
>
|
|
@@ -85,45 +131,51 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
85
131
|
string,
|
|
86
132
|
Array<{ [x: string]: Record<string, string | number> }>
|
|
87
133
|
> = {}
|
|
134
|
+
const usedProjectStyleIds: Set<string> = new Set()
|
|
88
135
|
|
|
89
|
-
|
|
136
|
+
const generateStylesForElementNode = (element: UIDLElement) => {
|
|
90
137
|
const classNamesToAppend: Set<string> = new Set()
|
|
91
|
-
const dynamicVariantsToAppend: Set<string> = new Set()
|
|
138
|
+
const dynamicVariantsToAppend: Set<string | types.ConditionalExpression> = new Set()
|
|
92
139
|
const {
|
|
93
140
|
style = {},
|
|
94
141
|
key,
|
|
95
142
|
referencedStyles = {},
|
|
96
|
-
dependency,
|
|
97
143
|
attrs = {},
|
|
98
144
|
elementType,
|
|
145
|
+
dependency,
|
|
99
146
|
} = element
|
|
100
|
-
const
|
|
147
|
+
const { dynamicStyleBindings } = element
|
|
148
|
+
const hasDynamicBindings =
|
|
149
|
+
dynamicStyleBindings && Object.keys(dynamicStyleBindings).length > 0
|
|
101
150
|
|
|
102
|
-
|
|
151
|
+
const root = jsxNodesLookup[key]
|
|
152
|
+
if (!root) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Refer to line 323 all component scoped styles are appended with component name by default
|
|
157
|
+
if (dependency?.type === 'local') {
|
|
103
158
|
StyleBuilders.setPropValueForCompStyle({
|
|
104
159
|
attrs,
|
|
105
|
-
|
|
106
|
-
jsxNodesLookup: templateLookup,
|
|
160
|
+
root,
|
|
107
161
|
templateStyle,
|
|
162
|
+
// elementType is used here in-order to target the component name that the class is actually defined.
|
|
163
|
+
// Here we are appendigng to the node where the component is being called.
|
|
108
164
|
getClassName: (styleName: string) =>
|
|
109
|
-
|
|
165
|
+
StringUtils.camelCaseToDashCase(elementType + styleName),
|
|
110
166
|
})
|
|
111
167
|
}
|
|
112
168
|
|
|
113
169
|
if (
|
|
114
170
|
Object.keys(style).length === 0 &&
|
|
115
171
|
Object.keys(referencedStyles).length === 0 &&
|
|
116
|
-
Object.keys(componentStyleSet).length === 0
|
|
172
|
+
Object.keys(componentStyleSet).length === 0 &&
|
|
173
|
+
!hasDynamicBindings
|
|
117
174
|
) {
|
|
118
175
|
return
|
|
119
176
|
}
|
|
120
177
|
|
|
121
|
-
const
|
|
122
|
-
if (!root) {
|
|
123
|
-
throw new PluginCSS(`Node - ${key} is missing from the template chunk`)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const className = getClassName(forceScoping, uidl.name, elementClassName)
|
|
178
|
+
const className = StringUtils.camelCaseToDashCase(key)
|
|
127
179
|
|
|
128
180
|
const { staticStyles, dynamicStyles, tokenStyles } =
|
|
129
181
|
UIDLUtils.splitDynamicAndStaticStyles(style)
|
|
@@ -138,28 +190,58 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
138
190
|
classNamesToAppend.add(className)
|
|
139
191
|
}
|
|
140
192
|
|
|
193
|
+
let jsxInlineStyles: Record<string, unknown> | null = null
|
|
194
|
+
|
|
141
195
|
if (Object.keys(dynamicStyles).length > 0) {
|
|
196
|
+
for (const styleValue of Object.values(dynamicStyles)) {
|
|
197
|
+
if (
|
|
198
|
+
styleValue.type === 'dynamic' &&
|
|
199
|
+
styleValue.content.referenceType === 'global' &&
|
|
200
|
+
templateChunk.meta.globalReferences
|
|
201
|
+
) {
|
|
202
|
+
;(templateChunk.meta.globalReferences as string[]).push(styleValue.content.id)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
142
206
|
/* If dynamic styles are on nested-styles they are unfortunately lost,
|
|
143
207
|
since inline style does not support that */
|
|
144
208
|
if (templateStyle === 'html') {
|
|
145
209
|
const inlineStyles = createDynamicInlineStyle(dynamicStyles)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
210
|
+
const bindingParts = hasDynamicBindings
|
|
211
|
+
? createDynamicBindingInlineStyle(dynamicStyleBindings)
|
|
212
|
+
: ''
|
|
213
|
+
const combined = bindingParts ? `${inlineStyles}, ${bindingParts}` : inlineStyles
|
|
214
|
+
HASTUtils.addAttributeToNode(root as HastNode, inlineStyleAttributeKey, `{${combined}}`)
|
|
151
215
|
} else {
|
|
152
|
-
|
|
216
|
+
jsxInlineStyles = UIDLUtils.transformDynamicStyles(dynamicStyles, (styleValue) =>
|
|
153
217
|
StyleBuilders.createDynamicStyleExpression(styleValue, propsPrefix)
|
|
154
218
|
)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
219
|
+
}
|
|
220
|
+
} else if (hasDynamicBindings && templateStyle === 'html') {
|
|
221
|
+
const bindingParts = createDynamicBindingInlineStyle(dynamicStyleBindings)
|
|
222
|
+
HASTUtils.addAttributeToNode(root as HastNode, inlineStyleAttributeKey, `{${bindingParts}}`)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (hasDynamicBindings && templateStyle !== 'html') {
|
|
226
|
+
if (!jsxInlineStyles) {
|
|
227
|
+
jsxInlineStyles = {}
|
|
228
|
+
}
|
|
229
|
+
for (const [cssProperty, binding] of Object.entries(dynamicStyleBindings)) {
|
|
230
|
+
const camelCaseProperty = cssProperty.replace(/-([a-z])/g, (_, letter: string) =>
|
|
231
|
+
letter.toUpperCase()
|
|
159
232
|
)
|
|
233
|
+
jsxInlineStyles[camelCaseProperty] = StyleBuilders.createDynamicBindingExpression(binding)
|
|
160
234
|
}
|
|
161
235
|
}
|
|
162
236
|
|
|
237
|
+
if (jsxInlineStyles) {
|
|
238
|
+
ASTUtils.addAttributeToJSXTag(
|
|
239
|
+
root as types.JSXElement,
|
|
240
|
+
inlineStyleAttributeKey,
|
|
241
|
+
jsxInlineStyles
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
163
245
|
Object.values(referencedStyles).forEach((styleRef: UIDLElementNodeReferenceStyles) => {
|
|
164
246
|
switch (styleRef.content.mapType) {
|
|
165
247
|
case 'inlined': {
|
|
@@ -206,24 +288,21 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
206
288
|
propDefinitions[styleRef.content.content.content.id]?.defaultValue
|
|
207
289
|
|
|
208
290
|
if (defaultPropValue) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
propDefinitions[styleRef.content.content.content.id].defaultValue = getClassName(
|
|
212
|
-
forceScoping,
|
|
213
|
-
uidl.name,
|
|
214
|
-
String(defaultPropValue)
|
|
215
|
-
)
|
|
291
|
+
propDefinitions[styleRef.content.content.content.id].defaultValue =
|
|
292
|
+
StringUtils.camelCaseToDashCase(String(defaultPropValue))
|
|
216
293
|
}
|
|
217
294
|
|
|
295
|
+
// staticPropReferences flag is only used for just html-code generation.
|
|
296
|
+
// This is used to append the class name to the node where the component is being called.
|
|
297
|
+
// Instead of how frameworks handle them using props at runtime. This makes the props
|
|
298
|
+
// to behave statically during the generation time instead by appending them directly.
|
|
218
299
|
if (staticPropReferences) {
|
|
219
|
-
if (
|
|
300
|
+
if (defaultPropValue === undefined || typeof defaultPropValue !== 'string') {
|
|
220
301
|
return
|
|
221
302
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
}
|
|
303
|
+
classNamesToAppend.add(
|
|
304
|
+
StringUtils.camelCaseToDashCase(uidl.name + defaultPropValue)
|
|
305
|
+
)
|
|
227
306
|
} else {
|
|
228
307
|
dynamicVariantsToAppend.add(styleRef.content.content.content.id)
|
|
229
308
|
}
|
|
@@ -239,7 +318,7 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
239
318
|
)
|
|
240
319
|
}
|
|
241
320
|
classNamesToAppend.add(
|
|
242
|
-
|
|
321
|
+
StringUtils.camelCaseToDashCase(uidl.name + styleRef.content.content.content.id)
|
|
243
322
|
)
|
|
244
323
|
}
|
|
245
324
|
|
|
@@ -250,12 +329,246 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
250
329
|
const { content } = styleRef
|
|
251
330
|
const referedStyle = styleSetDefinitions[content.referenceId]
|
|
252
331
|
if (!referedStyle) {
|
|
253
|
-
|
|
254
|
-
`Style used from global stylesheet is missing - ${content.referenceId}`
|
|
255
|
-
)
|
|
332
|
+
return
|
|
256
333
|
}
|
|
257
334
|
|
|
258
|
-
|
|
335
|
+
usedProjectStyleIds.add(content.referenceId)
|
|
336
|
+
|
|
337
|
+
if (styleRef.content.condition) {
|
|
338
|
+
if (templateStyle === 'html') {
|
|
339
|
+
const {
|
|
340
|
+
value: staticValue,
|
|
341
|
+
reference,
|
|
342
|
+
expression: { conditions, matchingCriteria },
|
|
343
|
+
} = styleRef.content.condition
|
|
344
|
+
|
|
345
|
+
const {
|
|
346
|
+
content: { referenceType, id, refPath = [] },
|
|
347
|
+
} = reference
|
|
348
|
+
|
|
349
|
+
switch (referenceType) {
|
|
350
|
+
case 'prop': {
|
|
351
|
+
const usedProp = propDefinitions[id]
|
|
352
|
+
if (usedProp === undefined || usedProp.defaultValue === undefined) {
|
|
353
|
+
throw new PluginCSS(`Prop with ${id} is missing in the propDefinitions.`)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let defaultValue = usedProp.defaultValue
|
|
357
|
+
let operandDefaultValue: StateDefaultValueTypes | PropDefaultValueTypes | string
|
|
358
|
+
for (const path of refPath) {
|
|
359
|
+
defaultValue = (defaultValue as Record<string, unknown[]>)?.[path]
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// If defaultValue is undefined or null after path traversal, use original default
|
|
363
|
+
defaultValue = defaultValue ?? usedProp.defaultValue
|
|
364
|
+
|
|
365
|
+
// our conditions can be of type 'expr' or they can be a dynamic reference
|
|
366
|
+
const rightSideCondition = conditions[0].operand
|
|
367
|
+
const rightSideConditionType = typeof rightSideCondition
|
|
368
|
+
if (rightSideConditionType === 'object') {
|
|
369
|
+
const type = (
|
|
370
|
+
rightSideCondition as UIDLDynamicReference | UIDLExpressionValue
|
|
371
|
+
).type
|
|
372
|
+
if (type === 'dynamic') {
|
|
373
|
+
const dynamicRef = rightSideCondition as UIDLDynamicReference
|
|
374
|
+
if (dynamicRef.content.referenceType === 'prop') {
|
|
375
|
+
const prop = propDefinitions[dynamicRef.content.id]
|
|
376
|
+
if (prop && prop.defaultValue) {
|
|
377
|
+
operandDefaultValue = prop.defaultValue
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (dynamicRef.content.referenceType === 'state') {
|
|
381
|
+
const state = stateDefinitions[dynamicRef.content.id]
|
|
382
|
+
if (state && state.defaultValue) {
|
|
383
|
+
defaultValue = state.defaultValue
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (type === 'expr') {
|
|
388
|
+
operandDefaultValue = (rightSideCondition as UIDLExpressionValue).content
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
operandDefaultValue = rightSideCondition
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Since we know the operand and the default value from the prop.
|
|
395
|
+
// We can try building the condition and check if the condition is true or false.
|
|
396
|
+
// @todo: You can only use a 'value' in UIDL or 'conditions' but not both.
|
|
397
|
+
// UIDL validations need to be improved on this aspect.
|
|
398
|
+
const resolvedConditions = [
|
|
399
|
+
{
|
|
400
|
+
operation: conditions[0].operation,
|
|
401
|
+
operand: operandDefaultValue as string,
|
|
402
|
+
},
|
|
403
|
+
]
|
|
404
|
+
|
|
405
|
+
const dynamicConditions = createConditionalStatement(
|
|
406
|
+
staticValue !== undefined
|
|
407
|
+
? [{ operand: staticValue, operation: '===' }]
|
|
408
|
+
: resolvedConditions,
|
|
409
|
+
defaultValue
|
|
410
|
+
)
|
|
411
|
+
const matchCondition =
|
|
412
|
+
matchingCriteria && matchingCriteria === 'all' ? '&&' : '||'
|
|
413
|
+
const conditionString = dynamicConditions.join(` ${matchCondition} `)
|
|
414
|
+
|
|
415
|
+
// tslint:disable-next-line function-constructor
|
|
416
|
+
const isConditionPassing = new Function(`return ${conditionString}`)()
|
|
417
|
+
if (isConditionPassing) {
|
|
418
|
+
classNamesToAppend.add(styleRef.content.referenceId)
|
|
419
|
+
return
|
|
420
|
+
}
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
case 'state': {
|
|
424
|
+
const usedState = stateDefinitions[id]
|
|
425
|
+
if (usedState === undefined || usedState.defaultValue === undefined) {
|
|
426
|
+
throw new PluginCSS(`State with ${id} is missing in the stateDefinitions.`)
|
|
427
|
+
}
|
|
428
|
+
let defaultValue = usedState.defaultValue
|
|
429
|
+
for (const path of refPath) {
|
|
430
|
+
defaultValue = (defaultValue as Record<string, unknown[]>)?.[path]
|
|
431
|
+
}
|
|
432
|
+
if (
|
|
433
|
+
typeof defaultValue === 'object' &&
|
|
434
|
+
defaultValue !== null &&
|
|
435
|
+
'type' in (defaultValue as Record<string, unknown>) &&
|
|
436
|
+
'content' in (defaultValue as Record<string, unknown>)
|
|
437
|
+
) {
|
|
438
|
+
const entry = defaultValue as { type: string; content: unknown }
|
|
439
|
+
if (entry.type === 'static') {
|
|
440
|
+
defaultValue = entry.content as typeof defaultValue
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
defaultValue = defaultValue ?? usedState.defaultValue
|
|
444
|
+
|
|
445
|
+
const dynamicConditions = createConditionalStatement(
|
|
446
|
+
staticValue !== undefined
|
|
447
|
+
? [{ operand: staticValue, operation: '===' }]
|
|
448
|
+
: conditions,
|
|
449
|
+
defaultValue
|
|
450
|
+
)
|
|
451
|
+
const matchCondition =
|
|
452
|
+
matchingCriteria && matchingCriteria === 'all' ? '&&' : '||'
|
|
453
|
+
const conditionString = dynamicConditions.join(` ${matchCondition} `)
|
|
454
|
+
// tslint:disable-next-line function-constructor
|
|
455
|
+
const isConditionPassing = new Function(`return ${conditionString}`)()
|
|
456
|
+
if (isConditionPassing) {
|
|
457
|
+
classNamesToAppend.add(styleRef.content.referenceId)
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
case 'globalState' as any: {
|
|
464
|
+
const gsDef = findGlobalStateDefinitionById(id, globalStateDefinitions)
|
|
465
|
+
if (!gsDef) {
|
|
466
|
+
classNamesToAppend.add(styleRef.content.referenceId)
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
let defaultValue: StateDefaultValueTypes | PropDefaultValueTypes =
|
|
470
|
+
gsDef.defaultValue as StateDefaultValueTypes
|
|
471
|
+
for (const path of refPath) {
|
|
472
|
+
defaultValue = (defaultValue as Record<string, unknown>)?.[
|
|
473
|
+
path
|
|
474
|
+
] as typeof defaultValue
|
|
475
|
+
}
|
|
476
|
+
defaultValue = defaultValue ?? (gsDef.defaultValue as StateDefaultValueTypes)
|
|
477
|
+
const dynamicConditions = createConditionalStatement(
|
|
478
|
+
staticValue !== undefined
|
|
479
|
+
? [{ operand: staticValue, operation: '===' }]
|
|
480
|
+
: conditions,
|
|
481
|
+
defaultValue
|
|
482
|
+
)
|
|
483
|
+
const matchCondition =
|
|
484
|
+
matchingCriteria && matchingCriteria === 'all' ? '&&' : '||'
|
|
485
|
+
const conditionString = dynamicConditions.join(` ${matchCondition} `)
|
|
486
|
+
// tslint:disable-next-line function-constructor
|
|
487
|
+
const isConditionPassing = new Function(`return ${conditionString}`)()
|
|
488
|
+
if (isConditionPassing) {
|
|
489
|
+
classNamesToAppend.add(styleRef.content.referenceId)
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
default: {
|
|
496
|
+
classNamesToAppend.add(styleRef.content.referenceId)
|
|
497
|
+
return
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
const referenceContent = styleRef.content.condition.reference.content
|
|
502
|
+
const referenceType = referenceContent.referenceType
|
|
503
|
+
const nameToAppend =
|
|
504
|
+
referenceType === 'local' && referenceContent.refPath?.length
|
|
505
|
+
? referenceContent.refPath.join('.')
|
|
506
|
+
: referenceContent.id
|
|
507
|
+
|
|
508
|
+
const { conditions } = styleRef.content.condition.expression
|
|
509
|
+
|
|
510
|
+
const operator = conditions[0].operation as '===' | '!==' | '<' | '<=' | '>' | '>='
|
|
511
|
+
const right = conditions[0].operand as string | number | boolean
|
|
512
|
+
|
|
513
|
+
let binaryExpressionType = ''
|
|
514
|
+
switch (referenceType) {
|
|
515
|
+
case 'prop': {
|
|
516
|
+
binaryExpressionType = propDefinitions[nameToAppend].type
|
|
517
|
+
break
|
|
518
|
+
}
|
|
519
|
+
case 'state': {
|
|
520
|
+
binaryExpressionType = stateDefinitions[nameToAppend].type
|
|
521
|
+
break
|
|
522
|
+
}
|
|
523
|
+
case 'local': {
|
|
524
|
+
binaryExpressionType = typeof right as string
|
|
525
|
+
break
|
|
526
|
+
}
|
|
527
|
+
default: {
|
|
528
|
+
if ((referenceType as string) === 'globalState') {
|
|
529
|
+
const gsDef = findGlobalStateDefinitionById(
|
|
530
|
+
nameToAppend,
|
|
531
|
+
globalStateDefinitions
|
|
532
|
+
)
|
|
533
|
+
if (gsDef) {
|
|
534
|
+
binaryExpressionType = gsDef.type
|
|
535
|
+
}
|
|
536
|
+
break
|
|
537
|
+
}
|
|
538
|
+
throw new PluginCSS(
|
|
539
|
+
`Un-supported reference type ${referenceType} for ${nameToAppend}`
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
let binaryKey = nameToAppend
|
|
545
|
+
if (referenceType === 'prop') {
|
|
546
|
+
binaryKey = 'props?.' + nameToAppend
|
|
547
|
+
} else if ((referenceType as string) === 'globalState') {
|
|
548
|
+
const gsDef = findGlobalStateDefinitionById(nameToAppend, globalStateDefinitions)
|
|
549
|
+
binaryKey = gsDef ? gsDef.name : nameToAppend
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const binaryExpression = createBinaryExpression(
|
|
553
|
+
{ operation: operator, operand: right },
|
|
554
|
+
{
|
|
555
|
+
key: binaryKey,
|
|
556
|
+
type: binaryExpressionType,
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
const conditionalExpression = types.conditionalExpression(
|
|
561
|
+
binaryExpression,
|
|
562
|
+
types.stringLiteral(content.referenceId),
|
|
563
|
+
types.stringLiteral('')
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
dynamicVariantsToAppend.add(conditionalExpression)
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
classNamesToAppend.add(content.referenceId)
|
|
571
|
+
}
|
|
259
572
|
return
|
|
260
573
|
}
|
|
261
574
|
|
|
@@ -296,18 +609,35 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
296
609
|
? types.identifier(dynamicVariantPrefix)
|
|
297
610
|
: types.identifier(propsPrefix)
|
|
298
611
|
|
|
299
|
-
|
|
612
|
+
if (typeof variant === 'string') {
|
|
613
|
+
return types.memberExpression(dynamicAttrValueIdentifier, types.identifier(variant))
|
|
614
|
+
} else {
|
|
615
|
+
// variant is a ConditionalExpression, so return it directly
|
|
616
|
+
return variant
|
|
617
|
+
}
|
|
300
618
|
})
|
|
301
619
|
)
|
|
302
620
|
}
|
|
303
|
-
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
UIDLUtils.traverseElements(node, generateStylesForElementNode)
|
|
624
|
+
for (const prop of Object.values(propDefinitions)) {
|
|
625
|
+
if (prop.type === 'element' && prop.defaultValue) {
|
|
626
|
+
UIDLUtils.traverseElements(
|
|
627
|
+
prop.defaultValue as UIDLElementNode,
|
|
628
|
+
generateStylesForElementNode
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
}
|
|
304
632
|
|
|
305
633
|
if (Object.keys(componentStyleSet).length > 0) {
|
|
306
634
|
StyleBuilders.generateStylesFromStyleSetDefinitions(
|
|
307
635
|
componentStyleSet,
|
|
308
636
|
cssMap,
|
|
309
637
|
mediaStylesMap,
|
|
310
|
-
(styleName: string) =>
|
|
638
|
+
(styleName: string) => {
|
|
639
|
+
return StringUtils.camelCaseToDashCase(uidl.name + styleName)
|
|
640
|
+
}
|
|
311
641
|
)
|
|
312
642
|
}
|
|
313
643
|
|
|
@@ -315,7 +645,210 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
315
645
|
cssMap.push(...StyleBuilders.generateMediaStyle(mediaStylesMap))
|
|
316
646
|
}
|
|
317
647
|
|
|
318
|
-
|
|
648
|
+
// Handle inline style tag for HTML templates (self-contained fragments)
|
|
649
|
+
if (standaloneHtmlComponents && templateStyle === 'html' && templateChunk) {
|
|
650
|
+
const inlineCssMap: string[] = []
|
|
651
|
+
const hasTokens = Object.keys(tokens).length > 0
|
|
652
|
+
const hasUsedProjectStyles = usedProjectStyleIds.size > 0
|
|
653
|
+
const globalAssets = options.globalAssets || []
|
|
654
|
+
|
|
655
|
+
// Get the scoped root class from the template chunk meta
|
|
656
|
+
const scopedRootClass = templateChunk.meta?.scopedRootClass as string | undefined
|
|
657
|
+
|
|
658
|
+
// Utility function to scope CSS selectors
|
|
659
|
+
const scopeCssSelectors = (css: string, rootSelector: string): string => {
|
|
660
|
+
// Replace :root with scoped root class
|
|
661
|
+
let scopedCss = css.replace(/:root\s*\{/g, `${rootSelector} {`)
|
|
662
|
+
|
|
663
|
+
// Scope regular selectors (but not inside @media or @keyframes)
|
|
664
|
+
// This regex matches CSS rules outside of @ blocks
|
|
665
|
+
scopedCss = scopedCss.replace(/(?:^|\})\s*([^@{}]+?)\s*\{/gm, (match, selectors) => {
|
|
666
|
+
// Skip if it's just closing brace or empty
|
|
667
|
+
if (!selectors || selectors.trim() === '') {
|
|
668
|
+
return match
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Skip if selector is already the root class
|
|
672
|
+
if (selectors.trim() === rootSelector) {
|
|
673
|
+
return match
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Handle multiple selectors (comma-separated)
|
|
677
|
+
const scopedSelectors = selectors
|
|
678
|
+
.split(',')
|
|
679
|
+
.map((sel: string) => {
|
|
680
|
+
const trimmedSel = sel.trim()
|
|
681
|
+
// Skip empty selectors
|
|
682
|
+
if (!trimmedSel) {
|
|
683
|
+
return ''
|
|
684
|
+
}
|
|
685
|
+
// Skip if already scoped
|
|
686
|
+
if (trimmedSel.startsWith(rootSelector)) {
|
|
687
|
+
return trimmedSel
|
|
688
|
+
}
|
|
689
|
+
// Replace html/body with root selector
|
|
690
|
+
if (trimmedSel === 'html' || trimmedSel === 'body') {
|
|
691
|
+
return rootSelector
|
|
692
|
+
}
|
|
693
|
+
// For universal selector, scope it
|
|
694
|
+
if (trimmedSel === '*') {
|
|
695
|
+
return `${rootSelector} *`
|
|
696
|
+
}
|
|
697
|
+
// Prefix other selectors with root
|
|
698
|
+
return `${rootSelector} ${trimmedSel}`
|
|
699
|
+
})
|
|
700
|
+
.filter(Boolean)
|
|
701
|
+
.join(', ')
|
|
702
|
+
|
|
703
|
+
return match.replace(selectors, scopedSelectors)
|
|
704
|
+
})
|
|
705
|
+
|
|
706
|
+
return scopedCss
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// 0. Add reset and default styles from global assets (scoped)
|
|
710
|
+
const inlineStyleAssets = globalAssets.filter(
|
|
711
|
+
(asset): asset is UIDLStyleInlineAsset => asset.type === 'style' && 'content' in asset
|
|
712
|
+
)
|
|
713
|
+
for (const styleAsset of inlineStyleAssets) {
|
|
714
|
+
if (styleAsset.content) {
|
|
715
|
+
const css = scopedRootClass
|
|
716
|
+
? scopeCssSelectors(styleAsset.content, `.${scopedRootClass}`)
|
|
717
|
+
: styleAsset.content
|
|
718
|
+
inlineCssMap.push(css)
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// 1. Add CSS variables (tokens) - scoped to root class instead of :root
|
|
723
|
+
if (hasTokens) {
|
|
724
|
+
const tokenSelector = scopedRootClass ? `.${scopedRootClass}` : ':root'
|
|
725
|
+
inlineCssMap.push(
|
|
726
|
+
StyleBuilders.createCSSClassWithSelector(
|
|
727
|
+
'@global',
|
|
728
|
+
tokenSelector,
|
|
729
|
+
StyleUtils.getTokensContentFromTokensObject(tokens)
|
|
730
|
+
)
|
|
731
|
+
)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// 2. Add project-referenced styles that this component uses (scoped)
|
|
735
|
+
if (hasUsedProjectStyles) {
|
|
736
|
+
const usedProjectStyles = Array.from(usedProjectStyleIds)
|
|
737
|
+
// Include exact matches and compound selectors that reference the same base class
|
|
738
|
+
const filteredDefinitions = Object.fromEntries(
|
|
739
|
+
Object.entries(styleSetDefinitions).filter(([key, definition]) => {
|
|
740
|
+
// Include if exact match
|
|
741
|
+
if (usedProjectStyles.includes(key)) {
|
|
742
|
+
return true
|
|
743
|
+
}
|
|
744
|
+
// Include compound selectors where className matches a used project style
|
|
745
|
+
// e.g., "navigation-mobile-overlay.navigation-mobile-overlay-active" has className "navigation-mobile-overlay"
|
|
746
|
+
const className = (definition as { className?: string }).className
|
|
747
|
+
if (className && usedProjectStyles.includes(className)) {
|
|
748
|
+
return true
|
|
749
|
+
}
|
|
750
|
+
// Include compound selectors where the base class (before the dot or space) is a used style
|
|
751
|
+
// e.g., "dashboard-sidebar.collapsed" should be included if "dashboard-sidebar" is used
|
|
752
|
+
// e.g., "dashboard-sidebar.collapsed .sidebar-brand-text" should also be included
|
|
753
|
+
const dotIdx = key.indexOf('.')
|
|
754
|
+
const spaceIdx = key.indexOf(' ')
|
|
755
|
+
if (dotIdx > 0) {
|
|
756
|
+
const baseClass = key.substring(0, dotIdx)
|
|
757
|
+
if (usedProjectStyles.includes(baseClass)) {
|
|
758
|
+
return true
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (spaceIdx > 0 && dotIdx < 0) {
|
|
762
|
+
const baseClass = key.substring(0, spaceIdx)
|
|
763
|
+
if (usedProjectStyles.includes(baseClass)) {
|
|
764
|
+
return true
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return false
|
|
768
|
+
})
|
|
769
|
+
)
|
|
770
|
+
const projectStylesCssMap: string[] = []
|
|
771
|
+
const projectMediaStylesMap: Record<
|
|
772
|
+
string,
|
|
773
|
+
Array<{ [x: string]: Record<string, string | number> }>
|
|
774
|
+
> = {}
|
|
775
|
+
StyleBuilders.generateStylesFromStyleSetDefinitions(
|
|
776
|
+
filteredDefinitions,
|
|
777
|
+
projectStylesCssMap,
|
|
778
|
+
projectMediaStylesMap,
|
|
779
|
+
(styleName) => styleName
|
|
780
|
+
)
|
|
781
|
+
// Scope the project styles if scoped root class is available
|
|
782
|
+
if (scopedRootClass) {
|
|
783
|
+
const scopedProjectStyles = projectStylesCssMap.map((css) =>
|
|
784
|
+
scopeCssSelectors(css, `.${scopedRootClass}`)
|
|
785
|
+
)
|
|
786
|
+
inlineCssMap.push(...scopedProjectStyles)
|
|
787
|
+
} else {
|
|
788
|
+
inlineCssMap.push(...projectStylesCssMap)
|
|
789
|
+
}
|
|
790
|
+
if (Object.keys(projectMediaStylesMap).length > 0) {
|
|
791
|
+
const mediaStyles = StyleBuilders.generateMediaStyle(projectMediaStylesMap)
|
|
792
|
+
if (scopedRootClass) {
|
|
793
|
+
const scopedMediaStyles = mediaStyles.map((css) =>
|
|
794
|
+
scopeCssSelectors(css, `.${scopedRootClass}`)
|
|
795
|
+
)
|
|
796
|
+
inlineCssMap.push(...scopedMediaStyles)
|
|
797
|
+
} else {
|
|
798
|
+
inlineCssMap.push(...mediaStyles)
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// 3. Add component-specific styles (scoped)
|
|
804
|
+
if (scopedRootClass) {
|
|
805
|
+
// Scope the component CSS
|
|
806
|
+
const scopedCssMap = cssMap.map((css) => scopeCssSelectors(css, `.${scopedRootClass}`))
|
|
807
|
+
inlineCssMap.push(...scopedCssMap)
|
|
808
|
+
} else {
|
|
809
|
+
inlineCssMap.push(...cssMap)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// 4. Create font link tags from global assets
|
|
813
|
+
const fontAssets = globalAssets.filter(
|
|
814
|
+
(asset): asset is UIDLFontAsset => asset.type === 'font' && 'path' in asset
|
|
815
|
+
)
|
|
816
|
+
const fontLinkNodes: HastNode[] = []
|
|
817
|
+
for (const fontAsset of fontAssets) {
|
|
818
|
+
const linkNode = HASTBuilders.createHTMLNode('link')
|
|
819
|
+
HASTUtils.addAttributeToNode(linkNode, 'rel', 'stylesheet')
|
|
820
|
+
HASTUtils.addAttributeToNode(linkNode, 'href', fontAsset.path)
|
|
821
|
+
if (fontAsset.attrs) {
|
|
822
|
+
for (const [attrKey, attrValue] of Object.entries(fontAsset.attrs)) {
|
|
823
|
+
const value =
|
|
824
|
+
typeof attrValue === 'object' && 'content' in attrValue
|
|
825
|
+
? String(attrValue.content)
|
|
826
|
+
: String(attrValue)
|
|
827
|
+
HASTUtils.addAttributeToNode(linkNode, attrKey, value)
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
fontLinkNodes.push(linkNode)
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// 5. Create <style> node and prepend to HTML (fonts first, then styles)
|
|
834
|
+
if (inlineCssMap.length > 0 || fontLinkNodes.length > 0) {
|
|
835
|
+
const nodesToPrepend: HastNode[] = []
|
|
836
|
+
|
|
837
|
+
// Add font links first
|
|
838
|
+
nodesToPrepend.push(...fontLinkNodes)
|
|
839
|
+
|
|
840
|
+
// Add style tag with all CSS
|
|
841
|
+
if (inlineCssMap.length > 0) {
|
|
842
|
+
const styleNode = HASTBuilders.createHTMLNode('style')
|
|
843
|
+
const cssContent = HASTBuilders.createTextNode(inlineCssMap.join('\n\n'))
|
|
844
|
+
styleNode.children.push(cssContent)
|
|
845
|
+
nodesToPrepend.push(styleNode)
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
;(templateChunk.content as HastNode).children.unshift(...nodesToPrepend)
|
|
849
|
+
}
|
|
850
|
+
} else if (cssMap.length > 0) {
|
|
851
|
+
// Original behavior: create separate CSS file
|
|
319
852
|
/**
|
|
320
853
|
* Setup an import statement for the styles
|
|
321
854
|
* The name of the file is either in the meta of the component generator
|
|
@@ -344,11 +877,17 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
|
|
|
344
877
|
}
|
|
345
878
|
}
|
|
346
879
|
|
|
880
|
+
const folderPath = uidl.outputOptions?.folderPath
|
|
881
|
+
const cssContent =
|
|
882
|
+
folderPath?.length > 0
|
|
883
|
+
? cssMap.map((css) => prefixUrlPathsInCss(css, folderPath)).join('\n \n')
|
|
884
|
+
: cssMap.join('\n \n')
|
|
885
|
+
|
|
347
886
|
chunks.push({
|
|
348
887
|
type: ChunkType.STRING,
|
|
349
888
|
name: chunkName,
|
|
350
889
|
fileType: FileType.CSS,
|
|
351
|
-
content:
|
|
890
|
+
content: cssContent,
|
|
352
891
|
linkAfter: [],
|
|
353
892
|
})
|
|
354
893
|
}
|
|
@@ -363,6 +902,18 @@ export { createStyleSheetPlugin, createCSSPlugin }
|
|
|
363
902
|
|
|
364
903
|
export default createCSSPlugin()
|
|
365
904
|
|
|
905
|
+
const findGlobalStateDefinitionById = (
|
|
906
|
+
id: string,
|
|
907
|
+
definitions: Record<string, UIDLGlobalStateDefinition>
|
|
908
|
+
): UIDLGlobalStateDefinition | undefined => {
|
|
909
|
+
for (const def of Object.values(definitions)) {
|
|
910
|
+
if (def.id === id) {
|
|
911
|
+
return def
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return undefined
|
|
915
|
+
}
|
|
916
|
+
|
|
366
917
|
const createDynamicInlineStyle = (styles: UIDLStyleDefinitions) => {
|
|
367
918
|
return Object.keys(styles)
|
|
368
919
|
.map((styleKey) => {
|
|
@@ -371,8 +922,25 @@ const createDynamicInlineStyle = (styles: UIDLStyleDefinitions) => {
|
|
|
371
922
|
.join(', ')
|
|
372
923
|
}
|
|
373
924
|
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
925
|
+
const createDynamicBindingInlineStyle = (
|
|
926
|
+
bindings: Record<
|
|
927
|
+
string,
|
|
928
|
+
{
|
|
929
|
+
referenceType: string
|
|
930
|
+
stateKey: string
|
|
931
|
+
defaultValue: string
|
|
932
|
+
contextName?: string
|
|
933
|
+
stateDefinitionId?: string
|
|
934
|
+
}
|
|
935
|
+
>
|
|
936
|
+
): string => {
|
|
937
|
+
return Object.entries(bindings)
|
|
938
|
+
.map(([prop, binding]) => {
|
|
939
|
+
const value =
|
|
940
|
+
binding.referenceType === 'ctx' && binding.contextName
|
|
941
|
+
? `${binding.contextName}.${binding.stateKey}`
|
|
942
|
+
: binding.stateKey
|
|
943
|
+
return `${prop}: ${value} != null ? ${value} : '${binding.defaultValue || ''}'`
|
|
944
|
+
})
|
|
945
|
+
.join(', ')
|
|
378
946
|
}
|