@quicktvui/web-renderer 1.0.9 → 1.0.10
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 +2 -2
- package/src/components/QtFastListView.js +285 -2
- package/src/components/QtText.js +15 -24
- package/src/components/QtView.js +2 -9
- package/src/core/TVFocusManager.js +264 -31
- package/src/core/patches.js +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quicktvui/web-renderer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Web renderer for QuickTVUI - provides web browser rendering support",
|
|
5
5
|
"author": "QuickTVUI Team",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -21,4 +21,4 @@
|
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"vue": "^3.0.0"
|
|
23
23
|
}
|
|
24
|
-
}
|
|
24
|
+
}
|
|
@@ -306,6 +306,12 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
306
306
|
|
|
307
307
|
// Override beforeChildMount - called before a child is mounted
|
|
308
308
|
async beforeChildMount(child, childPosition) {
|
|
309
|
+
console.log(
|
|
310
|
+
'[QtFastListView] beforeChildMount called, child:',
|
|
311
|
+
child?.dom?.getAttribute?.('data-component-name'),
|
|
312
|
+
'type:',
|
|
313
|
+
child?.dom?.getAttribute?.('type')
|
|
314
|
+
)
|
|
309
315
|
if (child && child.dom) {
|
|
310
316
|
// Check if this child's parent is NOT this FastListView
|
|
311
317
|
if (child.pId && child.pId !== this.id) {
|
|
@@ -388,23 +394,39 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
388
394
|
|
|
389
395
|
// Set list data and render items
|
|
390
396
|
setListData(data) {
|
|
397
|
+
console.log(
|
|
398
|
+
'[QtFastListView] setListData called, data length:',
|
|
399
|
+
data?.length,
|
|
400
|
+
'this.dom:',
|
|
401
|
+
this.dom.getAttribute?.('data-component-name')
|
|
402
|
+
)
|
|
391
403
|
// Handle wrapped params - sometimes data comes as [dataArray]
|
|
392
404
|
if (Array.isArray(data) && data.length === 1 && Array.isArray(data[0])) {
|
|
393
405
|
data = data[0]
|
|
394
406
|
}
|
|
395
407
|
|
|
396
408
|
if (!Array.isArray(data)) {
|
|
409
|
+
console.log('[QtFastListView] setListData: data is not array, returning')
|
|
397
410
|
return
|
|
398
411
|
}
|
|
399
412
|
this._listData = data
|
|
400
413
|
|
|
401
414
|
// Wait for templates to be ready, then render
|
|
402
415
|
const tryRender = (attempts = 0) => {
|
|
416
|
+
console.log(
|
|
417
|
+
'[QtFastListView] tryRender, attempts:',
|
|
418
|
+
attempts,
|
|
419
|
+
'templateChildren:',
|
|
420
|
+
this._templateChildren.length,
|
|
421
|
+
'singletonTemplates:',
|
|
422
|
+
this._singletonTemplates.length
|
|
423
|
+
)
|
|
403
424
|
if (
|
|
404
425
|
this._templateChildren.length > 0 ||
|
|
405
426
|
this._singletonTemplates.length > 0 ||
|
|
406
427
|
attempts >= 10
|
|
407
428
|
) {
|
|
429
|
+
console.log('[QtFastListView] calling _renderItems')
|
|
408
430
|
this._renderItems()
|
|
409
431
|
} else {
|
|
410
432
|
setTimeout(() => tryRender(attempts + 1), 50)
|
|
@@ -475,6 +497,13 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
475
497
|
}
|
|
476
498
|
})
|
|
477
499
|
|
|
500
|
+
console.log(
|
|
501
|
+
'[QtFastListView] _renderItems complete, itemContainer children:',
|
|
502
|
+
this._itemContainer.children.length,
|
|
503
|
+
'dom:',
|
|
504
|
+
this.dom.getAttribute?.('data-component-name')
|
|
505
|
+
)
|
|
506
|
+
|
|
478
507
|
// Initialize showOnState visibility for dynamically created elements
|
|
479
508
|
requestAnimationFrame(() => {
|
|
480
509
|
const focusManager = global.__TV_FOCUS_MANAGER__
|
|
@@ -525,6 +554,8 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
525
554
|
const item = document.createElement('div')
|
|
526
555
|
item.setAttribute('data-index', index)
|
|
527
556
|
item.setAttribute('data-position', index)
|
|
557
|
+
// 添加 FastListView 实例引用,用于焦点导航
|
|
558
|
+
item.__fastListViewInstance = this
|
|
528
559
|
|
|
529
560
|
// Get the item's type
|
|
530
561
|
const itemType = itemData.type !== undefined ? itemData.type : null
|
|
@@ -865,9 +896,23 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
865
896
|
}
|
|
866
897
|
|
|
867
898
|
notifyItemFocusChanged(currentElement, hasFocus) {
|
|
899
|
+
console.log(
|
|
900
|
+
'[QtFastListView] notifyItemFocusChanged called, hasFocus:',
|
|
901
|
+
hasFocus,
|
|
902
|
+
'element:',
|
|
903
|
+
currentElement?.getAttribute?.('data-component-name'),
|
|
904
|
+
'this.dom:',
|
|
905
|
+
this.dom?.getAttribute?.('data-component-name')
|
|
906
|
+
)
|
|
868
907
|
if (!currentElement || !this._itemContainer) return
|
|
869
908
|
const items = this._itemContainer.children
|
|
870
909
|
const itemCount = items.length
|
|
910
|
+
console.log(
|
|
911
|
+
'[QtFastListView] itemCount:',
|
|
912
|
+
itemCount,
|
|
913
|
+
'itemContainer:',
|
|
914
|
+
this._itemContainer.className
|
|
915
|
+
)
|
|
871
916
|
if (itemCount === 0) return
|
|
872
917
|
|
|
873
918
|
let currentIndex = -1
|
|
@@ -879,6 +924,14 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
879
924
|
break
|
|
880
925
|
}
|
|
881
926
|
}
|
|
927
|
+
console.log(
|
|
928
|
+
'[QtFastListView] currentIndex:',
|
|
929
|
+
currentIndex,
|
|
930
|
+
'focusedIndex:',
|
|
931
|
+
this._focusedIndex,
|
|
932
|
+
'selectedPosition:',
|
|
933
|
+
this._selectedPosition
|
|
934
|
+
)
|
|
882
935
|
if (currentIndex === -1) return
|
|
883
936
|
|
|
884
937
|
const itemData = this._listData[currentIndex]
|
|
@@ -909,6 +962,13 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
909
962
|
if (hasFocus) {
|
|
910
963
|
if (this._focusedIndex === currentIndex) return
|
|
911
964
|
this._focusedIndex = currentIndex
|
|
965
|
+
|
|
966
|
+
// 自动设置 selected 状态(模拟原生端行为)
|
|
967
|
+
// 当获取焦点时,自动将当前 item 设为选中状态
|
|
968
|
+
if (this._selectedPosition !== currentIndex) {
|
|
969
|
+
console.log('[QtFastListView] calling setSelectChildPosition for index:', currentIndex)
|
|
970
|
+
this.setSelectChildPosition(currentIndex, false)
|
|
971
|
+
}
|
|
912
972
|
} else {
|
|
913
973
|
if (this._focusedIndex !== currentIndex) return
|
|
914
974
|
this._focusedIndex = -1
|
|
@@ -1005,10 +1065,28 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
1005
1065
|
}
|
|
1006
1066
|
|
|
1007
1067
|
// Request focus on item (called by tv-list requestFocus)
|
|
1008
|
-
requestChildFocus(position) {
|
|
1009
|
-
|
|
1068
|
+
requestChildFocus(position, retryCount = 0) {
|
|
1069
|
+
// 处理 Native.callUIFunction 传递的数组参数
|
|
1070
|
+
if (Array.isArray(position)) {
|
|
1071
|
+
position = position[0]
|
|
1072
|
+
}
|
|
1073
|
+
console.log(
|
|
1074
|
+
'[QtFastListView] requestChildFocus called with position:',
|
|
1075
|
+
position,
|
|
1076
|
+
'retry:',
|
|
1077
|
+
retryCount,
|
|
1078
|
+
'this.dom:',
|
|
1079
|
+
this.dom?.getAttribute?.('data-component-name')
|
|
1080
|
+
)
|
|
1010
1081
|
const items = this._itemContainer.children
|
|
1082
|
+
console.log('[QtFastListView] requestChildFocus: items.length:', items.length)
|
|
1011
1083
|
if (position < 0 || position >= items.length) {
|
|
1084
|
+
// 如果 items 还没有渲染完成,重试几次
|
|
1085
|
+
if (retryCount < 10 && items.length === 0) {
|
|
1086
|
+
console.log('[QtFastListView] requestChildFocus: items not ready, retrying...')
|
|
1087
|
+
setTimeout(() => this.requestChildFocus(position, retryCount + 1), 100)
|
|
1088
|
+
return
|
|
1089
|
+
}
|
|
1012
1090
|
console.warn(
|
|
1013
1091
|
'[QtFastListView] requestChildFocus: invalid position',
|
|
1014
1092
|
position,
|
|
@@ -1035,6 +1113,211 @@ export class QtFastListView extends QtBaseComponent {
|
|
|
1035
1113
|
console.log('[QtFastListView] requestChildFocus: focused item at position', position)
|
|
1036
1114
|
}
|
|
1037
1115
|
|
|
1116
|
+
// Set selected child position (called by Native.callUIFunction)
|
|
1117
|
+
setSelectChildPosition(position, changeFocusTarget = false) {
|
|
1118
|
+
console.log(
|
|
1119
|
+
'[QtFastListView] setSelectChildPosition called with position:',
|
|
1120
|
+
position,
|
|
1121
|
+
'this.dom:',
|
|
1122
|
+
this.dom?.getAttribute?.('data-component-name')
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
// Get TVFocusManager instance from global
|
|
1126
|
+
const focusManager = global.__TV_FOCUS_MANAGER__
|
|
1127
|
+
|
|
1128
|
+
// Helper to get select style from multiple sources (data attribute, CSS variable)
|
|
1129
|
+
const getSelectStyle = (element, attrName, cssVarName) => {
|
|
1130
|
+
return (
|
|
1131
|
+
element.getAttribute(attrName) ||
|
|
1132
|
+
element.getAttribute('data-' + attrName) ||
|
|
1133
|
+
window.getComputedStyle(element).getPropertyValue(cssVarName)?.trim()
|
|
1134
|
+
)
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Clear previous selected item
|
|
1138
|
+
if (this._selectedPosition !== undefined && this._selectedPosition >= 0) {
|
|
1139
|
+
console.log(
|
|
1140
|
+
'[QtFastListView] setSelectChildPosition: clearing previous selected position:',
|
|
1141
|
+
this._selectedPosition
|
|
1142
|
+
)
|
|
1143
|
+
const prevItems = this._itemContainer.querySelectorAll('[selected="true"], [selected=""]')
|
|
1144
|
+
console.log(
|
|
1145
|
+
'[QtFastListView] setSelectChildPosition: found',
|
|
1146
|
+
prevItems.length,
|
|
1147
|
+
'items with selected attribute'
|
|
1148
|
+
)
|
|
1149
|
+
prevItems.forEach((item) => {
|
|
1150
|
+
console.log(
|
|
1151
|
+
'[QtFastListView] setSelectChildPosition: removing selected from:',
|
|
1152
|
+
item.getAttribute?.('data-component-name'),
|
|
1153
|
+
'text:',
|
|
1154
|
+
item.getAttribute?.('text')
|
|
1155
|
+
)
|
|
1156
|
+
item.removeAttribute('selected')
|
|
1157
|
+
|
|
1158
|
+
// 检查是否有焦点
|
|
1159
|
+
const hasFocus =
|
|
1160
|
+
item.classList.contains('focused') ||
|
|
1161
|
+
(focusManager && focusManager.focusedElement === item)
|
|
1162
|
+
|
|
1163
|
+
// 如果没有焦点,恢复原始背景色
|
|
1164
|
+
if (!hasFocus && focusManager) {
|
|
1165
|
+
const originalStyles = focusManager.focusedElementOriginalStyles?.get(item)
|
|
1166
|
+
// 获取 select 背景色,用于判断保存的背景色是否是 select 颜色
|
|
1167
|
+
const selectBg = getSelectStyle(
|
|
1168
|
+
item,
|
|
1169
|
+
'select-background-color',
|
|
1170
|
+
'--select-background-color'
|
|
1171
|
+
)
|
|
1172
|
+
if (originalStyles && originalStyles.backgroundColor !== undefined) {
|
|
1173
|
+
// 如果保存的背景色是 select 颜色,则恢复为空字符串
|
|
1174
|
+
const savedBg = originalStyles.backgroundColor
|
|
1175
|
+
const isSelectColor =
|
|
1176
|
+
savedBg === selectBg ||
|
|
1177
|
+
savedBg === selectBg?.replace(/\s/g, '') ||
|
|
1178
|
+
savedBg === selectBg?.replace('rgba', 'rgb').replace(/,([^,]+)\)$/, '$1)')
|
|
1179
|
+
const bgToRestore = isSelectColor ? '' : savedBg
|
|
1180
|
+
console.log(
|
|
1181
|
+
'[QtFastListView] setSelectChildPosition: restoring background color to:',
|
|
1182
|
+
bgToRestore,
|
|
1183
|
+
'savedBg:',
|
|
1184
|
+
savedBg,
|
|
1185
|
+
'selectBg:',
|
|
1186
|
+
selectBg,
|
|
1187
|
+
'isSelectColor:',
|
|
1188
|
+
isSelectColor
|
|
1189
|
+
)
|
|
1190
|
+
item.style.backgroundColor = bgToRestore
|
|
1191
|
+
} else {
|
|
1192
|
+
// 如果没有保存原始样式,清除背景色
|
|
1193
|
+
console.log('[QtFastListView] setSelectChildPosition: clearing background color')
|
|
1194
|
+
item.style.backgroundColor = ''
|
|
1195
|
+
}
|
|
1196
|
+
// 触发 TVFocusManager 移除 selected 样式
|
|
1197
|
+
focusManager._removeDuplicateParentStateStyles(item)
|
|
1198
|
+
}
|
|
1199
|
+
})
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// Store new selected position
|
|
1203
|
+
this._selectedPosition = position
|
|
1204
|
+
|
|
1205
|
+
// Set new selected item
|
|
1206
|
+
if (position >= 0) {
|
|
1207
|
+
const items = this._itemContainer.children
|
|
1208
|
+
console.log(
|
|
1209
|
+
'[QtFastListView] setSelectChildPosition: items.length:',
|
|
1210
|
+
items.length,
|
|
1211
|
+
'position:',
|
|
1212
|
+
position
|
|
1213
|
+
)
|
|
1214
|
+
console.log('[QtFastListView] setSelectChildPosition: items[position]:', items[position])
|
|
1215
|
+
console.log('[QtFastListView] setSelectChildPosition: items[0]:', items[0])
|
|
1216
|
+
if (position < items.length) {
|
|
1217
|
+
const item = items[position]
|
|
1218
|
+
console.log(
|
|
1219
|
+
'[QtFastListView] setSelectChildPosition: item:',
|
|
1220
|
+
item,
|
|
1221
|
+
'tagName:',
|
|
1222
|
+
item?.tagName,
|
|
1223
|
+
'children:',
|
|
1224
|
+
item?.children?.length
|
|
1225
|
+
)
|
|
1226
|
+
if (item) {
|
|
1227
|
+
// Set selected attribute on the wrapper div
|
|
1228
|
+
item.setAttribute('selected', 'true')
|
|
1229
|
+
|
|
1230
|
+
// Set selected on all direct children (QtView, etc.)
|
|
1231
|
+
const directChildren = item.children
|
|
1232
|
+
for (let i = 0; i < directChildren.length; i++) {
|
|
1233
|
+
console.log(
|
|
1234
|
+
'[QtFastListView] setSelectChildPosition: setting selected on child:',
|
|
1235
|
+
i,
|
|
1236
|
+
directChildren[i].getAttribute?.('data-component-name')
|
|
1237
|
+
)
|
|
1238
|
+
directChildren[i].setAttribute('selected', 'true')
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Also set selected on all descendants with duplicateParentState
|
|
1242
|
+
const duplicateChildren = item.querySelectorAll('[duplicateparentstate]')
|
|
1243
|
+
duplicateChildren.forEach((child) => {
|
|
1244
|
+
child.setAttribute('selected', 'true')
|
|
1245
|
+
})
|
|
1246
|
+
|
|
1247
|
+
console.log('[QtFastListView] setSelectChildPosition: set selected on position', position)
|
|
1248
|
+
|
|
1249
|
+
// 应用 selected 样式(只对无焦点元素)
|
|
1250
|
+
if (focusManager) {
|
|
1251
|
+
// 检查当前元素是否有焦点
|
|
1252
|
+
const hasFocus =
|
|
1253
|
+
focusManager.focusedElement &&
|
|
1254
|
+
(item.contains(focusManager.focusedElement) || focusManager.focusedElement === item)
|
|
1255
|
+
|
|
1256
|
+
// 对 QtView 本身应用 select-background-color(只在没有焦点时)
|
|
1257
|
+
for (let i = 0; i < directChildren.length; i++) {
|
|
1258
|
+
const child = directChildren[i]
|
|
1259
|
+
const childHasFocus =
|
|
1260
|
+
child.classList.contains('focused') || focusManager.focusedElement === child
|
|
1261
|
+
|
|
1262
|
+
// 只有在没有焦点时才设置 select 样式
|
|
1263
|
+
if (!childHasFocus) {
|
|
1264
|
+
const selectBg = getSelectStyle(
|
|
1265
|
+
child,
|
|
1266
|
+
'select-background-color',
|
|
1267
|
+
'--select-background-color'
|
|
1268
|
+
)
|
|
1269
|
+
if (selectBg) {
|
|
1270
|
+
// 保存原始背景色(在应用 select 样式之前)
|
|
1271
|
+
if (!focusManager.focusedElementOriginalStyles) {
|
|
1272
|
+
focusManager.focusedElementOriginalStyles = new Map()
|
|
1273
|
+
}
|
|
1274
|
+
if (!focusManager.focusedElementOriginalStyles.has(child)) {
|
|
1275
|
+
const currentBg = child.style.backgroundColor
|
|
1276
|
+
// 如果当前背景色已经是 select 颜色,说明之前已经被错误地设置了
|
|
1277
|
+
// 此时应该保存空字符串作为原始背景色
|
|
1278
|
+
const isSelectColor =
|
|
1279
|
+
currentBg === selectBg ||
|
|
1280
|
+
currentBg === selectBg.replace(/\s/g, '') ||
|
|
1281
|
+
currentBg === selectBg.replace('rgba', 'rgb').replace(/,([^,]+)\)$/, '$1)')
|
|
1282
|
+
const bgToSave = isSelectColor ? '' : currentBg
|
|
1283
|
+
console.log(
|
|
1284
|
+
'[QtFastListView] setSelectChildPosition: saving original background color:',
|
|
1285
|
+
bgToSave,
|
|
1286
|
+
'currentBg:',
|
|
1287
|
+
currentBg,
|
|
1288
|
+
'selectBg:',
|
|
1289
|
+
selectBg,
|
|
1290
|
+
'isSelectColor:',
|
|
1291
|
+
isSelectColor
|
|
1292
|
+
)
|
|
1293
|
+
focusManager.focusedElementOriginalStyles.set(child, {
|
|
1294
|
+
backgroundColor: bgToSave,
|
|
1295
|
+
})
|
|
1296
|
+
}
|
|
1297
|
+
console.log(
|
|
1298
|
+
'[QtFastListView] setSelectChildPosition: setting select background color:',
|
|
1299
|
+
selectBg,
|
|
1300
|
+
'for child:',
|
|
1301
|
+
child.getAttribute?.('data-component-name')
|
|
1302
|
+
)
|
|
1303
|
+
child.style.backgroundColor = selectBg
|
|
1304
|
+
}
|
|
1305
|
+
// 对 QtView 的子元素(如 QtText)应用 duplicateParentState 样式
|
|
1306
|
+
// 现在可以安全调用,因为 _applyDuplicateParentStateStyles 只在 hasFocus=true 时保存原始样式
|
|
1307
|
+
focusManager._applyDuplicateParentStateStyles(child, false)
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// Optionally request focus on the selected item
|
|
1316
|
+
if (changeFocusTarget) {
|
|
1317
|
+
this.requestChildFocus(position)
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1038
1321
|
// Set span count for grid layout
|
|
1039
1322
|
setSpanCount(count) {
|
|
1040
1323
|
if (count > 0) {
|
package/src/components/QtText.js
CHANGED
|
@@ -104,30 +104,14 @@ export class QtText extends QtBaseComponent {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// Handle duplicateParentState and focus style attributes
|
|
107
|
-
const tvFocusAttrs = ['duplicateParentState', 'focusable', 'showOnState']
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
'focusTextColor',
|
|
113
|
-
'focusBorderColor',
|
|
114
|
-
'focusBorderWidth',
|
|
115
|
-
'focusBorderRadius',
|
|
116
|
-
'focusOpacity',
|
|
117
|
-
'focusScale',
|
|
118
|
-
]
|
|
107
|
+
const tvFocusAttrs = ['duplicateParentState', 'focusable', 'showOnState', 'select']
|
|
108
|
+
|
|
109
|
+
// QtText 支持的颜色属性:textColor, focusColor, selectColor
|
|
110
|
+
// 注意:QtText 没有 selectBackgroundColor,只有 selectColor
|
|
111
|
+
const textStyleAttrs = ['textColor', 'focusColor', 'selectColor']
|
|
119
112
|
|
|
120
113
|
// Also handle kebab-case versions
|
|
121
|
-
const
|
|
122
|
-
'focus-color',
|
|
123
|
-
'focus-background-color',
|
|
124
|
-
'focus-text-color',
|
|
125
|
-
'focus-border-color',
|
|
126
|
-
'focus-border-width',
|
|
127
|
-
'focus-border-radius',
|
|
128
|
-
'focus-opacity',
|
|
129
|
-
'focus-scale',
|
|
130
|
-
]
|
|
114
|
+
const kebabTextStyleAttrs = ['text-color', 'focus-color', 'select-color']
|
|
131
115
|
|
|
132
116
|
if (tvFocusAttrs.includes(key)) {
|
|
133
117
|
if (value !== null && value !== undefined) {
|
|
@@ -145,7 +129,14 @@ export class QtText extends QtBaseComponent {
|
|
|
145
129
|
return
|
|
146
130
|
}
|
|
147
131
|
|
|
148
|
-
|
|
132
|
+
// Handle textColor directly - apply to DOM
|
|
133
|
+
if (key === 'textColor' || key === 'text-color') {
|
|
134
|
+
this.setTextColor(value)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Handle focusColor and selectColor - store as data attributes for TVFocusManager
|
|
139
|
+
if (textStyleAttrs.includes(key)) {
|
|
149
140
|
if (value !== null && value !== undefined) {
|
|
150
141
|
const dataKey = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
151
142
|
this.dom.setAttribute(dataKey, String(value))
|
|
@@ -156,7 +147,7 @@ export class QtText extends QtBaseComponent {
|
|
|
156
147
|
return
|
|
157
148
|
}
|
|
158
149
|
|
|
159
|
-
if (
|
|
150
|
+
if (kebabTextStyleAttrs.includes(key)) {
|
|
160
151
|
if (value !== null && value !== undefined) {
|
|
161
152
|
this.dom.setAttribute('data-' + key, String(value))
|
|
162
153
|
} else {
|
package/src/components/QtView.js
CHANGED
|
@@ -139,19 +139,16 @@ export class QtView extends QtBaseComponent {
|
|
|
139
139
|
'duplicateParentState',
|
|
140
140
|
'blockFocusDirections',
|
|
141
141
|
'blockfocusdirections',
|
|
142
|
+
'selected',
|
|
142
143
|
]
|
|
143
144
|
|
|
144
145
|
// Focus style attributes (for duplicateParentState)
|
|
145
|
-
//
|
|
146
|
+
// QtView 支持:focus-background-color, focus-border-color, focus-border-width, focus-border-radius, focusScale
|
|
146
147
|
const focusStyleAttrs = [
|
|
147
|
-
'focusColor',
|
|
148
148
|
'focusBackgroundColor',
|
|
149
|
-
'focusBackground',
|
|
150
|
-
'focusTextColor',
|
|
151
149
|
'focusBorderColor',
|
|
152
150
|
'focusBorderWidth',
|
|
153
151
|
'focusBorderRadius',
|
|
154
|
-
'focusOpacity',
|
|
155
152
|
'focusScale',
|
|
156
153
|
]
|
|
157
154
|
|
|
@@ -172,14 +169,10 @@ export class QtView extends QtBaseComponent {
|
|
|
172
169
|
|
|
173
170
|
// Also handle kebab-case versions (from CSS or direct assignment)
|
|
174
171
|
const kebabFocusStyleAttrs = [
|
|
175
|
-
'focus-color',
|
|
176
172
|
'focus-background-color',
|
|
177
|
-
'focus-background',
|
|
178
|
-
'focus-text-color',
|
|
179
173
|
'focus-border-color',
|
|
180
174
|
'focus-border-width',
|
|
181
175
|
'focus-border-radius',
|
|
182
|
-
'focus-opacity',
|
|
183
176
|
'focus-scale',
|
|
184
177
|
]
|
|
185
178
|
|
|
@@ -1503,6 +1503,16 @@ export class TVFocusManager {
|
|
|
1503
1503
|
props.focusColor = focusColor
|
|
1504
1504
|
}
|
|
1505
1505
|
|
|
1506
|
+
// Select text color - for QtText when select="true"
|
|
1507
|
+
const selectColor =
|
|
1508
|
+
element.getAttribute('selectColor') ||
|
|
1509
|
+
element.getAttribute('select-color') ||
|
|
1510
|
+
element.getAttribute('data-select-color') ||
|
|
1511
|
+
computedStyle.getPropertyValue('--select-color')?.trim()
|
|
1512
|
+
if (selectColor) {
|
|
1513
|
+
props.selectColor = selectColor
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1506
1516
|
return props
|
|
1507
1517
|
}
|
|
1508
1518
|
|
|
@@ -1723,17 +1733,90 @@ export class TVFocusManager {
|
|
|
1723
1733
|
|
|
1724
1734
|
_maybeNotifyFastListItemFocusChanged(element, hasFocus) {
|
|
1725
1735
|
if (!element) return
|
|
1736
|
+
console.log(
|
|
1737
|
+
'[TVFocusManager] _maybeNotifyFastListItemFocusChanged, element:',
|
|
1738
|
+
element.getAttribute?.('data-component-name'),
|
|
1739
|
+
'hasFocus:',
|
|
1740
|
+
hasFocus,
|
|
1741
|
+
'data-position:',
|
|
1742
|
+
element.getAttribute?.('data-position'),
|
|
1743
|
+
'data-index:',
|
|
1744
|
+
element.getAttribute?.('data-index')
|
|
1745
|
+
)
|
|
1746
|
+
console.log(
|
|
1747
|
+
'[TVFocusManager] element.__fastListViewInstance:',
|
|
1748
|
+
!!element.__fastListViewInstance,
|
|
1749
|
+
'parent:',
|
|
1750
|
+
element.parentElement?.getAttribute?.('data-component-name')
|
|
1751
|
+
)
|
|
1752
|
+
|
|
1753
|
+
// 首先检查元素自身是否有 __fastListViewInstance(新创建的 item wrapper 会有)
|
|
1754
|
+
if (element.__fastListViewInstance) {
|
|
1755
|
+
const instance = element.__fastListViewInstance
|
|
1756
|
+
console.log(
|
|
1757
|
+
'[TVFocusManager] Found __fastListViewInstance on element itself, itemContainer children:',
|
|
1758
|
+
instance._itemContainer?.children?.length
|
|
1759
|
+
)
|
|
1760
|
+
if (instance && typeof instance.notifyItemFocusChanged === 'function') {
|
|
1761
|
+
instance.notifyItemFocusChanged(element, hasFocus)
|
|
1762
|
+
return
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// 检查父元素是否有 __fastListViewInstance(item wrapper 的子元素)
|
|
1726
1767
|
let parent = element.parentElement
|
|
1768
|
+
if (parent && parent.__fastListViewInstance) {
|
|
1769
|
+
const instance = parent.__fastListViewInstance
|
|
1770
|
+
console.log(
|
|
1771
|
+
'[TVFocusManager] Found __fastListViewInstance on direct parent, itemContainer children:',
|
|
1772
|
+
instance._itemContainer?.children?.length
|
|
1773
|
+
)
|
|
1774
|
+
if (instance && typeof instance.notifyItemFocusChanged === 'function') {
|
|
1775
|
+
instance.notifyItemFocusChanged(parent, hasFocus)
|
|
1776
|
+
return
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1727
1780
|
let fastListViewInstance = null
|
|
1781
|
+
// 向上查找最近的 __fastListViewInstance,但要确保元素确实在该 FastListView 的 _itemContainer 内部
|
|
1728
1782
|
while (parent) {
|
|
1729
1783
|
if (parent.__fastListViewInstance) {
|
|
1730
1784
|
fastListViewInstance = parent.__fastListViewInstance
|
|
1731
|
-
|
|
1785
|
+
console.log(
|
|
1786
|
+
'[TVFocusManager] Found __fastListViewInstance on parent:',
|
|
1787
|
+
parent.getAttribute?.('data-component-name'),
|
|
1788
|
+
'itemContainer children:',
|
|
1789
|
+
fastListViewInstance._itemContainer?.children?.length
|
|
1790
|
+
)
|
|
1791
|
+
// 检查 element 是否在这个 FastListView 的 _itemContainer 内部
|
|
1792
|
+
const itemContainer = fastListViewInstance._itemContainer
|
|
1793
|
+
if (itemContainer && (itemContainer === element || itemContainer.contains(element))) {
|
|
1794
|
+
console.log('[TVFocusManager] element is in itemContainer, using this FastListView')
|
|
1795
|
+
break // 找到正确的实例
|
|
1796
|
+
}
|
|
1797
|
+
// 额外检查:如果 element 有 data-position 或 data-index,检查它是否在 itemContainer 的直接子元素中
|
|
1798
|
+
const pos = element.getAttribute?.('data-position') ?? element.getAttribute?.('data-index')
|
|
1799
|
+
if (pos !== null && pos !== undefined) {
|
|
1800
|
+
const posNum = Number(pos)
|
|
1801
|
+
const item = itemContainer?.children?.[posNum]
|
|
1802
|
+
if (item && (item === element || item.contains(element))) {
|
|
1803
|
+
console.log(
|
|
1804
|
+
'[TVFocusManager] element matched by data-position, using this FastListView'
|
|
1805
|
+
)
|
|
1806
|
+
break
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
console.log('[TVFocusManager] element NOT in itemContainer, continuing search...')
|
|
1810
|
+
// 如果不在,继续向上查找
|
|
1811
|
+
fastListViewInstance = null
|
|
1732
1812
|
}
|
|
1733
1813
|
parent = parent.parentElement
|
|
1734
1814
|
}
|
|
1735
1815
|
if (fastListViewInstance && typeof fastListViewInstance.notifyItemFocusChanged === 'function') {
|
|
1816
|
+
console.log('[TVFocusManager] Calling notifyItemFocusChanged')
|
|
1736
1817
|
fastListViewInstance.notifyItemFocusChanged(element, hasFocus)
|
|
1818
|
+
} else {
|
|
1819
|
+
console.log('[TVFocusManager] No FastListView found for element')
|
|
1737
1820
|
}
|
|
1738
1821
|
}
|
|
1739
1822
|
|
|
@@ -1842,12 +1925,14 @@ export class TVFocusManager {
|
|
|
1842
1925
|
}
|
|
1843
1926
|
|
|
1844
1927
|
// Apply focusColor if specified (for elements that have text)
|
|
1928
|
+
// 焦点优先:获得焦点时总是使用 focus 样式,不管是否有 selected 状态
|
|
1845
1929
|
if (props.focusColor) {
|
|
1846
1930
|
element.style.color = props.focusColor
|
|
1847
1931
|
}
|
|
1848
1932
|
|
|
1849
1933
|
// Handle duplicateParentState children - apply focus styles to children with duplicateParentState="true"
|
|
1850
|
-
|
|
1934
|
+
// 传递 hasFocus=true 表示当前元素有焦点
|
|
1935
|
+
this._applyDuplicateParentStateStyles(element, true)
|
|
1851
1936
|
|
|
1852
1937
|
// Handle showOnState - update visibility based on focused state
|
|
1853
1938
|
this._updateShowOnStateVisibility(element, 'focused')
|
|
@@ -1857,10 +1942,21 @@ export class TVFocusManager {
|
|
|
1857
1942
|
|
|
1858
1943
|
// Apply focus styles to children with duplicateParentState attribute
|
|
1859
1944
|
// Supports: duplicateParentState, duplicateParentState="true", duplicateParentState=""
|
|
1860
|
-
|
|
1945
|
+
// hasFocus: 是否有焦点(焦点优先于选中状态)
|
|
1946
|
+
_applyDuplicateParentStateStyles(parentElement, hasFocus = false) {
|
|
1861
1947
|
// Match elements with duplicateParentState attribute present (regardless of value)
|
|
1862
1948
|
// This handles both <div duplicateParentState> and <div duplicateParentState="true">
|
|
1863
1949
|
const duplicateChildren = parentElement.querySelectorAll('[duplicateparentstate]')
|
|
1950
|
+
console.log(
|
|
1951
|
+
'[TVFocusManager] _applyDuplicateParentStateStyles: parentElement:',
|
|
1952
|
+
parentElement.getAttribute?.('data-component-name'),
|
|
1953
|
+
'text:',
|
|
1954
|
+
parentElement.getAttribute?.('text'),
|
|
1955
|
+
'hasFocus:',
|
|
1956
|
+
hasFocus,
|
|
1957
|
+
'children count:',
|
|
1958
|
+
duplicateChildren.length
|
|
1959
|
+
)
|
|
1864
1960
|
|
|
1865
1961
|
duplicateChildren.forEach((child) => {
|
|
1866
1962
|
// Skip if explicitly set to false
|
|
@@ -1869,6 +1965,13 @@ export class TVFocusManager {
|
|
|
1869
1965
|
return
|
|
1870
1966
|
}
|
|
1871
1967
|
|
|
1968
|
+
console.log(
|
|
1969
|
+
'[TVFocusManager] _applyDuplicateParentStateStyles: processing child:',
|
|
1970
|
+
child.getAttribute?.('data-component-name'),
|
|
1971
|
+
'text:',
|
|
1972
|
+
child.getAttribute?.('text')
|
|
1973
|
+
)
|
|
1974
|
+
|
|
1872
1975
|
// Get focus styles from multiple sources:
|
|
1873
1976
|
// 1. Child's own focus attributes
|
|
1874
1977
|
// 2. Data attributes (from style extraction)
|
|
@@ -1896,35 +1999,105 @@ export class TVFocusManager {
|
|
|
1896
1999
|
const focusBorderWidth = getFocusAttr('focusBorderWidth', 'focus-border-width')
|
|
1897
2000
|
const focusBorderColor = getFocusAttr('focusBorderColor', 'focus-border-color')
|
|
1898
2001
|
|
|
2002
|
+
// Get select style attributes (for selected state)
|
|
2003
|
+
const selectColor = getFocusAttr('selectColor', 'select-color')
|
|
2004
|
+
const selectBackgroundColor = getFocusAttr('selectBackgroundColor', 'select-background-color')
|
|
2005
|
+
|
|
2006
|
+
// Check if parentElement itself is in selected state
|
|
2007
|
+
// duplicateParentState 的意思是复制父元素的状态,所以只检查直接父元素
|
|
2008
|
+
// 不要向上查找祖先,否则会错误地找到其他列表项的 selected 状态
|
|
2009
|
+
const isParentSelect =
|
|
2010
|
+
(parentElement.hasAttribute('select') &&
|
|
2011
|
+
parentElement.getAttribute('select') !== 'false') ||
|
|
2012
|
+
(parentElement.hasAttribute('selected') &&
|
|
2013
|
+
parentElement.getAttribute('selected') !== 'false')
|
|
2014
|
+
|
|
2015
|
+
if (isParentSelect) {
|
|
2016
|
+
console.log(
|
|
2017
|
+
'[TVFocusManager] _applyDuplicateParentStateStyles: parentElement is selected:',
|
|
2018
|
+
parentElement.getAttribute?.('data-component-name'),
|
|
2019
|
+
'text:',
|
|
2020
|
+
parentElement.getAttribute?.('text')
|
|
2021
|
+
)
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// Determine which colors to use: focus takes priority over select
|
|
2025
|
+
// 焦点优先:有焦点时用 focus 样式,无焦点但有选中时用 select 样式
|
|
2026
|
+
const effectiveColor = hasFocus
|
|
2027
|
+
? focusColor
|
|
2028
|
+
: isParentSelect && selectColor
|
|
2029
|
+
? selectColor
|
|
2030
|
+
: null
|
|
2031
|
+
const effectiveBackgroundColor = hasFocus
|
|
2032
|
+
? focusBackgroundColor
|
|
2033
|
+
: isParentSelect && selectBackgroundColor
|
|
2034
|
+
? selectBackgroundColor
|
|
2035
|
+
: null
|
|
2036
|
+
|
|
1899
2037
|
// Check if enableFocusBorder is set
|
|
1900
2038
|
const enableFocusBorder =
|
|
1901
2039
|
child.getAttribute('enableFocusBorder') || child.getAttribute('enable-focus-border')
|
|
1902
2040
|
const shouldShowBorder =
|
|
1903
2041
|
enableFocusBorder === 'true' || enableFocusBorder === '1' || enableFocusBorder === ''
|
|
1904
2042
|
|
|
1905
|
-
// Store original styles
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2043
|
+
// Store original styles - 只在有焦点时保存,避免覆盖 setSelectChildPosition 中保存的原始样式
|
|
2044
|
+
// 当 hasFocus=false 时,我们只是在应用 select 样式,不需要保存原始样式
|
|
2045
|
+
if (hasFocus && !this.focusedElementOriginalStyles.has(child)) {
|
|
2046
|
+
// 如果内联样式为空,从 computedStyle 获取计算后的样式值
|
|
2047
|
+
const computedStyle = window.getComputedStyle(child)
|
|
2048
|
+
const originalStyles = {
|
|
2049
|
+
backgroundColor: child.style.backgroundColor || computedStyle.backgroundColor,
|
|
2050
|
+
color: child.style.color || computedStyle.color,
|
|
2051
|
+
borderRadius: child.style.borderRadius,
|
|
2052
|
+
transform: child.style.transform,
|
|
2053
|
+
zIndex: child.style.zIndex,
|
|
2054
|
+
transition: child.style.transition,
|
|
2055
|
+
outline: child.style.outline,
|
|
2056
|
+
outlineOffset: child.style.outlineOffset,
|
|
2057
|
+
}
|
|
2058
|
+
this.focusedElementOriginalStyles.set(child, originalStyles)
|
|
2059
|
+
console.log(
|
|
2060
|
+
'[TVFocusManager] _applyDuplicateParentStateStyles: saving original styles for focus state - color:',
|
|
2061
|
+
originalStyles.color,
|
|
2062
|
+
'bg:',
|
|
2063
|
+
originalStyles.backgroundColor
|
|
2064
|
+
)
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// 当 hasFocus=false 且要应用 select 样式时,也需要保存原始样式
|
|
2068
|
+
// 这样在清除 select 状态时才能正确恢复
|
|
2069
|
+
if (!hasFocus && isParentSelect && !this.focusedElementOriginalStyles.has(child)) {
|
|
2070
|
+
// 如果内联样式为空,从 computedStyle 获取计算后的样式值
|
|
2071
|
+
const computedStyle = window.getComputedStyle(child)
|
|
2072
|
+
const originalStyles = {
|
|
2073
|
+
backgroundColor: child.style.backgroundColor || computedStyle.backgroundColor,
|
|
2074
|
+
color: child.style.color || computedStyle.color,
|
|
2075
|
+
}
|
|
2076
|
+
this.focusedElementOriginalStyles.set(child, originalStyles)
|
|
2077
|
+
console.log(
|
|
2078
|
+
'[TVFocusManager] _applyDuplicateParentStateStyles: saving original styles for select state - color:',
|
|
2079
|
+
originalStyles.color,
|
|
2080
|
+
'bg:',
|
|
2081
|
+
originalStyles.backgroundColor
|
|
2082
|
+
)
|
|
1915
2083
|
}
|
|
1916
|
-
this.focusedElementOriginalStyles.set(child, originalStyles)
|
|
1917
2084
|
|
|
1918
2085
|
// Apply transition
|
|
1919
2086
|
child.style.transition = this.defaultFocusStyle.transition
|
|
1920
2087
|
|
|
1921
|
-
// Apply focus styles
|
|
1922
|
-
if (
|
|
1923
|
-
child.style.backgroundColor =
|
|
2088
|
+
// Apply focus/select styles
|
|
2089
|
+
if (effectiveBackgroundColor) {
|
|
2090
|
+
child.style.backgroundColor = effectiveBackgroundColor
|
|
1924
2091
|
}
|
|
1925
2092
|
|
|
1926
|
-
if (
|
|
1927
|
-
child.style.color =
|
|
2093
|
+
if (effectiveColor) {
|
|
2094
|
+
child.style.color = effectiveColor
|
|
2095
|
+
} else if (hasFocus) {
|
|
2096
|
+
// 当有焦点但没有 focusColor 时,恢复原始颜色
|
|
2097
|
+
const savedOriginalStyles = this.focusedElementOriginalStyles.get(child)
|
|
2098
|
+
if (savedOriginalStyles && savedOriginalStyles.color !== undefined) {
|
|
2099
|
+
child.style.color = savedOriginalStyles.color
|
|
2100
|
+
}
|
|
1928
2101
|
}
|
|
1929
2102
|
|
|
1930
2103
|
if (focusBorderRadius) {
|
|
@@ -1942,8 +2115,9 @@ export class TVFocusManager {
|
|
|
1942
2115
|
}
|
|
1943
2116
|
}
|
|
1944
2117
|
|
|
1945
|
-
// Apply border if enableFocusBorder is set
|
|
1946
|
-
|
|
2118
|
+
// Apply border if enableFocusBorder is set AND has focus
|
|
2119
|
+
// 只有在获得焦点时才显示 focus border,selected 状态不显示边框
|
|
2120
|
+
if (shouldShowBorder && hasFocus) {
|
|
1947
2121
|
const borderWidth = focusBorderWidth ? parseFloat(focusBorderWidth) : 3
|
|
1948
2122
|
const borderColor = focusBorderColor || '#FFFFFF'
|
|
1949
2123
|
const borderRadius = focusBorderRadius ? parseFloat(focusBorderRadius) : 8
|
|
@@ -2006,6 +2180,21 @@ export class TVFocusManager {
|
|
|
2006
2180
|
// Remove focus styles from duplicateParentState children
|
|
2007
2181
|
this._removeDuplicateParentStateStyles(element)
|
|
2008
2182
|
|
|
2183
|
+
// Check if element is in selected state, apply select styles
|
|
2184
|
+
const isSelected =
|
|
2185
|
+
element.hasAttribute('selected') && element.getAttribute('selected') !== 'false'
|
|
2186
|
+
if (isSelected) {
|
|
2187
|
+
const computedStyle = window.getComputedStyle(element)
|
|
2188
|
+
const selectBackgroundColor =
|
|
2189
|
+
element.getAttribute('selectBackgroundColor') ||
|
|
2190
|
+
element.getAttribute('select-background-color') ||
|
|
2191
|
+
element.getAttribute('data-select-background-color') ||
|
|
2192
|
+
computedStyle.getPropertyValue('--select-background-color')?.trim()
|
|
2193
|
+
if (selectBackgroundColor) {
|
|
2194
|
+
element.style.backgroundColor = selectBackgroundColor
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2009
2198
|
// Handle showOnState - update visibility based on normal state
|
|
2010
2199
|
this._updateShowOnStateVisibility(element, 'normal')
|
|
2011
2200
|
|
|
@@ -2023,6 +2212,14 @@ export class TVFocusManager {
|
|
|
2023
2212
|
return
|
|
2024
2213
|
}
|
|
2025
2214
|
const originalStyles = this.focusedElementOriginalStyles.get(child)
|
|
2215
|
+
console.log(
|
|
2216
|
+
'[TVFocusManager] _removeDuplicateParentStateStyles: child:',
|
|
2217
|
+
child.getAttribute?.('data-component-name'),
|
|
2218
|
+
'text:',
|
|
2219
|
+
child.getAttribute?.('text'),
|
|
2220
|
+
'originalStyles:',
|
|
2221
|
+
originalStyles
|
|
2222
|
+
)
|
|
2026
2223
|
if (originalStyles) {
|
|
2027
2224
|
child.style.backgroundColor = originalStyles.backgroundColor
|
|
2028
2225
|
child.style.color = originalStyles.color
|
|
@@ -2032,15 +2229,8 @@ export class TVFocusManager {
|
|
|
2032
2229
|
child.style.transition = originalStyles.transition
|
|
2033
2230
|
child.style.outline = originalStyles.outline
|
|
2034
2231
|
child.style.outlineOffset = originalStyles.outlineOffset
|
|
2035
|
-
} else {
|
|
2036
|
-
child.style.backgroundColor = ''
|
|
2037
|
-
child.style.color = ''
|
|
2038
|
-
child.style.borderRadius = ''
|
|
2039
|
-
child.style.transform = ''
|
|
2040
|
-
child.style.zIndex = ''
|
|
2041
|
-
child.style.outline = ''
|
|
2042
|
-
child.style.outlineOffset = ''
|
|
2043
2232
|
}
|
|
2233
|
+
// 如果没有 originalStyles,不要清除样式,保持当前状态
|
|
2044
2234
|
child.classList.remove('focused-child')
|
|
2045
2235
|
|
|
2046
2236
|
// Handle showOnState for duplicateParentState children
|
|
@@ -2054,7 +2244,38 @@ export class TVFocusManager {
|
|
|
2054
2244
|
}
|
|
2055
2245
|
}
|
|
2056
2246
|
|
|
2057
|
-
|
|
2247
|
+
// 不要在这里删除 originalStyles,因为可能还需要用于后续恢复
|
|
2248
|
+
// this.focusedElementOriginalStyles.delete(child)
|
|
2249
|
+
|
|
2250
|
+
// Check if parentElement itself is in selected state
|
|
2251
|
+
// 只检查直接父元素,不向上查找祖先
|
|
2252
|
+
const isParentSelect =
|
|
2253
|
+
(parentElement.hasAttribute('select') &&
|
|
2254
|
+
parentElement.getAttribute('select') !== 'false') ||
|
|
2255
|
+
(parentElement.hasAttribute('selected') &&
|
|
2256
|
+
parentElement.getAttribute('selected') !== 'false')
|
|
2257
|
+
|
|
2258
|
+
// If parent is selected, apply select styles
|
|
2259
|
+
if (isParentSelect) {
|
|
2260
|
+
const computedStyle = window.getComputedStyle(child)
|
|
2261
|
+
const selectColor =
|
|
2262
|
+
child.getAttribute('selectColor') ||
|
|
2263
|
+
child.getAttribute('select-color') ||
|
|
2264
|
+
child.getAttribute('data-select-color') ||
|
|
2265
|
+
computedStyle.getPropertyValue('--select-color')?.trim()
|
|
2266
|
+
const selectBackgroundColor =
|
|
2267
|
+
child.getAttribute('selectBackgroundColor') ||
|
|
2268
|
+
child.getAttribute('select-background-color') ||
|
|
2269
|
+
child.getAttribute('data-select-background-color') ||
|
|
2270
|
+
computedStyle.getPropertyValue('--select-background-color')?.trim()
|
|
2271
|
+
|
|
2272
|
+
if (selectBackgroundColor) {
|
|
2273
|
+
child.style.backgroundColor = selectBackgroundColor
|
|
2274
|
+
}
|
|
2275
|
+
if (selectColor) {
|
|
2276
|
+
child.style.color = selectColor
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2058
2279
|
})
|
|
2059
2280
|
}
|
|
2060
2281
|
|
|
@@ -2106,10 +2327,22 @@ export class TVFocusManager {
|
|
|
2106
2327
|
let target = e.target
|
|
2107
2328
|
const componentRegistry = window.__HIPPY_COMPONENT_REGISTRY__
|
|
2108
2329
|
|
|
2330
|
+
console.log(
|
|
2331
|
+
'[TVFocusManager] handleClick called, target:',
|
|
2332
|
+
target?.getAttribute?.('data-component-name'),
|
|
2333
|
+
'text:',
|
|
2334
|
+
target?.getAttribute?.('text')
|
|
2335
|
+
)
|
|
2336
|
+
|
|
2109
2337
|
while (target && target !== document.body) {
|
|
2110
2338
|
// Check if this element is focusable
|
|
2111
2339
|
if (target.hasAttribute && target.hasAttribute('focusable')) {
|
|
2112
|
-
|
|
2340
|
+
console.log(
|
|
2341
|
+
'[TVFocusManager] handleClick: found focusable element:',
|
|
2342
|
+
target?.getAttribute?.('data-component-name'),
|
|
2343
|
+
'text:',
|
|
2344
|
+
target?.getAttribute?.('text')
|
|
2345
|
+
)
|
|
2113
2346
|
|
|
2114
2347
|
// Update focus to this element
|
|
2115
2348
|
if (this.focusableElements.includes(target)) {
|
package/src/core/patches.js
CHANGED
|
@@ -648,6 +648,8 @@ const FOCUS_STYLE_PROPS = [
|
|
|
648
648
|
'focusBorderRadius',
|
|
649
649
|
'focusOpacity',
|
|
650
650
|
'focusScale',
|
|
651
|
+
'selectColor',
|
|
652
|
+
'selectBackgroundColor',
|
|
651
653
|
]
|
|
652
654
|
|
|
653
655
|
// Properties that are numeric values, not colors (should not be converted via argbToCssColor)
|
|
@@ -828,6 +830,8 @@ function patchFocusCssVars() {
|
|
|
828
830
|
'focus-border-radius',
|
|
829
831
|
'focus-opacity',
|
|
830
832
|
'focus-scale',
|
|
833
|
+
'select-color',
|
|
834
|
+
'select-background-color',
|
|
831
835
|
]
|
|
832
836
|
|
|
833
837
|
const patchRuleStyle = (style) => {
|