@quicktvui/web-renderer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/package.json +24 -0
  2. package/src/adapters/es3-video-player.js +828 -0
  3. package/src/components/Modal.js +119 -0
  4. package/src/components/QtAnimationView.js +678 -0
  5. package/src/components/QtBaseComponent.js +165 -0
  6. package/src/components/QtFastListView.js +1920 -0
  7. package/src/components/QtFlexView.js +799 -0
  8. package/src/components/QtImage.js +203 -0
  9. package/src/components/QtItemFrame.js +239 -0
  10. package/src/components/QtItemStoreView.js +93 -0
  11. package/src/components/QtItemView.js +125 -0
  12. package/src/components/QtListView.js +331 -0
  13. package/src/components/QtLoadingView.js +55 -0
  14. package/src/components/QtPageRootView.js +19 -0
  15. package/src/components/QtPlayMark.js +168 -0
  16. package/src/components/QtProgressBar.js +199 -0
  17. package/src/components/QtQRCode.js +78 -0
  18. package/src/components/QtReplaceChild.js +149 -0
  19. package/src/components/QtRippleView.js +166 -0
  20. package/src/components/QtSeekBar.js +409 -0
  21. package/src/components/QtText.js +679 -0
  22. package/src/components/QtTransitionImage.js +170 -0
  23. package/src/components/QtView.js +706 -0
  24. package/src/components/QtWebView.js +613 -0
  25. package/src/components/TabsView.js +420 -0
  26. package/src/components/ViewPager.js +206 -0
  27. package/src/components/index.js +24 -0
  28. package/src/components/plugins/TextV2Component.js +70 -0
  29. package/src/components/plugins/index.js +7 -0
  30. package/src/core/SceneBuilder.js +58 -0
  31. package/src/core/TVFocusManager.js +2014 -0
  32. package/src/core/asyncLocalStorage.js +175 -0
  33. package/src/core/autoProxy.js +165 -0
  34. package/src/core/componentRegistry.js +84 -0
  35. package/src/core/constants.js +6 -0
  36. package/src/core/index.js +8 -0
  37. package/src/core/moduleUtils.js +36 -0
  38. package/src/core/patches.js +958 -0
  39. package/src/core/templateBinding.js +666 -0
  40. package/src/index.js +246 -0
  41. package/src/modules/AndroidDevelopModule.js +101 -0
  42. package/src/modules/AndroidDeviceModule.js +341 -0
  43. package/src/modules/AndroidNetworkModule.js +178 -0
  44. package/src/modules/AndroidSharedPreferencesModule.js +100 -0
  45. package/src/modules/ESDeviceInfoModule.js +450 -0
  46. package/src/modules/ESGroupDataModule.js +195 -0
  47. package/src/modules/ESIJKAudioPlayerModule.js +477 -0
  48. package/src/modules/ESLocalStorageModule.js +100 -0
  49. package/src/modules/ESLogModule.js +65 -0
  50. package/src/modules/ESModule.js +106 -0
  51. package/src/modules/ESNetworkSpeedModule.js +117 -0
  52. package/src/modules/ESToastModule.js +172 -0
  53. package/src/modules/EsNativeModule.js +117 -0
  54. package/src/modules/FastListModule.js +101 -0
  55. package/src/modules/FocusModule.js +145 -0
  56. package/src/modules/RuntimeDeviceModule.js +176 -0
@@ -0,0 +1,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
+ }