@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.
- package/package.json +24 -0
- package/src/adapters/es3-video-player.js +828 -0
- package/src/components/Modal.js +119 -0
- package/src/components/QtAnimationView.js +678 -0
- package/src/components/QtBaseComponent.js +165 -0
- package/src/components/QtFastListView.js +1920 -0
- package/src/components/QtFlexView.js +799 -0
- package/src/components/QtImage.js +203 -0
- package/src/components/QtItemFrame.js +239 -0
- package/src/components/QtItemStoreView.js +93 -0
- package/src/components/QtItemView.js +125 -0
- package/src/components/QtListView.js +331 -0
- package/src/components/QtLoadingView.js +55 -0
- package/src/components/QtPageRootView.js +19 -0
- package/src/components/QtPlayMark.js +168 -0
- package/src/components/QtProgressBar.js +199 -0
- package/src/components/QtQRCode.js +78 -0
- package/src/components/QtReplaceChild.js +149 -0
- package/src/components/QtRippleView.js +166 -0
- package/src/components/QtSeekBar.js +409 -0
- package/src/components/QtText.js +679 -0
- package/src/components/QtTransitionImage.js +170 -0
- package/src/components/QtView.js +706 -0
- package/src/components/QtWebView.js +613 -0
- package/src/components/TabsView.js +420 -0
- package/src/components/ViewPager.js +206 -0
- package/src/components/index.js +24 -0
- package/src/components/plugins/TextV2Component.js +70 -0
- package/src/components/plugins/index.js +7 -0
- package/src/core/SceneBuilder.js +58 -0
- package/src/core/TVFocusManager.js +2014 -0
- package/src/core/asyncLocalStorage.js +175 -0
- package/src/core/autoProxy.js +165 -0
- package/src/core/componentRegistry.js +84 -0
- package/src/core/constants.js +6 -0
- package/src/core/index.js +8 -0
- package/src/core/moduleUtils.js +36 -0
- package/src/core/patches.js +958 -0
- package/src/core/templateBinding.js +666 -0
- package/src/index.js +246 -0
- package/src/modules/AndroidDevelopModule.js +101 -0
- package/src/modules/AndroidDeviceModule.js +341 -0
- package/src/modules/AndroidNetworkModule.js +178 -0
- package/src/modules/AndroidSharedPreferencesModule.js +100 -0
- package/src/modules/ESDeviceInfoModule.js +450 -0
- package/src/modules/ESGroupDataModule.js +195 -0
- package/src/modules/ESIJKAudioPlayerModule.js +477 -0
- package/src/modules/ESLocalStorageModule.js +100 -0
- package/src/modules/ESLogModule.js +65 -0
- package/src/modules/ESModule.js +106 -0
- package/src/modules/ESNetworkSpeedModule.js +117 -0
- package/src/modules/ESToastModule.js +172 -0
- package/src/modules/EsNativeModule.js +117 -0
- package/src/modules/FastListModule.js +101 -0
- package/src/modules/FocusModule.js +145 -0
- 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
|
+
}
|