@quicktvui/web-renderer 1.0.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 (56) hide show
  1. package/package.json +24 -0
  2. package/src/adapters/es3-video-player.js +828 -0
  3. package/src/components/Modal.js +119 -0
  4. package/src/components/QtAnimationView.js +678 -0
  5. package/src/components/QtBaseComponent.js +165 -0
  6. package/src/components/QtFastListView.js +1920 -0
  7. package/src/components/QtFlexView.js +799 -0
  8. package/src/components/QtImage.js +203 -0
  9. package/src/components/QtItemFrame.js +239 -0
  10. package/src/components/QtItemStoreView.js +93 -0
  11. package/src/components/QtItemView.js +125 -0
  12. package/src/components/QtListView.js +331 -0
  13. package/src/components/QtLoadingView.js +55 -0
  14. package/src/components/QtPageRootView.js +19 -0
  15. package/src/components/QtPlayMark.js +168 -0
  16. package/src/components/QtProgressBar.js +199 -0
  17. package/src/components/QtQRCode.js +78 -0
  18. package/src/components/QtReplaceChild.js +149 -0
  19. package/src/components/QtRippleView.js +166 -0
  20. package/src/components/QtSeekBar.js +409 -0
  21. package/src/components/QtText.js +679 -0
  22. package/src/components/QtTransitionImage.js +170 -0
  23. package/src/components/QtView.js +706 -0
  24. package/src/components/QtWebView.js +613 -0
  25. package/src/components/TabsView.js +420 -0
  26. package/src/components/ViewPager.js +206 -0
  27. package/src/components/index.js +24 -0
  28. package/src/components/plugins/TextV2Component.js +70 -0
  29. package/src/components/plugins/index.js +7 -0
  30. package/src/core/SceneBuilder.js +58 -0
  31. package/src/core/TVFocusManager.js +2014 -0
  32. package/src/core/asyncLocalStorage.js +175 -0
  33. package/src/core/autoProxy.js +165 -0
  34. package/src/core/componentRegistry.js +84 -0
  35. package/src/core/constants.js +6 -0
  36. package/src/core/index.js +8 -0
  37. package/src/core/moduleUtils.js +36 -0
  38. package/src/core/patches.js +958 -0
  39. package/src/core/templateBinding.js +666 -0
  40. package/src/index.js +246 -0
  41. package/src/modules/AndroidDevelopModule.js +101 -0
  42. package/src/modules/AndroidDeviceModule.js +341 -0
  43. package/src/modules/AndroidNetworkModule.js +178 -0
  44. package/src/modules/AndroidSharedPreferencesModule.js +100 -0
  45. package/src/modules/ESDeviceInfoModule.js +450 -0
  46. package/src/modules/ESGroupDataModule.js +195 -0
  47. package/src/modules/ESIJKAudioPlayerModule.js +477 -0
  48. package/src/modules/ESLocalStorageModule.js +100 -0
  49. package/src/modules/ESLogModule.js +65 -0
  50. package/src/modules/ESModule.js +106 -0
  51. package/src/modules/ESNetworkSpeedModule.js +117 -0
  52. package/src/modules/ESToastModule.js +172 -0
  53. package/src/modules/EsNativeModule.js +117 -0
  54. package/src/modules/FastListModule.js +101 -0
  55. package/src/modules/FocusModule.js +145 -0
  56. package/src/modules/RuntimeDeviceModule.js +176 -0
