@teleporthq/teleport-plugin-html-base-component 0.43.0-alpha.0 → 0.43.3
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__/index.ts +5 -3
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +48 -17
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/node-handlers.d.ts +10 -2
- package/dist/cjs/node-handlers.d.ts.map +1 -1
- package/dist/cjs/node-handlers.js +817 -181
- package/dist/cjs/node-handlers.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +49 -18
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/node-handlers.d.ts +10 -2
- package/dist/esm/node-handlers.d.ts.map +1 -1
- package/dist/esm/node-handlers.js +817 -181
- package/dist/esm/node-handlers.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/index.ts +71 -20
- package/src/node-handlers.ts +1152 -207
package/src/node-handlers.ts
CHANGED
|
@@ -21,44 +21,136 @@ import {
|
|
|
21
21
|
ComponentStructure,
|
|
22
22
|
UIDLComponentOutputOptions,
|
|
23
23
|
UIDLElement,
|
|
24
|
+
ElementsLookup,
|
|
25
|
+
UIDLConditionalNode,
|
|
26
|
+
PropDefaultValueTypes,
|
|
27
|
+
UIDLCMSListRepeaterNode,
|
|
28
|
+
UIDLStaticValue,
|
|
29
|
+
UIDLRawValue,
|
|
24
30
|
} from '@teleporthq/teleport-types'
|
|
25
31
|
import { join, relative } from 'path'
|
|
26
|
-
import { HASTBuilders, HASTUtils } from '@teleporthq/teleport-plugin-common'
|
|
27
|
-
import { StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
|
|
32
|
+
import { HASTBuilders, HASTUtils, ASTUtils } from '@teleporthq/teleport-plugin-common'
|
|
33
|
+
import { GenericUtils, StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
|
|
28
34
|
import { staticNode } from '@teleporthq/teleport-uidl-builders'
|
|
29
35
|
import { createCSSPlugin } from '@teleporthq/teleport-plugin-css'
|
|
36
|
+
import { generateUniqueKeys, createNodesLookup } from '@teleporthq/teleport-uidl-resolver'
|
|
30
37
|
import { DEFAULT_COMPONENT_CHUNK_NAME } from './constants'
|
|
31
38
|
|
|
39
|
+
const getTranslation = (
|
|
40
|
+
id: string,
|
|
41
|
+
options: GeneratorOptions
|
|
42
|
+
): UIDLElementNode | UIDLStaticValue | null => {
|
|
43
|
+
const i18n = options.internationalization
|
|
44
|
+
if (!i18n?.translations) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
const locale = i18n.targetLocale || i18n.main?.locale
|
|
48
|
+
if (!locale) {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
return i18n.translations[locale]?.[id] || null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const resolveTranslationText = (translation: UIDLElementNode | UIDLStaticValue): string => {
|
|
55
|
+
if (translation.type === 'static') {
|
|
56
|
+
return String(translation.content)
|
|
57
|
+
}
|
|
58
|
+
if (translation.type === 'element' && translation.content.children) {
|
|
59
|
+
return translation.content.children
|
|
60
|
+
.map((child) => {
|
|
61
|
+
if (child.type === 'static') {
|
|
62
|
+
return String(child.content)
|
|
63
|
+
}
|
|
64
|
+
if (child.type === 'element') {
|
|
65
|
+
return resolveTranslationText(child)
|
|
66
|
+
}
|
|
67
|
+
return ''
|
|
68
|
+
})
|
|
69
|
+
.join('')
|
|
70
|
+
}
|
|
71
|
+
return ''
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isValidURL = (url: string) => {
|
|
75
|
+
try {
|
|
76
|
+
/* tslint:disable:no-unused-expression */
|
|
77
|
+
new URL(url)
|
|
78
|
+
return true
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const addNodeToLookup = (
|
|
85
|
+
key: string,
|
|
86
|
+
tag: HastNode | HastText | Array<HastNode | HastText>,
|
|
87
|
+
nodesLoookup: Record<string, HastNode | HastText | Array<HastNode | HastText>>
|
|
88
|
+
) => {
|
|
89
|
+
// In html code-generation we combine the nodes of the component that is being consumed with the current component.
|
|
90
|
+
// As html can't load the component at runtime like react or any other frameworks. So, we merge the component as a standalone
|
|
91
|
+
// component in the current component.
|
|
92
|
+
const currentLookup = nodesLoookup[key]
|
|
93
|
+
if (currentLookup) {
|
|
94
|
+
if (Array.isArray(currentLookup)) {
|
|
95
|
+
Array.isArray(tag) ? currentLookup.push(...tag) : currentLookup.push(tag)
|
|
96
|
+
} else {
|
|
97
|
+
nodesLoookup[key] = Array.isArray(tag) ? [currentLookup, ...tag] : [currentLookup, tag]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
nodesLoookup[key] = tag
|
|
104
|
+
}
|
|
105
|
+
|
|
32
106
|
type NodeToHTML<NodeType, ReturnType> = (
|
|
33
107
|
node: NodeType,
|
|
34
|
-
|
|
108
|
+
componentName: string,
|
|
109
|
+
nodesLookup: Record<string, HastNode | HastText | Array<HastNode | HastText>>,
|
|
35
110
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
36
111
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
37
112
|
subComponentOptions: {
|
|
38
113
|
externals: Record<string, ComponentUIDL>
|
|
39
114
|
plugins: ComponentPlugin[]
|
|
115
|
+
standaloneHtmlComponents?: boolean
|
|
40
116
|
},
|
|
41
117
|
structure: {
|
|
42
118
|
chunks: ChunkDefinition[]
|
|
43
119
|
dependencies: Record<string, UIDLDependency>
|
|
44
120
|
options: GeneratorOptions
|
|
45
121
|
outputOptions: UIDLComponentOutputOptions
|
|
122
|
+
},
|
|
123
|
+
/**
|
|
124
|
+
* This param is just to be able to handle CMS array mappers/Repeater nodes. A bit hacky, better support should be implemented
|
|
125
|
+
*/
|
|
126
|
+
resolvedExpressions?: {
|
|
127
|
+
expressions: Record<string, UIDLPropDefinition>
|
|
128
|
+
currentIndex: number
|
|
46
129
|
}
|
|
47
130
|
) => ReturnType
|
|
48
131
|
|
|
49
|
-
export const
|
|
132
|
+
export const generateHtmlSyntax: NodeToHTML<
|
|
133
|
+
UIDLNode,
|
|
134
|
+
Promise<HastNode | HastText | Array<HastNode | HastText>>
|
|
135
|
+
> = async (
|
|
50
136
|
node,
|
|
51
|
-
|
|
137
|
+
compName,
|
|
138
|
+
nodesLookup,
|
|
52
139
|
propDefinitions,
|
|
53
140
|
stateDefinitions,
|
|
54
141
|
subComponentOptions,
|
|
55
|
-
structure
|
|
142
|
+
structure,
|
|
143
|
+
resolvedExpressions
|
|
56
144
|
) => {
|
|
57
145
|
switch (node.type) {
|
|
58
146
|
case 'inject':
|
|
59
|
-
case 'raw':
|
|
60
147
|
return HASTBuilders.createTextNode(node.content.toString())
|
|
61
148
|
|
|
149
|
+
case 'raw': {
|
|
150
|
+
const rawNode = node as UIDLRawValue
|
|
151
|
+
return HASTBuilders.createTextNode((rawNode.fallback || rawNode.content).toString())
|
|
152
|
+
}
|
|
153
|
+
|
|
62
154
|
case 'static':
|
|
63
155
|
return HASTBuilders.createTextNode(StringUtils.encode(node.content.toString()))
|
|
64
156
|
|
|
@@ -66,24 +158,247 @@ export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
|
|
|
66
158
|
return HASTBuilders.createHTMLNode(node.type)
|
|
67
159
|
|
|
68
160
|
case 'element':
|
|
69
|
-
|
|
161
|
+
const elementNode = await generateElementNode(
|
|
70
162
|
node,
|
|
71
|
-
|
|
163
|
+
compName,
|
|
164
|
+
nodesLookup,
|
|
72
165
|
propDefinitions,
|
|
73
166
|
stateDefinitions,
|
|
74
167
|
subComponentOptions,
|
|
75
|
-
structure
|
|
168
|
+
structure,
|
|
169
|
+
resolvedExpressions
|
|
76
170
|
)
|
|
171
|
+
return elementNode
|
|
77
172
|
|
|
78
173
|
case 'dynamic':
|
|
79
|
-
|
|
174
|
+
const dynamicNode = await generateDynamicNode(
|
|
80
175
|
node,
|
|
81
|
-
|
|
176
|
+
compName,
|
|
177
|
+
nodesLookup,
|
|
178
|
+
propDefinitions,
|
|
179
|
+
stateDefinitions,
|
|
180
|
+
subComponentOptions,
|
|
181
|
+
structure,
|
|
182
|
+
resolvedExpressions
|
|
183
|
+
)
|
|
184
|
+
return dynamicNode
|
|
185
|
+
|
|
186
|
+
case 'repeat':
|
|
187
|
+
const repeatNode = await generateHtmlSyntax(
|
|
188
|
+
node.content.node,
|
|
189
|
+
compName,
|
|
190
|
+
nodesLookup,
|
|
82
191
|
propDefinitions,
|
|
83
192
|
stateDefinitions,
|
|
84
193
|
subComponentOptions,
|
|
85
|
-
structure
|
|
194
|
+
structure,
|
|
195
|
+
resolvedExpressions
|
|
86
196
|
)
|
|
197
|
+
return repeatNode
|
|
198
|
+
|
|
199
|
+
case 'conditional':
|
|
200
|
+
const conditionalNodeComment = HASTBuilders.createTextNode('')
|
|
201
|
+
const {
|
|
202
|
+
value: staticValue,
|
|
203
|
+
reference,
|
|
204
|
+
condition: { conditions, matchingCriteria },
|
|
205
|
+
} = node.content
|
|
206
|
+
|
|
207
|
+
if (reference.type !== 'dynamic') {
|
|
208
|
+
return conditionalNodeComment
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const {
|
|
212
|
+
content: { referenceType, id, refPath = [] },
|
|
213
|
+
} = reference
|
|
214
|
+
|
|
215
|
+
switch (referenceType) {
|
|
216
|
+
case 'prop': {
|
|
217
|
+
const usedProp = propDefinitions[id]
|
|
218
|
+
if (usedProp === undefined || usedProp.defaultValue === undefined) {
|
|
219
|
+
return conditionalNodeComment
|
|
220
|
+
}
|
|
221
|
+
let defaultValue = usedProp.defaultValue
|
|
222
|
+
for (const path of refPath) {
|
|
223
|
+
defaultValue = (defaultValue as Record<string, unknown[]>)?.[path]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// If defaultValue is undefined or null after path traversal, use original default
|
|
227
|
+
defaultValue = defaultValue ?? usedProp.defaultValue
|
|
228
|
+
|
|
229
|
+
// Since we know the operand and the default value from the prop.
|
|
230
|
+
// We can try building the condition and check if the condition is true or false.
|
|
231
|
+
// @todo: You can only use a 'value' in UIDL or 'conditions' but not both.
|
|
232
|
+
// UIDL validations need to be improved on this aspect.
|
|
233
|
+
const dynamicConditions = createConditionalStatement(
|
|
234
|
+
staticValue !== undefined ? [{ operand: staticValue, operation: '===' }] : conditions,
|
|
235
|
+
defaultValue
|
|
236
|
+
)
|
|
237
|
+
const matchCondition = matchingCriteria && matchingCriteria === 'all' ? '&&' : '||'
|
|
238
|
+
const conditionString = dynamicConditions.join(` ${matchCondition} `)
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
// tslint:disable-next-line function-constructor
|
|
242
|
+
const isConditionPassing = new Function(`return ${conditionString}`)()
|
|
243
|
+
if (isConditionPassing) {
|
|
244
|
+
return generateHtmlSyntax(
|
|
245
|
+
node.content.node,
|
|
246
|
+
compName,
|
|
247
|
+
nodesLookup,
|
|
248
|
+
propDefinitions,
|
|
249
|
+
stateDefinitions,
|
|
250
|
+
subComponentOptions,
|
|
251
|
+
structure,
|
|
252
|
+
resolvedExpressions
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return conditionalNodeComment
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return conditionalNodeComment
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case 'local': {
|
|
263
|
+
if (!resolvedExpressions || resolvedExpressions.currentIndex === undefined) {
|
|
264
|
+
return conditionalNodeComment
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Find the matching expression context for this local reference
|
|
268
|
+
const expressionEntries = Object.values(resolvedExpressions.expressions || {})
|
|
269
|
+
let localValue: unknown
|
|
270
|
+
for (const expr of expressionEntries) {
|
|
271
|
+
if (expr && Array.isArray(expr.defaultValue)) {
|
|
272
|
+
const currentItem = expr.defaultValue[resolvedExpressions.currentIndex]
|
|
273
|
+
if (currentItem !== undefined) {
|
|
274
|
+
localValue = currentItem
|
|
275
|
+
for (const path of refPath) {
|
|
276
|
+
localValue = (localValue as Record<string, unknown>)?.[path]
|
|
277
|
+
}
|
|
278
|
+
break
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (localValue === undefined) {
|
|
284
|
+
return conditionalNodeComment
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const localConditions = createConditionalStatement(
|
|
288
|
+
staticValue !== undefined ? [{ operand: staticValue, operation: '===' }] : conditions,
|
|
289
|
+
localValue as UIDLPropDefinition['defaultValue']
|
|
290
|
+
)
|
|
291
|
+
const localMatchCondition = matchingCriteria && matchingCriteria === 'all' ? '&&' : '||'
|
|
292
|
+
const localConditionString = localConditions.join(` ${localMatchCondition} `)
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
// tslint:disable-next-line function-constructor
|
|
296
|
+
const isLocalConditionPassing = new Function(`return ${localConditionString}`)()
|
|
297
|
+
if (isLocalConditionPassing) {
|
|
298
|
+
return generateHtmlSyntax(
|
|
299
|
+
node.content.node,
|
|
300
|
+
compName,
|
|
301
|
+
nodesLookup,
|
|
302
|
+
propDefinitions,
|
|
303
|
+
stateDefinitions,
|
|
304
|
+
subComponentOptions,
|
|
305
|
+
structure,
|
|
306
|
+
resolvedExpressions
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return conditionalNodeComment
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return conditionalNodeComment
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'state':
|
|
317
|
+
default:
|
|
318
|
+
return conditionalNodeComment
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case 'expr':
|
|
322
|
+
const content = node.content.split('?.')
|
|
323
|
+
|
|
324
|
+
if (resolvedExpressions && resolvedExpressions.expressions?.[content[0] || '']) {
|
|
325
|
+
const uidlDynamicRef: UIDLDynamicReference = {
|
|
326
|
+
type: 'dynamic',
|
|
327
|
+
content: {
|
|
328
|
+
referenceType: 'prop',
|
|
329
|
+
refPath: [resolvedExpressions.currentIndex.toString(), ...content.slice(1)],
|
|
330
|
+
id: content[0],
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
const generatedNode = await generateDynamicNode(
|
|
334
|
+
uidlDynamicRef,
|
|
335
|
+
compName,
|
|
336
|
+
nodesLookup,
|
|
337
|
+
resolvedExpressions.expressions,
|
|
338
|
+
stateDefinitions,
|
|
339
|
+
subComponentOptions,
|
|
340
|
+
structure
|
|
341
|
+
)
|
|
342
|
+
return generatedNode
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Fallback: support simple prop/state expressions outside of repeater context
|
|
346
|
+
if (content[0] && (propDefinitions?.[content[0]] || stateDefinitions?.[content[0]])) {
|
|
347
|
+
const isProp = Boolean(propDefinitions?.[content[0]])
|
|
348
|
+
const uidlDynamicRef: UIDLDynamicReference = {
|
|
349
|
+
type: 'dynamic',
|
|
350
|
+
content: {
|
|
351
|
+
referenceType: isProp ? 'prop' : 'state',
|
|
352
|
+
refPath: content.slice(1),
|
|
353
|
+
id: content[0],
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
const generatedNode = await generateDynamicNode(
|
|
357
|
+
uidlDynamicRef,
|
|
358
|
+
compName,
|
|
359
|
+
nodesLookup,
|
|
360
|
+
isProp ? propDefinitions : stateDefinitions,
|
|
361
|
+
stateDefinitions,
|
|
362
|
+
subComponentOptions,
|
|
363
|
+
structure
|
|
364
|
+
)
|
|
365
|
+
return generatedNode
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return HASTBuilders.createComment('Expressions are not supported in HTML')
|
|
369
|
+
case 'cms-list-repeater':
|
|
370
|
+
return generateRepeaterNode(
|
|
371
|
+
node,
|
|
372
|
+
compName,
|
|
373
|
+
nodesLookup,
|
|
374
|
+
propDefinitions,
|
|
375
|
+
stateDefinitions,
|
|
376
|
+
subComponentOptions,
|
|
377
|
+
structure,
|
|
378
|
+
resolvedExpressions
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
case 'cms-item':
|
|
382
|
+
case 'cms-list':
|
|
383
|
+
case 'data-source-item':
|
|
384
|
+
case 'data-source-list':
|
|
385
|
+
// For HTML generation, render the success node content
|
|
386
|
+
// Since HTML doesn't support dynamic data fetching, we just render the static structure
|
|
387
|
+
const successNode = (node.content as any).nodes?.success
|
|
388
|
+
if (successNode) {
|
|
389
|
+
return generateHtmlSyntax(
|
|
390
|
+
successNode,
|
|
391
|
+
compName,
|
|
392
|
+
nodesLookup,
|
|
393
|
+
propDefinitions,
|
|
394
|
+
stateDefinitions,
|
|
395
|
+
subComponentOptions,
|
|
396
|
+
structure,
|
|
397
|
+
resolvedExpressions
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
// If no success node, return empty div
|
|
401
|
+
return HASTBuilders.createHTMLNode('div')
|
|
87
402
|
|
|
88
403
|
default:
|
|
89
404
|
throw new HTMLComponentGeneratorError(
|
|
@@ -96,14 +411,269 @@ export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
|
|
|
96
411
|
}
|
|
97
412
|
}
|
|
98
413
|
|
|
99
|
-
const
|
|
414
|
+
const createConditionalStatement = (
|
|
415
|
+
conditions: UIDLConditionalNode['content']['condition']['conditions'],
|
|
416
|
+
leftOperand: UIDLPropDefinition['defaultValue']
|
|
417
|
+
) => {
|
|
418
|
+
return conditions.map((condition) => {
|
|
419
|
+
const { operation, operand } = condition
|
|
420
|
+
|
|
421
|
+
if (operand === undefined) {
|
|
422
|
+
return `${ASTUtils.convertToUnaryOperator(operation)}${getValueType(operand)}`
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return `${getValueType(leftOperand)} ${ASTUtils.convertToBinaryOperator(
|
|
426
|
+
operation
|
|
427
|
+
)} ${getValueType(operand)}`
|
|
428
|
+
})
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const getValueType = (value: UIDLPropDefinition['defaultValue']) => {
|
|
432
|
+
const valueType = typeof value
|
|
433
|
+
switch (valueType) {
|
|
434
|
+
case 'string':
|
|
435
|
+
return `"${value}"`
|
|
436
|
+
case 'number':
|
|
437
|
+
return value
|
|
438
|
+
case 'boolean':
|
|
439
|
+
return value
|
|
440
|
+
case 'object':
|
|
441
|
+
// `typeof null === 'object'` — render as the null literal so comparisons
|
|
442
|
+
// against a missing/null default evaluate sensibly.
|
|
443
|
+
if (value === null) {
|
|
444
|
+
return 'null'
|
|
445
|
+
}
|
|
446
|
+
// Handle dynamic references (local, prop, state)
|
|
447
|
+
if (value && typeof value === 'object' && 'type' in value) {
|
|
448
|
+
const dynamicValue = value as unknown as UIDLDynamicReference
|
|
449
|
+
if (dynamicValue.type === 'dynamic' && dynamicValue.content) {
|
|
450
|
+
const { referenceType, id, refPath } = dynamicValue.content
|
|
451
|
+
if (referenceType === 'local' && id) {
|
|
452
|
+
// Local reference from repeater context
|
|
453
|
+
if (refPath && refPath.length > 0) {
|
|
454
|
+
return `${id}.${refPath.join('.')}`
|
|
455
|
+
}
|
|
456
|
+
return id
|
|
457
|
+
} else if (referenceType === 'prop' || referenceType === 'state') {
|
|
458
|
+
// Prop or state reference
|
|
459
|
+
const key = refPath && refPath.length > 0 ? refPath.join('.') : id
|
|
460
|
+
return key
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Handle link-type prop default values ({ url, newTab }). Collapse to the
|
|
465
|
+
// url string so comparisons like `mapUrl !== '--'` evaluate sensibly.
|
|
466
|
+
if (
|
|
467
|
+
value &&
|
|
468
|
+
typeof value === 'object' &&
|
|
469
|
+
'url' in (value as Record<string, unknown>) &&
|
|
470
|
+
typeof (value as Record<string, unknown>).url === 'string'
|
|
471
|
+
) {
|
|
472
|
+
return `"${(value as Record<string, unknown>).url}"`
|
|
473
|
+
}
|
|
474
|
+
throw new HTMLComponentGeneratorError(
|
|
475
|
+
`Conditional node received an operand of type ${valueType} \n
|
|
476
|
+
Received ${JSON.stringify(value)}`
|
|
477
|
+
)
|
|
478
|
+
default:
|
|
479
|
+
throw new HTMLComponentGeneratorError(
|
|
480
|
+
`Conditional node received an operand of type ${valueType} \n
|
|
481
|
+
Received ${JSON.stringify(value)}`
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const generateRepeaterNode: NodeToHTML<
|
|
487
|
+
UIDLCMSListRepeaterNode,
|
|
488
|
+
Promise<HastNode | HastText>
|
|
489
|
+
> = async (
|
|
490
|
+
node,
|
|
491
|
+
compName,
|
|
492
|
+
nodesLookup,
|
|
493
|
+
propDefinitions,
|
|
494
|
+
stateDefinitions,
|
|
495
|
+
subComponentOptions,
|
|
496
|
+
structure,
|
|
497
|
+
resolvedExpressions
|
|
498
|
+
) => {
|
|
499
|
+
const { nodes } = node.content
|
|
500
|
+
|
|
501
|
+
const contextId = node.content.renderPropIdentifier
|
|
502
|
+
const sourceValue = node.content.source
|
|
503
|
+
let propDef =
|
|
504
|
+
sourceValue && typeof sourceValue === 'string'
|
|
505
|
+
? propDefinitions[
|
|
506
|
+
Object.keys(propDefinitions).find((propKey) => sourceValue.includes(propKey)) || ''
|
|
507
|
+
]
|
|
508
|
+
: undefined
|
|
509
|
+
|
|
510
|
+
/*
|
|
511
|
+
* When we have a nested repeater (e.g. source = "context_yu137?.list || []"),
|
|
512
|
+
* `propDef` points to the parent context's full array. We need to resolve the
|
|
513
|
+
* nested path (e.g. ".list") from each item in the parent iteration instead.
|
|
514
|
+
*/
|
|
515
|
+
let nestedPath: string[] | null = null
|
|
516
|
+
if (propDef && resolvedExpressions && sourceValue && typeof sourceValue === 'string') {
|
|
517
|
+
const parentContextKey = Object.keys(resolvedExpressions.expressions || {}).find((key) =>
|
|
518
|
+
sourceValue.includes(key)
|
|
519
|
+
)
|
|
520
|
+
if (parentContextKey) {
|
|
521
|
+
const parentPropDef = resolvedExpressions.expressions[parentContextKey]
|
|
522
|
+
// Extract the nested path from the source expression (e.g. "context_yu137?.list || []" → ["list"])
|
|
523
|
+
const pathMatch = sourceValue.match(
|
|
524
|
+
new RegExp(`${parentContextKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}((?:\\?\\.\\w+)+)`)
|
|
525
|
+
)
|
|
526
|
+
if (pathMatch && pathMatch[1]) {
|
|
527
|
+
nestedPath = pathMatch[1].split('?.').filter(Boolean)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (nestedPath && parentPropDef && Array.isArray(parentPropDef.defaultValue)) {
|
|
531
|
+
const parentItem = parentPropDef.defaultValue[resolvedExpressions.currentIndex]
|
|
532
|
+
if (parentItem && typeof parentItem === 'object' && !Array.isArray(parentItem)) {
|
|
533
|
+
const nestedValue = nestedPath.reduce(
|
|
534
|
+
(acc: unknown, key: string) =>
|
|
535
|
+
acc && typeof acc === 'object' && !Array.isArray(acc)
|
|
536
|
+
? (acc as Record<string, unknown>)[key]
|
|
537
|
+
: acc,
|
|
538
|
+
parentItem
|
|
539
|
+
)
|
|
540
|
+
if (Array.isArray(nestedValue)) {
|
|
541
|
+
propDef = {
|
|
542
|
+
defaultValue: nestedValue,
|
|
543
|
+
id: contextId,
|
|
544
|
+
type: 'array',
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (!propDef || !Array.isArray(propDef.defaultValue)) {
|
|
553
|
+
// If no prop is found we might have a static source value
|
|
554
|
+
try {
|
|
555
|
+
const parsedSource = JSON.parse(sourceValue)
|
|
556
|
+
propDef = {
|
|
557
|
+
defaultValue: parsedSource,
|
|
558
|
+
id: contextId,
|
|
559
|
+
type: 'array',
|
|
560
|
+
}
|
|
561
|
+
} catch {
|
|
562
|
+
// Silent fail
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// We do the check again to keep typescript happy, otherwise this could be in catch
|
|
567
|
+
if (!propDef || !Array.isArray(propDef.defaultValue)) {
|
|
568
|
+
return HASTBuilders.createComment(
|
|
569
|
+
'CMS Array Mapper/Repeater not supported in HTML without a prop source'
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
propDefinitions[contextId] = propDef
|
|
573
|
+
|
|
574
|
+
const elementNode = HASTBuilders.createHTMLNode('div')
|
|
575
|
+
node.content.nodes.list.content.style = { display: { type: 'static', content: 'contents' } }
|
|
576
|
+
if (node.content.nodes.empty) {
|
|
577
|
+
node.content.nodes.empty.content.style = { display: { type: 'static', content: 'contents' } }
|
|
578
|
+
}
|
|
579
|
+
if (node.content.nodes.loading) {
|
|
580
|
+
node.content.nodes.loading.content.style = { display: { type: 'static', content: 'contents' } }
|
|
581
|
+
}
|
|
582
|
+
// Empty case
|
|
583
|
+
if (propDef.defaultValue.length === 0) {
|
|
584
|
+
const emptyChildren = nodes.empty?.content.children
|
|
585
|
+
if (emptyChildren) {
|
|
586
|
+
for (const child of emptyChildren) {
|
|
587
|
+
const childTag = await generateHtmlSyntax(
|
|
588
|
+
child,
|
|
589
|
+
compName,
|
|
590
|
+
nodesLookup,
|
|
591
|
+
propDefinitions,
|
|
592
|
+
stateDefinitions,
|
|
593
|
+
subComponentOptions,
|
|
594
|
+
structure
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
if (typeof childTag === 'string') {
|
|
598
|
+
HASTUtils.addTextNode(elementNode, childTag)
|
|
599
|
+
} else {
|
|
600
|
+
HASTUtils.addChildNode(elementNode, childTag as HastNode)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (nodes.empty) {
|
|
606
|
+
addNodeToLookup(`${node.content.nodes.empty.content.key}`, elementNode, nodesLookup)
|
|
607
|
+
}
|
|
608
|
+
return elementNode
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const listChildren = nodes.list.content.children
|
|
612
|
+
if (listChildren) {
|
|
613
|
+
for (let index = 0; index < propDef.defaultValue.length; index++) {
|
|
614
|
+
for (const child of listChildren) {
|
|
615
|
+
const childTag = await generateHtmlSyntax(
|
|
616
|
+
child,
|
|
617
|
+
compName,
|
|
618
|
+
nodesLookup,
|
|
619
|
+
propDefinitions,
|
|
620
|
+
stateDefinitions,
|
|
621
|
+
subComponentOptions,
|
|
622
|
+
structure,
|
|
623
|
+
{ currentIndex: index, expressions: { [contextId]: propDef } }
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
if (typeof childTag === 'string') {
|
|
627
|
+
HASTUtils.addTextNode(elementNode, childTag)
|
|
628
|
+
} else {
|
|
629
|
+
HASTUtils.addChildNode(elementNode, childTag as HastNode)
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
addNodeToLookup(`${node.content.nodes.list.content.key}`, elementNode, nodesLookup)
|
|
636
|
+
return elementNode
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const generateElementNode: NodeToHTML<
|
|
640
|
+
UIDLElementNode,
|
|
641
|
+
Promise<HastNode | HastText | Array<HastNode | HastText>>
|
|
642
|
+
> = async (
|
|
100
643
|
node,
|
|
101
|
-
|
|
644
|
+
compName,
|
|
645
|
+
nodesLookup,
|
|
102
646
|
propDefinitions,
|
|
103
647
|
stateDefinitions,
|
|
104
648
|
subComponentOptions,
|
|
105
|
-
structure
|
|
649
|
+
structure,
|
|
650
|
+
resolvedExpressions
|
|
106
651
|
) => {
|
|
652
|
+
// Check if this is a wrapped data-source node
|
|
653
|
+
if (
|
|
654
|
+
node.content &&
|
|
655
|
+
typeof node.content === 'object' &&
|
|
656
|
+
'type' in node.content &&
|
|
657
|
+
(node.content.type === 'data-source-item' || node.content.type === 'data-source-list')
|
|
658
|
+
) {
|
|
659
|
+
// Handle as data-source node - just render the success content
|
|
660
|
+
const successNode = (node.content as any).nodes?.success
|
|
661
|
+
if (successNode) {
|
|
662
|
+
return generateHtmlSyntax(
|
|
663
|
+
successNode,
|
|
664
|
+
compName,
|
|
665
|
+
nodesLookup,
|
|
666
|
+
propDefinitions,
|
|
667
|
+
stateDefinitions,
|
|
668
|
+
subComponentOptions,
|
|
669
|
+
structure,
|
|
670
|
+
resolvedExpressions
|
|
671
|
+
)
|
|
672
|
+
}
|
|
673
|
+
// If no success node, return empty div
|
|
674
|
+
return HASTBuilders.createHTMLNode('div')
|
|
675
|
+
}
|
|
676
|
+
|
|
107
677
|
const {
|
|
108
678
|
elementType,
|
|
109
679
|
children,
|
|
@@ -111,43 +681,45 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
|
|
|
111
681
|
style = {},
|
|
112
682
|
referencedStyles = {},
|
|
113
683
|
dependency,
|
|
114
|
-
key,
|
|
115
684
|
} = node.content
|
|
116
|
-
|
|
117
|
-
const elementNode = HASTBuilders.createHTMLNode(elementType)
|
|
118
|
-
templatesLookUp[key] = elementNode
|
|
119
|
-
|
|
120
685
|
const { dependencies } = structure
|
|
121
|
-
if (dependency && (dependency as UIDLDependency)?.type !== 'local') {
|
|
122
|
-
dependencies[dependency.path] = dependency
|
|
123
|
-
}
|
|
124
|
-
|
|
125
686
|
if (dependency && (dependency as UIDLDependency)?.type === 'local') {
|
|
126
687
|
const compTag = await generateComponentContent(
|
|
127
688
|
node,
|
|
689
|
+
nodesLookup,
|
|
128
690
|
propDefinitions,
|
|
129
691
|
stateDefinitions,
|
|
130
692
|
subComponentOptions,
|
|
131
|
-
structure
|
|
693
|
+
structure,
|
|
694
|
+
resolvedExpressions
|
|
132
695
|
)
|
|
696
|
+
|
|
697
|
+
if ('tagName' in compTag) {
|
|
698
|
+
compTag.children.unshift(HASTBuilders.createComment(`${node.content.semanticType} component`))
|
|
699
|
+
}
|
|
700
|
+
|
|
133
701
|
return compTag
|
|
134
702
|
}
|
|
135
703
|
|
|
704
|
+
if (dependency && (dependency as UIDLDependency)?.type !== 'local') {
|
|
705
|
+
dependencies[dependency.path] = dependency
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const elementNode = HASTBuilders.createHTMLNode(elementType)
|
|
709
|
+
|
|
136
710
|
if (children) {
|
|
137
711
|
for (const child of children) {
|
|
138
|
-
const childTag = await
|
|
712
|
+
const childTag = await generateHtmlSyntax(
|
|
139
713
|
child,
|
|
140
|
-
|
|
714
|
+
compName,
|
|
715
|
+
nodesLookup,
|
|
141
716
|
propDefinitions,
|
|
142
717
|
stateDefinitions,
|
|
143
718
|
subComponentOptions,
|
|
144
|
-
structure
|
|
719
|
+
structure,
|
|
720
|
+
resolvedExpressions
|
|
145
721
|
)
|
|
146
722
|
|
|
147
|
-
if (!childTag) {
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
|
|
151
723
|
if (typeof childTag === 'string') {
|
|
152
724
|
HASTUtils.addTextNode(elementNode, childTag)
|
|
153
725
|
} else {
|
|
@@ -160,61 +732,87 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
|
|
|
160
732
|
Object.keys(referencedStyles).forEach((styleRef) => {
|
|
161
733
|
const refStyle = referencedStyles[styleRef]
|
|
162
734
|
if (refStyle.content.mapType === 'inlined') {
|
|
163
|
-
handleStyles(
|
|
164
|
-
|
|
735
|
+
handleStyles(
|
|
736
|
+
node,
|
|
737
|
+
refStyle.content.styles,
|
|
738
|
+
propDefinitions,
|
|
739
|
+
stateDefinitions,
|
|
740
|
+
structure.options
|
|
741
|
+
)
|
|
165
742
|
}
|
|
166
743
|
})
|
|
167
744
|
}
|
|
168
745
|
|
|
169
746
|
if (Object.keys(style).length > 0) {
|
|
170
|
-
handleStyles(node, style, propDefinitions, stateDefinitions)
|
|
747
|
+
handleStyles(node, style, propDefinitions, stateDefinitions, structure.options)
|
|
171
748
|
}
|
|
172
749
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
750
|
+
handleAttributes(
|
|
751
|
+
elementType,
|
|
752
|
+
elementNode,
|
|
753
|
+
attrs,
|
|
754
|
+
propDefinitions,
|
|
755
|
+
stateDefinitions,
|
|
756
|
+
structure.options.projectRouteDefinition,
|
|
757
|
+
structure.outputOptions,
|
|
758
|
+
structure.options,
|
|
759
|
+
resolvedExpressions?.currentIndex
|
|
760
|
+
)
|
|
184
761
|
|
|
762
|
+
addNodeToLookup(node.content.key, elementNode, nodesLookup)
|
|
185
763
|
return elementNode
|
|
186
764
|
}
|
|
187
765
|
|
|
766
|
+
const createLookupTable = (
|
|
767
|
+
component: ComponentUIDL,
|
|
768
|
+
nodesLookup: Record<string, HastNode | HastText | Array<HastNode | HastText>>
|
|
769
|
+
): ElementsLookup => {
|
|
770
|
+
const lookup: ElementsLookup = {}
|
|
771
|
+
for (const node of Object.keys(nodesLookup)) {
|
|
772
|
+
lookup[node] = {
|
|
773
|
+
count: 1,
|
|
774
|
+
nextKey: '1',
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
createNodesLookup(component, lookup)
|
|
778
|
+
return lookup
|
|
779
|
+
}
|
|
780
|
+
|
|
188
781
|
const generateComponentContent = async (
|
|
189
782
|
node: UIDLElementNode,
|
|
783
|
+
nodesLookup: Record<string, HastNode | HastText | Array<HastNode | HastText>>,
|
|
190
784
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
191
785
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
192
786
|
subComponentOptions: {
|
|
193
787
|
externals: Record<string, ComponentUIDL>
|
|
194
788
|
plugins: ComponentPlugin[]
|
|
789
|
+
standaloneHtmlComponents?: boolean
|
|
195
790
|
},
|
|
196
791
|
structure: {
|
|
197
792
|
chunks: ChunkDefinition[]
|
|
198
793
|
dependencies: Record<string, UIDLDependency>
|
|
199
794
|
options: GeneratorOptions
|
|
200
795
|
outputOptions: UIDLComponentOutputOptions
|
|
796
|
+
},
|
|
797
|
+
resolvedExpressions?: {
|
|
798
|
+
expressions: Record<string, UIDLPropDefinition>
|
|
799
|
+
currentIndex: number
|
|
201
800
|
}
|
|
202
801
|
) => {
|
|
203
|
-
const { externals, plugins } = subComponentOptions
|
|
204
|
-
const { elementType, attrs = {},
|
|
802
|
+
const { externals, plugins, standaloneHtmlComponents = false } = subComponentOptions
|
|
803
|
+
const { elementType, attrs = {}, children = [] } = node.content
|
|
205
804
|
const { dependencies, chunks = [], options } = structure
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
throw new HTMLComponentGeneratorError(`${elementType} is not found from the externals. \n
|
|
212
|
-
Received ${JSON.stringify(Object.keys(externals), null, 2)}`)
|
|
805
|
+
// "Component" will not exist when generating a component because the resolver checks for illegal class names
|
|
806
|
+
const componentName = elementType === 'Component' ? 'AppComponent' : elementType
|
|
807
|
+
const component = externals[componentName]
|
|
808
|
+
if (component === undefined) {
|
|
809
|
+
throw new HTMLComponentGeneratorError(`${componentName} is missing from externals object`)
|
|
213
810
|
}
|
|
214
811
|
|
|
812
|
+
const componentClone = UIDLUtils.cloneObject<ComponentUIDL>(component)
|
|
813
|
+
|
|
215
814
|
if (children.length) {
|
|
216
|
-
|
|
217
|
-
UIDLUtils.traverseNodes(comp.node, (childNode, parentNode) => {
|
|
815
|
+
UIDLUtils.traverseNodes(componentClone.node, (childNode, parentNode) => {
|
|
218
816
|
if (childNode.type === 'slot' && parentNode.type === 'element') {
|
|
219
817
|
const nonSlotNodes = parentNode.content?.children?.filter((n) => n.type !== 'slot')
|
|
220
818
|
parentNode.content.children = [
|
|
@@ -224,6 +822,7 @@ const generateComponentContent = async (
|
|
|
224
822
|
content: {
|
|
225
823
|
key: 'custom-slot',
|
|
226
824
|
elementType: 'slot',
|
|
825
|
+
name: componentClone.name + 'slot',
|
|
227
826
|
style: {
|
|
228
827
|
display: {
|
|
229
828
|
type: 'static',
|
|
@@ -243,31 +842,47 @@ const generateComponentContent = async (
|
|
|
243
842
|
node.content.children = []
|
|
244
843
|
}
|
|
245
844
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
845
|
+
// In UIDL, we define only the link between a component and a page.
|
|
846
|
+
// We define this link using the UIDLLocalDependency approach.
|
|
847
|
+
// So, during the page resolution step, where we ideally generate the unique keys for the components.
|
|
848
|
+
// We can't generate the unique keys for the components because we don't have the full UIDL of the component.
|
|
849
|
+
// When we are using components in a page, the `addExternalComponents` step of the
|
|
850
|
+
// html-component-generator will add the full UIDL of the component to the externals object after resolving them.
|
|
851
|
+
// But when a component is used multiple number of times, we are basically using the same nodes again and again.
|
|
852
|
+
// Which indivates duplication. So, we create a lookup table of all the nodes present with us in the page
|
|
853
|
+
// And then pass it to the component to avoid any coilissions.
|
|
854
|
+
const lookupTableForCurrentPage = createLookupTable(componentClone, nodesLookup)
|
|
855
|
+
generateUniqueKeys(componentClone, lookupTableForCurrentPage)
|
|
258
856
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
857
|
+
// We are combining props of the current component
|
|
858
|
+
// with props of the component that we need to generate.
|
|
859
|
+
// Refer to line 309, for element props. We either pick from the attr of the current instance of component
|
|
860
|
+
// or from the propDefinitions of the component that we are generating.
|
|
861
|
+
// We NEED to keep passing the props of the current component to the child component and so on,
|
|
862
|
+
// so that all props are forwarded.
|
|
863
|
+
const combinedProps: Record<string, UIDLPropDefinition> = {
|
|
864
|
+
...Object.keys(propDefinitions).reduce<Record<string, UIDLPropDefinition>>(
|
|
865
|
+
(acc: Record<string, UIDLPropDefinition>, propKey) => {
|
|
866
|
+
acc[propKey] = propDefinitions[propKey]
|
|
867
|
+
return acc
|
|
868
|
+
},
|
|
869
|
+
{}
|
|
870
|
+
),
|
|
871
|
+
...(componentClone?.propDefinitions || {}),
|
|
872
|
+
}
|
|
873
|
+
const combinedStates = { ...stateDefinitions, ...(componentClone?.stateDefinitions || {}) }
|
|
265
874
|
const statesForInstance = Object.keys(combinedStates).reduce(
|
|
266
875
|
(acc: Record<string, UIDLStateDefinition>, propKey) => {
|
|
267
|
-
|
|
876
|
+
const attr = attrs[propKey]
|
|
877
|
+
|
|
878
|
+
if (attr?.type === 'object') {
|
|
879
|
+
throw new Error(`Object attributes are not supported in html exports`)
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (attr) {
|
|
268
883
|
acc[propKey] = {
|
|
269
884
|
...combinedStates[propKey],
|
|
270
|
-
defaultValue:
|
|
885
|
+
defaultValue: attr?.content ?? combinedStates[propKey]?.defaultValue,
|
|
271
886
|
}
|
|
272
887
|
} else {
|
|
273
888
|
acc[propKey] = combinedStates[propKey]
|
|
@@ -278,42 +893,150 @@ const generateComponentContent = async (
|
|
|
278
893
|
{}
|
|
279
894
|
)
|
|
280
895
|
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
896
|
+
const propsForInstance: Record<string, UIDLPropDefinition> = {}
|
|
897
|
+
// this is where we check if the component we are conusming is actually passing any props to the instance.
|
|
898
|
+
// We check if we are passing any props and pick the value from the atrrs, if not we pick the value from the propDefinitions of
|
|
899
|
+
// the component instance that we are using here.
|
|
900
|
+
for (const propKey of Object.keys(combinedProps)) {
|
|
901
|
+
const attribute = attrs[propKey]
|
|
902
|
+
|
|
903
|
+
if (attribute?.type === 'element') {
|
|
904
|
+
propsForInstance[propKey] = {
|
|
905
|
+
...combinedProps[propKey],
|
|
906
|
+
defaultValue: attrs[propKey],
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (attribute?.type === 'dynamic') {
|
|
911
|
+
// When we are using a component instance in a component and the attribute
|
|
912
|
+
// that is passed to the component is of dynamic reference.
|
|
913
|
+
// If means, the component is redirecting the prop that is received to the prop of the component that it is consuming.
|
|
914
|
+
// In this case, we need to pass the value of the prop that is received to the prop of the component that it is consuming.
|
|
915
|
+
// And similary we do the same for the states.
|
|
916
|
+
switch (attribute.content.referenceType) {
|
|
917
|
+
case 'prop':
|
|
918
|
+
propsForInstance[propKey] = combinedProps[attribute.content.id]
|
|
919
|
+
break
|
|
920
|
+
case 'state':
|
|
921
|
+
propsForInstance[propKey] = combinedStates[attribute.content.id]
|
|
922
|
+
break
|
|
923
|
+
case 'expr':
|
|
924
|
+
// Ignore expr type attributes in html comp instances for the time being.
|
|
925
|
+
break
|
|
926
|
+
default:
|
|
927
|
+
throw new Error(
|
|
928
|
+
`ReferenceType ${attribute.content.referenceType} is not supported in HTML Export.`
|
|
929
|
+
)
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (attribute?.type === 'object') {
|
|
934
|
+
propsForInstance[propKey] = {
|
|
935
|
+
...combinedProps[propKey],
|
|
936
|
+
defaultValue: (attribute?.content as object) || combinedProps[propKey]?.defaultValue,
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (attribute?.type === 'expr') {
|
|
941
|
+
const [ctxId, ...refPath] = attribute.content.split('?.')
|
|
942
|
+
const propKeyFromAttr = Object.keys(combinedProps).find((key) => key === ctxId)
|
|
943
|
+
|
|
944
|
+
const resolvedValue = combinedProps[propKeyFromAttr]
|
|
945
|
+
const hasRefPath = refPath.length > 0
|
|
946
|
+
// Build the path using repeater index when available
|
|
947
|
+
const fullRefPath =
|
|
948
|
+
typeof resolvedExpressions?.currentIndex === 'number' && hasRefPath
|
|
949
|
+
? [resolvedExpressions.currentIndex.toString(), ...refPath]
|
|
950
|
+
: refPath
|
|
951
|
+
|
|
952
|
+
if (Array.isArray(resolvedValue)) {
|
|
953
|
+
// If the resolved value itself is an array definition, pass it through
|
|
954
|
+
propsForInstance[propKey] = resolvedValue
|
|
955
|
+
} else {
|
|
956
|
+
const defaultVal = resolvedValue?.defaultValue
|
|
957
|
+
const extracted =
|
|
958
|
+
hasRefPath && defaultVal !== undefined
|
|
959
|
+
? extractDefaultValueFromRefPath(defaultVal, fullRefPath)
|
|
960
|
+
: defaultVal ?? null
|
|
961
|
+
propsForInstance[propKey] = {
|
|
962
|
+
...resolvedValue,
|
|
963
|
+
defaultValue: extracted,
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (
|
|
969
|
+
attribute?.type !== 'dynamic' &&
|
|
970
|
+
attribute?.type !== 'element' &&
|
|
971
|
+
attribute?.type !== 'object' &&
|
|
972
|
+
attribute?.type !== 'expr'
|
|
973
|
+
) {
|
|
974
|
+
propsForInstance[propKey] = {
|
|
975
|
+
...combinedProps[propKey],
|
|
976
|
+
defaultValue: attribute?.content ?? combinedProps[propKey]?.defaultValue,
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (attribute === undefined) {
|
|
981
|
+
const propFromCurrentComponent = combinedProps[propKey]
|
|
982
|
+
propsForInstance[propKey] = propFromCurrentComponent
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
let componentWrapper = StringUtils.camelCaseToDashCase(`${componentName}-wrapper`)
|
|
987
|
+
const isExistingNode = nodesLookup[componentWrapper]
|
|
988
|
+
if (isExistingNode !== undefined) {
|
|
989
|
+
componentWrapper = `${componentWrapper}-${StringUtils.generateRandomString()}`
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Transfer data-node attributes from component instance to wrapper
|
|
993
|
+
const wrapperAttrs: Record<string, UIDLAttributeValue> = {}
|
|
994
|
+
Object.keys(attrs).forEach((attrKey) => {
|
|
995
|
+
if (attrKey.startsWith('dataNode') || attrKey.startsWith('data-node')) {
|
|
996
|
+
wrapperAttrs[attrKey] = attrs[attrKey]
|
|
997
|
+
}
|
|
998
|
+
})
|
|
999
|
+
|
|
1000
|
+
const componentInstanceToGenerate: UIDLElementNode = {
|
|
1001
|
+
type: 'element',
|
|
1002
|
+
content: {
|
|
1003
|
+
elementType: componentWrapper,
|
|
1004
|
+
key: componentWrapper,
|
|
1005
|
+
children: [componentClone.node],
|
|
1006
|
+
attrs: wrapperAttrs,
|
|
1007
|
+
style: {
|
|
1008
|
+
display: {
|
|
1009
|
+
type: 'static',
|
|
1010
|
+
content: 'contents',
|
|
295
1011
|
},
|
|
296
1012
|
},
|
|
297
1013
|
},
|
|
298
|
-
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const compTag = await generateHtmlSyntax(
|
|
1017
|
+
componentInstanceToGenerate,
|
|
1018
|
+
component.name,
|
|
1019
|
+
nodesLookup,
|
|
299
1020
|
propsForInstance,
|
|
300
1021
|
statesForInstance,
|
|
301
1022
|
subComponentOptions,
|
|
302
|
-
structure
|
|
303
|
-
|
|
1023
|
+
structure,
|
|
1024
|
+
resolvedExpressions
|
|
1025
|
+
)
|
|
304
1026
|
|
|
305
1027
|
const cssPlugin = createCSSPlugin({
|
|
306
1028
|
templateStyle: 'html',
|
|
307
1029
|
templateChunkName: DEFAULT_COMPONENT_CHUNK_NAME,
|
|
308
|
-
declareDependency: 'import',
|
|
309
|
-
|
|
310
|
-
chunkName: comp.name,
|
|
1030
|
+
declareDependency: standaloneHtmlComponents ? 'none' : 'import',
|
|
1031
|
+
chunkName: componentClone.name,
|
|
311
1032
|
staticPropReferences: true,
|
|
1033
|
+
standaloneHtmlComponents,
|
|
312
1034
|
})
|
|
313
1035
|
|
|
314
1036
|
const initialStructure: ComponentStructure = {
|
|
315
1037
|
uidl: {
|
|
316
|
-
...
|
|
1038
|
+
...componentClone,
|
|
1039
|
+
node: componentInstanceToGenerate,
|
|
317
1040
|
propDefinitions: propsForInstance,
|
|
318
1041
|
stateDefinitions: statesForInstance,
|
|
319
1042
|
},
|
|
@@ -325,7 +1048,7 @@ const generateComponentContent = async (
|
|
|
325
1048
|
linkAfter: [],
|
|
326
1049
|
content: compTag,
|
|
327
1050
|
meta: {
|
|
328
|
-
nodesLookup
|
|
1051
|
+
nodesLookup,
|
|
329
1052
|
},
|
|
330
1053
|
},
|
|
331
1054
|
],
|
|
@@ -341,41 +1064,132 @@ const generateComponentContent = async (
|
|
|
341
1064
|
Promise.resolve(initialStructure)
|
|
342
1065
|
)
|
|
343
1066
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
chunks.push(chunk)
|
|
348
|
-
}
|
|
349
|
-
})
|
|
350
|
-
} else {
|
|
351
|
-
const chunk = chunks.find((item) => item.name === comp.name)
|
|
352
|
-
if (!chunk) {
|
|
353
|
-
const styleChunk = result.chunks.find(
|
|
354
|
-
(item: ChunkDefinition) => item.fileType === FileType.CSS
|
|
355
|
-
)
|
|
356
|
-
if (!styleChunk) {
|
|
357
|
-
return
|
|
358
|
-
}
|
|
359
|
-
chunks.push(styleChunk)
|
|
1067
|
+
result.chunks.forEach((chunk) => {
|
|
1068
|
+
if (chunk.fileType === FileType.CSS) {
|
|
1069
|
+
chunks.push(chunk)
|
|
360
1070
|
}
|
|
361
|
-
}
|
|
1071
|
+
})
|
|
362
1072
|
|
|
1073
|
+
addNodeToLookup(node.content.key, compTag, nodesLookup)
|
|
363
1074
|
return compTag
|
|
364
1075
|
}
|
|
365
1076
|
|
|
366
|
-
const generateDynamicNode: NodeToHTML<
|
|
1077
|
+
const generateDynamicNode: NodeToHTML<
|
|
1078
|
+
UIDLDynamicReference,
|
|
1079
|
+
Promise<HastNode | HastText | Array<HastNode | HastText>>
|
|
1080
|
+
> = async (
|
|
367
1081
|
node,
|
|
368
|
-
|
|
1082
|
+
compName,
|
|
1083
|
+
nodesLookup,
|
|
369
1084
|
propDefinitions,
|
|
370
|
-
stateDefinitions
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
1085
|
+
stateDefinitions,
|
|
1086
|
+
subComponentOptions,
|
|
1087
|
+
structure,
|
|
1088
|
+
resolvedExpressions?
|
|
1089
|
+
): Promise<HastNode | HastText | Array<HastNode | HastText>> => {
|
|
1090
|
+
if (node.content.referenceType === 'locale') {
|
|
1091
|
+
const translation = getTranslation(node.content.id, structure.options)
|
|
1092
|
+
if (translation) {
|
|
1093
|
+
if (translation.type === 'static') {
|
|
1094
|
+
return HASTBuilders.createTextNode(String(translation.content))
|
|
1095
|
+
}
|
|
1096
|
+
if (translation.type === 'element') {
|
|
1097
|
+
return generateHtmlSyntax(
|
|
1098
|
+
translation,
|
|
1099
|
+
compName,
|
|
1100
|
+
nodesLookup,
|
|
1101
|
+
propDefinitions,
|
|
1102
|
+
stateDefinitions,
|
|
1103
|
+
subComponentOptions,
|
|
1104
|
+
structure
|
|
1105
|
+
)
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const localeTag = HASTBuilders.createHTMLNode('span')
|
|
1109
|
+
const commentNode = HASTBuilders.createComment(`Content for locale ${node.content.id}`)
|
|
1110
|
+
HASTUtils.addChildNode(localeTag, commentNode)
|
|
1111
|
+
return localeTag
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (
|
|
1115
|
+
node.content.referenceType === 'global' ||
|
|
1116
|
+
(node.content.referenceType as string) === 'globalState'
|
|
1117
|
+
) {
|
|
1118
|
+
const globalTag = HASTBuilders.createHTMLNode('span')
|
|
1119
|
+
const commentNode = HASTBuilders.createComment(`Global reference: ${node.content.id}`)
|
|
1120
|
+
HASTUtils.addChildNode(globalTag, commentNode)
|
|
1121
|
+
return globalTag
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const usedReferenceValue = getValueFromReference(
|
|
1125
|
+
node.content.id,
|
|
1126
|
+
node.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
1127
|
+
)
|
|
1128
|
+
|
|
1129
|
+
if (
|
|
1130
|
+
(usedReferenceValue.type === 'object' || usedReferenceValue.type === 'array') &&
|
|
1131
|
+
usedReferenceValue.defaultValue
|
|
1132
|
+
) {
|
|
1133
|
+
// Let's say users are biding the prop to a node using something like this "fields.Title"
|
|
1134
|
+
// But the fields in the object is the value where the object is defined either in propDefinitions
|
|
1135
|
+
// or on the attrs. So, we just need to parsed the rest of the object path and get the value from the object.
|
|
1136
|
+
const extracted = extractDefaultValueFromRefPath(
|
|
1137
|
+
usedReferenceValue.defaultValue as Record<string, UIDLPropDefinition>,
|
|
1138
|
+
node.content.refPath
|
|
1139
|
+
)
|
|
1140
|
+
if (extracted === undefined || extracted === null) {
|
|
1141
|
+
return HASTBuilders.createTextNode('')
|
|
1142
|
+
}
|
|
1143
|
+
return HASTBuilders.createTextNode(String(extracted))
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (usedReferenceValue.type === 'element') {
|
|
1147
|
+
const elementNode = usedReferenceValue.defaultValue as UIDLElementNode
|
|
1148
|
+
if (elementNode) {
|
|
1149
|
+
// In repeater context, avoid reusing cached nodes; uniquify key per iteration
|
|
1150
|
+
if (resolvedExpressions && typeof resolvedExpressions.currentIndex === 'number') {
|
|
1151
|
+
const elementClone = UIDLUtils.cloneObject<UIDLElementNode>(elementNode)
|
|
1152
|
+
if (elementClone?.content?.key) {
|
|
1153
|
+
elementClone.content.key = `${elementClone.content.key}-${resolvedExpressions.currentIndex}`
|
|
1154
|
+
}
|
|
1155
|
+
const iterElementTag = await generateHtmlSyntax(
|
|
1156
|
+
elementClone,
|
|
1157
|
+
compName,
|
|
1158
|
+
nodesLookup,
|
|
1159
|
+
propDefinitions,
|
|
1160
|
+
stateDefinitions,
|
|
1161
|
+
subComponentOptions,
|
|
1162
|
+
structure,
|
|
1163
|
+
resolvedExpressions
|
|
1164
|
+
)
|
|
1165
|
+
return iterElementTag
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
if (elementNode.content.key in nodesLookup) {
|
|
1169
|
+
return nodesLookup[elementNode.content.key]
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const generatedElementTag = await generateHtmlSyntax(
|
|
1173
|
+
elementNode,
|
|
1174
|
+
compName,
|
|
1175
|
+
nodesLookup,
|
|
1176
|
+
propDefinitions,
|
|
1177
|
+
stateDefinitions,
|
|
1178
|
+
subComponentOptions,
|
|
1179
|
+
structure,
|
|
1180
|
+
resolvedExpressions
|
|
1181
|
+
)
|
|
1182
|
+
return generatedElementTag
|
|
1183
|
+
}
|
|
377
1184
|
|
|
378
|
-
|
|
1185
|
+
const spanTagWrapper = HASTBuilders.createHTMLNode('span')
|
|
1186
|
+
const commentNode = HASTBuilders.createComment(`Content for slot ${node.content.id}`)
|
|
1187
|
+
HASTUtils.addChildNode(spanTagWrapper, commentNode)
|
|
1188
|
+
return spanTagWrapper
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
const spanTag = HASTBuilders.createHTMLNode('span')
|
|
1192
|
+
HASTUtils.addTextNode(spanTag, String(usedReferenceValue.defaultValue))
|
|
379
1193
|
return spanTag
|
|
380
1194
|
}
|
|
381
1195
|
|
|
@@ -383,15 +1197,35 @@ const handleStyles = (
|
|
|
383
1197
|
node: UIDLElementNode,
|
|
384
1198
|
styles: UIDLStyleDefinitions,
|
|
385
1199
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
386
|
-
stateDefinitions: Record<string, UIDLStateDefinition
|
|
1200
|
+
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
1201
|
+
options: GeneratorOptions
|
|
387
1202
|
) => {
|
|
388
1203
|
Object.keys(styles).forEach((styleKey) => {
|
|
389
1204
|
let style: string | UIDLStyleValue = styles[styleKey]
|
|
390
1205
|
if (style.type === 'dynamic' && style.content?.referenceType !== 'token') {
|
|
391
|
-
if (style.content
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
1206
|
+
if (style.content?.referenceType === 'locale') {
|
|
1207
|
+
const translation = getTranslation(style.content.id, options)
|
|
1208
|
+
const resolvedText = translation
|
|
1209
|
+
? resolveTranslationText(translation)
|
|
1210
|
+
: `[locale: ${style.content.id}]`
|
|
1211
|
+
node.content.style[styleKey] = staticNode(resolvedText)
|
|
1212
|
+
return
|
|
1213
|
+
}
|
|
1214
|
+
if (
|
|
1215
|
+
style.content?.referenceType === 'global' ||
|
|
1216
|
+
(style.content?.referenceType as string) === 'globalState'
|
|
1217
|
+
) {
|
|
1218
|
+
node.content.style[styleKey] = staticNode(`[global: ${style.content.id}]`)
|
|
1219
|
+
return
|
|
1220
|
+
}
|
|
1221
|
+
const referencedValue = getValueFromReference(
|
|
1222
|
+
style.content.id,
|
|
1223
|
+
style.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
1224
|
+
)
|
|
1225
|
+
if (referencedValue.type === 'string' || referencedValue.type === 'number') {
|
|
1226
|
+
style = String(
|
|
1227
|
+
extractDefaultValueFromRefPath(referencedValue.defaultValue, style?.content?.refPath)
|
|
1228
|
+
)
|
|
395
1229
|
}
|
|
396
1230
|
node.content.style[styleKey] = typeof style === 'string' ? staticNode(style) : style
|
|
397
1231
|
}
|
|
@@ -405,102 +1239,177 @@ const handleAttributes = (
|
|
|
405
1239
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
406
1240
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
407
1241
|
routeDefinitions: UIDLRouteDefinitions,
|
|
408
|
-
outputOptions: UIDLComponentOutputOptions
|
|
1242
|
+
outputOptions: UIDLComponentOutputOptions,
|
|
1243
|
+
options: GeneratorOptions,
|
|
1244
|
+
currentIndex?: number
|
|
409
1245
|
) => {
|
|
410
|
-
Object.keys(attrs)
|
|
1246
|
+
for (const attrKey of Object.keys(attrs)) {
|
|
411
1247
|
const attrValue = attrs[attrKey]
|
|
1248
|
+
const { type, content } = attrValue
|
|
412
1249
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
attrValue.content.startsWith('/')
|
|
418
|
-
) {
|
|
419
|
-
let targetLink
|
|
1250
|
+
switch (type) {
|
|
1251
|
+
case 'static': {
|
|
1252
|
+
if (attrKey === 'href' && typeof content === 'string' && content.startsWith('/')) {
|
|
1253
|
+
let targetLink
|
|
420
1254
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
1255
|
+
const targetRoute = (routeDefinitions?.values || []).find(
|
|
1256
|
+
(route) => route.pageOptions.navLink === content
|
|
1257
|
+
)
|
|
424
1258
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
1259
|
+
if (targetRoute) {
|
|
1260
|
+
targetLink = targetRoute.pageOptions.navLink
|
|
1261
|
+
}
|
|
428
1262
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
1263
|
+
if (!targetRoute && content === '/home') {
|
|
1264
|
+
targetLink = '/'
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (!targetLink && !targetRoute) {
|
|
1268
|
+
targetLink = content
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
const currentPageRoute = join(...(outputOptions?.folderPath || []), './')
|
|
1272
|
+
const localPrefix = relative(
|
|
1273
|
+
`/${currentPageRoute}`,
|
|
1274
|
+
`/${targetLink === '/' ? 'index' : targetLink}`
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, `${localPrefix}.html`)
|
|
1278
|
+
break
|
|
1279
|
+
}
|
|
432
1280
|
|
|
433
|
-
|
|
434
|
-
|
|
1281
|
+
if (typeof content === 'boolean') {
|
|
1282
|
+
htmlNode.properties[attrKey] = content === true ? 'true' : 'false'
|
|
1283
|
+
} else if (typeof content === 'string' || typeof attrValue.content === 'number') {
|
|
1284
|
+
let value = StringUtils.encode(String(attrValue.content))
|
|
1285
|
+
|
|
1286
|
+
/*
|
|
1287
|
+
elementType of image is always mapped to img.
|
|
1288
|
+
For reference, check `html-mapping` file.
|
|
1289
|
+
*/
|
|
1290
|
+
if (
|
|
1291
|
+
(elementType === 'img' || elementType === 'video') &&
|
|
1292
|
+
attrKey === 'src' &&
|
|
1293
|
+
!isValidURL(value)
|
|
1294
|
+
) {
|
|
1295
|
+
/*
|
|
1296
|
+
By default we just prefix all the asset paths with just the
|
|
1297
|
+
assetPrefix that is configured in the project. But for `html` generators
|
|
1298
|
+
we need to prefix that with the current file location.
|
|
1299
|
+
|
|
1300
|
+
Because, all the other frameworks have a build setup. which serves all the
|
|
1301
|
+
assets from the `public` folder. But in the case of `html` here is how it works
|
|
1302
|
+
|
|
1303
|
+
We load a file from `index.html` the request for the image goes from
|
|
1304
|
+
'...url.../public/...image...'
|
|
1305
|
+
If it's a nested url, then the request goes from
|
|
1306
|
+
'...url/nested/public/...image..'
|
|
1307
|
+
|
|
1308
|
+
But the nested folder is available only on the root. With this
|
|
1309
|
+
The url changes prefixes to
|
|
1310
|
+
|
|
1311
|
+
../public/playground_assets/..image.. etc depending on the dept the file is in.
|
|
1312
|
+
*/
|
|
1313
|
+
value = join(relative(join(...outputOptions.folderPath), './'), value)
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, value)
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
break
|
|
435
1320
|
}
|
|
436
1321
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
1322
|
+
case 'dynamic': {
|
|
1323
|
+
if (content.referenceType === 'locale') {
|
|
1324
|
+
const translation = getTranslation(content.id, options)
|
|
1325
|
+
const resolvedText = translation
|
|
1326
|
+
? resolveTranslationText(translation)
|
|
1327
|
+
: `[locale: ${content.id}]`
|
|
1328
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, resolvedText)
|
|
1329
|
+
break
|
|
1330
|
+
}
|
|
442
1331
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
1332
|
+
if (
|
|
1333
|
+
content.referenceType === 'global' ||
|
|
1334
|
+
(content.referenceType as string) === 'globalState'
|
|
1335
|
+
) {
|
|
1336
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, `[global: ${content.id}]`)
|
|
1337
|
+
break
|
|
1338
|
+
}
|
|
446
1339
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
: getValueFromReference(attrValue.content.id, stateDefinitions)
|
|
452
|
-
HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value))
|
|
453
|
-
return
|
|
454
|
-
}
|
|
1340
|
+
const value = getValueFromReference(
|
|
1341
|
+
content.id,
|
|
1342
|
+
content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
1343
|
+
)
|
|
455
1344
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1345
|
+
// A `func` prop has no meaningful static HTML representation; skip
|
|
1346
|
+
// rather than emitting the stringified function body as an attribute.
|
|
1347
|
+
if (value.type === 'func') {
|
|
1348
|
+
break
|
|
1349
|
+
}
|
|
460
1350
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
return
|
|
464
|
-
} else if (typeof attrValue.content === 'string' || typeof attrValue.content === 'number') {
|
|
465
|
-
let value = StringUtils.encode(String(attrValue.content))
|
|
1351
|
+
const extracted = extractDefaultValueFromRefPath(value.defaultValue, content.refPath)
|
|
1352
|
+
const extractedValue = String(extracted)
|
|
466
1353
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
1354
|
+
if (
|
|
1355
|
+
(elementType === 'img' || elementType === 'video') &&
|
|
1356
|
+
attrKey === 'src' &&
|
|
1357
|
+
!extractedValue.startsWith('http')
|
|
1358
|
+
) {
|
|
1359
|
+
const path = join(relative(join(...outputOptions.folderPath), './'), extractedValue)
|
|
1360
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, path)
|
|
1361
|
+
break
|
|
1362
|
+
}
|
|
476
1363
|
|
|
477
|
-
|
|
478
|
-
|
|
1364
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, extractedValue)
|
|
1365
|
+
break
|
|
1366
|
+
}
|
|
479
1367
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
1368
|
+
case 'raw': {
|
|
1369
|
+
const rawAttr = attrValue as UIDLRawValue
|
|
1370
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, rawAttr.fallback || rawAttr.content)
|
|
1371
|
+
break
|
|
1372
|
+
}
|
|
484
1373
|
|
|
485
|
-
|
|
486
|
-
|
|
1374
|
+
case 'expr': {
|
|
1375
|
+
const fullPath = content.split('?.')
|
|
1376
|
+
const prop = propDefinitions[fullPath?.[0] || '']
|
|
1377
|
+
|
|
1378
|
+
if (!prop) {
|
|
1379
|
+
break
|
|
1380
|
+
}
|
|
487
1381
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
1382
|
+
const path =
|
|
1383
|
+
typeof currentIndex === 'number'
|
|
1384
|
+
? [currentIndex.toString(), ...fullPath.slice(1)]
|
|
1385
|
+
: fullPath.slice(1)
|
|
1386
|
+
const value = extractDefaultValueFromRefPath(prop.defaultValue, path)
|
|
1387
|
+
if (!value) {
|
|
1388
|
+
break
|
|
1389
|
+
}
|
|
1390
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value))
|
|
1391
|
+
break
|
|
491
1392
|
}
|
|
492
1393
|
|
|
493
|
-
|
|
494
|
-
|
|
1394
|
+
case 'element':
|
|
1395
|
+
case 'import':
|
|
1396
|
+
case 'object':
|
|
1397
|
+
break
|
|
1398
|
+
|
|
1399
|
+
default: {
|
|
1400
|
+
throw new HTMLComponentGeneratorError(
|
|
1401
|
+
`Received ${JSON.stringify(attrValue, null, 2)} \n in handleAttributes for html`
|
|
1402
|
+
)
|
|
1403
|
+
}
|
|
495
1404
|
}
|
|
496
|
-
}
|
|
1405
|
+
}
|
|
497
1406
|
}
|
|
498
1407
|
|
|
499
1408
|
const getValueFromReference = (
|
|
500
1409
|
key: string,
|
|
501
1410
|
definitions: Record<string, UIDLPropDefinition>
|
|
502
|
-
):
|
|
503
|
-
const usedReferenceValue = definitions[key.includes('
|
|
1411
|
+
): UIDLPropDefinition | undefined => {
|
|
1412
|
+
const usedReferenceValue = definitions[key.includes('?.') ? key.split('?.')[0] : key]
|
|
504
1413
|
|
|
505
1414
|
if (!usedReferenceValue) {
|
|
506
1415
|
throw new HTMLComponentGeneratorError(
|
|
@@ -508,9 +1417,14 @@ const getValueFromReference = (
|
|
|
508
1417
|
)
|
|
509
1418
|
}
|
|
510
1419
|
|
|
511
|
-
if (
|
|
1420
|
+
if (
|
|
1421
|
+
usedReferenceValue?.type &&
|
|
1422
|
+
['string', 'number', 'object', 'element', 'array', 'boolean', 'link', 'func'].includes(
|
|
1423
|
+
usedReferenceValue.type
|
|
1424
|
+
) === false
|
|
1425
|
+
) {
|
|
512
1426
|
throw new HTMLComponentGeneratorError(
|
|
513
|
-
`
|
|
1427
|
+
`Attribute is using dynamic value, but received of type ${JSON.stringify(
|
|
514
1428
|
usedReferenceValue,
|
|
515
1429
|
null,
|
|
516
1430
|
2
|
|
@@ -518,9 +1432,12 @@ const getValueFromReference = (
|
|
|
518
1432
|
)
|
|
519
1433
|
}
|
|
520
1434
|
|
|
521
|
-
if (
|
|
1435
|
+
if (
|
|
1436
|
+
usedReferenceValue.type !== 'element' &&
|
|
1437
|
+
usedReferenceValue.hasOwnProperty('defaultValue') === false
|
|
1438
|
+
) {
|
|
522
1439
|
throw new HTMLComponentGeneratorError(
|
|
523
|
-
`
|
|
1440
|
+
`Default value is missing from dynamic reference - ${JSON.stringify(
|
|
524
1441
|
usedReferenceValue,
|
|
525
1442
|
null,
|
|
526
1443
|
2
|
|
@@ -528,5 +1445,33 @@ const getValueFromReference = (
|
|
|
528
1445
|
)
|
|
529
1446
|
}
|
|
530
1447
|
|
|
531
|
-
return
|
|
1448
|
+
return usedReferenceValue
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
const extractDefaultValueFromRefPath = (
|
|
1452
|
+
propDefaultValue: PropDefaultValueTypes,
|
|
1453
|
+
refPath?: string[]
|
|
1454
|
+
): PropDefaultValueTypes => {
|
|
1455
|
+
if (!refPath || refPath.length === 0) {
|
|
1456
|
+
return propDefaultValue
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Directly handle array indexing for the first segment when applicable
|
|
1460
|
+
if (Array.isArray(propDefaultValue)) {
|
|
1461
|
+
const [first, ...rest] = refPath
|
|
1462
|
+
const idx = Number(first)
|
|
1463
|
+
if (!Number.isNaN(idx) && idx >= 0 && idx < propDefaultValue.length) {
|
|
1464
|
+
const nextVal = propDefaultValue[idx] as PropDefaultValueTypes
|
|
1465
|
+
if (rest.length === 0) {
|
|
1466
|
+
return nextVal
|
|
1467
|
+
}
|
|
1468
|
+
return extractDefaultValueFromRefPath(nextVal, rest)
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
if (typeof propDefaultValue !== 'object') {
|
|
1473
|
+
return propDefaultValue
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
return GenericUtils.getValueFromPath(refPath.join('.'), propDefaultValue) as PropDefaultValueTypes
|
|
532
1477
|
}
|