@quicktvui/web-renderer 1.0.0 → 1.0.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quicktvui/web-renderer",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Web renderer for QuickTVUI - provides web browser rendering support",
5
5
  "author": "QuickTVUI Team",
6
6
  "license": "Apache-2.0",
@@ -62,6 +62,17 @@ export class QtBaseComponent extends HippyWebView {
62
62
  }
63
63
  return true
64
64
 
65
+ case 'name':
66
+ // 存储 name 属性到组件实例和 DOM 上,用于子组件查找
67
+ this.props = this.props || {}
68
+ this.props.name = value
69
+ if (value) {
70
+ this.dom.setAttribute('name', String(value))
71
+ } else {
72
+ this.dom.removeAttribute('name')
73
+ }
74
+ return true
75
+
65
76
  default:
66
77
  return false
67
78
  }
@@ -3,6 +3,9 @@ import { QtBaseComponent } from './QtBaseComponent'
3
3
  import { registerComponent } from '../core/componentRegistry'
4
4
 
5
5
  export class TabsView extends QtBaseComponent {
6
+ // 静态变量追踪实例数量
7
+ static _instanceCount = 0
8
+
6
9
  constructor(context, id, pId) {
7
10
  super(context, id, pId)
8
11
  this.tagName = 'TabsView'
@@ -10,12 +13,22 @@ export class TabsView extends QtBaseComponent {
10
13
  this.dom.setAttribute('data-component-name', 'TabsView')
11
14
  registerComponent(id, this)
12
15
 
16
+ // 追踪实例
17
+ this._instanceId = ++TabsView._instanceCount
18
+ console.log('[TabsView] constructor, instanceId:', this._instanceId)
19
+
13
20
  // Tabs state
14
21
  this._tabData = null
15
22
  this._currentPageIndex = 0
16
23
  this._pages = new Map()
17
24
  this._tabItems = []
18
25
 
26
+ // 记录已加载过数据的页面,避免重复加载
27
+ this._loadedPageIndices = new Set()
28
+
29
+ // 记录最后一次分发 pageChanged 的页面索引,避免重复触发
30
+ this._lastPageChangedIndex = -1
31
+
19
32
  // 子组件引用,通过 name 属性存储
20
33
  // 'tabList' -> qt-nav-bar
21
34
  // 'content' -> recycler-view-pager
@@ -70,11 +83,17 @@ export class TabsView extends QtBaseComponent {
70
83
  console.log('[TabsView] intercepted event from tabList:', eventName, params)
71
84
 
72
85
  // 处理 onItemFocused 事件 (hasFocus === true 时切换页面)
73
- if (eventName === 'onItemFocused' && params && params.hasFocus === true) {
86
+ // 注意:事件名可能是 onItemfocused onItemFocused
87
+ if (
88
+ (eventName === 'onItemFocused' || eventName === 'onItemfocused') &&
89
+ params &&
90
+ params.hasFocus === true
91
+ ) {
74
92
  const pageIndex = params.position
75
93
  if (pageIndex !== undefined && pageIndex !== this._currentPageIndex) {
76
94
  console.log('[TabsView] tab focus changed to:', pageIndex)
77
95
  this._currentPageIndex = pageIndex
96
+ this._syncContentCurrentPage(pageIndex)
78
97
  this._dispatchPageChanged(pageIndex)
79
98
  this._dispatchLoadPageData(pageIndex, false)
80
99
  }
@@ -86,15 +105,58 @@ export class TabsView extends QtBaseComponent {
86
105
  if (pageIndex !== undefined && pageIndex !== this._currentPageIndex) {
87
106
  console.log('[TabsView] tab clicked, switching to:', pageIndex)
88
107
  this._currentPageIndex = pageIndex
108
+ this._syncContentCurrentPage(pageIndex)
89
109
  this._dispatchPageChanged(pageIndex)
90
110
  this._dispatchLoadPageData(pageIndex, false)
91
111
  }
92
112
  }
93
113
  }
94
114
 
115
+ // 添加 DOM 焦点事件监听,用于捕获 tab 切换
116
+ this._boundHandleTabFocusIn = this._handleTabFocusIn.bind(this)
117
+ tabList.dom.addEventListener('focusin', this._boundHandleTabFocusIn)
118
+
95
119
  console.log('[TabsView] tabList listeners setup complete')
96
120
  }
97
121
 
122
+ // 处理 tab 导航栏中的 DOM 焦点事件
123
+ _handleTabFocusIn(event) {
124
+ const tabList = this.getChildByName('tabList')
125
+ if (!tabList) return
126
+
127
+ // 查找 itemContainer
128
+ let itemContainer = tabList._itemContainer
129
+ if (!itemContainer) {
130
+ itemContainer = tabList.dom.querySelector('.flex-view-item-container')
131
+ }
132
+ if (!itemContainer) return
133
+
134
+ // 找到焦点元素对应的 tab 索引
135
+ const items = itemContainer.children
136
+ let focusIndex = -1
137
+ for (let i = 0; i < items.length; i++) {
138
+ if (items[i] === event.target || items[i].contains(event.target)) {
139
+ focusIndex = i
140
+ break
141
+ }
142
+ }
143
+
144
+ if (focusIndex !== -1 && focusIndex !== this._currentPageIndex) {
145
+ console.log('[TabsView] DOM focus changed to tab:', focusIndex)
146
+ this._currentPageIndex = focusIndex
147
+ this._syncContentCurrentPage(focusIndex)
148
+ this._dispatchPageChanged(focusIndex)
149
+ this._dispatchLoadPageData(focusIndex, false)
150
+ }
151
+ }
152
+
153
+ _syncContentCurrentPage(pageIndex) {
154
+ const content = this.getChildByName('content')
155
+ if (content && typeof content.setCurrentPage === 'function') {
156
+ content.setCurrentPage(pageIndex)
157
+ }
158
+ }
159
+
98
160
  // Remove child
99
161
  removeChild(view) {
100
162
  if (view && view.dom) {
@@ -157,14 +219,19 @@ export class TabsView extends QtBaseComponent {
157
219
  this._tabData = { ...config, data }
158
220
  this._tabItems = Array.isArray(data) ? data : []
159
221
  this._currentPageIndex = config.defaultIndex ?? 0
160
- this._focusIndex = config.focusIndex ?? this._currentPageIndex
222
+ // 修复:使用正确的属性名 defaultFocusIndex
223
+ this._focusIndex = config.defaultFocusIndex ?? config.focusIndex ?? this._currentPageIndex
161
224
 
162
225
  console.log(
163
226
  '[TabsView] setTabsData:',
164
227
  config,
165
228
  data,
166
229
  'tabList:',
167
- this._childrenByName.get('tabList')
230
+ this._childrenByName.get('tabList'),
231
+ 'defaultFocusIndex:',
232
+ config.defaultFocusIndex,
233
+ '_focusIndex:',
234
+ this._focusIndex
168
235
  )
169
236
 
170
237
  // 通过 name 获取 tabList (qt-nav-bar) 并设置数据
@@ -187,10 +254,103 @@ export class TabsView extends QtBaseComponent {
187
254
  }
188
255
  // Dispatch page changed event
189
256
  this._dispatchPageChanged(this._currentPageIndex)
257
+ // 分发加载数据事件
190
258
  this._dispatchLoadPageData(this._currentPageIndex, false)
259
+
260
+ // 延迟设置焦点,等待 QtFlexView 渲染完成
261
+ setTimeout(() => {
262
+ this._setInitialTabFocus()
263
+ }, 100)
191
264
  })
192
265
  }
193
266
 
267
+ // 设置 tab 导航栏的初始焦点
268
+ _setInitialTabFocus() {
269
+ const tabList = this.getChildByName('tabList')
270
+ if (!tabList || !tabList.dom) {
271
+ console.log('[TabsView] _setInitialTabFocus: tabList not found')
272
+ return
273
+ }
274
+
275
+ const focusIndex = this._focusIndex
276
+
277
+ // 查找 tabList 中的 itemContainer(实际渲染的项目容器)
278
+ // QtFlexView 使用 _itemContainer 存储渲染的项目
279
+ let itemContainer = tabList._itemContainer
280
+ if (!itemContainer) {
281
+ // 尝试通过 DOM 查找
282
+ itemContainer = tabList.dom.querySelector('.flex-view-item-container')
283
+ }
284
+
285
+ if (itemContainer) {
286
+ // 从 itemContainer 中查找实际渲染的项目
287
+ const renderedItems = itemContainer.children
288
+ console.log(
289
+ '[TabsView] _setInitialTabFocus: itemContainer found, children:',
290
+ renderedItems.length
291
+ )
292
+
293
+ if (renderedItems.length > 0 && focusIndex >= 0 && focusIndex < renderedItems.length) {
294
+ const targetWrapper = renderedItems[focusIndex]
295
+ // 在 wrapper 内部查找可聚焦元素
296
+ const focusableItem =
297
+ targetWrapper.querySelector('[focusable="true"], [focusable="1"], [focusable]') ||
298
+ targetWrapper
299
+
300
+ console.log(
301
+ '[TabsView] _setInitialTabFocus: setting focus to rendered item index',
302
+ focusIndex,
303
+ focusableItem
304
+ )
305
+
306
+ // 设置 autofocus 属性
307
+ focusableItem.setAttribute('autofocus', 'true')
308
+
309
+ // 直接触发焦点设置
310
+ if (global.__TV_FOCUS_MANAGER__) {
311
+ // 先标记该页已加载,防止焦点事件触发重复加载
312
+ // 注意:这里不需要,因为焦点设置的是同一个页面(focusIndex === _currentPageIndex)
313
+ global.__TV_FOCUS_MANAGER__.updateFocusableElements()
314
+ global.__TV_FOCUS_MANAGER__._doFocusElement(focusableItem)
315
+ }
316
+ return
317
+ }
318
+ }
319
+
320
+ // 回退:查找非模板的可聚焦元素
321
+ const allFocusable = tabList.dom.querySelectorAll(
322
+ '[focusable="true"], [focusable="1"], [focusable]'
323
+ )
324
+ const focusableItems = Array.from(allFocusable).filter((el) => {
325
+ // 排除模板元素
326
+ return (
327
+ el.getAttribute('data-template') !== 'true' &&
328
+ el.style.display !== 'none' &&
329
+ !el.closest('[data-template="true"]')
330
+ )
331
+ })
332
+
333
+ console.log('[TabsView] _setInitialTabFocus: fallback, focusableItems:', focusableItems.length)
334
+
335
+ if (focusableItems.length > 0 && focusIndex >= 0 && focusIndex < focusableItems.length) {
336
+ const targetItem = focusableItems[focusIndex]
337
+ console.log('[TabsView] _setInitialTabFocus: setting focus to index', focusIndex, targetItem)
338
+
339
+ targetItem.setAttribute('autofocus', 'true')
340
+
341
+ if (global.__TV_FOCUS_MANAGER__) {
342
+ global.__TV_FOCUS_MANAGER__.updateFocusableElements()
343
+ global.__TV_FOCUS_MANAGER__._doFocusElement(targetItem)
344
+ }
345
+ } else {
346
+ console.log(
347
+ '[TabsView] _setInitialTabFocus: no focusable items found or invalid index',
348
+ focusIndex,
349
+ focusableItems.length
350
+ )
351
+ }
352
+ }
353
+
194
354
  // Set page data - 设置页面内容数据
195
355
  setPageData(...args) {
196
356
  args = Array.isArray(args[0]) && args.length === 1 ? args[0] : args
@@ -301,12 +461,12 @@ export class TabsView extends QtBaseComponent {
301
461
 
302
462
  // Reload all pages
303
463
  reloadAll(updateCurrent = true) {
304
- this._dispatchLoadPageData(this._currentPageIndex, false)
464
+ this._dispatchLoadPageData(this._currentPageIndex, false, true)
305
465
  }
306
466
 
307
467
  // Reload specific page
308
468
  reloadPage(pageIndex) {
309
- this._dispatchLoadPageData(pageIndex, false)
469
+ this._dispatchLoadPageData(pageIndex, false, true)
310
470
  }
311
471
 
312
472
  // Invoke content function
@@ -396,24 +556,67 @@ export class TabsView extends QtBaseComponent {
396
556
 
397
557
  // Dispatch page changed event
398
558
  _dispatchPageChanged(pageIndex) {
559
+ // 防止重复触发同一页面的 pageChanged 事件
560
+ if (this._lastPageChangedIndex === pageIndex) {
561
+ console.log(
562
+ '[TabsView] _dispatchPageChanged: skipping duplicate page changed for index',
563
+ pageIndex,
564
+ 'instanceId:',
565
+ this._instanceId
566
+ )
567
+ return
568
+ }
569
+ this._lastPageChangedIndex = pageIndex
570
+
399
571
  const params = {
400
572
  pageIndex,
401
573
  data: this._tabItems[pageIndex] || null,
402
574
  }
403
- console.log('[TabsView] _dispatchPageChanged, pageIndex:', pageIndex, 'events:', this.events)
575
+ console.log(
576
+ '[TabsView] _dispatchPageChanged, pageIndex:',
577
+ pageIndex,
578
+ 'instanceId:',
579
+ this._instanceId,
580
+ 'events:',
581
+ this.events
582
+ )
404
583
  // es3-vue 将 @page-changed 转换为 pagechanged 存储在 events 中
405
584
  this.dispatchEvent('onPagechanged', params)
406
585
  }
407
586
 
408
587
  // Dispatch load page event
409
- _dispatchLoadPageData(pageIndex, useDiff) {
588
+ _dispatchLoadPageData(pageIndex, useDiff, force = false) {
589
+ // 防止重复加载同一页数据(除非强制刷新)
590
+ if (!force && this._loadedPageIndices.has(pageIndex)) {
591
+ console.log(
592
+ '[TabsView] _dispatchLoadPageData: skipping already loaded page',
593
+ pageIndex,
594
+ 'instanceId:',
595
+ this._instanceId
596
+ )
597
+ return
598
+ }
599
+
410
600
  const params = {
411
601
  pageIndex,
412
602
  itemCount: 0,
413
603
  data: this._tabItems[pageIndex] || null,
414
604
  useDiff: !!useDiff,
415
605
  }
416
- console.log('[TabsView] _dispatchLoadPageData, pageIndex:', pageIndex, 'events:', this.events)
606
+ console.log(
607
+ '[TabsView] _dispatchLoadPageData, instanceId:',
608
+ this._instanceId,
609
+ 'pageIndex:',
610
+ pageIndex,
611
+ 'force:',
612
+ force,
613
+ 'alreadyLoaded:',
614
+ this._loadedPageIndices.has(pageIndex),
615
+ 'loadedIndices:',
616
+ Array.from(this._loadedPageIndices)
617
+ )
618
+ // 标记该页已加载
619
+ this._loadedPageIndices.add(pageIndex)
417
620
  // es3-vue 将 @load-page 转换为 loadpagedata 存储在 events 中
418
621
  this.dispatchEvent('onLoadpagedata', params)
419
622
  }