@teleporthq/teleport-plugin-html-base-component 0.34.0-alpha.0 → 0.34.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,48 @@ 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,
304
349
  propDefinitions: propsForInstance,
305
350
  stateDefinitions: statesForInstance,
306
351
  },
@@ -312,13 +357,21 @@ const generateComponentContent = async (
312
357
  linkAfter: [],
313
358
  content: compTag,
314
359
  meta: {
315
- nodesLookup: lookUpTemplates,
360
+ nodesLookup,
316
361
  },
317
362
  },
318
363
  ],
319
364
  dependencies,
320
365
  options,
321
- })
366
+ }
367
+
368
+ const result = await [cssPlugin, ...plugins].reduce(
369
+ async (previousPluginOperation: Promise<ComponentStructure>, plugin) => {
370
+ const modifiedStructure = await previousPluginOperation
371
+ return plugin(modifiedStructure)
372
+ },
373
+ Promise.resolve(initialStructure)
374
+ )
322
375
 
323
376
  if (compHasSlots) {
324
377
  result.chunks.forEach((chunk) => {
@@ -327,29 +380,57 @@ const generateComponentContent = async (
327
380
  }
328
381
  })
329
382
  } else {
330
- const chunk = chunks.find((item) => item.name === comp.name)
383
+ const chunk = chunks.find((item) => item.name === componentClone.name)
331
384
  if (!chunk) {
332
- const styleChunk = result.chunks.find((item: ChunkDefinition) => item.name === comp.name)
385
+ const styleChunk = result.chunks.find(
386
+ (item: ChunkDefinition) => item.fileType === FileType.CSS
387
+ )
388
+ if (!styleChunk) {
389
+ return
390
+ }
333
391
  chunks.push(styleChunk)
334
392
  }
335
393
  }
336
394
 
395
+ nodesLookup[key] = compTag
337
396
  return compTag
338
397
  }
339
398
 
340
- const generateDynamicNode: NodeToHTML<UIDLDynamicReference, HastNode> = (
399
+ const generateDynamicNode: NodeToHTML<UIDLDynamicReference, Promise<HastNode>> = async (
341
400
  node,
342
- _,
401
+ nodesLookup,
343
402
  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)
403
+ stateDefinitions,
404
+ subComponentOptions,
405
+ structure
406
+ ): Promise<HastNode> => {
407
+ const usedReferenceValue = getValueFromReference(
408
+ node.content.id,
409
+ node.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
410
+ )
411
+
412
+ if (usedReferenceValue.type === 'element' && usedReferenceValue.defaultValue) {
413
+ const slotNode = await generateElementNode(
414
+ usedReferenceValue.defaultValue as UIDLElementNode,
415
+ nodesLookup,
416
+ propDefinitions,
417
+ stateDefinitions,
418
+ subComponentOptions,
419
+ structure
420
+ )
351
421
 
352
- HASTUtils.addTextNode(spanTag, String(usedReferenceValue))
422
+ return slotNode as HastNode
423
+ }
424
+
425
+ if (usedReferenceValue.type === 'element' && usedReferenceValue.defaultValue === undefined) {
426
+ const spanTagWrapper = HASTBuilders.createHTMLNode('span')
427
+ const commentNode = HASTBuilders.createComment(`Content for slot ${node.content.id}`)
428
+ HASTUtils.addChildNode(spanTagWrapper, commentNode)
429
+ return spanTagWrapper
430
+ }
431
+
432
+ const spanTag = HASTBuilders.createHTMLNode('span')
433
+ HASTUtils.addTextNode(spanTag, String(usedReferenceValue.defaultValue))
353
434
  return spanTag
354
435
  }
355
436
 
