@teleporthq/teleport-plugin-html-base-component 0.35.0-alpha.0 → 0.35.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.
@@ -17,34 +17,51 @@ import {
17
17
  UIDLStyleValue,
18
18
  GeneratorOptions,
19
19
  UIDLRouteDefinitions,
20
+ ComponentPlugin,
21
+ ComponentStructure,
22
+ UIDLComponentOutputOptions,
23
+ UIDLElement,
20
24
  } from '@teleporthq/teleport-types'
25
+ import { join, relative } from 'path'
21
26
  import { HASTBuilders, HASTUtils } from '@teleporthq/teleport-plugin-common'
22
27
  import { StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
23
28
  import { staticNode } from '@teleporthq/teleport-uidl-builders'
24
29
  import { createCSSPlugin } from '@teleporthq/teleport-plugin-css'
25
30
  import { DEFAULT_COMPONENT_CHUNK_NAME } from './constants'
26
31
 
32
+ const isValidURL = (url: string) => {
33
+ try {
34
+ /* tslint:disable:no-unused-expression */
35
+ new URL(url)
36
+ return true
37
+ } catch (error) {
38
+ return false
39
+ }
40
+ }
41
+
27
42
  type NodeToHTML<NodeType, ReturnType> = (
28
43
  node: NodeType,
29
- templatesLookUp: Record<string, unknown>,
44
+ nodesLookup: Record<string, HastNode | HastText>,
30
45
  propDefinitions: Record<string, UIDLPropDefinition>,
31
46
  stateDefinitions: Record<string, UIDLStateDefinition>,
32
- externals: Record<string, ComponentUIDL>,
33
- routeDefinitions: UIDLRouteDefinitions,
47
+ subComponentOptions: {
48
+ externals: Record<string, ComponentUIDL>
49
+ plugins: ComponentPlugin[]
50
+ },
34
51
  structure: {
35
52
  chunks: ChunkDefinition[]
36
53
  dependencies: Record<string, UIDLDependency>
37
54
  options: GeneratorOptions
55
+ outputOptions: UIDLComponentOutputOptions
38
56
  }
39
57
  ) => ReturnType
40
58
 
41
- export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastText>> = async (
59
+ export const generateHtmlSyntax: NodeToHTML<UIDLNode, Promise<HastNode | HastText>> = async (
42
60
  node,
43
- templatesLookUp,
61
+ nodesLookup,
44
62
  propDefinitions,
45
63
  stateDefinitions,
46
- externals,
47
- routeDefinitions,
64
+ subComponentOptions,
48
65
  structure
49
66
  ) => {
50
67
  switch (node.type) {
@@ -59,27 +76,28 @@ export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
59
76
  return HASTBuilders.createHTMLNode(node.type)
60
77
 
61
78
  case 'element':
62
- return generatElementNode(
79
+ const elementNode = await generateElementNode(
63
80
  node,
64
- templatesLookUp,
81
+ nodesLookup,
65
82
  propDefinitions,
66
83
  stateDefinitions,
67
- externals,
68
- routeDefinitions,
84
+ subComponentOptions,
69
85
  structure
70
86
  )
87
+ return elementNode
71
88
 
72
89
  case 'dynamic':
73
- return generateDynamicNode(
90
+ const dynamicNode = await generateDynamicNode(
74
91
  node,
75
- templatesLookUp,
92
+ nodesLookup,
76
93
  propDefinitions,
77
94
  stateDefinitions,
78
- externals,
79
- routeDefinitions,
95
+ subComponentOptions,
80
96
  structure
81
97
  )
82
98
 
99
+ return dynamicNode
100
+
83
101
  default:
84
102
  throw new HTMLComponentGeneratorError(
85
103
  `generateHtmlSyntax encountered a node of unsupported type: ${JSON.stringify(
@@ -91,13 +109,12 @@ export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
91
109
  }
92
110
  }
93
111
 
94
- const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastText>> = async (
112
+ const generateElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastText>> = async (
95
113
  node,
96
- templatesLookUp,
114
+ nodesLookup,
97
115
  propDefinitions,
98
116
  stateDefinitions,
99
- externals,
100
- routeDefinitions,
117
+ subComponentOptions,
101
118
  structure
102
119
  ) => {
103
120
  const {
@@ -109,43 +126,51 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
109
126
  dependency,
110
127
  key,
111
128
  } = node.content
112
-
113
- const elementNode = HASTBuilders.createHTMLNode(elementType)
114
- templatesLookUp[key] = elementNode
115
-
116
129
  const { dependencies } = structure
117
130
  if (dependency && (dependency as UIDLDependency)?.type !== 'local') {
118
131
  dependencies[dependency.path] = dependency
119
132
  }
120
133
 
121
134
  if (dependency && (dependency as UIDLDependency)?.type === 'local') {
135
+ for (const attrKey of Object.keys(attrs)) {
136
+ const attr = attrs[attrKey]
137
+ if (attr.type === 'element') {
138
+ await generateElementNode(
139
+ attr,
140
+ nodesLookup,
141
+ propDefinitions,
142
+ stateDefinitions,
143
+ subComponentOptions,
144
+ structure
145
+ )
146
+ }
147
+ }
148
+
122
149
  const compTag = await generateComponentContent(
123
150
  node,
151
+ nodesLookup,
124
152
  propDefinitions,
125
153
  stateDefinitions,
126
- externals,
127
- routeDefinitions,
154
+ subComponentOptions,
128
155
  structure
129
156
  )
157
+
130
158
  return compTag
131
159
  }
132
160
 
161
+ const elementNode = HASTBuilders.createHTMLNode(elementType)
162
+
133
163
  if (children) {
134
164
  for (const child of children) {
135
- const childTag = await generateHtmlSynatx(
165
+ const childTag = await generateHtmlSyntax(
136
166
  child,
137
- templatesLookUp,
167
+ nodesLookup,
138
168
  propDefinitions,
139
169
  stateDefinitions,
140
- externals,
141
- routeDefinitions,
170
+ subComponentOptions,
142
171
  structure
143
172
  )
144
173
 
145
- if (!childTag) {
146
- return
147
- }
148
-
149
174
  if (typeof childTag === 'string') {
150
175
  HASTUtils.addTextNode(elementNode, childTag)
151
176
  } else {
@@ -168,39 +193,52 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
168
193
  handleStyles(node, style, propDefinitions, stateDefinitions)
169
194
  }
170
195
 
171
- if (Object.keys(attrs).length > 0) {
172
- handleAttributes(elementNode, attrs, propDefinitions, stateDefinitions, routeDefinitions)
173
- }
196
+ handleAttributes(
197
+ elementType,
198
+ elementNode,
199
+ attrs,
200
+ propDefinitions,
201
+ stateDefinitions,
202
+ structure.options.projectRouteDefinition,
203
+ structure.outputOptions
204
+ )
174
205
 
206
+ nodesLookup[key] = elementNode
175
207
  return elementNode
176
208
  }
177
209
 
178
210
  const generateComponentContent = async (
179
211
  node: UIDLElementNode,
212
+ nodesLookup: Record<string, HastNode | HastText>,
180
213
  propDefinitions: Record<string, UIDLPropDefinition>,
181
214
  stateDefinitions: Record<string, UIDLStateDefinition>,
182
- externals: Record<string, ComponentUIDL>,
183
- routeDefinitions: UIDLRouteDefinitions,
215
+ subComponentOptions: {
216
+ externals: Record<string, ComponentUIDL>
217
+ plugins: ComponentPlugin[]
218
+ },
184
219
  structure: {
185
220
  chunks: ChunkDefinition[]
186
221
  dependencies: Record<string, UIDLDependency>
187
222
  options: GeneratorOptions
223
+ outputOptions: UIDLComponentOutputOptions
188
224
  }
189
225
  ) => {
226
+ const { externals, plugins } = subComponentOptions
190
227
  const { elementType, attrs = {}, key, children = [] } = node.content
191
- const { dependencies, chunks, options } = structure
192
- const comp = UIDLUtils.cloneObject(externals[elementType] || {}) as ComponentUIDL
193
- const lookUpTemplates: Record<string, unknown> = {}
194
- let compHasSlots: boolean = false
195
-
196
- if (!comp || !comp?.node) {
197
- throw new HTMLComponentGeneratorError(`${elementType} is not found from the externals. \n
198
- Received ${JSON.stringify(Object.keys(externals), null, 2)}`)
228
+ const { dependencies, chunks = [], options } = structure
229
+ // "Component" will not exist when generating a component because the resolver checks for illegal class names
230
+ const compName = elementType === 'Component' ? 'AppComponent' : elementType
231
+ const component = externals[compName]
232
+ if (component === undefined) {
233
+ throw new HTMLComponentGeneratorError(`${compName} is missing from externals object`)
199
234
  }
200
235
 
236
+ const componentClone = UIDLUtils.cloneObject(component) as ComponentUIDL
237
+ let compHasSlots: boolean = false
238
+
201
239
  if (children.length) {
202
240
  compHasSlots = true
203
- UIDLUtils.traverseNodes(comp.node, (childNode, parentNode) => {
241
+ UIDLUtils.traverseNodes(componentClone.node, (childNode, parentNode) => {
204
242
  if (childNode.type === 'slot' && parentNode.type === 'element') {
205
243
  const nonSlotNodes = parentNode.content?.children?.filter((n) => n.type !== 'slot')
206
244
  parentNode.content.children = [
@@ -229,25 +267,27 @@ const generateComponentContent = async (
229
267
  node.content.children = []
230
268
  }
231
269
 
232
- const combinedProps = { ...propDefinitions, ...(comp?.propDefinitions || {}) }
270
+ const combinedProps = { ...propDefinitions, ...(componentClone?.propDefinitions || {}) }
271
+ const propsForInstance: Record<string, UIDLPropDefinition> = {}
233
272
 
234
- const propsForInstance = Object.keys(combinedProps).reduce(
235
- (acc: Record<string, UIDLPropDefinition>, propKey) => {
236
- if (attrs[propKey]) {
237
- acc[propKey] = {
238
- ...combinedProps[propKey],
239
- defaultValue: attrs[propKey]?.content || combinedProps[propKey]?.defaultValue,
240
- }
241
- } else {
242
- acc[propKey] = combinedProps[propKey]
273
+ for (const propKey of Object.keys(combinedProps)) {
274
+ // If the attribute is a named-slot, then we can directly pass the value instead of just the content
275
+ if (attrs[propKey]?.type === 'element') {
276
+ propsForInstance[propKey] = {
277
+ ...combinedProps[propKey],
278
+ defaultValue: attrs[propKey],
243
279
  }
280
+ } else if (attrs[propKey]) {
281
+ propsForInstance[propKey] = {
282
+ ...combinedProps[propKey],
283
+ defaultValue: attrs[propKey]?.content || combinedProps[propKey]?.defaultValue,
284
+ }
285
+ } else {
286
+ propsForInstance[propKey] = combinedProps[propKey]
287
+ }
288
+ }
244
289
 
245
- return acc
246
- },
247
- {}
248
- )
249
-
250
- const combinedStates = { ...stateDefinitions, ...(comp?.stateDefinitions || {}) }
290
+ const combinedStates = { ...stateDefinitions, ...(componentClone?.stateDefinitions || {}) }
251
291
  const statesForInstance = Object.keys(combinedStates).reduce(
252
292
  (acc: Record<string, UIDLStateDefinition>, propKey) => {
253
293
  if (attrs[propKey]) {
@@ -264,43 +304,49 @@ const generateComponentContent = async (
264
304
  {}
265
305
  )
266
306
 
267
- const elementNode = HASTBuilders.createHTMLNode(StringUtils.camelCaseToDashCase(elementType))
268
- lookUpTemplates[key] = elementNode
269
-
270
- const compTag = (await generateHtmlSynatx(
271
- {
272
- ...comp.node,
273
- content: {
274
- ...comp.node.content,
275
- style: {
276
- ...(comp.node.content?.style || {}),
277
- display: {
278
- type: 'static',
279
- content: 'contents',
280
- },
307
+ let componentWrapper = StringUtils.camelCaseToDashCase(`${compName}-wrapper`)
308
+ const isExistingNode = nodesLookup[componentWrapper]
309
+ if (isExistingNode !== undefined) {
310
+ componentWrapper = `${componentWrapper}-${StringUtils.generateRandomString()}`
311
+ }
312
+
313
+ const componentInstanceToGenerate: UIDLElementNode = {
314
+ type: 'element',
315
+ content: {
316
+ elementType: componentWrapper,
317
+ key: componentWrapper,
318
+ children: [componentClone.node],
319
+ style: {
320
+ display: {
321
+ type: 'static',
322
+ content: 'contents',
281
323
  },
282
324
  },
283
325
  },
284
- lookUpTemplates,
326
+ }
327
+
328
+ const compTag = await generateHtmlSyntax(
329
+ componentInstanceToGenerate,
330
+ nodesLookup,
285
331
  propsForInstance,
286
332
  statesForInstance,
287
- externals,
288
- routeDefinitions,
333
+ subComponentOptions,
289
334
  structure
290
- )) as unknown as HastNode
335
+ )
291
336
 
292
337
  const cssPlugin = createCSSPlugin({
293
338
  templateStyle: 'html',
294
- templateChunkName: 'html-template',
339
+ templateChunkName: DEFAULT_COMPONENT_CHUNK_NAME,
295
340
  declareDependency: 'import',
296
341
  forceScoping: true,
297
- chunkName: comp.name,
342
+ chunkName: componentClone.name,
298
343
  staticPropReferences: true,
299
344
  })
300
345
 
301
- const result = await cssPlugin({
346
+ const initialStructure: ComponentStructure = {
302
347
  uidl: {
303
- ...comp,
348
+ ...componentClone,
349
+ node: componentInstanceToGenerate,
304
350
  propDefinitions: propsForInstance,
305
351
  stateDefinitions: statesForInstance,
306
352
  },
@@ -312,13 +358,21 @@ const generateComponentContent = async (
312
358
  linkAfter: [],
313
359
  content: compTag,
314
360
  meta: {
315
- nodesLookup: lookUpTemplates,
361
+ nodesLookup,
316
362
  },
317
363
  },
318
364
  ],
319
365
  dependencies,
320
366
  options,
321
- })
367
+ }
368
+
369
+ const result = await [cssPlugin, ...plugins].reduce(
370
+ async (previousPluginOperation: Promise<ComponentStructure>, plugin) => {
371
+ const modifiedStructure = await previousPluginOperation
372
+ return plugin(modifiedStructure)
373
+ },
374
+ Promise.resolve(initialStructure)
375
+ )
322
376
 
323
377
  if (compHasSlots) {
324
378
  result.chunks.forEach((chunk) => {
@@ -327,29 +381,57 @@ const generateComponentContent = async (
327
381
  }
328
382
  })
329
383
  } else {
330
- const chunk = chunks.find((item) => item.name === comp.name)
384
+ const chunk = chunks.find((item) => item.name === componentClone.name)
331
385
  if (!chunk) {
332
- const styleChunk = result.chunks.find((item: ChunkDefinition) => item.name === comp.name)
386
+ const styleChunk = result.chunks.find(
387
+ (item: ChunkDefinition) => item.fileType === FileType.CSS
388
+ )
389
+ if (!styleChunk) {
390
+ return
391
+ }
333
392
  chunks.push(styleChunk)
334
393
  }
335
394
  }
336
395
 
396
+ nodesLookup[key] = compTag
337
397
  return compTag
338
398
  }
339
399
 
340
- const generateDynamicNode: NodeToHTML<UIDLDynamicReference, HastNode> = (
400
+ const generateDynamicNode: NodeToHTML<UIDLDynamicReference, Promise<HastNode>> = async (
341
401
  node,
342
- _,
402
+ nodesLookup,
343
403
  propDefinitions,
344
- stateDefinitions
345
- ) => {
346
- const spanTag = HASTBuilders.createHTMLNode('span')
347
- const usedReferenceValue =
348
- node.content.referenceType === 'prop'
349
- ? getValueFromReference(node.content.id, propDefinitions)
350
- : getValueFromReference(node.content.id, stateDefinitions)
404
+ stateDefinitions,
405
+ subComponentOptions,
406
+ structure
407
+ ): Promise<HastNode> => {
408
+ const usedReferenceValue = getValueFromReference(
409
+ node.content.id,
410
+ node.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
411
+ )
412
+
413
+ if (usedReferenceValue.type === 'element' && usedReferenceValue.defaultValue) {
414
+ const slotNode = await generateElementNode(
415
+ usedReferenceValue.defaultValue as UIDLElementNode,
416
+ nodesLookup,
417
+ propDefinitions,
418
+ stateDefinitions,
419
+ subComponentOptions,
420
+ structure
421
+ )
351
422
 
352
- HASTUtils.addTextNode(spanTag, String(usedReferenceValue))
423
+ return slotNode as HastNode
424
+ }
425
+
426
+ if (usedReferenceValue.type === 'element' && usedReferenceValue.defaultValue === undefined) {
427
+ const spanTagWrapper = HASTBuilders.createHTMLNode('span')
428
+ const commentNode = HASTBuilders.createComment(`Content for slot ${node.content.id}`)
429
+ HASTUtils.addChildNode(spanTagWrapper, commentNode)
430
+ return spanTagWrapper
431
+ }
432
+
433
+ const spanTag = HASTBuilders.createHTMLNode('span')
434
+ HASTUtils.addTextNode(spanTag, String(usedReferenceValue.defaultValue))
353
435
  return spanTag
354
436
  }
355
437
 
@@ -362,10 +444,12 @@ const handleStyles = (
362
444
  Object.keys(styles).forEach((styleKey) => {
363
445
  let style: string | UIDLStyleValue = styles[styleKey]
364
446
  if (style.type === 'dynamic' && style.content?.referenceType !== 'token') {
365
- if (style.content.referenceType === 'prop') {
366
- style = getValueFromReference(style.content.id, propDefinitions)
367
- } else if (style.content.referenceType === 'state') {
368
- style = getValueFromReference(style.content.id, stateDefinitions)
447
+ const referencedValue = getValueFromReference(
448
+ style.content.id,
449
+ style.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
450
+ )
451
+ if (referencedValue.type === 'string' || referencedValue.type === 'number') {
452
+ style = String(referencedValue.defaultValue)
369
453
  }
370
454
  node.content.style[styleKey] = typeof style === 'string' ? staticNode(style) : style
371
455
  }
@@ -373,61 +457,119 @@ const handleStyles = (
373
457
  }
374
458
 
375
459
  const handleAttributes = (
460
+ elementType: UIDLElement['elementType'],
376
461
  htmlNode: HastNode,
377
462
  attrs: Record<string, UIDLAttributeValue>,
378
463
  propDefinitions: Record<string, UIDLPropDefinition>,
379
464
  stateDefinitions: Record<string, UIDLStateDefinition>,
380
- routeDefinitions: UIDLRouteDefinitions
465
+ routeDefinitions: UIDLRouteDefinitions,
466
+ outputOptions: UIDLComponentOutputOptions
381
467
  ) => {
382
- Object.keys(attrs).forEach((attrKey) => {
383
- let attrValue = attrs[attrKey]
384
-
385
- if (
386
- attrKey === 'href' &&
387
- attrValue.type === 'static' &&
388
- typeof attrValue.content === 'string' &&
389
- attrValue.content.startsWith('/')
390
- ) {
391
- attrValue =
392
- attrValue.content === '/' ||
393
- attrValue.content ===
394
- `/${StringUtils.camelCaseToDashCase(
395
- StringUtils.removeIllegalCharacters(routeDefinitions?.defaultValue || '')
396
- )}`
397
- ? staticNode('index.html')
398
- : staticNode(`${attrValue.content.split('/').pop()}.html`)
399
- HASTUtils.addAttributeToNode(htmlNode, attrKey, String(attrValue.content))
400
- return
401
- }
468
+ for (const attrKey of Object.keys(attrs)) {
469
+ const attrValue = attrs[attrKey]
470
+ const { type, content } = attrValue
471
+
472
+ switch (type) {
473
+ case 'static': {
474
+ if (attrKey === 'href' && typeof content === 'string' && content.startsWith('/')) {
475
+ let targetLink
476
+
477
+ const targetRoute = (routeDefinitions?.values || []).find(
478
+ (route) => route.pageOptions.navLink === content
479
+ )
480
+
481
+ if (targetRoute) {
482
+ targetLink = targetRoute.pageOptions.navLink
483
+ }
484
+
485
+ if (!targetRoute && content === '/home') {
486
+ targetLink = '/'
487
+ }
488
+
489
+ if (!targetLink && !targetRoute) {
490
+ targetLink = content
491
+ }
492
+
493
+ const currentPageRoute = join(...(outputOptions?.folderPath || []), './')
494
+ const localPrefix = relative(
495
+ `/${currentPageRoute}`,
496
+ `/${targetLink === '/' ? 'index' : targetLink}`
497
+ )
498
+
499
+ HASTUtils.addAttributeToNode(htmlNode, attrKey, `${localPrefix}.html`)
500
+ break
501
+ }
402
502
 
403
- if (attrValue.type === 'dynamic') {
404
- const value =
405
- attrValue.content.referenceType === 'prop'
406
- ? getValueFromReference(attrValue.content.id, propDefinitions)
407
- : getValueFromReference(attrValue.content.id, stateDefinitions)
408
- HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value))
409
- return
410
- }
503
+ if (typeof content === 'boolean') {
504
+ htmlNode.properties[attrKey] = content === true ? 'true' : 'false'
505
+ } else if (typeof content === 'string' || typeof attrValue.content === 'number') {
506
+ let value = StringUtils.encode(String(attrValue.content))
507
+
508
+ /*
509
+ elementType of image is always mapped to img.
510
+ For reference, check `html-mapping` file.
511
+ */
512
+ if (elementType === 'img' && attrKey === 'src' && !isValidURL(value)) {
513
+ /*
514
+ By default we just prefix all the asset paths with just the
515
+ assetPrefix that is configured in the project. But for `html` generators
516
+ we need to prefix that with the current file location.
517
+
518
+ Because, all the other frameworks have a build setup. which serves all the
519
+ assets from the `public` folder. But in the case of `html` here is how it works
520
+
521
+ We load a file from `index.html` the request for the image goes from
522
+ '...url.../public/...image...'
523
+ If it's a nested url, then the request goes from
524
+ '...url/nested/public/...image..'
525
+
526
+ But the nested folder is available only on the root. With this
527
+ The url changes prefixes to
528
+
529
+ ../public/playground_assets/..image.. etc depending on the dept the file is in.
530
+ */
531
+ value = join(relative(join(...outputOptions.folderPath), './'), value)
532
+ }
533
+
534
+ HASTUtils.addAttributeToNode(htmlNode, attrKey, value)
535
+ }
411
536
 
412
- if (attrValue.type === 'raw') {
413
- HASTUtils.addAttributeToNode(htmlNode, attrKey, attrValue.content)
414
- return
415
- }
537
+ break
538
+ }
539
+
540
+ case 'dynamic': {
541
+ const value = getValueFromReference(
542
+ content.id,
543
+ content.referenceType === 'prop' ? propDefinitions : stateDefinitions
544
+ )
545
+
546
+ HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value.defaultValue))
547
+ break
548
+ }
416
549
 
417
- if (typeof attrValue.content === 'boolean') {
418
- HASTUtils.addBooleanAttributeToNode(htmlNode, attrKey)
419
- return
420
- } else if (typeof attrValue.content === 'string' || typeof attrValue.content === 'number') {
421
- HASTUtils.addAttributeToNode(htmlNode, attrKey, StringUtils.encode(String(attrValue.content)))
422
- return
550
+ case 'raw': {
551
+ HASTUtils.addAttributeToNode(htmlNode, attrKey, content)
552
+ break
553
+ }
554
+
555
+ case 'element':
556
+ case 'import':
557
+ case 'expr':
558
+ break
559
+
560
+ default: {
561
+ throw new HTMLComponentGeneratorError(
562
+ `Received ${JSON.stringify(attrValue, null, 2)} \n in handleAttributes for html`
563
+ )
564
+ }
423
565
  }
424
- })
566
+ }
425
567
  }
426
568
 
427
569
  const getValueFromReference = (
428
570
  key: string,
429
571
  definitions: Record<string, UIDLPropDefinition>
430
- ): string => {
572
+ ): UIDLPropDefinition | undefined => {
431
573
  const usedReferenceValue = definitions[key.includes('.') ? key.split('.')[0] : key]
432
574
 
433
575
  if (!usedReferenceValue) {
@@ -436,9 +578,9 @@ const getValueFromReference = (
436
578
  )
437
579
  }
438
580
 
439
- if (!usedReferenceValue.hasOwnProperty('defaultValue')) {
581
+ if (['string', 'number', 'object', 'element'].includes(usedReferenceValue?.type) === false) {
440
582
  throw new HTMLComponentGeneratorError(
441
- `Default value is missing from dynamic reference - ${JSON.stringify(
583
+ `Attribute is using dynamic value, but received of type ${JSON.stringify(
442
584
  usedReferenceValue,
443
585
  null,
444
586
  2
@@ -446,9 +588,12 @@ const getValueFromReference = (
446
588
  )
447
589
  }
448
590
 
449
- if (!['string', 'number', 'object'].includes(usedReferenceValue?.type)) {
591
+ if (
592
+ usedReferenceValue.type !== 'element' &&
593
+ usedReferenceValue.hasOwnProperty('defaultValue') === false
594
+ ) {
450
595
  throw new HTMLComponentGeneratorError(
451
- `Attribute is using dynamic value, but received of type ${JSON.stringify(
596
+ `Default value is missing from dynamic reference - ${JSON.stringify(
452
597
  usedReferenceValue,
453
598
  null,
454
599
  2
@@ -456,5 +601,5 @@ const getValueFromReference = (
456
601
  )
457
602
  }
458
603
 
459
- return String(usedReferenceValue.defaultValue)
604
+ return usedReferenceValue
460
605
  }