@teleporthq/teleport-plugin-common 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/__tests__/builders/html-builders.ts +9 -2
  2. package/__tests__/node-handlers/node-to-html/index.ts +2 -0
  3. package/__tests__/node-handlers/node-to-jsx/index.ts +1 -1
  4. package/__tests__/node-handlers/node-to-jsx/utils.ts +16 -3
  5. package/dist/cjs/builders/ast-builders.d.ts +1 -0
  6. package/dist/cjs/builders/ast-builders.d.ts.map +1 -1
  7. package/dist/cjs/builders/ast-builders.js +19 -3
  8. package/dist/cjs/builders/ast-builders.js.map +1 -1
  9. package/dist/cjs/builders/hast-builders.d.ts +1 -0
  10. package/dist/cjs/builders/hast-builders.d.ts.map +1 -1
  11. package/dist/cjs/builders/hast-builders.js +11 -1
  12. package/dist/cjs/builders/hast-builders.js.map +1 -1
  13. package/dist/cjs/builders/style-builders.d.ts +2 -2
  14. package/dist/cjs/builders/style-builders.d.ts.map +1 -1
  15. package/dist/cjs/builders/style-builders.js +39 -19
  16. package/dist/cjs/builders/style-builders.js.map +1 -1
  17. package/dist/cjs/index.d.ts +2 -1
  18. package/dist/cjs/index.d.ts.map +1 -1
  19. package/dist/cjs/index.js +3 -1
  20. package/dist/cjs/index.js.map +1 -1
  21. package/dist/cjs/node-handlers/node-to-html/constants.d.ts.map +1 -1
  22. package/dist/cjs/node-handlers/node-to-html/constants.js +2 -0
  23. package/dist/cjs/node-handlers/node-to-html/constants.js.map +1 -1
  24. package/dist/cjs/node-handlers/node-to-html/index.d.ts.map +1 -1
  25. package/dist/cjs/node-handlers/node-to-html/index.js +53 -1
  26. package/dist/cjs/node-handlers/node-to-html/index.js.map +1 -1
  27. package/dist/cjs/node-handlers/node-to-html/types.d.ts +5 -1
  28. package/dist/cjs/node-handlers/node-to-html/types.d.ts.map +1 -1
  29. package/dist/cjs/node-handlers/node-to-html/utils.d.ts.map +1 -1
  30. package/dist/cjs/node-handlers/node-to-html/utils.js +48 -4
  31. package/dist/cjs/node-handlers/node-to-html/utils.js.map +1 -1
  32. package/dist/cjs/node-handlers/node-to-jsx/index.d.ts.map +1 -1
  33. package/dist/cjs/node-handlers/node-to-jsx/index.js +325 -44
  34. package/dist/cjs/node-handlers/node-to-jsx/index.js.map +1 -1
  35. package/dist/cjs/node-handlers/node-to-jsx/types.d.ts +5 -5
  36. package/dist/cjs/node-handlers/node-to-jsx/types.d.ts.map +1 -1
  37. package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts +5 -5
  38. package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
  39. package/dist/cjs/node-handlers/node-to-jsx/utils.js +79 -36
  40. package/dist/cjs/node-handlers/node-to-jsx/utils.js.map +1 -1
  41. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  42. package/dist/cjs/utils/ast-utils.d.ts +16 -6
  43. package/dist/cjs/utils/ast-utils.d.ts.map +1 -1
  44. package/dist/cjs/utils/ast-utils.js +182 -40
  45. package/dist/cjs/utils/ast-utils.js.map +1 -1
  46. package/dist/cjs/utils/hast-utils.d.ts +8 -6
  47. package/dist/cjs/utils/hast-utils.d.ts.map +1 -1
  48. package/dist/cjs/utils/hast-utils.js +28 -4
  49. package/dist/cjs/utils/hast-utils.js.map +1 -1
  50. package/dist/cjs/utils/types.d.ts +3 -0
  51. package/dist/cjs/utils/types.d.ts.map +1 -0
  52. package/dist/cjs/utils/types.js +3 -0
  53. package/dist/cjs/utils/types.js.map +1 -0
  54. package/dist/esm/builders/ast-builders.d.ts +1 -0
  55. package/dist/esm/builders/ast-builders.d.ts.map +1 -1
  56. package/dist/esm/builders/ast-builders.js +18 -3
  57. package/dist/esm/builders/ast-builders.js.map +1 -1
  58. package/dist/esm/builders/hast-builders.d.ts +1 -0
  59. package/dist/esm/builders/hast-builders.d.ts.map +1 -1
  60. package/dist/esm/builders/hast-builders.js +9 -0
  61. package/dist/esm/builders/hast-builders.js.map +1 -1
  62. package/dist/esm/builders/style-builders.d.ts +2 -2
  63. package/dist/esm/builders/style-builders.d.ts.map +1 -1
  64. package/dist/esm/builders/style-builders.js +37 -18
  65. package/dist/esm/builders/style-builders.js.map +1 -1
  66. package/dist/esm/index.d.ts +2 -1
  67. package/dist/esm/index.d.ts.map +1 -1
  68. package/dist/esm/index.js +2 -1
  69. package/dist/esm/index.js.map +1 -1
  70. package/dist/esm/node-handlers/node-to-html/constants.d.ts.map +1 -1
  71. package/dist/esm/node-handlers/node-to-html/constants.js +2 -0
  72. package/dist/esm/node-handlers/node-to-html/constants.js.map +1 -1
  73. package/dist/esm/node-handlers/node-to-html/index.d.ts.map +1 -1
  74. package/dist/esm/node-handlers/node-to-html/index.js +54 -2
  75. package/dist/esm/node-handlers/node-to-html/index.js.map +1 -1
  76. package/dist/esm/node-handlers/node-to-html/types.d.ts +5 -1
  77. package/dist/esm/node-handlers/node-to-html/types.d.ts.map +1 -1
  78. package/dist/esm/node-handlers/node-to-html/utils.d.ts.map +1 -1
  79. package/dist/esm/node-handlers/node-to-html/utils.js +45 -4
  80. package/dist/esm/node-handlers/node-to-html/utils.js.map +1 -1
  81. package/dist/esm/node-handlers/node-to-jsx/index.d.ts.map +1 -1
  82. package/dist/esm/node-handlers/node-to-jsx/index.js +326 -45
  83. package/dist/esm/node-handlers/node-to-jsx/index.js.map +1 -1
  84. package/dist/esm/node-handlers/node-to-jsx/types.d.ts +5 -5
  85. package/dist/esm/node-handlers/node-to-jsx/types.d.ts.map +1 -1
  86. package/dist/esm/node-handlers/node-to-jsx/utils.d.ts +5 -5
  87. package/dist/esm/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
  88. package/dist/esm/node-handlers/node-to-jsx/utils.js +79 -36
  89. package/dist/esm/node-handlers/node-to-jsx/utils.js.map +1 -1
  90. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  91. package/dist/esm/utils/ast-utils.d.ts +16 -6
  92. package/dist/esm/utils/ast-utils.d.ts.map +1 -1
  93. package/dist/esm/utils/ast-utils.js +176 -39
  94. package/dist/esm/utils/ast-utils.js.map +1 -1
  95. package/dist/esm/utils/hast-utils.d.ts +8 -6
  96. package/dist/esm/utils/hast-utils.d.ts.map +1 -1
  97. package/dist/esm/utils/hast-utils.js +26 -3
  98. package/dist/esm/utils/hast-utils.js.map +1 -1
  99. package/dist/esm/utils/types.d.ts +3 -0
  100. package/dist/esm/utils/types.d.ts.map +1 -0
  101. package/dist/esm/utils/types.js +2 -0
  102. package/dist/esm/utils/types.js.map +1 -0
  103. package/package.json +4 -4
  104. package/src/builders/ast-builders.ts +36 -2
  105. package/src/builders/hast-builders.ts +11 -0
  106. package/src/builders/style-builders.ts +51 -27
  107. package/src/index.ts +12 -1
  108. package/src/node-handlers/node-to-html/constants.ts +2 -0
  109. package/src/node-handlers/node-to-html/index.ts +95 -5
  110. package/src/node-handlers/node-to-html/types.ts +6 -0
  111. package/src/node-handlers/node-to-html/utils.ts +60 -5
  112. package/src/node-handlers/node-to-jsx/index.ts +566 -62
  113. package/src/node-handlers/node-to-jsx/types.ts +10 -29
  114. package/src/node-handlers/node-to-jsx/utils.ts +106 -42
  115. package/src/utils/ast-utils.ts +283 -82
  116. package/src/utils/hast-utils.ts +37 -9
  117. package/src/utils/types.ts +25 -0