@@ -362,10 +443,12 @@ const handleStyles = (
362
443
  Object.keys(styles).forEach((styleKey) => {
363
444
  let style: string | UIDLStyleValue = styles[styleKey]
364
445
  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)
446
+ const referencedValue = getValueFromReference(
447
+ style.content.id,
448
+ style.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
449
+ )
450
+ if (referencedValue.type === 'string' || referencedValue.type === 'number') {
451
+ style = String(referencedValue.defaultValue)
369
452
  }
370
453
  node.content.style[styleKey] = typeof style === 'string' ? staticNode(style) : style
371
454
  }
@@ -373,61 +456,119 @@ const handleStyles = (
373
456
  }
374
457
 
375
458
  const handleAttributes = (
459
+ elementType: UIDLElement['elementType'],
376
460
  htmlNode: HastNode,
377
461
  attrs: Record<string, UIDLAttributeValue>,
378
462
  propDefinitions: Record<string, UIDLPropDefinition>,
379
463
  stateDefinitions: Record<string, UIDLStateDefinition>,
380
- routeDefinitions: UIDLRouteDefinitions
464
+ routeDefinitions: UIDLRouteDefinitions,
465
+ outputOptions: UIDLComponentOutputOptions
381
466
  ) => {
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
- }
467
+ for (const attrKey of Object.keys(attrs)) {
468
+ const attrValue = attrs[attrKey]
469
+ const { type, content } = attrValue
470
+
471
+ switch (type) {
472
+ case 'static': {
473
+ if (attrKey === 'href' && typeof content === 'string' && content.startsWith('/')) {
474
+ let targetLink
475
+
476
+ const targetRoute = (routeDefinitions?.values || []).find(
477
+ (route) => route.pageOptions.navLink === content
478
+ )
479
+
480
+ if (targetRoute) {
481
+ targetLink = targetRoute.pageOptions.navLink
482
+ }
483
+
484
+ if (!targetRoute && content === '/home') {
485
+ targetLink = '/'
486
+ }
487
+
488
+ if (!targetLink && !targetRoute) {
489
+ targetLink = content
490
+ }
491
+
492
+ const currentPageRoute = join(...(outputOptions?.folderPath || []), './')
493
+ const localPrefix = relative(
494
+ `/${currentPageRoute}`,
495
+ `/${targetLink === '/' ? 'index' : targetLink}`
496
+ )
497
+
498
+ HASTUtils.addAttributeToNode(htmlNode, attrKey, `${localPrefix}.html`)
499
+ break
500
+ }
402
501
 
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
- }
502
+ if (typeof content === 'boolean') {
503
+ htmlNode.properties[attrKey] = content === true ? 'true' : 'false'
504
+ } else if (typeof content === 'string' || typeof attrValue.content === 'number') {
505
+ let value = StringUtils.encode(String(attrValue.content))
506
+
507
+ /*
508
+ elementType of image is always mapped to img.
509
+ For reference, check `html-mapping` file.
510
+ */
511
+ if (elementType === 'img' && attrKey === 'src' && !isValidURL(value)) {
512
+ /*
513
+ By default we just prefix all the asset paths with just the
514
+ assetPrefix that is configured in the project. But for `html` generators
515
+ we need to prefix that with the current file location.
516
+
517
+ Because, all the other frameworks have a build setup. which serves all the
518
+ assets from the `public` folder. But in the case of `html` here is how it works
519
+
520
+ We load a file from `index.html` the request for the image goes from
521
+ '...url.../public/...image...'
522
+ If it's a nested url, then the request goes from
523
+ '...url/nested/public/...image..'
524
+
525
+ But the nested folder is available only on the root. With this
526
+ The url changes prefixes to
527
+
528
+ ../public/playground_assets/..image.. etc depending on the dept the file is in.
529
+ */
530
+ value = join(relative(join(...outputOptions.folderPath), './'), value)
531
+ }
532
+
533
+ HASTUtils.addAttributeToNode(htmlNode, attrKey, value)
534
+ }
411
535
 
412
- if (attrValue.type === 'raw') {
413
- HASTUtils.addAttributeToNode(htmlNode, attrKey, attrValue.content)
414
- return
415
- }
536
+ break
537
+ }
538
+
539
+ case 'dynamic': {
540
+ const value = getValueFromReference(
541
+ content.id,
542
+ content.referenceType === 'prop' ? propDefinitions : stateDefinitions
543
+ )
544
+
545
+ HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value.defaultValue))
546
+ break
547
+ }
416
548
 
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
549
+ case 'raw': {
550
+ HASTUtils.addAttributeToNode(htmlNode, attrKey, content)
551
+ break
552
+ }
553
+
554
+ case 'element':
555
+ case 'import':
556
+ case 'expr':
557
+ break
558
+
559
+ default: {
560
+ throw new HTMLComponentGeneratorError(
561
+ `Received ${JSON.stringify(attrValue, null, 2)} \n in handleAttributes for html`
562
+ )
563
+ }
423
564
  }
424
- })
565
+ }
425
566
  }
426
567
 
427
568
  const getValueFromReference = (
428
569
  key: string,
429
570
  definitions: Record<string, UIDLPropDefinition>
430
- ): string => {
571
+ ): UIDLPropDefinition | undefined => {
431
572
  const usedReferenceValue = definitions[key.includes('.') ? key.split('.')[0] : key]
432
573
 
433
574
  if (!usedReferenceValue) {
@@ -436,9 +577,9 @@ const getValueFromReference = (
436
577
  )
437
578
  }
438
579
 
439
- if (!usedReferenceValue.hasOwnProperty('defaultValue')) {
580
+ if (['string', 'number', 'object', 'element'].includes(usedReferenceValue?.type) === false) {
440
581
  throw new HTMLComponentGeneratorError(
441
- `Default value is missing from dynamic reference - ${JSON.stringify(
582
+ `Attribute is using dynamic value, but received of type ${JSON.stringify(
442
583
  usedReferenceValue,
443
584
  null,
444
585
  2
@@ -446,9 +587,12 @@ const getValueFromReference = (
446
587
  )
447
588
  }
448
589
 
449
- if (!['string', 'number', 'object'].includes(usedReferenceValue?.type)) {
590
+ if (
591
+ usedReferenceValue.type !== 'element' &&
592
+ usedReferenceValue.hasOwnProperty('defaultValue') === false
593
+ ) {
450
594
  throw new HTMLComponentGeneratorError(
451
- `Attribute is using dynamic value, but received of type ${JSON.stringify(
595
+ `Default value is missing from dynamic reference - ${JSON.stringify(
452
596
  usedReferenceValue,
453
597
  null,
454
598
  2
@@ -456,5 +600,5 @@ const getValueFromReference = (
456
600
  )
457
601
  }
458
602
 
459
- return String(usedReferenceValue.defaultValue)
603
+ return usedReferenceValue
460
604
  }