@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,331 @@
1
+ // QtListView - Adapter for Hippy's ListView with TV-specific features 标签对应ul标签
2
+ import { QtBaseComponent } from './QtBaseComponent'
3
+ import { registerComponent } from '../core/componentRegistry'
4
+
5
+ export class QtListView extends QtBaseComponent {
6
+ constructor(context, id, pId) {
7
+ super(context, id, pId)
8
+ this.tagName = 'ListView'
9
+ this.dom = document.createElement('div')
10
+ this.dom.setAttribute('data-component-name', 'ListView')
11
+ registerComponent(id, this)
12
+
13
+ // TV-specific properties
14
+ this._horizontal = false
15
+ this._fadingEdgeLength = 0
16
+ this._horizontalFadingEdgeEnabled = false
17
+ this._verticalFadingEdgeEnabled = false
18
+ this._clipChildren = true
19
+
20
+ // Child items
21
+ this._children = []
22
+
23
+ // Gradient overlays for fading edge effect
24
+ this._startGradient = null
25
+ this._endGradient = null
26
+ }
27
+
28
+ defaultStyle() {
29
+ return {
30
+ display: 'flex',
31
+ flexDirection: this._horizontal ? 'row' : 'column',
32
+ flexShrink: 0,
33
+ boxSizing: 'border-box',
34
+ overflow: 'hidden',
35
+ position: 'relative',
36
+ }
37
+ }
38
+
39
+ updateProperty(key, value) {
40
+ switch (key) {
41
+ case 'horizontal':
42
+ this._horizontal = !!value
43
+ this._updateLayoutDirection()
44
+ break
45
+
46
+ case 'fadingEdgeLength':
47
+ this._fadingEdgeLength = value || 0
48
+ this._updateFadingEdge()
49
+ break
50
+
51
+ case 'horizontalFadingEdgeEnabled':
52
+ this._horizontalFadingEdgeEnabled = !!value
53
+ this._updateFadingEdge()
54
+ break
55
+
56
+ case 'verticalFadingEdgeEnabled':
57
+ this._verticalFadingEdgeEnabled = !!value
58
+ this._updateFadingEdge()
59
+ break
60
+
61
+ case 'clipChildren':
62
+ this._clipChildren = value !== false
63
+ this._updateClipChildren()
64
+ break
65
+
66
+ case 'scrollEnabled':
67
+ this._scrollEnabled = value !== false
68
+ this._updateScrollEnabled()
69
+ break
70
+
71
+ default:
72
+ if (typeof value === 'string' && value.startsWith('${')) {
73
+ this.dom.setAttribute(key, value)
74
+ }
75
+ super.updateProperty(key, value)
76
+ }
77
+ }
78
+
79
+ _updateLayoutDirection() {
80
+ if (!this.dom) return
81
+ this.dom.style.flexDirection = this._horizontal ? 'row' : 'column'
82
+ this._updateFadingEdge()
83
+ }
84
+
85
+ _updateClipChildren() {
86
+ if (!this.dom) return
87
+ this.dom.style.overflow = this._clipChildren ? 'hidden' : 'visible'
88
+ }
89
+
90
+ _updateScrollEnabled() {
91
+ if (!this.dom) return
92
+ this.dom.style.overflow = this._scrollEnabled ? 'auto' : 'hidden'
93
+ }
94
+
95
+ _updateFadingEdge() {
96
+ if (!this.dom) return
97
+
98
+ // Remove existing gradients
99
+ if (this._startGradient) {
100
+ this._startGradient.remove()
101
+ this._startGradient = null
102
+ }
103
+ if (this._endGradient) {
104
+ this._endGradient.remove()
105
+ this._endGradient = null
106
+ }
107
+
108
+ // Check if fading edge is enabled
109
+ const fadingEnabled = this._horizontal
110
+ ? this._horizontalFadingEdgeEnabled
111
+ : this._verticalFadingEdgeEnabled
112
+
113
+ if (!fadingEnabled || this._fadingEdgeLength <= 0) {
114
+ return
115
+ }
116
+
117
+ const length = this._fadingEdgeLength
118
+
119
+ // Create gradient overlays
120
+ if (this._horizontal) {
121
+ // Left gradient (start)
122
+ this._startGradient = document.createElement('div')
123
+ this._startGradient.style.cssText = `
124
+ position: absolute;
125
+ left: 0;
126
+ top: 0;
127
+ bottom: 0;
128
+ width: ${length}px;
129
+ background: linear-gradient(to right, rgba(0,0,0,0.3), transparent);
130
+ pointer-events: none;
131
+ z-index: 10;
132
+ `
133
+ this.dom.appendChild(this._startGradient)
134
+
135
+ // Right gradient (end)
136
+ this._endGradient = document.createElement('div')
137
+ this._endGradient.style.cssText = `
138
+ position: absolute;
139
+ right: 0;
140
+ top: 0;
141
+ bottom: 0;
142
+ width: ${length}px;
143
+ background: linear-gradient(to left, rgba(0,0,0,0.3), transparent);
144
+ pointer-events: none;
145
+ z-index: 10;
146
+ `
147
+ this.dom.appendChild(this._endGradient)
148
+ } else {
149
+ // Top gradient (start)
150
+ this._startGradient = document.createElement('div')
151
+ this._startGradient.style.cssText = `
152
+ position: absolute;
153
+ left: 0;
154
+ top: 0;
155
+ right: 0;
156
+ height: ${length}px;
157
+ background: linear-gradient(to bottom, rgba(0,0,0,0.3), transparent);
158
+ pointer-events: none;
159
+ z-index: 10;
160
+ `
161
+ this.dom.appendChild(this._startGradient)
162
+
163
+ // Bottom gradient (end)
164
+ this._endGradient = document.createElement('div')
165
+ this._endGradient.style.cssText = `
166
+ position: absolute;
167
+ left: 0;
168
+ bottom: 0;
169
+ right: 0;
170
+ height: ${length}px;
171
+ background: linear-gradient(to top, rgba(0,0,0,0.3), transparent);
172
+ pointer-events: none;
173
+ z-index: 10;
174
+ `
175
+ this.dom.appendChild(this._endGradient)
176
+ }
177
+ }
178
+
179
+ // Handle child components
180
+ insertChild(view, index) {
181
+ if (view && view.dom) {
182
+ // Insert before gradient overlays
183
+ if (this._endGradient) {
184
+ this.dom.insertBefore(view.dom, this._endGradient)
185
+ } else if (this._startGradient) {
186
+ this.dom.insertBefore(view.dom, this._startGradient)
187
+ } else {
188
+ this.dom.appendChild(view.dom)
189
+ }
190
+ this._children.push(view)
191
+ }
192
+ }
193
+
194
+ removeChild(view) {
195
+ if (view && view.dom && view.dom.parentNode === this.dom) {
196
+ this.dom.removeChild(view.dom)
197
+ const idx = this._children.indexOf(view)
198
+ if (idx > -1) {
199
+ this._children.splice(idx, 1)
200
+ }
201
+ }
202
+ }
203
+
204
+ // Scroll methods
205
+ scrollToIndex(index, animated = true) {
206
+ const child = this._children[index]
207
+ if (child && child.dom) {
208
+ child.dom.scrollIntoView({
209
+ behavior: animated ? 'smooth' : 'auto',
210
+ block: 'nearest',
211
+ inline: 'nearest',
212
+ })
213
+ }
214
+ }
215
+
216
+ scrollToPosition(position, offset = 0, animated = true) {
217
+ if (this._horizontal) {
218
+ this.dom.scrollTo({
219
+ left: position + offset,
220
+ behavior: animated ? 'smooth' : 'auto',
221
+ })
222
+ } else {
223
+ this.dom.scrollTo({
224
+ top: position + offset,
225
+ behavior: animated ? 'smooth' : 'auto',
226
+ })
227
+ }
228
+ }
229
+
230
+ // End batch - called by UIManagerModule after batch updates
231
+ endBatch() {
232
+ // No-op for now
233
+ }
234
+
235
+ destroy() {
236
+ if (this._startGradient) {
237
+ this._startGradient.remove()
238
+ this._startGradient = null
239
+ }
240
+ if (this._endGradient) {
241
+ this._endGradient.remove()
242
+ this._endGradient = null
243
+ }
244
+ this._children = []
245
+ super.destroy()
246
+ }
247
+ }
248
+
249
+ // QtListViewItem - Adapter for Hippy's ListViewItem,标签对应li标签
250
+ export class QtListViewItem extends QtBaseComponent {
251
+ constructor(context, id, pId) {
252
+ super(context, id, pId)
253
+ this.tagName = 'ListViewItem'
254
+ this.dom = document.createElement('div')
255
+ this.dom.setAttribute('data-component-name', 'ListViewItem')
256
+ registerComponent(id, this)
257
+
258
+ // Height tracking for virtual list
259
+ this.height = 0
260
+ this.isDirty = false
261
+ this.dirtyListener = null
262
+ }
263
+
264
+ defaultStyle() {
265
+ return {
266
+ display: 'flex',
267
+ flexDirection: 'column',
268
+ flexShrink: 0,
269
+ boxSizing: 'border-box',
270
+ }
271
+ }
272
+
273
+ updateProperty(key, value) {
274
+ switch (key) {
275
+ case 'sticky':
276
+ this._sticky = !!value
277
+ this._updateSticky()
278
+ break
279
+
280
+ case 'type':
281
+ // Item type for recycling
282
+ this._type = value
283
+ break
284
+
285
+ default:
286
+ if (typeof value === 'string' && value.startsWith('${')) {
287
+ this.dom.setAttribute(key, value)
288
+ }
289
+ super.updateProperty(key, value)
290
+ }
291
+ }
292
+
293
+ _updateSticky() {
294
+ if (!this.dom) return
295
+ if (this._sticky) {
296
+ this.dom.style.position = 'sticky'
297
+ this.dom.style.top = '0'
298
+ this.dom.style.zIndex = '100'
299
+ } else {
300
+ this.dom.style.position = ''
301
+ this.dom.style.top = ''
302
+ this.dom.style.zIndex = ''
303
+ }
304
+ }
305
+
306
+ addDirtyListener(callback) {
307
+ this.dirtyListener = callback
308
+ }
309
+
310
+ getItemHeight() {
311
+ return this.dom?.clientHeight || 0
312
+ }
313
+
314
+ // End batch - called by UIManagerModule after batch updates
315
+ endBatch() {
316
+ // No-op for now
317
+ }
318
+
319
+ mounted() {
320
+ super.mounted()
321
+ // Update height after mount
322
+ requestAnimationFrame(() => {
323
+ this.height = this.getItemHeight()
324
+ })
325
+ }
326
+
327
+ destroy() {
328
+ this.dirtyListener = null
329
+ super.destroy()
330
+ }
331
+ }
@@ -0,0 +1,55 @@
1
+ // QtLoadingView component for web platform
2
+ // Implements a loading spinner animation
3
+ import { QtBaseComponent } from './QtBaseComponent'
4
+
5
+ export class QtLoadingView extends QtBaseComponent {
6
+ constructor(context, id, pId) {
7
+ super(context, id, pId)
8
+ this.tagName = 'div'
9
+ this.dom = document.createElement('div')
10
+
11
+ // Create spinner element
12
+ this.spinner = document.createElement('div')
13
+ this.spinner.className = 'qt-loading-spinner'
14
+ this.dom.appendChild(this.spinner)
15
+
16
+ // Inject styles if not already present
17
+ this._injectStyles()
18
+ }
19
+
20
+ _injectStyles() {
21
+ const styleId = 'qt-loading-view-styles'
22
+ if (document.getElementById(styleId)) return
23
+
24
+ const style = document.createElement('style')
25
+ style.id = styleId
26
+ style.textContent = `
27
+ .qt-loading-spinner {
28
+ width: 100%;
29
+ height: 100%;
30
+ border: 5px solid rgba(200, 200, 200, 0.3);
31
+ border-top-color: #409eff;
32
+ border-radius: 50%;
33
+ animation: qt-loading-spin 0.8s linear infinite;
34
+ box-sizing: border-box;
35
+ }
36
+ @keyframes qt-loading-spin {
37
+ to {
38
+ transform: rotate(360deg);
39
+ }
40
+ }
41
+ `
42
+ document.head.appendChild(style)
43
+ }
44
+
45
+ updateProperty(key, value) {
46
+ if (key === 'color') {
47
+ // Update spinner border color
48
+ if (value) {
49
+ this.spinner.style.borderTopColor = value
50
+ }
51
+ } else {
52
+ super.updateProperty(key, value)
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,19 @@
1
+ import { QtView } from './QtView'
2
+
3
+ export class QtPageRootView extends QtView {
4
+ static componentName = 'ESPageRootView'
5
+
6
+ constructor(context, id, pId) {
7
+ super(context, id, pId)
8
+ this.dom.setAttribute('data-component-name', 'ESPageRootView')
9
+ this._originalComponentName = 'ESPageRootView'
10
+ }
11
+
12
+ defaultStyle() {
13
+ const style = super.defaultStyle ? super.defaultStyle() : {}
14
+ return {
15
+ ...style,
16
+ backgroundColor: '#000000', // default black background for TV root
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,168 @@
1
+ // QtPlayMark - A play mark animation view component
2
+ import { QtBaseComponent } from './QtBaseComponent'
3
+ import { registerComponent } from '../core/componentRegistry'
4
+
5
+ /**
6
+ * QtPlayMark Component for web platform
7
+ * Shows a playing indicator with animation
8
+ */
9
+ export class QtPlayMark extends QtBaseComponent {
10
+ constructor(context, id, pId) {
11
+ super(context, id, pId)
12
+ this.tagName = 'ESPlayMarkViewComponent'
13
+ this.dom = document.createElement('div')
14
+ this.dom.setAttribute('data-component-name', 'QtPlayMark')
15
+
16
+ this.dom.style.cssText = `
17
+ display: block;
18
+ position: relative;
19
+ box-sizing: border-box;
20
+ overflow: hidden;
21
+ `
22
+
23
+ // Props
24
+ this._markColor = '#FF4E46'
25
+ this._gap = 2
26
+ this._showType = 0 // 0: bars, 1: dots
27
+ this._roundCorner = 0
28
+ this._startColor = null
29
+ this._endColor = null
30
+ this._isSupportGradient = false
31
+ this._animationFrame = null
32
+ this._bars = []
33
+
34
+ // Create bars immediately
35
+ this._createBars()
36
+
37
+ registerComponent(id, this)
38
+ }
39
+
40
+ defaultStyle() {
41
+ return {
42
+ display: 'block',
43
+ position: 'relative',
44
+ boxSizing: 'border-box',
45
+ overflow: 'hidden',
46
+ }
47
+ }
48
+
49
+ _createBars() {
50
+ const barCount = 4
51
+ for (let i = 0; i < barCount; i++) {
52
+ const bar = document.createElement('div')
53
+ bar.className = 'play-mark-bar'
54
+ bar.style.cssText = `
55
+ position: absolute;
56
+ bottom: 0;
57
+ background-color: ${this._markColor};
58
+ border-radius: ${this._roundCorner}px;
59
+ `
60
+ this.dom.appendChild(bar)
61
+ this._bars.push(bar)
62
+ }
63
+ this._startAnimation()
64
+ }
65
+
66
+ updateProperty(key, value) {
67
+ switch (key) {
68
+ case 'markColor':
69
+ case 'markcolor':
70
+ this._markColor = value || '#FF4E46'
71
+ this._updateMarkColor()
72
+ break
73
+ case 'gap':
74
+ this._gap = value !== undefined ? Number(value) : 2
75
+ break
76
+ case 'showType':
77
+ case 'showtype':
78
+ this._showType = value !== undefined ? Number(value) : 0
79
+ break
80
+ case 'roundCorner':
81
+ case 'roundcorner':
82
+ this._roundCorner = value !== undefined ? Number(value) : 0
83
+ this._bars.forEach((bar) => {
84
+ bar.style.borderRadius = `${this._roundCorner}px`
85
+ })
86
+ break
87
+ case 'startColor':
88
+ case 'startcolor':
89
+ this._startColor = value
90
+ this._updateMarkColor()
91
+ break
92
+ case 'endColor':
93
+ case 'endcolor':
94
+ this._endColor = value
95
+ this._updateMarkColor()
96
+ break
97
+ case 'isSupportGradient':
98
+ case 'issupportgradient':
99
+ this._isSupportGradient = value === true || value === 'true'
100
+ this._updateMarkColor()
101
+ break
102
+ default:
103
+ super.updateProperty(key, value)
104
+ }
105
+ }
106
+
107
+ _getBarColor() {
108
+ if (this._isSupportGradient && this._startColor && this._endColor) {
109
+ return this._startColor
110
+ }
111
+ return this._markColor
112
+ }
113
+
114
+ _updateMarkColor() {
115
+ const color = this._getBarColor()
116
+ this._bars.forEach((bar) => {
117
+ bar.style.backgroundColor = color
118
+ })
119
+ }
120
+
121
+ _startAnimation() {
122
+ // 使用固定周期实现完美循环动画
123
+ const cycleDuration = 60 // 60帧一个完整周期
124
+ let frame = 0
125
+
126
+ const animate = () => {
127
+ const rect = this.dom.getBoundingClientRect()
128
+ const containerWidth = rect.width || 26
129
+ const containerHeight = rect.height || 30
130
+ const barCount = this._bars.length
131
+
132
+ // 计算 bar 宽度,确保不超出容器
133
+ const totalGap = this._gap * (barCount - 1)
134
+ const barWidth = Math.floor((containerWidth - totalGap) / barCount)
135
+
136
+ // 循环帧计数
137
+ frame = (frame + 1) % cycleDuration
138
+ const t = (frame / cycleDuration) * Math.PI * 2
139
+
140
+ this._bars.forEach((bar, i) => {
141
+ // 每条线有不同的相位偏移,形成波浪式音乐节奏效果
142
+ const phaseOffset = (i / barCount) * Math.PI * 2
143
+ const wave = Math.sin(t * 2 + phaseOffset)
144
+
145
+ // 高度范围:最小 20%,最大 95%
146
+ const heightRatio = 0.2 + (wave + 1) * 0.375
147
+ const barHeight = Math.max(4, containerHeight * heightRatio)
148
+
149
+ bar.style.width = `${barWidth}px`
150
+ bar.style.height = `${barHeight}px`
151
+ bar.style.left = `${i * (barWidth + this._gap)}px`
152
+ })
153
+
154
+ this._animationFrame = requestAnimationFrame(animate)
155
+ }
156
+
157
+ animate()
158
+ }
159
+
160
+ destroy() {
161
+ if (this._animationFrame) {
162
+ cancelAnimationFrame(this._animationFrame)
163
+ this._animationFrame = null
164
+ }
165
+ this._bars = []
166
+ super.destroy()
167
+ }
168
+ }