@quicktvui/web-renderer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +24 -0
- package/src/adapters/es3-video-player.js +828 -0
- package/src/components/Modal.js +119 -0
- package/src/components/QtAnimationView.js +678 -0
- package/src/components/QtBaseComponent.js +165 -0
- package/src/components/QtFastListView.js +1920 -0
- package/src/components/QtFlexView.js +799 -0
- package/src/components/QtImage.js +203 -0
- package/src/components/QtItemFrame.js +239 -0
- package/src/components/QtItemStoreView.js +93 -0
- package/src/components/QtItemView.js +125 -0
- package/src/components/QtListView.js +331 -0
- package/src/components/QtLoadingView.js +55 -0
- package/src/components/QtPageRootView.js +19 -0
- package/src/components/QtPlayMark.js +168 -0
- package/src/components/QtProgressBar.js +199 -0
- package/src/components/QtQRCode.js +78 -0
- package/src/components/QtReplaceChild.js +149 -0
- package/src/components/QtRippleView.js +166 -0
- package/src/components/QtSeekBar.js +409 -0
- package/src/components/QtText.js +679 -0
- package/src/components/QtTransitionImage.js +170 -0
- package/src/components/QtView.js +706 -0
- package/src/components/QtWebView.js +613 -0
- package/src/components/TabsView.js +420 -0
- package/src/components/ViewPager.js +206 -0
- package/src/components/index.js +24 -0
- package/src/components/plugins/TextV2Component.js +70 -0
- package/src/components/plugins/index.js +7 -0
- package/src/core/SceneBuilder.js +58 -0
- package/src/core/TVFocusManager.js +2014 -0
- package/src/core/asyncLocalStorage.js +175 -0
- package/src/core/autoProxy.js +165 -0
- package/src/core/componentRegistry.js +84 -0
- package/src/core/constants.js +6 -0
- package/src/core/index.js +8 -0
- package/src/core/moduleUtils.js +36 -0
- package/src/core/patches.js +958 -0
- package/src/core/templateBinding.js +666 -0
- package/src/index.js +246 -0
- package/src/modules/AndroidDevelopModule.js +101 -0
- package/src/modules/AndroidDeviceModule.js +341 -0
- package/src/modules/AndroidNetworkModule.js +178 -0
- package/src/modules/AndroidSharedPreferencesModule.js +100 -0
- package/src/modules/ESDeviceInfoModule.js +450 -0
- package/src/modules/ESGroupDataModule.js +195 -0
- package/src/modules/ESIJKAudioPlayerModule.js +477 -0
- package/src/modules/ESLocalStorageModule.js +100 -0
- package/src/modules/ESLogModule.js +65 -0
- package/src/modules/ESModule.js +106 -0
- package/src/modules/ESNetworkSpeedModule.js +117 -0
- package/src/modules/ESToastModule.js +172 -0
- package/src/modules/EsNativeModule.js +117 -0
- package/src/modules/FastListModule.js +101 -0
- package/src/modules/FocusModule.js +145 -0
- package/src/modules/RuntimeDeviceModule.js +176 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// QtImage component for image handling
|
|
2
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
3
|
+
import { registerComponent } from '../core/componentRegistry'
|
|
4
|
+
|
|
5
|
+
export class QtImage extends QtBaseComponent {
|
|
6
|
+
constructor(context, id, pId) {
|
|
7
|
+
super(context, id, pId)
|
|
8
|
+
this.tagName = 'img'
|
|
9
|
+
this.dom = document.createElement('img')
|
|
10
|
+
|
|
11
|
+
// Default values - 默认拉伸填满
|
|
12
|
+
this._resizeMode = 'fill'
|
|
13
|
+
this._currentSrc = ''
|
|
14
|
+
|
|
15
|
+
// Default styles for images - 默认拉伸填满
|
|
16
|
+
this.dom.style.objectFit = 'fill'
|
|
17
|
+
this.dom.style.display = 'block'
|
|
18
|
+
|
|
19
|
+
// Register for callUIFunction support
|
|
20
|
+
registerComponent(id, this)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Handle src attribute via setter (called by HippyWebView.updateProperty)
|
|
24
|
+
set src(value) {
|
|
25
|
+
this._setSrc(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get src() {
|
|
29
|
+
return this.dom.src
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle source attribute (React Native style)
|
|
33
|
+
set source(value) {
|
|
34
|
+
this._setSrc(value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Handle resizeMode
|
|
38
|
+
set resizeMode(value) {
|
|
39
|
+
this._setResizeMode(value)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handle src attribute
|
|
43
|
+
updateProperty(key, value) {
|
|
44
|
+
switch (key) {
|
|
45
|
+
case 'src':
|
|
46
|
+
case 'source':
|
|
47
|
+
this._setSrc(value)
|
|
48
|
+
break
|
|
49
|
+
case 'resizeMode':
|
|
50
|
+
this._setResizeMode(value)
|
|
51
|
+
break
|
|
52
|
+
case 'tintColor':
|
|
53
|
+
this.dom.style.filter = `drop-shadow(0 0 0 ${value})`
|
|
54
|
+
break
|
|
55
|
+
default:
|
|
56
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
57
|
+
this.dom.setAttribute(key, value)
|
|
58
|
+
}
|
|
59
|
+
super.updateProperty(key, value)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_setSrc(value) {
|
|
64
|
+
console.log('[QtImage] _setSrc called with:', value, 'type:', typeof value)
|
|
65
|
+
|
|
66
|
+
if (!value) {
|
|
67
|
+
this.dom.src = ''
|
|
68
|
+
this.dom.style.backgroundImage = ''
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle different src formats
|
|
73
|
+
let srcValue = ''
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
// Handle file:// protocol - convert to relative path
|
|
76
|
+
if (value.startsWith('file://')) {
|
|
77
|
+
let rest = value.slice(7)
|
|
78
|
+
rest = rest.replace(/^\/+/, '')
|
|
79
|
+
if (rest.startsWith('./')) rest = rest.slice(2)
|
|
80
|
+
srcValue = './' + rest
|
|
81
|
+
}
|
|
82
|
+
// Handle assets/ prefix - convert to relative path for web
|
|
83
|
+
else if (value.startsWith('assets/')) {
|
|
84
|
+
srcValue = './' + value
|
|
85
|
+
}
|
|
86
|
+
// Direct URL or path
|
|
87
|
+
else if (
|
|
88
|
+
value.startsWith('data:') ||
|
|
89
|
+
value.startsWith('http') ||
|
|
90
|
+
value.startsWith('/') ||
|
|
91
|
+
value.startsWith('.')
|
|
92
|
+
) {
|
|
93
|
+
srcValue = value
|
|
94
|
+
} else {
|
|
95
|
+
// Assume it's a URL
|
|
96
|
+
srcValue = value
|
|
97
|
+
}
|
|
98
|
+
} else if (typeof value === 'object') {
|
|
99
|
+
// Handle {uri: '...'} format (React Native style)
|
|
100
|
+
if (value.uri) {
|
|
101
|
+
srcValue = value.uri
|
|
102
|
+
// Handle file:// protocol in uri
|
|
103
|
+
if (srcValue.startsWith('file://')) {
|
|
104
|
+
let rest = srcValue.slice(7)
|
|
105
|
+
rest = rest.replace(/^\/+/, '')
|
|
106
|
+
if (rest.startsWith('./')) rest = rest.slice(2)
|
|
107
|
+
srcValue = './' + rest
|
|
108
|
+
}
|
|
109
|
+
// Handle assets/ prefix in uri
|
|
110
|
+
else if (srcValue.startsWith('assets/')) {
|
|
111
|
+
srcValue = './' + srcValue
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Store for later use (e.g., repeat mode)
|
|
117
|
+
this._currentSrc = srcValue
|
|
118
|
+
|
|
119
|
+
// Handle repeat mode specially - use background image
|
|
120
|
+
if (this._resizeMode === 'repeat') {
|
|
121
|
+
this.dom.style.backgroundImage = `url(${srcValue})`
|
|
122
|
+
this.dom.src =
|
|
123
|
+
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' // 透明占位
|
|
124
|
+
} else {
|
|
125
|
+
this.dom.src = srcValue
|
|
126
|
+
this.dom.style.backgroundImage = ''
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('[QtImage] Set dom.src to:', this.dom.src)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_setResizeMode(value) {
|
|
133
|
+
// Store current resizeMode for reference in _setSrc
|
|
134
|
+
this._resizeMode = value
|
|
135
|
+
|
|
136
|
+
// Map resizeMode to CSS object-fit and related styles
|
|
137
|
+
// QTImageResizeMode enum values:
|
|
138
|
+
// - QT_IMAGE_RESIZE_MODE_CONTAIN = "contain"
|
|
139
|
+
// - QT_IMAGE_RESIZE_MODE_COVER = "cover"
|
|
140
|
+
// - QT_IMAGE_RESIZE_MODE_CENTER = "center"
|
|
141
|
+
// - QT_IMAGE_RESIZE_MODE_ORIGIN = "origin"
|
|
142
|
+
// - QT_IMAGE_RESIZE_MODE_FIT_XY = "fitxy"
|
|
143
|
+
|
|
144
|
+
// Reset position styles
|
|
145
|
+
this.dom.style.objectFit = ''
|
|
146
|
+
this.dom.style.objectPosition = ''
|
|
147
|
+
this.dom.style.backgroundSize = ''
|
|
148
|
+
this.dom.style.backgroundPosition = ''
|
|
149
|
+
this.dom.style.backgroundRepeat = ''
|
|
150
|
+
|
|
151
|
+
switch (value) {
|
|
152
|
+
case 'contain':
|
|
153
|
+
// 保持宽高比缩放,完整显示图片
|
|
154
|
+
this.dom.style.objectFit = 'contain'
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
case 'cover':
|
|
158
|
+
// 保持宽高比缩放,填满容器(可能裁剪)
|
|
159
|
+
this.dom.style.objectFit = 'cover'
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
case 'center':
|
|
163
|
+
// 居中显示,等比缩小适应容器(类似 contain,确保居中)
|
|
164
|
+
// Android 行为:如果图片大于容器则等比缩小,小于容器则原样居中
|
|
165
|
+
this.dom.style.objectFit = 'contain'
|
|
166
|
+
this.dom.style.objectPosition = 'center center'
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
case 'origin':
|
|
170
|
+
// 原始大小显示,从左上角开始
|
|
171
|
+
this.dom.style.objectFit = 'none'
|
|
172
|
+
this.dom.style.objectPosition = 'left top'
|
|
173
|
+
break
|
|
174
|
+
|
|
175
|
+
case 'fitxy':
|
|
176
|
+
// 拉伸填满容器,不保持宽高比
|
|
177
|
+
this.dom.style.objectFit = 'fill'
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
case 'stretch':
|
|
181
|
+
// 同 fitxy,拉伸填满
|
|
182
|
+
this.dom.style.objectFit = 'fill'
|
|
183
|
+
break
|
|
184
|
+
|
|
185
|
+
case 'repeat':
|
|
186
|
+
// 平铺重复 - 使用 CSS background 方式实现
|
|
187
|
+
if (this._currentSrc) {
|
|
188
|
+
this.dom.style.backgroundImage = `url(${this._currentSrc})`
|
|
189
|
+
this.dom.src =
|
|
190
|
+
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' // 透明占位
|
|
191
|
+
}
|
|
192
|
+
this.dom.style.objectFit = 'none'
|
|
193
|
+
this.dom.style.backgroundSize = 'auto'
|
|
194
|
+
this.dom.style.backgroundPosition = 'left top'
|
|
195
|
+
this.dom.style.backgroundRepeat = 'repeat'
|
|
196
|
+
break
|
|
197
|
+
|
|
198
|
+
default:
|
|
199
|
+
// 默认使用 cover
|
|
200
|
+
this.dom.style.objectFit = 'cover'
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// QtItemFrame - A simple container component for item-frame
|
|
2
|
+
// Acts as a div wrapper, preserving all child elements
|
|
3
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
4
|
+
import { registerComponent } from '../core/componentRegistry'
|
|
5
|
+
|
|
6
|
+
export class QtItemFrame extends QtBaseComponent {
|
|
7
|
+
constructor(context, id, pId) {
|
|
8
|
+
super(context, id, pId)
|
|
9
|
+
this.tagName = 'ItemFrameComponent'
|
|
10
|
+
this.dom = document.createElement('div')
|
|
11
|
+
this.dom.setAttribute('data-component-name', 'QtItemFrame')
|
|
12
|
+
|
|
13
|
+
this.dom.style.display = 'block'
|
|
14
|
+
this.dom.style.position = 'relative'
|
|
15
|
+
this.dom.style.boxSizing = 'border-box'
|
|
16
|
+
|
|
17
|
+
registerComponent(id, this)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
defaultStyle() {
|
|
21
|
+
return {
|
|
22
|
+
display: 'block',
|
|
23
|
+
position: 'relative',
|
|
24
|
+
boxSizing: 'border-box',
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Handle property updates from Hippy renderer
|
|
29
|
+
updateProperty(key, value) {
|
|
30
|
+
// Handle focusable property - enable keyboard focus
|
|
31
|
+
if (key === 'focusable') {
|
|
32
|
+
if (value === true || value === 'true') {
|
|
33
|
+
this.dom.setAttribute('tabindex', '0')
|
|
34
|
+
this.dom.setAttribute('focusable', 'true')
|
|
35
|
+
this._focusable = true
|
|
36
|
+
} else {
|
|
37
|
+
this.dom.removeAttribute('tabindex')
|
|
38
|
+
this.dom.setAttribute('focusable', 'false')
|
|
39
|
+
this._focusable = false
|
|
40
|
+
}
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Handle focusScale property - store for focus animation
|
|
45
|
+
if (key === 'focusScale' || key === 'focusscale') {
|
|
46
|
+
this._focusScale = typeof value === 'number' ? value : parseFloat(value) || 1.1
|
|
47
|
+
this.dom.setAttribute('data-focus-scale', String(this._focusScale))
|
|
48
|
+
if (!this._focusListenerAdded) {
|
|
49
|
+
this._setupFocusAnimation()
|
|
50
|
+
}
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle itemShowShimmer property
|
|
55
|
+
if (key === 'itemShowShimmer' || key === 'itemshowshimmer') {
|
|
56
|
+
this.dom.setAttribute('data-show-shimmer', String(value))
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Handle hideShadow property
|
|
61
|
+
if (key === 'hideShadow' || key === 'hideshadow') {
|
|
62
|
+
this.dom.setAttribute('data-hide-shadow', String(value))
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Handle shimmerSize property
|
|
67
|
+
if (key === 'shimmerSize' || key === 'shimmersize') {
|
|
68
|
+
this.dom.setAttribute('data-shimmer-size', String(value))
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (key === 'flexStyle' || key === 'flexstyle') {
|
|
73
|
+
if (typeof value === 'object' && value !== null) {
|
|
74
|
+
this._applyFlexStyle(value)
|
|
75
|
+
} else if (typeof value === 'string' && value.startsWith('${')) {
|
|
76
|
+
this.dom.setAttribute(key, value)
|
|
77
|
+
}
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (key === 'layout') {
|
|
82
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
83
|
+
this.dom.setAttribute(key, value)
|
|
84
|
+
} else {
|
|
85
|
+
this._applyLayout(value)
|
|
86
|
+
}
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
91
|
+
this.dom.setAttribute(key, value)
|
|
92
|
+
}
|
|
93
|
+
super.updateProperty(key, value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Setup focus animation for scale effect
|
|
97
|
+
_setupFocusAnimation() {
|
|
98
|
+
this._focusListenerAdded = true
|
|
99
|
+
this.dom.addEventListener('focus', () => {
|
|
100
|
+
if (this._focusScale && this._focusScale !== 1) {
|
|
101
|
+
this.dom.style.transform = `scale(${this._focusScale})`
|
|
102
|
+
this.dom.style.transition = 'transform 0.2s ease-out'
|
|
103
|
+
this.dom.style.zIndex = '10'
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
this.dom.addEventListener('blur', () => {
|
|
107
|
+
this.dom.style.transform = 'scale(1)'
|
|
108
|
+
this.dom.style.zIndex = ''
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Support flexStyle attribute
|
|
113
|
+
set flexStyle(styleObj) {
|
|
114
|
+
this.props.flexStyle = styleObj
|
|
115
|
+
this._applyFlexStyle(styleObj)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get flexStyle() {
|
|
119
|
+
return this.props.flexStyle
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Apply flexStyle object to DOM
|
|
123
|
+
_applyFlexStyle(styleObj) {
|
|
124
|
+
if (!styleObj || typeof styleObj !== 'object') return
|
|
125
|
+
|
|
126
|
+
Object.keys(styleObj).forEach((key) => {
|
|
127
|
+
let value = styleObj[key]
|
|
128
|
+
|
|
129
|
+
if (typeof value === 'number') {
|
|
130
|
+
const needsPx = [
|
|
131
|
+
'width',
|
|
132
|
+
'height',
|
|
133
|
+
'minWidth',
|
|
134
|
+
'minHeight',
|
|
135
|
+
'maxWidth',
|
|
136
|
+
'maxHeight',
|
|
137
|
+
'padding',
|
|
138
|
+
'paddingTop',
|
|
139
|
+
'paddingRight',
|
|
140
|
+
'paddingBottom',
|
|
141
|
+
'paddingLeft',
|
|
142
|
+
'margin',
|
|
143
|
+
'marginTop',
|
|
144
|
+
'marginRight',
|
|
145
|
+
'marginBottom',
|
|
146
|
+
'marginLeft',
|
|
147
|
+
'top',
|
|
148
|
+
'left',
|
|
149
|
+
'right',
|
|
150
|
+
'bottom',
|
|
151
|
+
'borderRadius',
|
|
152
|
+
'borderWidth',
|
|
153
|
+
'fontSize',
|
|
154
|
+
'lineHeight',
|
|
155
|
+
'letterSpacing',
|
|
156
|
+
'gap',
|
|
157
|
+
'rowGap',
|
|
158
|
+
'columnGap',
|
|
159
|
+
].includes(key)
|
|
160
|
+
|
|
161
|
+
if (needsPx) {
|
|
162
|
+
value = value + 'px'
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
if (key !== 'x' && key !== 'y') {
|
|
168
|
+
this.dom.style[key] = value
|
|
169
|
+
}
|
|
170
|
+
} catch (e) {
|
|
171
|
+
try {
|
|
172
|
+
if (key !== 'x' && key !== 'y') {
|
|
173
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
174
|
+
this.dom.style.setProperty(cssKey, value)
|
|
175
|
+
}
|
|
176
|
+
} catch (err) {}
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Apply layout object/array to DOM
|
|
182
|
+
_applyLayout(layout) {
|
|
183
|
+
if (!layout) return
|
|
184
|
+
|
|
185
|
+
if (Array.isArray(layout)) {
|
|
186
|
+
const x = layout[0]
|
|
187
|
+
const y = layout[1]
|
|
188
|
+
const width = layout[2]
|
|
189
|
+
const height = layout[3]
|
|
190
|
+
|
|
191
|
+
if (x !== undefined && x !== null) {
|
|
192
|
+
this.dom.style.left = typeof x === 'number' ? `${x}px` : x
|
|
193
|
+
}
|
|
194
|
+
if (y !== undefined && y !== null) {
|
|
195
|
+
this.dom.style.top = typeof y === 'number' ? `${y}px` : y
|
|
196
|
+
}
|
|
197
|
+
if (width !== undefined && width !== null) {
|
|
198
|
+
this.dom.style.width = typeof width === 'number' ? `${width}px` : width
|
|
199
|
+
}
|
|
200
|
+
if (height !== undefined && height !== null) {
|
|
201
|
+
this.dom.style.height = typeof height === 'number' ? `${height}px` : height
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if ((x !== undefined || y !== undefined) && !this.dom.style.position) {
|
|
205
|
+
this.dom.style.position = 'absolute'
|
|
206
|
+
}
|
|
207
|
+
// If position is relative (default for QtItemFrame), change to absolute if x/y are provided
|
|
208
|
+
if ((x !== undefined || y !== undefined) && this.dom.style.position === 'relative') {
|
|
209
|
+
this.dom.style.position = 'absolute'
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (typeof layout === 'object') {
|
|
216
|
+
const { x, y, width, height } = layout
|
|
217
|
+
|
|
218
|
+
if (x !== undefined && x !== null) {
|
|
219
|
+
this.dom.style.left = typeof x === 'number' ? `${x}px` : x
|
|
220
|
+
}
|
|
221
|
+
if (y !== undefined && y !== null) {
|
|
222
|
+
this.dom.style.top = typeof y === 'number' ? `${y}px` : y
|
|
223
|
+
}
|
|
224
|
+
if (width !== undefined && width !== null) {
|
|
225
|
+
this.dom.style.width = typeof width === 'number' ? `${width}px` : width
|
|
226
|
+
}
|
|
227
|
+
if (height !== undefined && height !== null) {
|
|
228
|
+
this.dom.style.height = typeof height === 'number' ? `${height}px` : height
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if ((x !== undefined || y !== undefined) && !this.dom.style.position) {
|
|
232
|
+
this.dom.style.position = 'absolute'
|
|
233
|
+
}
|
|
234
|
+
if ((x !== undefined || y !== undefined) && this.dom.style.position === 'relative') {
|
|
235
|
+
this.dom.style.position = 'absolute'
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// QtItemStoreView - A container component for shared item templates
|
|
2
|
+
// Used in waterfall to provide shared item templates across sections
|
|
3
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
4
|
+
import { registerComponent } from '../core/componentRegistry'
|
|
5
|
+
|
|
6
|
+
function _qtGetSharedTemplateStore() {
|
|
7
|
+
const w = window
|
|
8
|
+
if (!w.__QT_SHARED_TEMPLATES__) {
|
|
9
|
+
w.__QT_SHARED_TEMPLATES__ = new Map()
|
|
10
|
+
}
|
|
11
|
+
return w.__QT_SHARED_TEMPLATES__
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class QtItemStoreView extends QtBaseComponent {
|
|
15
|
+
constructor(context, id, pId) {
|
|
16
|
+
super(context, id, pId)
|
|
17
|
+
this.tagName = 'ItemStoreView'
|
|
18
|
+
this.dom = document.createElement('div')
|
|
19
|
+
this.dom.setAttribute('data-component-name', 'QtItemStoreView')
|
|
20
|
+
|
|
21
|
+
this.dom.style.display = 'none' // Item store templates are hidden by default
|
|
22
|
+
this.dom.style.position = 'relative'
|
|
23
|
+
this.dom.style.boxSizing = 'border-box'
|
|
24
|
+
|
|
25
|
+
this._templateChildren = []
|
|
26
|
+
|
|
27
|
+
registerComponent(id, this)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
defaultStyle() {
|
|
31
|
+
return {
|
|
32
|
+
display: 'none',
|
|
33
|
+
position: 'relative',
|
|
34
|
+
boxSizing: 'border-box',
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get all template children for this item store
|
|
39
|
+
getTemplateChildren() {
|
|
40
|
+
return this._templateChildren
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Add a template child
|
|
44
|
+
addTemplateChild(template) {
|
|
45
|
+
if (template && template.dom && !this._templateChildren.some((t) => t.dom === template.dom)) {
|
|
46
|
+
// Extract type from the template
|
|
47
|
+
let type = null
|
|
48
|
+
if (template.props && template.props.type !== undefined) {
|
|
49
|
+
type = template.props.type
|
|
50
|
+
} else if (template.dom.hasAttribute('type')) {
|
|
51
|
+
const typeAttr = template.dom.getAttribute('type')
|
|
52
|
+
type = typeAttr !== null ? (isNaN(Number(typeAttr)) ? typeAttr : Number(typeAttr)) : null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this._templateChildren.push({
|
|
56
|
+
dom: template.dom,
|
|
57
|
+
tagName: template.tagName,
|
|
58
|
+
component: template,
|
|
59
|
+
type: type,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (type !== null && type !== undefined) {
|
|
63
|
+
const store = _qtGetSharedTemplateStore()
|
|
64
|
+
store.set(type, {
|
|
65
|
+
dom: template.dom,
|
|
66
|
+
tagName: template.tagName,
|
|
67
|
+
component: template,
|
|
68
|
+
type,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
template.dom.setAttribute('data-template', 'true')
|
|
73
|
+
template.dom.setAttribute('data-shared-template', 'true')
|
|
74
|
+
template.dom.style.display = 'none'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle child mount - collect templates
|
|
79
|
+
async beforeChildMount(child, childPosition) {
|
|
80
|
+
if (child && child.dom) {
|
|
81
|
+
this.addTemplateChild(child)
|
|
82
|
+
}
|
|
83
|
+
await super.beforeChildMount(child, childPosition)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle property updates from Hippy renderer
|
|
87
|
+
updateProperty(key, value) {
|
|
88
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
89
|
+
this.dom.setAttribute(key, value)
|
|
90
|
+
}
|
|
91
|
+
super.updateProperty(key, value)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// QtItemView - An item view container for waterfall sections
|
|
2
|
+
// Used by tv-item and FastItemView components
|
|
3
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
4
|
+
import { registerComponent } from '../core/componentRegistry'
|
|
5
|
+
|
|
6
|
+
export class QtItemView extends QtBaseComponent {
|
|
7
|
+
constructor(context, id, pId) {
|
|
8
|
+
super(context, id, pId)
|
|
9
|
+
this.tagName = 'FastItemView'
|
|
10
|
+
this.dom = document.createElement('div')
|
|
11
|
+
this.dom.setAttribute('data-component-name', 'QtItemView')
|
|
12
|
+
|
|
13
|
+
this.dom.style.cssText = `
|
|
14
|
+
display: block;
|
|
15
|
+
position: relative;
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
this.props = {}
|
|
20
|
+
|
|
21
|
+
registerComponent(id, this)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
defaultStyle() {
|
|
25
|
+
return {
|
|
26
|
+
display: 'block',
|
|
27
|
+
position: 'relative',
|
|
28
|
+
boxSizing: 'border-box',
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle child elements (slot content)
|
|
33
|
+
insertChild(child, index) {
|
|
34
|
+
if (child && child.dom && !this.dom.contains(child.dom)) {
|
|
35
|
+
this.dom.appendChild(child.dom)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Handle child elements before mount
|
|
40
|
+
async beforeChildMount(child, childPosition) {
|
|
41
|
+
if (child && child.dom && !this.dom.contains(child.dom)) {
|
|
42
|
+
this.dom.appendChild(child.dom)
|
|
43
|
+
}
|
|
44
|
+
await super.beforeChildMount(child, childPosition)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
updateProperty(key, value) {
|
|
48
|
+
switch (key) {
|
|
49
|
+
case 'singleton':
|
|
50
|
+
// Store singleton flag for QtFastListView to detect
|
|
51
|
+
this.props = this.props || {}
|
|
52
|
+
this.props.singleton = true
|
|
53
|
+
this.dom.setAttribute('singleton', 'true')
|
|
54
|
+
break
|
|
55
|
+
case 'type':
|
|
56
|
+
// Store type for singleton templates to match with data
|
|
57
|
+
this.props = this.props || {}
|
|
58
|
+
this.props.type = value
|
|
59
|
+
this.dom.setAttribute('type', value)
|
|
60
|
+
break
|
|
61
|
+
case 'flexStyle':
|
|
62
|
+
case 'flexstyle':
|
|
63
|
+
if (typeof value === 'object' && value !== null) {
|
|
64
|
+
this._applyFlexStyle(value)
|
|
65
|
+
}
|
|
66
|
+
break
|
|
67
|
+
default:
|
|
68
|
+
super.updateProperty(key, value)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_applyFlexStyle(styleObj) {
|
|
73
|
+
if (!styleObj || typeof styleObj !== 'object') return
|
|
74
|
+
|
|
75
|
+
Object.keys(styleObj).forEach((key) => {
|
|
76
|
+
let value = styleObj[key]
|
|
77
|
+
|
|
78
|
+
if (typeof value === 'number') {
|
|
79
|
+
const needsPx = [
|
|
80
|
+
'width',
|
|
81
|
+
'height',
|
|
82
|
+
'minWidth',
|
|
83
|
+
'minHeight',
|
|
84
|
+
'maxWidth',
|
|
85
|
+
'maxHeight',
|
|
86
|
+
'padding',
|
|
87
|
+
'paddingTop',
|
|
88
|
+
'paddingRight',
|
|
89
|
+
'paddingBottom',
|
|
90
|
+
'paddingLeft',
|
|
91
|
+
'margin',
|
|
92
|
+
'marginTop',
|
|
93
|
+
'marginRight',
|
|
94
|
+
'marginBottom',
|
|
95
|
+
'marginLeft',
|
|
96
|
+
'top',
|
|
97
|
+
'left',
|
|
98
|
+
'right',
|
|
99
|
+
'bottom',
|
|
100
|
+
'borderRadius',
|
|
101
|
+
'borderWidth',
|
|
102
|
+
'fontSize',
|
|
103
|
+
'lineHeight',
|
|
104
|
+
'letterSpacing',
|
|
105
|
+
'gap',
|
|
106
|
+
'rowGap',
|
|
107
|
+
'columnGap',
|
|
108
|
+
].includes(key)
|
|
109
|
+
|
|
110
|
+
if (needsPx) {
|
|
111
|
+
value = value + 'px'
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
this.dom.style[key] = value
|
|
117
|
+
} catch (e) {
|
|
118
|
+
try {
|
|
119
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
120
|
+
this.dom.style.setProperty(cssKey, value)
|
|
121
|
+
} catch (err) {}
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
}
|