@@ -11,9 +11,13 @@ import {
11
11
  UIDLCMSItemNode,
12
12
  UIDLExpressionValue,
13
13
  UIDLCMSListRepeaterNode,
14
+ UIDLCMSMixedTypeNode,
15
+ UIDLElement,
16
+ UIDLDataSourceItemNode,
17
+ UIDLDataSourceListNode,
14
18
  } from '@teleporthq/teleport-types'
15
19
  import { UIDLUtils, StringUtils } from '@teleporthq/teleport-shared'
16
- import { JSXASTReturnType, NodeToJSX } from './types'
20
+ import { JSXASTReturnType, JSXGenerationOptions, JSXGenerationParams, NodeToJSX } from './types'
17
21
 
18
22
  import {
19
23
  addEventHandlerToTag,
@@ -31,23 +35,113 @@ import {
31
35
  generateDynamicWindowImport,
32
36
  addDynamicExpressionAttributeToJSXTag,
33
37
  resolveObjectValue,
38
+ objectToObjectExpression,
34
39
  } from '../../utils/ast-utils'
35
40
  import { createJSXTag, createSelfClosingJSXTag } from '../../builders/ast-builders'
36
41
  import { DEFAULT_JSX_OPTIONS } from './constants'
37
42
  import { ASTBuilders, ASTUtils } from '../..'
38
43
 
44
+ const getElementType = (node: UIDLNode): string | null => {
45
+ if (
46
+ node.type === 'element' &&
47
+ node.content &&
48
+ typeof node.content === 'object' &&
49
+ 'elementType' in node.content
50
+ ) {
51
+ return (node.content as any).elementType
52
+ }
53
+ if ((node.type === 'data-source-list' || node.type === 'data-source-item') && node.content) {
54
+ return (node.content as any).elementType || 'DataProvider'
55
+ }
56
+ return null
57
+ }
58
+
59
+ const getClassName = (node: UIDLNode): string | null => {
60
+ if (
61
+ node.type === 'element' &&
62
+ node.content &&
63
+ typeof node.content === 'object' &&
64
+ 'key' in node.content
65
+ ) {
66
+ return (node.content as any).key || null
67
+ }
68
+ return null
69
+ }
70
+
71
+ const reorderChildrenForSearch = (children: UIDLNode[]): UIDLNode[] => {
72
+ const searchNodes: UIDLNode[] = []
73
+ const dataProviderNodes: UIDLNode[] = []
74
+ const paginationNodes: UIDLNode[] = []
75
+ const otherNodes: UIDLNode[] = []
76
+
77
+ children.forEach((child) => {
78
+ const elementType = getElementType(child)
79
+ const nodeType = child.type
80
+ const className = getClassName(child)
81
+
82
+ // Check for search nodes - use className since elementType is replaced by semanticType
83
+ if (
84
+ className &&
85
+ (className.includes('data-source-search') || className.includes('search-node'))
86
+ ) {
87
+ searchNodes.push(child)
88
+ }
89
+ // Check for DataProvider nodes - these should appear SECOND
90
+ else if (
91
+ nodeType === 'data-source-list' ||
92
+ nodeType === 'data-source-item' ||
93
+ elementType === 'DataProvider'
94
+ ) {
95
+ dataProviderNodes.push(child)
96
+ }
97
+ // Check for pagination nodes - use className since elementType is replaced by semanticType
98
+ else if (
99
+ (elementType &&
100
+ (elementType.includes('pagination') || elementType.includes('cms-pagination'))) ||
101
+ (className && (className.includes('pagination') || className.includes('cms-pagination')))
102
+ ) {
103
+ paginationNodes.push(child)
104
+ }
105
+ // Everything else goes in the middle
106
+ else {
107
+ otherNodes.push(child)
108
+ }
109
+ })
110
+
111
+ // Only reorder if we have search or pagination nodes alongside a DataProvider
112
+ // This ensures we only reorder when there's an actual pagination/search group
113
+ // If there's no search/pagination, keep original order to preserve DataProvider positions
114
+ const hasSearchOrPagination = searchNodes.length > 0 || paginationNodes.length > 0
115
+ const hasDataProvider = dataProviderNodes.length > 0
116
+
117
+ if (hasSearchOrPagination && hasDataProvider) {
118
+ // Order: search nodes first, then data providers, then other nodes, then pagination last
119
+ // This ensures: search input -> DataProvider -> content -> pagination buttons
120
+ return [...searchNodes, ...dataProviderNodes, ...otherNodes, ...paginationNodes]
121
+ } else {
122
+ // No reordering needed - preserve original order
123
+ return children
124
+ }
125
+ }
126
+
39
127
  const generateElementNode: NodeToJSX<UIDLElementNode, types.JSXElement> = (
40
128
  node,
41
129
  params,
42
130
  jsxOptions
43
131
  ) => {
44
- const { dependencies, nodesLookup, projectResources = {} } = params
45
- const options = { ...DEFAULT_JSX_OPTIONS, ...jsxOptions, projectResources }
132
+ const { dependencies, nodesLookup } = params
133
+ const options = { ...DEFAULT_JSX_OPTIONS, ...jsxOptions }
46
134
  const { elementType, selfClosing, children, key, attrs, dependency, events } = node.content
47
135
 
48
136
  const originalElementName = elementType || 'component'
49
137
  let tagName = originalElementName
50
138
 
139
+ // Check if this is a fragment element - normalize to Fragment before processing
140
+ const isFragment = tagName.toLowerCase() === 'fragment'
141
+ if (isFragment) {
142
+ tagName = 'Fragment'
143
+ }
144
+
51
145
  if (dependency) {
52
146
  if (
53
147
  options.dependencyHandling === 'import' ||
@@ -75,59 +169,29 @@ const generateElementNode: NodeToJSX<UIDLElementNode, types.JSXElement> = (
75
169
  const dynamicWindowImport = generateDynamicWindowImport('useEffect', dependency.path)
76
170
  params.windowImports[dependency.path] = dynamicWindowImport
77
171
  }
172
+ } else if (isFragment) {
173
+ // If no dependency was provided but this is a fragment, add Fragment dependency
174
+ if (options.dependencyHandling === 'import') {
175
+ dependencies.Fragment = {
176
+ type: 'package',
177
+ path: 'react',
178
+ version: 'latest',
179
+ meta: {
180
+ namedImport: true,
181
+ },
182
+ }
183
+ }
78
184
  }
79
185
 
80
186
  const elementName =
81
187
  dependency && dependency.type === 'local' && options.customElementTag
82
188
  ? options.customElementTag(tagName)
83
189
  : tagName
190
+
84
191
  const elementTag = selfClosing ? createSelfClosingJSXTag(elementName) : createJSXTag(elementName)
85
192
 
86
193
  if (attrs) {
87
- Object.keys(attrs).forEach((attrKey) => {
88
- const attributeValue = attrs[attrKey]
89
-
90
- switch (attributeValue.type) {
91
- case 'dynamic':
92
- const {
93
- content: { referenceType },
94
- } = attributeValue
95
-
96
- switch (referenceType) {
97
- default:
98
- const prefix =
99
- options.dynamicReferencePrefixMap[referenceType as 'prop' | 'state' | 'local']
100
- addDynamicAttributeToJSXTag(
101
- elementTag,
102
- attrKey,
103
- (attributeValue as UIDLDynamicReference).content.id,
104
- prefix
105
- )
106
-
107
- break
108
- }
109
- break
110
- case 'import':
111
- addDynamicAttributeToJSXTag(elementTag, attrKey, attributeValue.content.id)
112
- break
113
- case 'raw':
114
- addRawAttributeToJSXTag(elementTag, attrKey, attributeValue)
115
- break
116
- case 'comp-style':
117
- case 'static':
118
- addAttributeToJSXTag(elementTag, attrKey, attributeValue.content)
119
- break
120
- case 'expr':
121
- addDynamicExpressionAttributeToJSXTag(elementTag, attributeValue, attrKey)
122
- break
123
- default:
124
- throw new Error(
125
- `generateElementNode could not generate code for attribute of type ${JSON.stringify(
126
- attributeValue
127
- )}`
128
- )
129
- }
130
- })
194
+ addAttributesToJSXTag(attrs, elementTag, options, params)
131
195
  }
132
196
 
133
197
  if (events) {
@@ -137,7 +201,10 @@ const generateElementNode: NodeToJSX<UIDLElementNode, types.JSXElement> = (
137
201
  }
138
202
 
139
203
  if (!selfClosing && children) {
140
- children.forEach((child) => {
204
+ // Reorder children to ensure search nodes appear before DataProvider nodes
205
+ const reorderedChildren = reorderChildrenForSearch(children)
206
+
207
+ reorderedChildren.forEach((child) => {
141
208
  const childTags = generateNode(child, params, options)
142
209
  childTags.forEach((childTag) => {
143
210
  if (typeof childTag === 'string') {
@@ -157,6 +224,84 @@ const generateElementNode: NodeToJSX<UIDLElementNode, types.JSXElement> = (
157
224
 
158
225
  export default generateElementNode
159
226
 
227
+ const addAttributesToJSXTag = (
228
+ attrs: UIDLElement['attrs'],
229
+ elementTag: types.JSXElement,
230
+ options: JSXGenerationOptions,
231
+ params: JSXGenerationParams
232
+ ) => {
233
+ Object.keys(attrs ?? {}).forEach((attrKey) => {
234
+ const attributeValue = attrs[attrKey]
235
+
236
+ if (!attributeValue.type) {
237
+ return
238
+ }
239
+
240
+ switch (attributeValue.type) {
241
+ case 'dynamic':
242
+ const {
243
+ content: { referenceType },
244
+ } = attributeValue
245
+
246
+ switch (referenceType) {
247
+ default:
248
+ const prefix =
249
+ options.dynamicReferencePrefixMap[referenceType as 'prop' | 'state' | 'local']
250
+ addDynamicAttributeToJSXTag(
251
+ elementTag,
252
+ attrKey,
253
+ (attributeValue as UIDLDynamicReference).content.id,
254
+ prefix
255
+ )
256
+
257
+ break
258
+ }
259
+ break
260
+ case 'import':
261
+ addDynamicAttributeToJSXTag(elementTag, attrKey, attributeValue.content.id)
262
+ break
263
+ case 'raw':
264
+ addRawAttributeToJSXTag(elementTag, attrKey, attributeValue)
265
+ break
266
+ case 'comp-style':
267
+ case 'static':
268
+ addAttributeToJSXTag(elementTag, attrKey, attributeValue.content)
269
+ break
270
+ case 'expr':
271
+ addDynamicExpressionAttributeToJSXTag(elementTag, attributeValue, attrKey)
272
+ break
273
+
274
+ case 'element':
275
+ addAttributeToJSXTag(
276
+ elementTag,
277
+ attrKey,
278
+ generateElementNode(attributeValue, params, options)
279
+ )
280
+
281
+ break
282
+
283
+ case 'object': {
284
+ const content = attributeValue.content
285
+ if (typeof content !== 'object') {
286
+ return
287
+ }
288
+ const expression = objectToObjectExpression(content as Record<string, unknown>)
289
+ elementTag.openingElement.attributes.push(
290
+ types.jsxAttribute(types.jsxIdentifier(attrKey), types.jsxExpressionContainer(expression))
291
+ )
292
+ break
293
+ }
294
+
295
+ default:
296
+ throw new Error(
297
+ `generateElementNode could not generate code for attribute of type ${JSON.stringify(
298
+ attributeValue
299
+ )}`
300
+ )
301
+ }
302
+ })
303
+ }
304
+
160
305
  const generateNode: NodeToJSX<UIDLNode, JSXASTReturnType[]> = (node, params, options) => {
161
306
  switch (node.type) {
162
307
  case 'expr':
@@ -171,8 +316,7 @@ const generateNode: NodeToJSX<UIDLNode, JSXASTReturnType[]> = (node, params, opt
171
316
 
172
317
  case 'inject':
173
318
  if (node?.dependency) {
174
- /* tslint:disable:no-string-literal */
175
- params.dependencies['Script'] = node.dependency
319
+ params.dependencies.Script = node.dependency
176
320
  }
177
321
  return [node.content.toString()]
178
322
 
@@ -180,13 +324,92 @@ const generateNode: NodeToJSX<UIDLNode, JSXASTReturnType[]> = (node, params, opt
180
324
  return [StringUtils.encode(node.content.toString())]
181
325
 
182
326
  case 'dynamic':
183
- return [createDynamicValueExpression(node, options, undefined)]
327
+ switch (node.content.referenceType) {
328
+ case 'prop': {
329
+ // If the dynamic node is a prop and has a default value of type UIDLElementNode,
330
+ // we should use it with a logical expression.
331
+ const prop = params.propDefinitions[node.content.id]
332
+ if (prop?.type === 'element' && prop.defaultValue) {
333
+ const prefix = options.dynamicReferencePrefixMap[node.content.referenceType] || ''
334
+
335
+ const propDefault = prop.defaultValue as UIDLElementNode
336
+ const jsxNode = params.nodesLookup[propDefault.content.key]
337
+
338
+ if (jsxNode === undefined) {
339
+ throw Error(`Prop ${node.content.id} is of type element \n
340
+ The JSXNode of the prop-${node.content.id} is missing from the nodesLookup`)
341
+ }
342
+
343
+ return [
344
+ types.logicalExpression(
345
+ '??',
346
+ prefix === ''
347
+ ? types.identifier(node.content.id)
348
+ : types.memberExpression(
349
+ types.identifier(prefix),
350
+ types.identifier(node.content.id)
351
+ ),
352
+ jsxNode as types.JSXElement
353
+ ),
354
+ ]
355
+ }
356
+
357
+ return [createDynamicValueExpression(node, options)]
358
+ }
359
+
360
+ case 'locale': {
361
+ // Locale is not handled the same in all frameworks.
362
+ // So, we need to handle it differently using individual plugins for each framework.
363
+ const emptyExpression = types.jsxEmptyExpression()
364
+ emptyExpression.innerComments = [
365
+ {
366
+ type: 'CommentBlock',
367
+ value: `locale-${node.content.id}`,
368
+ },
369
+ ]
370
+ const expression = types.jsxExpressionContainer(emptyExpression)
371
+ const jsxTag = createJSXTag('span')
372
+ addChildJSXTag(jsxTag, expression)
373
+
374
+ params.localeReferences.push(jsxTag)
375
+ return [jsxTag]
376
+ }
377
+
378
+ case 'global': {
379
+ params.globalReferences.push(node.content.id)
380
+ return [createDynamicValueExpression(node, options)]
381
+ }
382
+
383
+ default:
384
+ return [createDynamicValueExpression(node, options)]
385
+ }
184
386
 
185
387
  case 'cms-item':
186
388
  case 'cms-list':
187
389
  return generateCMSNode(node, params, options)
188
390
 
391
+ case 'cms-list-repeater':
392
+ return generateCMSListRepeaterNode(node, params, options)
393
+
394
+ case 'cms-mixed-type':
395
+ return generateCMSMixedTypeNode(node, params, options)
396
+
397
+ case 'data-source-item':
398
+ case 'data-source-list':
399
+ return generateDataSourceNode(node, params, options)
400
+
189
401
  case 'element':
402
+ // Check if this element node is wrapping a data-source node
403
+ if (
404
+ node.content &&
405
+ typeof node.content === 'object' &&
406
+ 'type' in node.content &&
407
+ (node.content.type === 'data-source-item' || node.content.type === 'data-source-list')
408
+ ) {
409
+ // Treat it as a data-source node
410
+ // tslint:disable-next-line:no-any
411
+ return generateDataSourceNode(node.content as any, params, options)
412
+ }
190
413
  return [generateElementNode(node, params, options)]
191
414
 
192
415
  case 'repeat':
@@ -195,9 +418,6 @@ const generateNode: NodeToJSX<UIDLNode, JSXASTReturnType[]> = (node, params, opt
195
418
  case 'conditional':
196
419
  return generateConditionalNode(node, params, options)
197
420
 
198
- case 'cms-list-repeater':
199
- return generateCMSListRepeaterNode(node, params, options)
200
-
201
421
  case 'slot':
202
422
  if (options.slotHandling === 'native') {
203
423
  return [generateNativeSlotNode(node, params, options)]
@@ -220,7 +440,99 @@ const generateExpressionNode: NodeToJSX<UIDLExpressionValue, types.JSXExpression
220
440
  node
221
441
  ) => {
222
442
  const expression = ASTUtils.getExpressionFromUIDLExpressionNode(node)
223
- return types.jsxExpressionContainer(expression)
443
+
444
+ // Wrap expression to safely handle objects/arrays: (typeof val === 'object' && val !== null) ? JSON.stringify(val) : val
445
+ const safeExpression = types.conditionalExpression(
446
+ types.logicalExpression(
447
+ '&&',
448
+ types.binaryExpression(
449
+ '===',
450
+ types.unaryExpression('typeof', expression, true),
451
+ types.stringLiteral('object')
452
+ ),
453
+ types.binaryExpression('!==', expression, types.nullLiteral())
454
+ ),
455
+ types.callExpression(
456
+ types.memberExpression(types.identifier('JSON'), types.identifier('stringify')),
457
+ [expression]
458
+ ),
459
+ expression
460
+ )
461
+
462
+ return types.jsxExpressionContainer(safeExpression)
463
+ }
464
+
465
+ const generateCMSMixedTypeNode: NodeToJSX<UIDLCMSMixedTypeNode, types.JSXElement[]> = (
466
+ node,
467
+ params,
468
+ options
469
+ ) => {
470
+ const {
471
+ nodes: { error, fallback },
472
+ elementType,
473
+ renderPropIdentifier,
474
+ mappings = {},
475
+ attrs,
476
+ dependency,
477
+ } = node.content
478
+ const jsxTag = StringUtils.dashCaseToUpperCamelCase(elementType)
479
+ const cmsMixedNode = ASTBuilders.createJSXTag(jsxTag, [], true)
480
+ const mappingsObject: types.ObjectProperty[] = []
481
+
482
+ if (attrs) {
483
+ addAttributesToJSXTag(attrs, cmsMixedNode, options, params)
484
+ }
485
+
486
+ if (dependency) {
487
+ params.dependencies[elementType] = dependency
488
+ }
489
+
490
+ Object.keys(mappings).forEach((key) => {
491
+ const element = generateElementNode(mappings[key], params, options)
492
+ mappingsObject.push(
493
+ types.objectProperty(
494
+ types.identifier(`"${key}"`),
495
+ types.arrowFunctionExpression([types.identifier(renderPropIdentifier)], element)
496
+ )
497
+ )
498
+ })
499
+
500
+ cmsMixedNode.openingElement.attributes.push(
501
+ types.jsxAttribute(
502
+ types.jsxIdentifier('mappingConfiguration'),
503
+ types.jsxExpressionContainer(types.objectExpression(mappingsObject))
504
+ )
505
+ )
506
+
507
+ if (fallback) {
508
+ cmsMixedNode.openingElement.attributes.push(
509
+ types.jSXAttribute(
510
+ types.jsxIdentifier('renderDefault'),
511
+ types.jsxExpressionContainer(
512
+ types.arrowFunctionExpression(
513
+ [types.identifier(renderPropIdentifier)],
514
+ generateElementNode(fallback, params, options)
515
+ )
516
+ )
517
+ )
518
+ )
519
+ }
520
+
521
+ if (error) {
522
+ cmsMixedNode.openingElement.attributes.push(
523
+ types.jSXAttribute(
524
+ types.jsxIdentifier('renderError'),
525
+ types.jsxExpressionContainer(
526
+ types.arrowFunctionExpression(
527
+ [types.identifier(renderPropIdentifier)],
528
+ generateElementNode(error, params, options)
529
+ )
530
+ )
531
+ )
532
+ )
533
+ }
534
+
535
+ return [cmsMixedNode]
224
536
  }
225
537
 
226
538
  const generateCMSNode: NodeToJSX<UIDLCMSListNode | UIDLCMSItemNode, types.JSXElement[]> = (
@@ -396,6 +708,158 @@ const generateCMSNode: NodeToJSX<UIDLCMSListNode | UIDLCMSItemNode, types.JSXEle
396
708
  return [cmsNode]
397
709
  }
398
710
 
711
+ const generateDataSourceNode: NodeToJSX<
712
+ UIDLDataSourceItemNode | UIDLDataSourceListNode,
713
+ types.JSXElement[]
714
+ > = (node, params, options) => {
715
+ const { renderPropIdentifier, elementType, dependency, resourceDefinition } = node.content
716
+
717
+ const key = node.content.key || `ds-${resourceDefinition.dataSourceId}-${Date.now()}`
718
+ const name = node.content.name || renderPropIdentifier
719
+
720
+ // tslint:disable-next-line:no-any
721
+ const children =
722
+ node.content.children && node.content.children.length > 0
723
+ ? node.content.children
724
+ : (node.content as any).nodes?.success
725
+ ? [(node.content as any).nodes.success]
726
+ : []
727
+
728
+ const jsxTag = StringUtils.dashCaseToUpperCamelCase(elementType)
729
+
730
+ if (dependency && options.dependencyHandling === 'import') {
731
+ params.dependencies[elementType] = dependency
732
+ }
733
+
734
+ const dataSourceNode = ASTBuilders.createJSXTag(jsxTag, [], true)
735
+
736
+ dataSourceNode.openingElement.attributes.push(
737
+ types.jsxAttribute(
738
+ types.jsxIdentifier('resourceDefinition'),
739
+ types.jsxExpressionContainer(
740
+ types.objectExpression([
741
+ types.objectProperty(
742
+ types.stringLiteral('type'),
743
+ types.stringLiteral('external-data-source')
744
+ ),
745
+ types.objectProperty(
746
+ types.stringLiteral('dataSourceId'),
747
+ types.stringLiteral(resourceDefinition.dataSourceId)
748
+ ),
749
+ types.objectProperty(
750
+ types.stringLiteral('tableName'),
751
+ types.stringLiteral(resourceDefinition.tableName)
752
+ ),
753
+ types.objectProperty(
754
+ types.stringLiteral('dataSourceType'),
755
+ types.stringLiteral(resourceDefinition.dataSourceType)
756
+ ),
757
+ ])
758
+ )
759
+ )
760
+ )
761
+
762
+ dataSourceNode.openingElement.attributes.push(
763
+ types.jsxAttribute(
764
+ types.jsxIdentifier('name'),
765
+ types.jsxExpressionContainer(types.stringLiteral(name))
766
+ )
767
+ )
768
+
769
+ if (children && children.length > 0) {
770
+ const childrenNodes = children.flatMap((child) => generateNode(child, params, options))
771
+ const renderFunction = types.arrowFunctionExpression(
772
+ [types.identifier(renderPropIdentifier)],
773
+ types.jsxFragment(
774
+ types.jsxOpeningFragment(),
775
+ types.jsxClosingFragment(),
776
+ childrenNodes as types.JSXElement[]
777
+ )
778
+ )
779
+ dataSourceNode.openingElement.attributes.push(
780
+ types.jsxAttribute(
781
+ types.jsxIdentifier('renderSuccess'),
782
+ types.jsxExpressionContainer(renderFunction)
783
+ )
784
+ )
785
+ }
786
+
787
+ // tslint:disable-next-line:no-any
788
+ if ((node.content as any).nodes?.loading) {
789
+ // tslint:disable-next-line:no-any
790
+ const loadingNode = generateNode((node.content as any).nodes.loading, params, options)[0]
791
+ dataSourceNode.openingElement.attributes.push(
792
+ types.jsxAttribute(
793
+ types.jsxIdentifier('renderLoading'),
794
+ types.jsxExpressionContainer(
795
+ types.arrowFunctionExpression([], loadingNode as types.JSXElement)
796
+ )
797
+ )
798
+ )
799
+ }
800
+
801
+ // tslint:disable-next-line:no-any
802
+ if ((node.content as any).nodes?.error) {
803
+ // tslint:disable-next-line:no-any
804
+ const errorNode = generateNode((node.content as any).nodes.error, params, options)[0]
805
+ dataSourceNode.openingElement.attributes.push(
806
+ types.jsxAttribute(
807
+ types.jsxIdentifier('renderError'),
808
+ types.jsxExpressionContainer(
809
+ types.arrowFunctionExpression([], errorNode as types.JSXElement)
810
+ )
811
+ )
812
+ )
813
+ }
814
+
815
+ // Add resource params if they exist
816
+ // tslint:disable-next-line:no-any
817
+ const resourceParams = (node.content as any).resource?.params
818
+ if (resourceParams && Object.keys(resourceParams).length > 0) {
819
+ const nodeParams: types.ObjectProperty[] = Object.keys(resourceParams).reduce(
820
+ (acc: types.ObjectProperty[], attrKey) => {
821
+ const property = resourceParams[attrKey]
822
+
823
+ if (property.type === 'static') {
824
+ acc.push(types.objectProperty(types.stringLiteral(attrKey), resolveObjectValue(property)))
825
+ }
826
+
827
+ if (property.type === 'expr') {
828
+ const expression = ASTUtils.getExpressionFromUIDLExpressionNode(property)
829
+ acc.push(types.objectProperty(types.stringLiteral(attrKey), expression))
830
+ }
831
+
832
+ if (property.type === 'dynamic') {
833
+ const refType = property.content.referenceType as 'prop' | 'state' | 'local'
834
+ acc.push(
835
+ types.objectProperty(
836
+ types.stringLiteral(attrKey),
837
+ property.content.referenceType === 'prop'
838
+ ? types.memberExpression(
839
+ types.identifier(options.dynamicReferencePrefixMap[refType]),
840
+ types.identifier(property.content.id)
841
+ )
842
+ : types.identifier(property.content.id)
843
+ )
844
+ )
845
+ }
846
+
847
+ return acc
848
+ },
849
+ []
850
+ )
851
+ dataSourceNode.openingElement.attributes.push(
852
+ types.jsxAttribute(
853
+ types.jsxIdentifier('params'),
854
+ types.jsxExpressionContainer(types.objectExpression(nodeParams))
855
+ )
856
+ )
857
+ }
858
+
859
+ params.nodesLookup[key] = dataSourceNode
860
+ return [dataSourceNode]
861
+ }
862
+
399
863
  const generateRepeatNode: NodeToJSX<UIDLRepeatNode, types.JSXExpressionContainer[]> = (
400
864
  node,
401
865
  params,
@@ -433,8 +897,6 @@ const generateConditionalNode: NodeToJSX<UIDLConditionalNode, types.LogicalExpre
433
897
  options
434
898
  ) => {
435
899
  const { reference, value } = node.content
436
- const conditionIdentifier = createConditionIdentifier(reference, params, options)
437
-
438
900
  const subTrees = generateNode(node.content.node, params, options)
439
901
 
440
902
  const condition: UIDLConditionalExpression =
@@ -442,9 +904,12 @@ const generateConditionalNode: NodeToJSX<UIDLConditionalNode, types.LogicalExpre
442
904
  ? { conditions: [{ operand: value, operation: '===' }] }
443
905
  : node.content.condition
444
906
 
445
- return subTrees.map((subTree) =>
907
+ const conditionIdentifier = createConditionIdentifier(reference, params, options)
908
+ const conditionalExpressions: types.LogicalExpression[] = subTrees.map((subTree) =>
446
909
  createConditionalJSXExpression(subTree, condition, conditionIdentifier)
447
910
  )
911
+
912
+ return conditionalExpressions
448
913
  }
449
914
 
450
915
  const generateCMSListRepeaterNode: NodeToJSX<UIDLCMSListRepeaterNode, types.JSXElement[]> = (
@@ -452,21 +917,52 @@ const generateCMSListRepeaterNode: NodeToJSX<UIDLCMSListRepeaterNode, types.JSXE
452
917
  params,
453
918
  options
454
919
  ) => {
455
- const repeaterNode = ASTBuilders.createJSXTag('Repeater', [], true)
920
+ const jsxTag = StringUtils.dashCaseToUpperCamelCase(node.content.elementType)
921
+ const repeaterNode = ASTBuilders.createJSXTag(jsxTag, [], true)
922
+
456
923
  repeaterNode.openingElement.attributes.push(
457
924
  types.jsxAttribute(
458
925
  types.jsxIdentifier('items'),
459
- types.jsxExpressionContainer(types.identifier('params'))
926
+ types.jsxExpressionContainer(types.identifier(node.content.source ?? 'params'))
460
927
  )
461
928
  )
462
929
 
930
+ const listElement = generateNode(node.content.nodes.list, params, {
931
+ ...options,
932
+ localIdentifier: node.content.renderPropIdentifier,
933
+ })[0] as types.JSXElement
934
+
935
+ // Create key as template literal: `${item?.id}${index}`
936
+ const keyAttribute = types.jsxAttribute(
937
+ types.jsxIdentifier('key'),
938
+ types.jsxExpressionContainer(
939
+ types.templateLiteral(
940
+ [
941
+ types.templateElement({ raw: '', cooked: '' }, false),
942
+ types.templateElement({ raw: '', cooked: '' }, false),
943
+ types.templateElement({ raw: '', cooked: '' }, true),
944
+ ],
945
+ [
946
+ types.optionalMemberExpression(
947
+ types.identifier(node.content.renderPropIdentifier),
948
+ types.identifier('id'),
949
+ false,
950
+ true
951
+ ),
952
+ types.identifier('index'),
953
+ ]
954
+ )
955
+ )
956
+ )
957
+ listElement.openingElement.attributes.push(keyAttribute)
958
+
463
959
  repeaterNode.openingElement.attributes.push(
464
960
  types.jsxAttribute(
465
961
  types.jSXIdentifier('renderItem'),
466
962
  types.jsxExpressionContainer(
467
963
  types.arrowFunctionExpression(
468
- [types.identifier(node.content.renderPropIdentifier)],
469
- generateNode(node.content.nodes.list, params, options)[0] as types.JSXElement
964
+ [types.identifier(node.content.renderPropIdentifier), types.identifier('index')],
965
+ listElement
470
966
  )
471
967
  )
472
968
  )
@@ -486,6 +982,14 @@ const generateCMSListRepeaterNode: NodeToJSX<UIDLCMSListRepeaterNode, types.JSXE
486
982
  )
487
983
  }
488
984
 
985
+ if ('loading' in node.content.nodes) {
986
+ generateNode(node.content.nodes.loading, params, options)
987
+ }
988
+
989
+ if (node.content?.dependency && options.dependencyHandling === 'import') {
990
+ params.dependencies[jsxTag] = node.content.dependency
991
+ }
992
+
489
993
  return [repeaterNode]
490
994
  }
491
995