@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,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
+ }