@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,679 @@
|
|
|
1
|
+
// QtText component that supports the 'text' property
|
|
2
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
3
|
+
|
|
4
|
+
window.__HIPPY_COMPONENT_REGISTRY__ = window.__HIPPY_COMPONENT_REGISTRY__ || new Map()
|
|
5
|
+
|
|
6
|
+
export class QtText extends QtBaseComponent {
|
|
7
|
+
constructor(context, id, pId) {
|
|
8
|
+
super(context, id, pId)
|
|
9
|
+
this.tagName = 'Text'
|
|
10
|
+
this.dom = document.createElement('span')
|
|
11
|
+
this._textValue = ''
|
|
12
|
+
this._gravity = null
|
|
13
|
+
|
|
14
|
+
// Create inner span for text content (needed for flexbox alignment)
|
|
15
|
+
this._innerSpan = document.createElement('span')
|
|
16
|
+
this._innerSpan.className = 'qt-text-inner'
|
|
17
|
+
// Inherit text-related styles from parent
|
|
18
|
+
this._innerSpan.style.cssText = `
|
|
19
|
+
font-size: inherit;
|
|
20
|
+
line-height: inherit;
|
|
21
|
+
letter-spacing: inherit;
|
|
22
|
+
color: inherit;
|
|
23
|
+
`
|
|
24
|
+
this.dom.appendChild(this._innerSpan)
|
|
25
|
+
|
|
26
|
+
window.__HIPPY_COMPONENT_REGISTRY__.set(this.dom, this)
|
|
27
|
+
|
|
28
|
+
// Default to white text for TV interfaces
|
|
29
|
+
this.dom.style.color = 'black'
|
|
30
|
+
|
|
31
|
+
this._autoWidthObserver = null
|
|
32
|
+
this._autoWidthSyncRaf = 0
|
|
33
|
+
this._autoWidthRetryCount = 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
defaultStyle() {
|
|
37
|
+
return {
|
|
38
|
+
boxSizing: 'border-box',
|
|
39
|
+
zIndex: 0,
|
|
40
|
+
display: 'inline-block',
|
|
41
|
+
color: 'black', // Default gray text for TV
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Override updateProperty to handle TV focus attributes and text-specific properties
|
|
46
|
+
updateProperty(key, value) {
|
|
47
|
+
// Handle autoWidth property - auto size width to content
|
|
48
|
+
if (key === 'autoWidth' || key === 'auto_width') {
|
|
49
|
+
this.setAutoWidth(value)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle autoHeight property - auto size height to content
|
|
54
|
+
if (key === 'autoHeight' || key === 'auto_height') {
|
|
55
|
+
this.setAutoHeight(value)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Handle fontSize property
|
|
60
|
+
if (key === 'fontSize') {
|
|
61
|
+
this.setTextSize(value)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle textSize property (alias for fontSize)
|
|
66
|
+
if (key === 'textSize' || key === 'textsize') {
|
|
67
|
+
this.setTextSize(value)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle flexStyle property
|
|
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
|
+
// Handle ellipsizeMode property
|
|
82
|
+
// 0: START, 1: MIDDLE, 2: END, 3: MARQUEE
|
|
83
|
+
if (key === 'ellipsizeMode') {
|
|
84
|
+
this.setEllipsizeMode(value)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle lines property (max number of lines)
|
|
89
|
+
if (key === 'lines') {
|
|
90
|
+
this.setLines(value)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle gravity property for text alignment
|
|
95
|
+
if (key === 'gravity') {
|
|
96
|
+
this.setGravity(value)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle textAlignVertical property
|
|
101
|
+
if (key === 'textAlignVertical' || key === 'text-align-vertical') {
|
|
102
|
+
this.setTextAlignVertical(value)
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Handle duplicateParentState and focus style attributes
|
|
107
|
+
const tvFocusAttrs = ['duplicateParentState', 'focusable', 'showOnState']
|
|
108
|
+
|
|
109
|
+
const focusStyleAttrs = [
|
|
110
|
+
'focusColor',
|
|
111
|
+
'focusBackgroundColor',
|
|
112
|
+
'focusTextColor',
|
|
113
|
+
'focusBorderColor',
|
|
114
|
+
'focusBorderWidth',
|
|
115
|
+
'focusBorderRadius',
|
|
116
|
+
'focusOpacity',
|
|
117
|
+
'focusScale',
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
// Also handle kebab-case versions
|
|
121
|
+
const kebabFocusStyleAttrs = [
|
|
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
|
+
]
|
|
131
|
+
|
|
132
|
+
if (tvFocusAttrs.includes(key)) {
|
|
133
|
+
if (value !== null && value !== undefined) {
|
|
134
|
+
// Special handling for showOnState: convert array to JSON string
|
|
135
|
+
if (key === 'showOnState' && Array.isArray(value)) {
|
|
136
|
+
console.log('[QtText] Setting showOnState (array):', value, '->', JSON.stringify(value))
|
|
137
|
+
this.dom.setAttribute(key, JSON.stringify(value))
|
|
138
|
+
} else {
|
|
139
|
+
console.log('[QtText] Setting attribute:', key, '=', value)
|
|
140
|
+
this.dom.setAttribute(key, String(value))
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
this.dom.removeAttribute(key)
|
|
144
|
+
}
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (focusStyleAttrs.includes(key)) {
|
|
149
|
+
if (value !== null && value !== undefined) {
|
|
150
|
+
const dataKey = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
151
|
+
this.dom.setAttribute(dataKey, String(value))
|
|
152
|
+
} else {
|
|
153
|
+
const dataKey = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
154
|
+
this.dom.removeAttribute(dataKey)
|
|
155
|
+
}
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (kebabFocusStyleAttrs.includes(key)) {
|
|
160
|
+
if (value !== null && value !== undefined) {
|
|
161
|
+
this.dom.setAttribute('data-' + key, String(value))
|
|
162
|
+
} else {
|
|
163
|
+
this.dom.removeAttribute('data-' + key)
|
|
164
|
+
}
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
169
|
+
this.dom.setAttribute(key, value)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// For other properties, use parent class handling
|
|
173
|
+
super.updateProperty(key, value)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Implement 'text' setter to handle text content
|
|
177
|
+
set text(value) {
|
|
178
|
+
this._textValue = value
|
|
179
|
+
this._updateTextNode(value)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get text() {
|
|
183
|
+
return this._textValue
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Update the text node in DOM
|
|
187
|
+
_updateTextNode(value) {
|
|
188
|
+
console.log('[QtText] _updateTextNode:', value)
|
|
189
|
+
if (this._innerSpan) {
|
|
190
|
+
this._innerSpan.textContent = value || ''
|
|
191
|
+
}
|
|
192
|
+
// 文本变化后同步宽度
|
|
193
|
+
if (this._autoWidth) {
|
|
194
|
+
this._scheduleAutoWidthSync(true)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Methods called by Native.callUIFunction(viewRef, 'setText', [text])
|
|
199
|
+
setText(text) {
|
|
200
|
+
this.text = text
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Set auto width - width adapts to content
|
|
204
|
+
setAutoWidth(value) {
|
|
205
|
+
// 空字符串 '' 也视为 true(HTML 属性不带值时为 '')
|
|
206
|
+
this._autoWidth = value === '' || !!value
|
|
207
|
+
if (this._autoWidth) {
|
|
208
|
+
this._ensureAutoWidthObserver()
|
|
209
|
+
this.dom.setAttribute('autowidth', '')
|
|
210
|
+
this.dom.style.width = 'auto'
|
|
211
|
+
this.dom.style.minWidth = '0'
|
|
212
|
+
this.dom.style.display = 'inline-block'
|
|
213
|
+
if (this._innerSpan) {
|
|
214
|
+
this._innerSpan.style.whiteSpace = 'nowrap'
|
|
215
|
+
}
|
|
216
|
+
// 延迟计算并同步宽度
|
|
217
|
+
this._scheduleAutoWidthSync(true)
|
|
218
|
+
} else {
|
|
219
|
+
this._teardownAutoWidthObserver()
|
|
220
|
+
this.dom.removeAttribute('autowidth')
|
|
221
|
+
if (this._autoWidthSyncRaf) {
|
|
222
|
+
cancelAnimationFrame(this._autoWidthSyncRaf)
|
|
223
|
+
this._autoWidthSyncRaf = 0
|
|
224
|
+
}
|
|
225
|
+
this.dom.style.width = ''
|
|
226
|
+
this.dom.style.display = ''
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
_ensureAutoWidthObserver() {
|
|
231
|
+
if (!this._innerSpan) return
|
|
232
|
+
if (this._autoWidthObserver) return
|
|
233
|
+
|
|
234
|
+
this._autoWidthObserver = new MutationObserver(() => {
|
|
235
|
+
if (this._autoWidth) this._scheduleAutoWidthSync(true)
|
|
236
|
+
})
|
|
237
|
+
this._autoWidthObserver.observe(this._innerSpan, {
|
|
238
|
+
characterData: true,
|
|
239
|
+
childList: true,
|
|
240
|
+
subtree: true,
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
_teardownAutoWidthObserver() {
|
|
245
|
+
if (!this._autoWidthObserver) return
|
|
246
|
+
this._autoWidthObserver.disconnect()
|
|
247
|
+
this._autoWidthObserver = null
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_scheduleAutoWidthSync(resetRetry = false) {
|
|
251
|
+
if (!this._autoWidth) return
|
|
252
|
+
if (resetRetry) this._autoWidthRetryCount = 0
|
|
253
|
+
if (this._autoWidthSyncRaf) cancelAnimationFrame(this._autoWidthSyncRaf)
|
|
254
|
+
this._autoWidthSyncRaf = requestAnimationFrame(() => {
|
|
255
|
+
this._autoWidthSyncRaf = 0
|
|
256
|
+
this._syncWidthToParent()
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 同步宽度到父容器(解决绝对定位元素无法撑开父容器的问题)
|
|
261
|
+
_syncWidthToParent() {
|
|
262
|
+
if (!this._autoWidth) return
|
|
263
|
+
|
|
264
|
+
const inner = this._innerSpan
|
|
265
|
+
if (!inner) return
|
|
266
|
+
|
|
267
|
+
const textWidth = inner.scrollWidth || inner.offsetWidth
|
|
268
|
+
if (textWidth <= 0) {
|
|
269
|
+
if (this.dom && this.dom.isConnected && this._autoWidthRetryCount < 5) {
|
|
270
|
+
this._autoWidthRetryCount++
|
|
271
|
+
this._scheduleAutoWidthSync(false)
|
|
272
|
+
}
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
this._autoWidthRetryCount = 0
|
|
276
|
+
|
|
277
|
+
// 设置文字元素的实际宽度
|
|
278
|
+
this.dom.style.width = textWidth + 'px'
|
|
279
|
+
this.dom.style.minWidth = textWidth + 'px'
|
|
280
|
+
|
|
281
|
+
// 查找有 autoWidth 属性的父容器并同步宽度
|
|
282
|
+
let parent = this.dom.parentElement
|
|
283
|
+
while (parent) {
|
|
284
|
+
if (
|
|
285
|
+
parent.hasAttribute &&
|
|
286
|
+
(parent.hasAttribute('autoWidth') || parent.hasAttribute('autowidth'))
|
|
287
|
+
) {
|
|
288
|
+
const style = window.getComputedStyle(parent)
|
|
289
|
+
const paddingLeft = parseInt(style.paddingLeft) || 0
|
|
290
|
+
const paddingRight = parseInt(style.paddingRight) || 0
|
|
291
|
+
const parentWidth = textWidth + paddingLeft + paddingRight
|
|
292
|
+
|
|
293
|
+
parent.style.width = parentWidth + 'px'
|
|
294
|
+
parent.style.minWidth = parentWidth + 'px'
|
|
295
|
+
|
|
296
|
+
// 更新父容器内其他绝对定位且有 autoWidth 的元素(如背景)
|
|
297
|
+
parent.querySelectorAll('[autoWidth], [autowidth]').forEach((el) => {
|
|
298
|
+
if (el === this.dom) return
|
|
299
|
+
if (el.tagName && el.tagName.toLowerCase() === 'qt-text') return
|
|
300
|
+
|
|
301
|
+
const elStyle = window.getComputedStyle(el)
|
|
302
|
+
if (elStyle.position === 'absolute') {
|
|
303
|
+
el.style.left = '0'
|
|
304
|
+
el.style.right = '0'
|
|
305
|
+
el.style.width = 'auto'
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
break
|
|
309
|
+
}
|
|
310
|
+
parent = parent.parentElement
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Set auto height - height adapts to content
|
|
315
|
+
setAutoHeight(value) {
|
|
316
|
+
// 空字符串 '' 也视为 true(HTML 属性不带值时为 '')
|
|
317
|
+
this._autoHeight = value === '' || !!value
|
|
318
|
+
if (this._autoHeight) {
|
|
319
|
+
this.dom.style.height = 'auto'
|
|
320
|
+
this.dom.style.minHeight = '0'
|
|
321
|
+
} else {
|
|
322
|
+
this.dom.style.height = ''
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
setTextSize(size) {
|
|
327
|
+
const target = this._innerSpan || this.dom
|
|
328
|
+
if (target && size) {
|
|
329
|
+
// Check if size is a template placeholder like ${titleSize}
|
|
330
|
+
if (typeof size === 'string' && size.startsWith('${') && size.endsWith('}')) {
|
|
331
|
+
// Store as data attribute for template processing
|
|
332
|
+
this.dom.setAttribute('fontSize', size)
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
target.style.fontSize = typeof size === 'number' ? size + 'px' : size
|
|
336
|
+
// 字体变化后同步宽度
|
|
337
|
+
if (this._autoWidth) {
|
|
338
|
+
this._scheduleAutoWidthSync(true)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
setFontSize(size) {
|
|
343
|
+
const target = this._innerSpan || this.dom
|
|
344
|
+
if (target && size) {
|
|
345
|
+
// Check if size is a template placeholder like ${titleSize}
|
|
346
|
+
if (typeof size === 'string' && size.startsWith('${') && size.endsWith('}')) {
|
|
347
|
+
// Store as data attribute for template processing
|
|
348
|
+
this.dom.setAttribute('fontSize', size)
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
target.style.fontSize = typeof size === 'number' ? size + 'px' : size
|
|
352
|
+
// 字体变化后同步宽度
|
|
353
|
+
if (this._autoWidth) {
|
|
354
|
+
this._scheduleAutoWidthSync(true)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
setTextColor(color) {
|
|
359
|
+
const target = this._innerSpan || this.dom
|
|
360
|
+
if (target && color) {
|
|
361
|
+
// Handle number color (like 0xFFFFFFFF)
|
|
362
|
+
if (typeof color === 'number') {
|
|
363
|
+
const a = (color >> 24) & 0xff
|
|
364
|
+
const r = (color >> 16) & 0xff
|
|
365
|
+
const g = (color >> 8) & 0xff
|
|
366
|
+
const b = color & 0xff
|
|
367
|
+
target.style.color = `rgba(${r}, ${g}, ${b}, ${a / 255})`
|
|
368
|
+
} else {
|
|
369
|
+
target.style.color = color
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Set gravity for text alignment
|
|
375
|
+
// Supports: centerVertical, centerHorizontal, center, left, right, top, bottom
|
|
376
|
+
// Can be combined with | like "centerVertical|left"
|
|
377
|
+
setGravity(gravity) {
|
|
378
|
+
if (!this.dom || !gravity) return
|
|
379
|
+
|
|
380
|
+
// Store gravity for later use
|
|
381
|
+
this._gravity = gravity
|
|
382
|
+
|
|
383
|
+
// Parse gravity value (can be string like "centerVertical" or "centerVertical|left")
|
|
384
|
+
const gravityStr = String(gravity)
|
|
385
|
+
const gravities = gravityStr.split('|').map((g) => g.trim())
|
|
386
|
+
|
|
387
|
+
// Use flexbox for alignment - the inner span is the flex item
|
|
388
|
+
this.dom.style.display = 'flex'
|
|
389
|
+
this.dom.style.alignItems = 'stretch'
|
|
390
|
+
this.dom.style.justifyContent = 'flex-start'
|
|
391
|
+
|
|
392
|
+
// Ensure inner span can expand
|
|
393
|
+
if (this._innerSpan) {
|
|
394
|
+
this._innerSpan.style.display = 'inline'
|
|
395
|
+
this._innerSpan.style.whiteSpace = ''
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Vertical alignment (cross-axis)
|
|
399
|
+
if (gravities.includes('centerVertical') || gravities.includes('center')) {
|
|
400
|
+
this.dom.style.alignItems = 'center'
|
|
401
|
+
} else if (gravities.includes('top')) {
|
|
402
|
+
this.dom.style.alignItems = 'flex-start'
|
|
403
|
+
} else if (gravities.includes('bottom')) {
|
|
404
|
+
this.dom.style.alignItems = 'flex-end'
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Horizontal alignment (main-axis)
|
|
408
|
+
if (gravities.includes('centerHorizontal') || gravities.includes('center')) {
|
|
409
|
+
this.dom.style.justifyContent = 'center'
|
|
410
|
+
} else if (gravities.includes('left')) {
|
|
411
|
+
this.dom.style.justifyContent = 'flex-start'
|
|
412
|
+
} else if (gravities.includes('right')) {
|
|
413
|
+
this.dom.style.justifyContent = 'flex-end'
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Set text align vertical for CSS text-align-vertical style
|
|
418
|
+
// Supports: center, top, bottom
|
|
419
|
+
setTextAlignVertical(value) {
|
|
420
|
+
if (!this.dom || !value) return
|
|
421
|
+
|
|
422
|
+
// Use flexbox for vertical alignment
|
|
423
|
+
this.dom.style.display = 'flex'
|
|
424
|
+
this._textAlignVertical = value
|
|
425
|
+
|
|
426
|
+
switch (String(value).toLowerCase()) {
|
|
427
|
+
case 'center':
|
|
428
|
+
this.dom.style.alignItems = 'center'
|
|
429
|
+
break
|
|
430
|
+
case 'top':
|
|
431
|
+
this.dom.style.alignItems = 'flex-start'
|
|
432
|
+
break
|
|
433
|
+
case 'bottom':
|
|
434
|
+
this.dom.style.alignItems = 'flex-end'
|
|
435
|
+
break
|
|
436
|
+
default:
|
|
437
|
+
this.dom.style.alignItems = 'center'
|
|
438
|
+
break
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Set text ellipsis mode
|
|
443
|
+
// 0: START - ellipsis at the beginning
|
|
444
|
+
// 1: MIDDLE - ellipsis in the middle
|
|
445
|
+
// 2: END - ellipsis at the end (default)
|
|
446
|
+
// 3: MARQUEE - scrolling text
|
|
447
|
+
setEllipsizeMode(mode) {
|
|
448
|
+
if (!this.dom) return
|
|
449
|
+
this._ellipsizeMode = mode
|
|
450
|
+
|
|
451
|
+
// Don't apply single-line ellipsis if lines > 1 is set
|
|
452
|
+
if (this._lines && this._lines > 1) {
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const target = this._innerSpan || this.dom
|
|
457
|
+
|
|
458
|
+
// Reset text styles on inner span
|
|
459
|
+
target.style.overflow = 'hidden'
|
|
460
|
+
target.style.whiteSpace = 'nowrap'
|
|
461
|
+
target.style.webkitLineClamp = ''
|
|
462
|
+
target.style.display = 'inline-block'
|
|
463
|
+
target.style.maxWidth = '100%'
|
|
464
|
+
|
|
465
|
+
// Keep flex display if gravity or textAlignVertical is set
|
|
466
|
+
if (this._gravity || this._textAlignVertical) {
|
|
467
|
+
this.dom.style.display = 'flex'
|
|
468
|
+
this.dom.style.overflow = 'hidden'
|
|
469
|
+
// Re-apply textAlignVertical if set
|
|
470
|
+
if (this._textAlignVertical) {
|
|
471
|
+
this.setTextAlignVertical(this._textAlignVertical)
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
this.dom.style.display = 'inline-block'
|
|
475
|
+
this.dom.style.overflow = 'hidden'
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
switch (mode) {
|
|
479
|
+
case 0: // START
|
|
480
|
+
target.style.textOverflow = 'ellipsis'
|
|
481
|
+
target.style.direction = 'rtl'
|
|
482
|
+
target.style.textAlign = 'left'
|
|
483
|
+
break
|
|
484
|
+
case 1: // MIDDLE
|
|
485
|
+
target.style.textOverflow = 'ellipsis'
|
|
486
|
+
target.style.direction = 'ltr'
|
|
487
|
+
break
|
|
488
|
+
case 2: // END
|
|
489
|
+
target.style.textOverflow = 'ellipsis'
|
|
490
|
+
target.style.direction = 'ltr'
|
|
491
|
+
break
|
|
492
|
+
case 3: // MARQUEE
|
|
493
|
+
this._enableMarquee()
|
|
494
|
+
break
|
|
495
|
+
default:
|
|
496
|
+
target.style.textOverflow = 'ellipsis'
|
|
497
|
+
target.style.direction = 'ltr'
|
|
498
|
+
break
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Enable marquee scrolling effect
|
|
503
|
+
_enableMarquee() {
|
|
504
|
+
if (!this.dom) return
|
|
505
|
+
|
|
506
|
+
const target = this._innerSpan || this.dom
|
|
507
|
+
target.style.textOverflow = 'clip'
|
|
508
|
+
target.style.whiteSpace = 'nowrap'
|
|
509
|
+
target.style.direction = 'ltr'
|
|
510
|
+
target.style.overflow = 'visible'
|
|
511
|
+
|
|
512
|
+
// Make sure container uses flex display and keep vertical alignment
|
|
513
|
+
this.dom.style.display = 'flex'
|
|
514
|
+
this.dom.style.overflow = 'hidden'
|
|
515
|
+
this.dom.style.justifyContent = 'flex-start'
|
|
516
|
+
|
|
517
|
+
// Keep textAlignVertical if set
|
|
518
|
+
if (this._textAlignVertical) {
|
|
519
|
+
this.setTextAlignVertical(this._textAlignVertical)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Add marquee animation via CSS
|
|
523
|
+
const keyframes = `
|
|
524
|
+
@keyframes qttext-marquee {
|
|
525
|
+
0% { transform: translateX(0); }
|
|
526
|
+
100% { transform: translateX(-100%); }
|
|
527
|
+
}
|
|
528
|
+
`
|
|
529
|
+
|
|
530
|
+
// Add keyframes if not exists
|
|
531
|
+
if (!document.querySelector('#qttext-marquee-style')) {
|
|
532
|
+
const style = document.createElement('style')
|
|
533
|
+
style.id = 'qttext-marquee-style'
|
|
534
|
+
style.textContent = keyframes
|
|
535
|
+
document.head.appendChild(style)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Apply marquee animation to inner span
|
|
539
|
+
// Use width: max-content so text is not truncated
|
|
540
|
+
target.style.display = 'inline-block'
|
|
541
|
+
target.style.whiteSpace = 'nowrap'
|
|
542
|
+
target.style.width = 'max-content'
|
|
543
|
+
|
|
544
|
+
// Calculate animation duration based on text width for consistent speed
|
|
545
|
+
// Speed: ~50px per second
|
|
546
|
+
requestAnimationFrame(() => {
|
|
547
|
+
const textWidth = target.scrollWidth
|
|
548
|
+
const containerWidth = this.dom.offsetWidth
|
|
549
|
+
const scrollDistance = textWidth - containerWidth
|
|
550
|
+
|
|
551
|
+
if (scrollDistance > 0) {
|
|
552
|
+
// 50 pixels per second = 20ms per pixel
|
|
553
|
+
const duration = Math.max(scrollDistance / 50, 3)
|
|
554
|
+
target.style.animation = `qttext-marquee ${duration}s linear infinite`
|
|
555
|
+
} else {
|
|
556
|
+
target.style.animation = ''
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Wrap text content in a span for marquee animation (deprecated - now uses _innerSpan)
|
|
562
|
+
_wrapTextForMarquee() {
|
|
563
|
+
// No longer needed - text is already in _innerSpan
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Set maximum number of lines
|
|
567
|
+
setLines(lines) {
|
|
568
|
+
if (!this.dom) return
|
|
569
|
+
this._lines = lines
|
|
570
|
+
|
|
571
|
+
const target = this._innerSpan || this.dom
|
|
572
|
+
|
|
573
|
+
if (lines && lines > 1) {
|
|
574
|
+
// Multi-line mode with line clamping
|
|
575
|
+
target.style.display = '-webkit-box'
|
|
576
|
+
target.style.webkitLineClamp = String(lines)
|
|
577
|
+
target.style.webkitBoxOrient = 'vertical'
|
|
578
|
+
target.style.overflow = 'hidden'
|
|
579
|
+
target.style.whiteSpace = 'normal'
|
|
580
|
+
target.style.textOverflow = 'ellipsis'
|
|
581
|
+
target.style.maxWidth = '100%'
|
|
582
|
+
|
|
583
|
+
// Keep flex container if gravity or textAlignVertical is set
|
|
584
|
+
if (this._gravity || this._textAlignVertical) {
|
|
585
|
+
this.dom.style.display = 'flex'
|
|
586
|
+
if (this._textAlignVertical) {
|
|
587
|
+
this.setTextAlignVertical(this._textAlignVertical)
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} else if (lines === 1) {
|
|
591
|
+
// Single line mode - use ellipsizeMode if set
|
|
592
|
+
target.style.display = 'inline-block'
|
|
593
|
+
target.style.whiteSpace = 'nowrap'
|
|
594
|
+
target.style.overflow = 'hidden'
|
|
595
|
+
target.style.webkitLineClamp = ''
|
|
596
|
+
|
|
597
|
+
// Keep flex display if gravity or textAlignVertical is set
|
|
598
|
+
if (this._gravity || this._textAlignVertical) {
|
|
599
|
+
this.dom.style.display = 'flex'
|
|
600
|
+
this.dom.style.overflow = 'hidden'
|
|
601
|
+
if (this._textAlignVertical) {
|
|
602
|
+
this.setTextAlignVertical(this._textAlignVertical)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Re-apply ellipsizeMode if it was set
|
|
607
|
+
if (this._ellipsizeMode !== undefined) {
|
|
608
|
+
this.setEllipsizeMode(this._ellipsizeMode)
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Apply flexStyle object to the component
|
|
614
|
+
_applyFlexStyle(styleObj) {
|
|
615
|
+
if (!styleObj || typeof styleObj !== 'object') return
|
|
616
|
+
|
|
617
|
+
const target = this._innerSpan || this.dom
|
|
618
|
+
|
|
619
|
+
Object.keys(styleObj).forEach((key) => {
|
|
620
|
+
let value = styleObj[key]
|
|
621
|
+
|
|
622
|
+
// Convert numeric values to pixels for appropriate properties
|
|
623
|
+
if (typeof value === 'number') {
|
|
624
|
+
const needsPx = [
|
|
625
|
+
'width',
|
|
626
|
+
'height',
|
|
627
|
+
'minWidth',
|
|
628
|
+
'minHeight',
|
|
629
|
+
'maxWidth',
|
|
630
|
+
'maxHeight',
|
|
631
|
+
'padding',
|
|
632
|
+
'paddingTop',
|
|
633
|
+
'paddingRight',
|
|
634
|
+
'paddingBottom',
|
|
635
|
+
'paddingLeft',
|
|
636
|
+
'margin',
|
|
637
|
+
'marginTop',
|
|
638
|
+
'marginRight',
|
|
639
|
+
'marginBottom',
|
|
640
|
+
'marginLeft',
|
|
641
|
+
'top',
|
|
642
|
+
'left',
|
|
643
|
+
'right',
|
|
644
|
+
'bottom',
|
|
645
|
+
'borderRadius',
|
|
646
|
+
'borderWidth',
|
|
647
|
+
'fontSize',
|
|
648
|
+
'lineHeight',
|
|
649
|
+
'letterSpacing',
|
|
650
|
+
].includes(key)
|
|
651
|
+
|
|
652
|
+
if (needsPx) {
|
|
653
|
+
value = value + 'px'
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Handle fontSize specially - apply to inner span
|
|
658
|
+
if (key === 'fontSize') {
|
|
659
|
+
target.style.fontSize = value
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Handle textColor specially
|
|
664
|
+
if (key === 'textColor' || key === 'color') {
|
|
665
|
+
this.setTextColor(value)
|
|
666
|
+
return
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
try {
|
|
670
|
+
this.dom.style[key] = value
|
|
671
|
+
} catch (e) {
|
|
672
|
+
try {
|
|
673
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
674
|
+
this.dom.style.setProperty(cssKey, value)
|
|
675
|
+
} catch (err) {}
|
|
676
|
+
}
|
|
677
|
+
})
|
|
678
|
+
}
|
|
679
|
+
}
|