@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,706 @@
|
|
|
1
|
+
// Base QtView component that maps to div
|
|
2
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
3
|
+
import { registerComponentBySid, unregisterComponentBySid } from '../core/componentRegistry'
|
|
4
|
+
|
|
5
|
+
// Global registry for DOM-to-Component mapping
|
|
6
|
+
window.__HIPPY_COMPONENT_REGISTRY__ = window.__HIPPY_COMPONENT_REGISTRY__ || new Map()
|
|
7
|
+
|
|
8
|
+
export class QtView extends QtBaseComponent {
|
|
9
|
+
constructor(context, id, pId) {
|
|
10
|
+
super(context, id, pId)
|
|
11
|
+
this.tagName = 'div'
|
|
12
|
+
this.dom = document.createElement('div')
|
|
13
|
+
|
|
14
|
+
// Store current sid for cleanup
|
|
15
|
+
this._currentSid = null
|
|
16
|
+
|
|
17
|
+
// Store reference from DOM element to component instance
|
|
18
|
+
// This allows TVFocusManager to dispatch events on the component
|
|
19
|
+
window.__HIPPY_COMPONENT_REGISTRY__.set(this.dom, this)
|
|
20
|
+
|
|
21
|
+
// Bind DOM events to Hippy event system
|
|
22
|
+
this._bindDOMEvents()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_bindDOMEvents() {
|
|
26
|
+
// Forward DOM click event to Hippy/Vue event system
|
|
27
|
+
this.dom.addEventListener('click', (e) => {
|
|
28
|
+
console.log('[QtView] DOM click event, dispatching to Hippy events')
|
|
29
|
+
if (this.events && this.events.onClick) {
|
|
30
|
+
this.dispatchEvent('onClick', { nativeEvent: e })
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Forward focus event
|
|
35
|
+
this.dom.addEventListener('focus', (e) => {
|
|
36
|
+
if (this.events && this.events.onFocus) {
|
|
37
|
+
this.dispatchEvent('onFocus', { isFocused: true, nativeEvent: e })
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Forward blur event
|
|
42
|
+
this.dom.addEventListener('blur', (e) => {
|
|
43
|
+
if (this.events && this.events.onFocus) {
|
|
44
|
+
this.dispatchEvent('onFocus', { isFocused: false, nativeEvent: e })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Override updateProperty to handle TV focus attributes
|
|
50
|
+
updateProperty(key, value) {
|
|
51
|
+
// Handle sid (string id) property - register to sidRegistry
|
|
52
|
+
if (key === 'sid') {
|
|
53
|
+
// Unregister previous sid if exists
|
|
54
|
+
if (this._currentSid) {
|
|
55
|
+
unregisterComponentBySid(this._currentSid)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Register new sid
|
|
59
|
+
this._currentSid = value
|
|
60
|
+
if (value) {
|
|
61
|
+
registerComponentBySid(value, this)
|
|
62
|
+
// Also set as DOM attribute for debugging
|
|
63
|
+
this.dom.setAttribute('sid', String(value))
|
|
64
|
+
} else {
|
|
65
|
+
this.dom.removeAttribute('sid')
|
|
66
|
+
}
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (key === 'autoWidth' || key === 'auto_width') {
|
|
71
|
+
const enabled = value === '' || !!value
|
|
72
|
+
if (enabled) {
|
|
73
|
+
this.dom.setAttribute('autowidth', '')
|
|
74
|
+
} else {
|
|
75
|
+
this.dom.removeAttribute('autowidth')
|
|
76
|
+
}
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle flexStyle property
|
|
81
|
+
if (key === 'flexStyle' || key === 'flexstyle') {
|
|
82
|
+
if (typeof value === 'object' && value !== null) {
|
|
83
|
+
this._applyFlexStyle(value)
|
|
84
|
+
} else if (typeof value === 'string' && value.startsWith('${')) {
|
|
85
|
+
this.dom.setAttribute(key, value)
|
|
86
|
+
}
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (key === 'layout') {
|
|
91
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
92
|
+
this.dom.setAttribute(key, value)
|
|
93
|
+
} else {
|
|
94
|
+
this._applyLayout(value)
|
|
95
|
+
}
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (key === 'size') {
|
|
100
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
101
|
+
this.dom.setAttribute(key, value)
|
|
102
|
+
} else {
|
|
103
|
+
this._applySize(value)
|
|
104
|
+
}
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (key === 'decoration') {
|
|
109
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
110
|
+
this.dom.setAttribute(key, value)
|
|
111
|
+
} else {
|
|
112
|
+
this._applyDecoration(value)
|
|
113
|
+
}
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle showIf attribute - store on DOM for templateBinding to process
|
|
118
|
+
if (key === 'showIf' || key === 'showif') {
|
|
119
|
+
if (value !== null && value !== undefined) {
|
|
120
|
+
this.dom.setAttribute('showIf', String(value))
|
|
121
|
+
} else {
|
|
122
|
+
this.dom.removeAttribute('showIf')
|
|
123
|
+
this.dom.removeAttribute('showif')
|
|
124
|
+
}
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Handle TV focus-related attributes - set them on DOM element
|
|
129
|
+
const tvFocusAttrs = [
|
|
130
|
+
'focusable',
|
|
131
|
+
'enableFocusBorder',
|
|
132
|
+
'focusScale',
|
|
133
|
+
'focusBorderWidth',
|
|
134
|
+
'focusBorderColor',
|
|
135
|
+
'focusBorderRadius',
|
|
136
|
+
'canFocus',
|
|
137
|
+
'requestFocus',
|
|
138
|
+
'duplicateParentState',
|
|
139
|
+
'blockFocusDirections',
|
|
140
|
+
'blockfocusdirections',
|
|
141
|
+
'showOnState',
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
// Focus style attributes (for duplicateParentState)
|
|
145
|
+
// These can be set via :focusColor="..." or focus-color in CSS
|
|
146
|
+
const focusStyleAttrs = [
|
|
147
|
+
'focusColor',
|
|
148
|
+
'focusBackgroundColor',
|
|
149
|
+
'focusBackground',
|
|
150
|
+
'focusTextColor',
|
|
151
|
+
'focusBorderColor',
|
|
152
|
+
'focusBorderWidth',
|
|
153
|
+
'focusBorderRadius',
|
|
154
|
+
'focusOpacity',
|
|
155
|
+
'focusScale',
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
// Layout attributes
|
|
159
|
+
const layoutAttrs = ['clipChildren', 'clipPadding']
|
|
160
|
+
|
|
161
|
+
if (layoutAttrs.includes(key)) {
|
|
162
|
+
if (key === 'clipChildren') {
|
|
163
|
+
// clipChildren: false → overflow: visible (允许子节点超出显示)
|
|
164
|
+
// clipChildren: true → overflow: hidden (裁剪超出部分)
|
|
165
|
+
this.dom.style.overflow = value === false ? 'visible' : 'hidden'
|
|
166
|
+
} else if (key === 'clipPadding') {
|
|
167
|
+
// clipPadding: 处理 padding 区域的裁剪
|
|
168
|
+
// 目前暂不处理,保留扩展点
|
|
169
|
+
}
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Also handle kebab-case versions (from CSS or direct assignment)
|
|
174
|
+
const kebabFocusStyleAttrs = [
|
|
175
|
+
'focus-color',
|
|
176
|
+
'focus-background-color',
|
|
177
|
+
'focus-background',
|
|
178
|
+
'focus-text-color',
|
|
179
|
+
'focus-border-color',
|
|
180
|
+
'focus-border-width',
|
|
181
|
+
'focus-border-radius',
|
|
182
|
+
'focus-opacity',
|
|
183
|
+
'focus-scale',
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
if (tvFocusAttrs.includes(key)) {
|
|
187
|
+
// Set as DOM attribute for TVFocusManager to find
|
|
188
|
+
if (value !== null && value !== undefined) {
|
|
189
|
+
// Special handling for showOnState: convert array to JSON string
|
|
190
|
+
if (key === 'showOnState' && Array.isArray(value)) {
|
|
191
|
+
this.dom.setAttribute(key, JSON.stringify(value))
|
|
192
|
+
} else {
|
|
193
|
+
this.dom.setAttribute(key, String(value))
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
this.dom.removeAttribute(key)
|
|
197
|
+
}
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (focusStyleAttrs.includes(key)) {
|
|
202
|
+
// Store focus styles as data attributes
|
|
203
|
+
if (value !== null && value !== undefined) {
|
|
204
|
+
// Convert camelCase to kebab-case for data attribute
|
|
205
|
+
const dataKey = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
206
|
+
this.dom.setAttribute(dataKey, String(value))
|
|
207
|
+
} else {
|
|
208
|
+
const dataKey = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
209
|
+
this.dom.removeAttribute(dataKey)
|
|
210
|
+
}
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (kebabFocusStyleAttrs.includes(key)) {
|
|
215
|
+
// Handle kebab-case focus styles (from CSS parsing)
|
|
216
|
+
if (value !== null && value !== undefined) {
|
|
217
|
+
this.dom.setAttribute('data-' + key, String(value))
|
|
218
|
+
} else {
|
|
219
|
+
this.dom.removeAttribute('data-' + key)
|
|
220
|
+
}
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
225
|
+
this.dom.setAttribute(key, value)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// For other properties, use parent class handling
|
|
229
|
+
super.updateProperty(key, value)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Apply flexStyle object to DOM
|
|
233
|
+
_applyFlexStyle(styleObj) {
|
|
234
|
+
if (!styleObj || typeof styleObj !== 'object') return
|
|
235
|
+
|
|
236
|
+
// Flex properties that require display: flex
|
|
237
|
+
const flexProperties = [
|
|
238
|
+
'flexDirection',
|
|
239
|
+
'justifyContent',
|
|
240
|
+
'alignItems',
|
|
241
|
+
'alignContent',
|
|
242
|
+
'flexWrap',
|
|
243
|
+
'flexGrow',
|
|
244
|
+
'flexShrink',
|
|
245
|
+
'flexBasis',
|
|
246
|
+
'order',
|
|
247
|
+
'alignSelf',
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
// Check if any flex property is being set
|
|
251
|
+
const hasFlexProperty = Object.keys(styleObj).some((key) => flexProperties.includes(key))
|
|
252
|
+
|
|
253
|
+
// If flex properties are set and display is not explicitly set, add display: flex
|
|
254
|
+
// This matches Hippy's behavior where all elements default to flex layout
|
|
255
|
+
if (hasFlexProperty && !Object.keys(styleObj).includes('display')) {
|
|
256
|
+
this.dom.style.display = 'flex'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
Object.keys(styleObj).forEach((key) => {
|
|
260
|
+
let value = styleObj[key]
|
|
261
|
+
|
|
262
|
+
// Convert numeric values to pixels for appropriate properties
|
|
263
|
+
if (typeof value === 'number') {
|
|
264
|
+
const needsPx = [
|
|
265
|
+
'width',
|
|
266
|
+
'height',
|
|
267
|
+
'minWidth',
|
|
268
|
+
'minHeight',
|
|
269
|
+
'maxWidth',
|
|
270
|
+
'maxHeight',
|
|
271
|
+
'padding',
|
|
272
|
+
'paddingTop',
|
|
273
|
+
'paddingRight',
|
|
274
|
+
'paddingBottom',
|
|
275
|
+
'paddingLeft',
|
|
276
|
+
'margin',
|
|
277
|
+
'marginTop',
|
|
278
|
+
'marginRight',
|
|
279
|
+
'marginBottom',
|
|
280
|
+
'marginLeft',
|
|
281
|
+
'top',
|
|
282
|
+
'left',
|
|
283
|
+
'right',
|
|
284
|
+
'bottom',
|
|
285
|
+
'borderRadius',
|
|
286
|
+
'borderWidth',
|
|
287
|
+
'fontSize',
|
|
288
|
+
'lineHeight',
|
|
289
|
+
'letterSpacing',
|
|
290
|
+
'gap',
|
|
291
|
+
'rowGap',
|
|
292
|
+
'columnGap',
|
|
293
|
+
].includes(key)
|
|
294
|
+
|
|
295
|
+
if (needsPx) {
|
|
296
|
+
value = value + 'px'
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
if (key !== 'x' && key !== 'y') {
|
|
302
|
+
this.dom.style[key] = value
|
|
303
|
+
}
|
|
304
|
+
} catch (e) {
|
|
305
|
+
try {
|
|
306
|
+
if (key !== 'x' && key !== 'y') {
|
|
307
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
308
|
+
this.dom.style.setProperty(cssKey, value)
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
// Ignore style errors
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
_applyLayout(layout) {
|
|
318
|
+
if (!layout) return
|
|
319
|
+
|
|
320
|
+
if (Array.isArray(layout)) {
|
|
321
|
+
const x = layout[0]
|
|
322
|
+
const y = layout[1]
|
|
323
|
+
const width = layout[2]
|
|
324
|
+
const height = layout[3]
|
|
325
|
+
|
|
326
|
+
if (x !== undefined && x !== null) {
|
|
327
|
+
this.dom.style.left = typeof x === 'number' ? `${x}px` : x
|
|
328
|
+
}
|
|
329
|
+
if (y !== undefined && y !== null) {
|
|
330
|
+
this.dom.style.top = typeof y === 'number' ? `${y}px` : y
|
|
331
|
+
}
|
|
332
|
+
if (width !== undefined && width !== null) {
|
|
333
|
+
this.dom.style.width = typeof width === 'number' ? `${width}px` : width
|
|
334
|
+
}
|
|
335
|
+
if (height !== undefined && height !== null) {
|
|
336
|
+
this.dom.style.height = typeof height === 'number' ? `${height}px` : height
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (
|
|
340
|
+
(x !== undefined || y !== undefined) &&
|
|
341
|
+
(!this.dom.style.position || this.dom.style.position === 'relative')
|
|
342
|
+
) {
|
|
343
|
+
this.dom.style.position = 'absolute'
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (typeof layout === 'object') {
|
|
350
|
+
const { x, y, width, height } = layout
|
|
351
|
+
|
|
352
|
+
if (x !== undefined && x !== null) {
|
|
353
|
+
this.dom.style.left = typeof x === 'number' ? `${x}px` : x
|
|
354
|
+
}
|
|
355
|
+
if (y !== undefined && y !== null) {
|
|
356
|
+
this.dom.style.top = typeof y === 'number' ? `${y}px` : y
|
|
357
|
+
}
|
|
358
|
+
if (width !== undefined && width !== null) {
|
|
359
|
+
this.dom.style.width = typeof width === 'number' ? `${width}px` : width
|
|
360
|
+
}
|
|
361
|
+
if (height !== undefined && height !== null) {
|
|
362
|
+
this.dom.style.height = typeof height === 'number' ? `${height}px` : height
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (
|
|
366
|
+
(x !== undefined || y !== undefined) &&
|
|
367
|
+
(!this.dom.style.position || this.dom.style.position === 'relative')
|
|
368
|
+
) {
|
|
369
|
+
this.dom.style.position = 'absolute'
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
_applySize(size) {
|
|
375
|
+
if (!size) return
|
|
376
|
+
|
|
377
|
+
if (Array.isArray(size)) {
|
|
378
|
+
const width = size[0]
|
|
379
|
+
const height = size[1]
|
|
380
|
+
|
|
381
|
+
if (width !== undefined && width !== null) {
|
|
382
|
+
this.dom.style.width = typeof width === 'number' ? `${width}px` : width
|
|
383
|
+
}
|
|
384
|
+
if (height !== undefined && height !== null) {
|
|
385
|
+
this.dom.style.height = typeof height === 'number' ? `${height}px` : height
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (typeof size === 'object') {
|
|
392
|
+
const { width, height } = size
|
|
393
|
+
|
|
394
|
+
if (width !== undefined && width !== null) {
|
|
395
|
+
this.dom.style.width = typeof width === 'number' ? `${width}px` : width
|
|
396
|
+
}
|
|
397
|
+
if (height !== undefined && height !== null) {
|
|
398
|
+
this.dom.style.height = typeof height === 'number' ? `${height}px` : height
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
_applyDecoration(decoration) {
|
|
404
|
+
if (!decoration || typeof decoration !== 'object') return
|
|
405
|
+
|
|
406
|
+
const { left, top, right, bottom } = decoration
|
|
407
|
+
|
|
408
|
+
if (left !== undefined && left !== null) {
|
|
409
|
+
this.dom.style.marginLeft = typeof left === 'number' ? `${left}px` : left
|
|
410
|
+
}
|
|
411
|
+
if (top !== undefined && top !== null) {
|
|
412
|
+
this.dom.style.marginTop = typeof top === 'number' ? `${top}px` : top
|
|
413
|
+
}
|
|
414
|
+
if (right !== undefined && right !== null) {
|
|
415
|
+
this.dom.style.marginRight = typeof right === 'number' ? `${right}px` : right
|
|
416
|
+
}
|
|
417
|
+
if (bottom !== undefined && bottom !== null) {
|
|
418
|
+
this.dom.style.marginBottom = typeof bottom === 'number' ? `${bottom}px` : bottom
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Request focus for this element
|
|
423
|
+
// direction is optional QTFocusDirection enum value
|
|
424
|
+
requestFocus(direction) {
|
|
425
|
+
const focusManager = global.__TV_FOCUS_MANAGER__
|
|
426
|
+
if (!focusManager) {
|
|
427
|
+
console.warn('[QtView] TVFocusManager not available for requestFocus')
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (direction !== undefined && direction !== null) {
|
|
432
|
+
// Move focus from this element in the specified direction
|
|
433
|
+
focusManager.moveFocusFromElement(this.dom, direction)
|
|
434
|
+
} else {
|
|
435
|
+
// No direction specified, just focus this element
|
|
436
|
+
focusManager.focusElement(this.dom)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Request focus directly (similar to requestFocus but without direction check)
|
|
441
|
+
requestFocusDirectly(direction) {
|
|
442
|
+
const focusManager = global.__TV_FOCUS_MANAGER__
|
|
443
|
+
if (!focusManager) {
|
|
444
|
+
console.warn('[QtView] TVFocusManager not available for requestFocusDirectly')
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (direction !== undefined && direction !== null) {
|
|
449
|
+
// Move focus from this element in the specified direction
|
|
450
|
+
focusManager.moveFocusFromElement(this.dom, direction)
|
|
451
|
+
} else {
|
|
452
|
+
// No direction specified, just focus this element
|
|
453
|
+
focusManager.focusElement(this.dom)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Request child focus at a specific position
|
|
458
|
+
requestChildFocus(position, direction) {
|
|
459
|
+
const focusManager = global.__TV_FOCUS_MANAGER__
|
|
460
|
+
if (!focusManager) {
|
|
461
|
+
console.warn('[QtView] TVFocusManager not available for requestChildFocus')
|
|
462
|
+
return
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Find child at the specified position
|
|
466
|
+
const children = this.dom.children
|
|
467
|
+
if (position >= 0 && position < children.length) {
|
|
468
|
+
const childElement = children[position]
|
|
469
|
+
if (childElement) {
|
|
470
|
+
if (direction !== undefined && direction !== null) {
|
|
471
|
+
focusManager.moveFocusFromElement(childElement, direction)
|
|
472
|
+
} else {
|
|
473
|
+
focusManager.focusElement(childElement)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Clear focus from this element
|
|
480
|
+
clearFocus() {
|
|
481
|
+
const focusManager = global.__TV_FOCUS_MANAGER__
|
|
482
|
+
if (!focusManager) {
|
|
483
|
+
console.warn('[QtView] TVFocusManager not available for clearFocus')
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
focusManager.clearFocusFromElement(this.dom)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Set block focus directions
|
|
491
|
+
setBlockFocusDirections(directionList) {
|
|
492
|
+
if (Array.isArray(directionList)) {
|
|
493
|
+
this.dom.setAttribute('blockFocusDirections', JSON.stringify(directionList))
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Set block focus directions on fail
|
|
498
|
+
setBlockFocusDirectionsOnFail(directionList) {
|
|
499
|
+
if (Array.isArray(directionList)) {
|
|
500
|
+
this.dom.setAttribute('blockFocusDirectionsOnFail', JSON.stringify(directionList))
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ========================================
|
|
505
|
+
// UI Functions for ExtendModule.callUIFunction
|
|
506
|
+
// ========================================
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Change the alpha (opacity) of the element
|
|
510
|
+
* @param {number} alpha - opacity value between 0 and 1
|
|
511
|
+
*/
|
|
512
|
+
changeAlpha(alpha) {
|
|
513
|
+
const opacity = Math.max(0, Math.min(1, Number(alpha) || 1))
|
|
514
|
+
this.dom.style.opacity = opacity
|
|
515
|
+
console.log('[QtView] changeAlpha:', opacity)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Set the scale of the element
|
|
520
|
+
* @param {number} scale - scale factor (e.g., 1.1 for 10% enlargement)
|
|
521
|
+
*/
|
|
522
|
+
setScale(scale) {
|
|
523
|
+
const scaleValue = Number(scale) || 1
|
|
524
|
+
this.dom.style.transform = this.dom.style.transform
|
|
525
|
+
? this.dom.style.transform.replace(/scale\([^)]*\)/, `scale(${scaleValue})`)
|
|
526
|
+
: `scale(${scaleValue})`
|
|
527
|
+
console.log('[QtView] setScale:', scaleValue)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Set the position of the element
|
|
532
|
+
* @param {number} x - x position
|
|
533
|
+
* @param {number} y - y position
|
|
534
|
+
*/
|
|
535
|
+
setPosition(x, y) {
|
|
536
|
+
const xValue = Number(x) || 0
|
|
537
|
+
const yValue = Number(y) || 0
|
|
538
|
+
this.dom.style.left = `${xValue}px`
|
|
539
|
+
this.dom.style.top = `${yValue}px`
|
|
540
|
+
console.log('[QtView] setPosition:', xValue, yValue)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Update the layout of the element
|
|
545
|
+
* @param {object} layout - layout object with width, height, x, y etc.
|
|
546
|
+
*/
|
|
547
|
+
updateLayout(layout) {
|
|
548
|
+
if (!layout || typeof layout !== 'object') return
|
|
549
|
+
|
|
550
|
+
if (layout.width !== undefined) {
|
|
551
|
+
this.dom.style.width = typeof layout.width === 'number' ? `${layout.width}px` : layout.width
|
|
552
|
+
}
|
|
553
|
+
if (layout.height !== undefined) {
|
|
554
|
+
this.dom.style.height =
|
|
555
|
+
typeof layout.height === 'number' ? `${layout.height}px` : layout.height
|
|
556
|
+
}
|
|
557
|
+
if (layout.x !== undefined) {
|
|
558
|
+
this.dom.style.left = typeof layout.x === 'number' ? `${layout.x}px` : layout.x
|
|
559
|
+
}
|
|
560
|
+
if (layout.y !== undefined) {
|
|
561
|
+
this.dom.style.top = typeof layout.y === 'number' ? `${layout.y}px` : layout.y
|
|
562
|
+
}
|
|
563
|
+
console.log('[QtView] updateLayout:', layout)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Set the background color of the element
|
|
568
|
+
* @param {number|string} color - ARGB color number or CSS color string
|
|
569
|
+
*/
|
|
570
|
+
setBackGroundColor(color) {
|
|
571
|
+
let cssColor = color
|
|
572
|
+
// Convert ARGB number to CSS color if needed
|
|
573
|
+
if (typeof color === 'number') {
|
|
574
|
+
const a = ((color >> 24) & 0xff) / 255
|
|
575
|
+
const r = (color >> 16) & 0xff
|
|
576
|
+
const g = (color >> 8) & 0xff
|
|
577
|
+
const b = color & 0xff
|
|
578
|
+
cssColor = `rgba(${r}, ${g}, ${b}, ${a})`
|
|
579
|
+
}
|
|
580
|
+
this.dom.style.backgroundColor = cssColor
|
|
581
|
+
console.log('[QtView] setBackGroundColor:', cssColor)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Set the size of the element
|
|
586
|
+
* @param {number} width - width in pixels
|
|
587
|
+
* @param {number} height - height in pixels
|
|
588
|
+
*/
|
|
589
|
+
setSize(width, height) {
|
|
590
|
+
if (width !== undefined) {
|
|
591
|
+
this.dom.style.width = typeof width === 'number' ? `${width}px` : width
|
|
592
|
+
}
|
|
593
|
+
if (height !== undefined) {
|
|
594
|
+
this.dom.style.height = typeof height === 'number' ? `${height}px` : height
|
|
595
|
+
}
|
|
596
|
+
console.log('[QtView] setSize:', width, height)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Set the translation of the element
|
|
601
|
+
* @param {number} x - x translation
|
|
602
|
+
* @param {number} y - y translation
|
|
603
|
+
*/
|
|
604
|
+
setTranslation(x, y) {
|
|
605
|
+
const xValue = Number(x) || 0
|
|
606
|
+
const yValue = Number(y) || 0
|
|
607
|
+
this.dom.style.transform = this.dom.style.transform
|
|
608
|
+
? this.dom.style.transform.replace(
|
|
609
|
+
/translate\([^)]*\)/,
|
|
610
|
+
`translate(${xValue}px, ${yValue}px)`
|
|
611
|
+
)
|
|
612
|
+
: `translate(${xValue}px, ${yValue}px)`
|
|
613
|
+
console.log('[QtView] setTranslation:', xValue, yValue)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Set initial focus on this element
|
|
618
|
+
* @param {string} sid - the sid of the element to focus
|
|
619
|
+
* @param {number} delay - optional delay in milliseconds
|
|
620
|
+
*/
|
|
621
|
+
setInitFocus(sid, delay = 0) {
|
|
622
|
+
const focusManager = global.__TV_FOCUS_MANAGER__
|
|
623
|
+
if (!focusManager) {
|
|
624
|
+
console.warn('[QtView] TVFocusManager not available for setInitFocus')
|
|
625
|
+
return
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const doFocus = () => {
|
|
629
|
+
focusManager.updateFocusableElements()
|
|
630
|
+
focusManager.focusElement(this.dom)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (delay > 0) {
|
|
634
|
+
setTimeout(doFocus, delay)
|
|
635
|
+
} else {
|
|
636
|
+
requestAnimationFrame(doFocus)
|
|
637
|
+
}
|
|
638
|
+
console.log('[QtView] setInitFocus:', sid, delay)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Set auto focus on this element
|
|
643
|
+
* @param {string} sid - the sid of the element to focus
|
|
644
|
+
* @param {number} delay - optional delay in milliseconds
|
|
645
|
+
*/
|
|
646
|
+
setAutoFocus(sid, delay = 0) {
|
|
647
|
+
// Same as setInitFocus for web
|
|
648
|
+
this.setInitFocus(sid, delay)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Called when component is mounted to the DOM
|
|
652
|
+
mounted() {
|
|
653
|
+
super.mounted?.()
|
|
654
|
+
|
|
655
|
+
// Check if requestFocus attribute is set to true
|
|
656
|
+
const requestFocusAttr = this.dom.getAttribute('requestFocus')
|
|
657
|
+
if (requestFocusAttr === 'true' || requestFocusAttr === '1' || requestFocusAttr === '') {
|
|
658
|
+
// Delay to ensure DOM is ready and focusable elements are updated
|
|
659
|
+
requestAnimationFrame(() => {
|
|
660
|
+
const focusManager = global.__TV_FOCUS_MANAGER__
|
|
661
|
+
if (focusManager) {
|
|
662
|
+
// Update focusable elements first
|
|
663
|
+
focusManager.updateFocusableElements()
|
|
664
|
+
focusManager.focusElement(this.dom)
|
|
665
|
+
}
|
|
666
|
+
})
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Called when component is unmounted - cleanup sid registration
|
|
671
|
+
beforeUnmount() {
|
|
672
|
+
// Cleanup sid registration
|
|
673
|
+
if (this._currentSid) {
|
|
674
|
+
unregisterComponentBySid(this._currentSid)
|
|
675
|
+
this._currentSid = null
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Cleanup DOM registry
|
|
679
|
+
if (window.__HIPPY_COMPONENT_REGISTRY__) {
|
|
680
|
+
window.__HIPPY_COMPONENT_REGISTRY__.delete(this.dom)
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Legacy destroy method
|
|
685
|
+
destroy() {
|
|
686
|
+
this.beforeUnmount?.()
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Factory function to create named components that preserve original component name
|
|
691
|
+
// This adds data-component-name attribute to DOM elements for easy identification
|
|
692
|
+
export function createNamedComponent(BaseClass, originalName, tagName = 'div') {
|
|
693
|
+
return class NamedComponent extends BaseClass {
|
|
694
|
+
static componentName = originalName
|
|
695
|
+
|
|
696
|
+
constructor(context, id, pId) {
|
|
697
|
+
super(context, id, pId)
|
|
698
|
+
// Mark the DOM element with original component name
|
|
699
|
+
if (this.dom) {
|
|
700
|
+
this.dom.setAttribute('data-component-name', originalName)
|
|
701
|
+
// Also store as property for easy access
|
|
702
|
+
this._originalComponentName = originalName
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|