@@ -0,0 +1,666 @@
1
+ export function extractTemplateValueByPath(propPath, data) {
2
+ let value = propPath.split('.').reduce((obj, key) => {
3
+ return obj && obj[key] !== undefined ? obj[key] : undefined
4
+ }, data)
5
+ return value
6
+ }
7
+
8
+ export function extractTemplateValue(placeholder, data) {
9
+ if (!placeholder || !data) return placeholder
10
+ const match = String(placeholder).match(/^\$\{([^}]+)\}$/)
11
+ if (!match) return placeholder
12
+ return extractTemplateValueByPath(match[1], data)
13
+ }
14
+
15
+ export function replaceTemplatePlaceholders(str, data) {
16
+ if (!str || !data) return str
17
+ return String(str).replace(/\$\{([^}]+)\}/g, (match, propPath) => {
18
+ const value = extractTemplateValueByPath(propPath, data)
19
+ return value !== undefined ? String(value) : match
20
+ })
21
+ }
22
+
23
+ function _normalizeWebAssetSrc(src) {
24
+ if (!src || typeof src !== 'string') return src
25
+ if (src.startsWith('file://')) {
26
+ let rest = src.slice(7)
27
+ rest = rest.replace(/^\/+/, '')
28
+ if (rest.startsWith('./')) rest = rest.slice(2)
29
+ return './' + rest
30
+ }
31
+ if (src.startsWith('assets/')) return './' + src
32
+ return src
33
+ }
34
+
35
+ function _applyFlexStyleToElement(element, styleObj) {
36
+ if (!styleObj || typeof styleObj !== 'object') return
37
+
38
+ // Flex properties that require display: flex
39
+ const flexProperties = [
40
+ 'flexDirection',
41
+ 'justifyContent',
42
+ 'alignItems',
43
+ 'alignContent',
44
+ 'flexWrap',
45
+ 'flexGrow',
46
+ 'flexShrink',
47
+ 'flexBasis',
48
+ 'order',
49
+ 'alignSelf',
50
+ ]
51
+
52
+ // Check if any flex property is being set
53
+ const hasFlexProperty = Object.keys(styleObj).some((key) => flexProperties.includes(key))
54
+
55
+ // If flex properties are set and display is not explicitly set, add display: flex
56
+ // This matches Hippy's behavior where all elements default to flex layout
57
+ if (hasFlexProperty && !Object.keys(styleObj).includes('display')) {
58
+ element.style.display = 'flex'
59
+ }
60
+
61
+ Object.keys(styleObj).forEach((key) => {
62
+ let value = styleObj[key]
63
+
64
+ if (typeof value === 'number') {
65
+ const needsPx = [
66
+ 'width',
67
+ 'height',
68
+ 'minWidth',
69
+ 'minHeight',
70
+ 'maxWidth',
71
+ 'maxHeight',
72
+ 'padding',
73
+ 'paddingTop',
74
+ 'paddingRight',
75
+ 'paddingBottom',
76
+ 'paddingLeft',
77
+ 'margin',
78
+ 'marginTop',
79
+ 'marginRight',
80
+ 'marginBottom',
81
+ 'marginLeft',
82
+ 'top',
83
+ 'left',
84
+ 'right',
85
+ 'bottom',
86
+ 'borderRadius',
87
+ 'borderWidth',
88
+ 'fontSize',
89
+ 'lineHeight',
90
+ 'letterSpacing',
91
+ 'gap',
92
+ 'rowGap',
93
+ 'columnGap',
94
+ ].includes(key)
95
+
96
+ if (needsPx) {
97
+ value = value + 'px'
98
+ }
99
+ }
100
+
101
+ try {
102
+ if (key !== 'x' && key !== 'y') {
103
+ element.style[key] = value
104
+ }
105
+ } catch (e) {
106
+ try {
107
+ if (key !== 'x' && key !== 'y') {
108
+ const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
109
+ element.style.setProperty(cssKey, value)
110
+ }
111
+ } catch (err) {}
112
+ }
113
+ })
114
+ }
115
+
116
+ function _applyLayoutToElement(element, layout) {
117
+ if (!layout) return
118
+
119
+ if (Array.isArray(layout)) {
120
+ const x = layout[0]
121
+ const y = layout[1]
122
+ const width = layout[2]
123
+ const height = layout[3]
124
+
125
+ const position = element.style.position || ''
126
+ const allowXY =
127
+ position === '' || position === 'absolute' || position === 'fixed' || position === 'relative'
128
+
129
+ if (allowXY && x !== undefined && x !== null) {
130
+ element.style.left = typeof x === 'number' ? `${x}px` : x
131
+ }
132
+ if (allowXY && y !== undefined && y !== null) {
133
+ element.style.top = typeof y === 'number' ? `${y}px` : y
134
+ }
135
+ if (width !== undefined && width !== null) {
136
+ element.style.width = typeof width === 'number' ? `${width}px` : width
137
+ }
138
+ if (height !== undefined && height !== null) {
139
+ element.style.height = typeof height === 'number' ? `${height}px` : height
140
+ }
141
+
142
+ if (
143
+ allowXY &&
144
+ (x !== undefined || y !== undefined) &&
145
+ (!element.style.position || element.style.position === 'relative')
146
+ ) {
147
+ element.style.position = 'absolute'
148
+ }
149
+ return
150
+ }
151
+
152
+ if (typeof layout === 'object') {
153
+ const { x, y, width, height } = layout
154
+
155
+ const position = element.style.position || ''
156
+ const allowXY =
157
+ position === '' || position === 'absolute' || position === 'fixed' || position === 'relative'
158
+
159
+ if (allowXY && x !== undefined && x !== null) {
160
+ element.style.left = typeof x === 'number' ? `${x}px` : x
161
+ }
162
+ if (allowXY && y !== undefined && y !== null) {
163
+ element.style.top = typeof y === 'number' ? `${y}px` : y
164
+ }
165
+ if (width !== undefined && width !== null) {
166
+ element.style.width = typeof width === 'number' ? `${width}px` : width
167
+ }
168
+ if (height !== undefined && height !== null) {
169
+ element.style.height = typeof height === 'number' ? `${height}px` : height
170
+ }
171
+
172
+ if (
173
+ allowXY &&
174
+ (x !== undefined || y !== undefined) &&
175
+ (!element.style.position || element.style.position === 'relative')
176
+ ) {
177
+ element.style.position = 'absolute'
178
+ }
179
+ }
180
+ }
181
+
182
+ function _applyDecorationToElement(element, decoration) {
183
+ if (!decoration || typeof decoration !== 'object') return
184
+
185
+ const { left, top, right, bottom } = decoration
186
+
187
+ if (left !== undefined && left !== null) {
188
+ element.style.marginLeft = typeof left === 'number' ? `${left}px` : left
189
+ }
190
+ if (top !== undefined && top !== null) {
191
+ element.style.marginTop = typeof top === 'number' ? `${top}px` : top
192
+ }
193
+ if (right !== undefined && right !== null) {
194
+ element.style.marginRight = typeof right === 'number' ? `${right}px` : right
195
+ }
196
+ if (bottom !== undefined && bottom !== null) {
197
+ element.style.marginBottom = typeof bottom === 'number' ? `${bottom}px` : bottom
198
+ }
199
+ }
200
+
201
+ function _applySizeToElement(element, size) {
202
+ if (!size) return
203
+
204
+ if (Array.isArray(size)) {
205
+ const width = size[0]
206
+ const height = size[1]
207
+
208
+ if (width !== undefined && width !== null) {
209
+ element.style.width = typeof width === 'number' ? `${width}px` : width
210
+ }
211
+ if (height !== undefined && height !== null) {
212
+ element.style.height = typeof height === 'number' ? `${height}px` : height
213
+ }
214
+ return
215
+ }
216
+
217
+ if (typeof size === 'object') {
218
+ const { width, height } = size
219
+
220
+ if (width !== undefined && width !== null) {
221
+ element.style.width = typeof width === 'number' ? `${width}px` : width
222
+ }
223
+ if (height !== undefined && height !== null) {
224
+ element.style.height = typeof height === 'number' ? `${height}px` : height
225
+ }
226
+ }
227
+ }
228
+
229
+ function _findComponentForElement(el) {
230
+ const registry = window.__HIPPY_COMPONENT_REGISTRY__
231
+ if (!registry) return null
232
+ return registry.get(el) || null
233
+ }
234
+
235
+ /**
236
+ * Evaluate showIf expression
237
+ * Supports (aligned with Android):
238
+ * - Simple property: showIf="${isPlaying}" - truthy check
239
+ * - Equality with true: showIf="${isPlaying == true}"
240
+ * - Equality with false: showIf="${isHidden == false}"
241
+ * @param {string} expression - The showIf expression
242
+ * @param {object} data - The data object to resolve values from
243
+ * @returns {boolean} - Whether the element should be shown
244
+ */
245
+ function _evaluateShowIf(expression, data) {
246
+ if (!expression) return true
247
+
248
+ // Check if it's a template variable ${...}
249
+ const templateMatch = expression.match(/^\$\{([^}]+)\}$/)
250
+ if (!templateMatch) {
251
+ // Not a template, treat as literal boolean
252
+ return !!expression
253
+ }
254
+
255
+ const expr = templateMatch[1].trim()
256
+
257
+ // Handle == true or == false
258
+ if (expr.includes('==')) {
259
+ const parts = expr.split('==').map((s) => s.trim())
260
+ if (parts.length === 2) {
261
+ const propValue = extractTemplateValueByPath(parts[0], data)
262
+ const compareValue = parts[1].trim()
263
+
264
+ if (compareValue === 'true') {
265
+ return propValue === true
266
+ } else if (compareValue === 'false') {
267
+ return propValue === false
268
+ }
269
+ }
270
+ }
271
+
272
+ // Simple property access - truthy check
273
+ const value = extractTemplateValueByPath(expr, data)
274
+ console.log(
275
+ '[templateBinding] _evaluateShowIf: expr=',
276
+ expr,
277
+ 'value=',
278
+ value,
279
+ 'typeof=',
280
+ typeof value,
281
+ 'result=',
282
+ !!value
283
+ )
284
+ return !!value
285
+ }
286
+
287
+ function _findComponentForNode(node) {
288
+ let el = node && node.nodeType === Node.TEXT_NODE ? node.parentElement : node
289
+ while (el) {
290
+ const component = _findComponentForElement(el)
291
+ if (component) return component
292
+ el = el.parentElement
293
+ }
294
+ return null
295
+ }
296
+
297
+ function _resolveValueOrString(templateStr, data) {
298
+ const match = String(templateStr).match(/^\$\{([^}]+)\}$/)
299
+ if (match) {
300
+ return { isPure: true, value: extractTemplateValueByPath(match[1], data) }
301
+ }
302
+ return { isPure: false, value: replaceTemplatePlaceholders(templateStr, data) }
303
+ }
304
+
305
+ function _getQtTextInnerSpan(textRoot) {
306
+ if (!textRoot || textRoot.nodeType !== Node.ELEMENT_NODE) return null
307
+ const children = textRoot.children
308
+ if (!children || children.length === 0) return null
309
+ for (const child of Array.from(children)) {
310
+ if (child.classList && child.classList.contains('qt-text-inner')) return child
311
+ }
312
+ return null
313
+ }
314
+
315
+ function _syncDomOnlyQtTextAutoWidth(textRoot) {
316
+ if (!textRoot || textRoot.nodeType !== Node.ELEMENT_NODE) return
317
+ if (
318
+ textRoot.hasAttribute &&
319
+ !(textRoot.hasAttribute('autoWidth') || textRoot.hasAttribute('autowidth'))
320
+ ) {
321
+ return
322
+ }
323
+
324
+ const inner = _getQtTextInnerSpan(textRoot)
325
+ if (!inner) return
326
+
327
+ const textWidth = inner.scrollWidth || inner.offsetWidth
328
+ if (textWidth <= 0) return
329
+
330
+ textRoot.style.width = textWidth + 'px'
331
+ textRoot.style.minWidth = textWidth + 'px'
332
+
333
+ let parent = textRoot.parentElement
334
+ while (parent) {
335
+ if (
336
+ parent.hasAttribute &&
337
+ (parent.hasAttribute('autoWidth') || parent.hasAttribute('autowidth'))
338
+ ) {
339
+ const style = window.getComputedStyle(parent)
340
+ const paddingLeft = parseInt(style.paddingLeft) || 0
341
+ const paddingRight = parseInt(style.paddingRight) || 0
342
+ const parentWidth = textWidth + paddingLeft + paddingRight
343
+
344
+ parent.style.width = parentWidth + 'px'
345
+ parent.style.minWidth = parentWidth + 'px'
346
+
347
+ parent.querySelectorAll('[autoWidth], [autowidth]').forEach((el) => {
348
+ if (el === textRoot) return
349
+ if (_getQtTextInnerSpan(el)) return
350
+ const elStyle = window.getComputedStyle(el)
351
+ if (elStyle.position === 'absolute') {
352
+ el.style.left = '0'
353
+ el.style.right = '0'
354
+ el.style.width = 'auto'
355
+ }
356
+ })
357
+ break
358
+ }
359
+ parent = parent.parentElement
360
+ }
361
+ }
362
+
363
+ export function syncDomAutoWidthIn(root) {
364
+ if (!root || root.nodeType !== Node.ELEMENT_NODE) return
365
+ if (!root.querySelector) return
366
+
367
+ root.querySelectorAll('[autoWidth], [autowidth]').forEach((el) => {
368
+ const inner = _getQtTextInnerSpan(el)
369
+ if (inner) _syncDomOnlyQtTextAutoWidth(el)
370
+ })
371
+ }
372
+
373
+ export function bindTemplateDataToNode(node, data, options = {}) {
374
+ if (!data) return
375
+
376
+ const scope = options.scope || {}
377
+ const combinedData = { ...scope, ...data }
378
+
379
+ if (node.nodeType === Node.TEXT_NODE) {
380
+ const text = node.textContent
381
+ if (text && text.includes('${')) {
382
+ const newText = replaceTemplatePlaceholders(text, combinedData)
383
+ if (newText !== text) {
384
+ const parent = node.parentElement
385
+ const component =
386
+ parent && parent.classList && parent.classList.contains('qt-text-inner')
387
+ ? _findComponentForNode(node)
388
+ : null
389
+ if (component && typeof component.setText === 'function') {
390
+ component.setText(newText)
391
+ } else {
392
+ node.textContent = newText
393
+ if (parent && parent.classList && parent.classList.contains('qt-text-inner')) {
394
+ _syncDomOnlyQtTextAutoWidth(parent.parentElement)
395
+ }
396
+ }
397
+ }
398
+ }
399
+ return
400
+ }
401
+
402
+ if (node.nodeType !== Node.ELEMENT_NODE) return
403
+
404
+ for (const child of Array.from(node.childNodes)) {
405
+ if (
406
+ child.nodeType === Node.TEXT_NODE &&
407
+ child.textContent &&
408
+ child.textContent.includes('${')
409
+ ) {
410
+ bindTemplateDataToNode(child, combinedData, options)
411
+ }
412
+ }
413
+
414
+ const component = _findComponentForElement(node)
415
+
416
+ // Flag to track if we should process children recursively
417
+ // When a node has a 'list' attribute, its children will be processed separately
418
+ // with the correct item data during sublist rendering
419
+ let shouldProcessChildren = true
420
+
421
+ // Handle type attribute - negative logic filtering
422
+ const typeAttr = node.getAttribute('data-template-type') ?? node.getAttribute('type')
423
+ if (typeAttr !== null && typeAttr !== undefined && typeAttr !== '') {
424
+ let nodeTypeStr = String(typeAttr)
425
+ // If type is bound dynamically (e.g., type="${type}"), resolve it first
426
+ if (nodeTypeStr.includes('${')) {
427
+ nodeTypeStr = String(replaceTemplatePlaceholders(nodeTypeStr, combinedData))
428
+ }
429
+ const dataType =
430
+ combinedData.type !== undefined && combinedData.type !== null
431
+ ? String(combinedData.type)
432
+ : null
433
+
434
+ // If node has type but it doesn't match data's type, hide it
435
+ if (nodeTypeStr !== dataType) {
436
+ node.style.display = 'none'
437
+ node.setAttribute('data-type-match', 'false')
438
+ return // Skip processing children for unmatched type
439
+ } else {
440
+ // Ensure display is restored if it was previously hidden by type mismatch
441
+ if (node.style.display === 'none' && node.getAttribute('data-type-match') === 'false') {
442
+ node.style.removeProperty('display')
443
+ }
444
+ node.setAttribute('data-type-match', 'true')
445
+ }
446
+ }
447
+
448
+ // Handle showIf attribute - evaluate condition and hide element if false
449
+ // First check for new showIf attribute (initial render)
450
+ // Note: HTML attributes are case-insensitive but may be stored as lowercase
451
+ // Check both 'showIf' and 'showif' to ensure compatibility
452
+ const allAttrs = Array.from(node.attributes)
453
+ .map((a) => `${a.name}="${a.value}"`)
454
+ .join(', ')
455
+ console.log('[templateBinding] Node:', node.tagName, 'All attrs:', allAttrs)
456
+ let showIfAttr = node.getAttribute('showIf') || node.getAttribute('showif')
457
+ console.log('[templateBinding] Checking showIf on node:', node.tagName, 'showIf:', showIfAttr)
458
+ if (showIfAttr) {
459
+ // Store the expression for re-evaluation when data changes
460
+ node.setAttribute('data-showif-expr', showIfAttr)
461
+ // Remove both possible attribute names to ensure cleanup
462
+ node.removeAttribute('showIf')
463
+ node.removeAttribute('showif')
464
+ console.log('[templateBinding] Stored showIf expr:', showIfAttr)
465
+ }
466
+
467
+ // Check for stored showIf expression (for re-evaluation on data updates)
468
+ const storedShowIfExpr = node.getAttribute('data-showif-expr')
469
+ if (storedShowIfExpr) {
470
+ console.log(
471
+ '[templateBinding] Found stored showIf expr:',
472
+ storedShowIfExpr,
473
+ 'data:',
474
+ combinedData
475
+ )
476
+ const shouldShow = _evaluateShowIf(storedShowIfExpr, combinedData)
477
+ const prevResult = node.getAttribute('data-showif-result')
478
+ const newResult = shouldShow === false ? 'false' : 'true'
479
+ console.log(
480
+ '[templateBinding] showIf result:',
481
+ shouldShow,
482
+ 'prev:',
483
+ prevResult,
484
+ 'new:',
485
+ newResult
486
+ )
487
+
488
+ // Only update if result changed
489
+ if (prevResult !== newResult) {
490
+ node.setAttribute('data-showif-result', newResult)
491
+
492
+ if (shouldShow === false) {
493
+ node.style.display = 'none'
494
+ if (component && typeof component.setVisible === 'function') {
495
+ component.setVisible(false)
496
+ }
497
+ return
498
+ } else {
499
+ if (node.style.display === 'none') {
500
+ node.style.removeProperty('display')
501
+ }
502
+ if (component && typeof component.setVisible === 'function') {
503
+ component.setVisible(true)
504
+ }
505
+ }
506
+ } else if (shouldShow === false) {
507
+ // Still hidden, skip processing children
508
+ return
509
+ }
510
+ }
511
+
512
+ for (const attr of Array.from(node.attributes)) {
513
+ const attrValue = attr.value
514
+ if (!attrValue || typeof attrValue !== 'string' || !attrValue.includes('${')) continue
515
+
516
+ const attrNameLower = attr.name.toLowerCase()
517
+ const resolved = _resolveValueOrString(attrValue, combinedData)
518
+
519
+ if (attrNameLower === 'list' || attrNameLower === 'listdata') {
520
+ const listValue = resolved.isPure
521
+ ? resolved.value
522
+ : extractTemplateValue(attrValue, combinedData)
523
+ if (Array.isArray(listValue)) {
524
+ if (typeof options.onListData === 'function') {
525
+ options.onListData(node, listValue, attr.name)
526
+ }
527
+ // Mark that children should not be processed - they will be handled
528
+ // separately when the sublist renders with correct item data
529
+ shouldProcessChildren = false
530
+ node.removeAttribute(attr.name)
531
+ } else {
532
+ // If listValue is not an array (could be undefined or a template variable),
533
+ // pass it to the component so it can handle it appropriately
534
+ if (component && typeof component.updateProperty === 'function') {
535
+ component.updateProperty(attr.name, listValue)
536
+ }
537
+ // Keep the attribute for debugging or later resolution
538
+ if (listValue === undefined) {
539
+ node.setAttribute(attr.name, attrValue)
540
+ } else {
541
+ node.setAttribute(attr.name, String(listValue))
542
+ }
543
+ }
544
+ continue
545
+ }
546
+
547
+ if (attrNameLower === 'flexstyle') {
548
+ const styleObj = resolved.isPure
549
+ ? resolved.value
550
+ : extractTemplateValue(attrValue, combinedData)
551
+ if (styleObj && typeof styleObj === 'object') {
552
+ if (component && typeof component.updateProperty === 'function') {
553
+ component.updateProperty(attr.name, styleObj)
554
+ } else {
555
+ _applyFlexStyleToElement(node, styleObj)
556
+ }
557
+ node.removeAttribute(attr.name)
558
+ }
559
+ continue
560
+ }
561
+
562
+ if (attrNameLower === 'layout') {
563
+ const layoutValue = resolved.isPure
564
+ ? resolved.value
565
+ : extractTemplateValue(attrValue, combinedData)
566
+ if (layoutValue !== undefined && layoutValue !== null) {
567
+ if (component && typeof component.updateProperty === 'function') {
568
+ component.updateProperty(attr.name, layoutValue)
569
+ } else {
570
+ _applyLayoutToElement(node, layoutValue)
571
+ }
572
+ node.removeAttribute(attr.name)
573
+ }
574
+ continue
575
+ }
576
+
577
+ if (attrNameLower === 'decoration') {
578
+ const decorationValue = resolved.isPure
579
+ ? resolved.value
580
+ : extractTemplateValue(attrValue, combinedData)
581
+ if (decorationValue && typeof decorationValue === 'object') {
582
+ if (component && typeof component.updateProperty === 'function') {
583
+ component.updateProperty(attr.name, decorationValue)
584
+ } else {
585
+ _applyDecorationToElement(node, decorationValue)
586
+ }
587
+ node.removeAttribute(attr.name)
588
+ }
589
+ continue
590
+ }
591
+
592
+ if (attrNameLower === 'size') {
593
+ const sizeValue = resolved.isPure
594
+ ? resolved.value
595
+ : extractTemplateValue(attrValue, combinedData)
596
+ if (sizeValue !== undefined && sizeValue !== null) {
597
+ if (component && typeof component.updateProperty === 'function') {
598
+ component.updateProperty(attr.name, sizeValue)
599
+ } else {
600
+ _applySizeToElement(node, sizeValue)
601
+ }
602
+ node.removeAttribute(attr.name)
603
+ }
604
+ continue
605
+ }
606
+
607
+ if (attrNameLower === 'textsize' || attrNameLower === 'fontsize') {
608
+ const sizeValue = resolved.isPure
609
+ ? resolved.value
610
+ : extractTemplateValue(attrValue, combinedData)
611
+ if (sizeValue !== undefined) {
612
+ if (component && typeof component.updateProperty === 'function') {
613
+ component.updateProperty(attr.name, sizeValue)
614
+ } else {
615
+ const fontSize = typeof sizeValue === 'number' ? sizeValue + 'px' : sizeValue
616
+ node.style.fontSize = fontSize
617
+ }
618
+ node.removeAttribute(attr.name)
619
+ }
620
+ continue
621
+ }
622
+
623
+ const finalValue = resolved.isPure
624
+ ? resolved.value !== undefined
625
+ ? String(resolved.value)
626
+ : attrValue
627
+ : resolved.value
628
+
629
+ if (component) {
630
+ if (attrNameLower === 'text' && typeof component.setText === 'function') {
631
+ component.setText(finalValue)
632
+ } else if (typeof component.updateProperty === 'function') {
633
+ component.updateProperty(attr.name, finalValue)
634
+ }
635
+ }
636
+
637
+ node.setAttribute(attr.name, finalValue)
638
+ }
639
+
640
+ if (node.style && node.style.cssText && node.style.cssText.includes('${')) {
641
+ node.style.cssText = replaceTemplatePlaceholders(node.style.cssText, combinedData)
642
+ _syncDomOnlyQtTextAutoWidth(node)
643
+ }
644
+
645
+ if (node.tagName === 'IMG' && node.getAttribute('src')) {
646
+ const src = node.getAttribute('src')
647
+ const resolvedSrc = src.includes('${') ? replaceTemplatePlaceholders(src, combinedData) : src
648
+ const newSrc = _normalizeWebAssetSrc(resolvedSrc)
649
+ if (newSrc !== src) {
650
+ if (component && typeof component.updateProperty === 'function') {
651
+ component.updateProperty('src', newSrc)
652
+ }
653
+ node.setAttribute('src', newSrc)
654
+ node.src = newSrc
655
+ }
656
+ }
657
+
658
+ // Only process children if this node doesn't have a list attribute
659
+ // Nodes with list attribute will have their children processed separately
660
+ // during sublist rendering with correct item data
661
+ if (shouldProcessChildren) {
662
+ Array.from(node.childNodes).forEach((child) => {
663
+ bindTemplateDataToNode(child, combinedData, options)
664
+ })
665
+ }
666
+ }