@lingxia/elements 0.5.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 (46) hide show
  1. package/dist/component.d.ts +50 -0
  2. package/dist/component.js +139 -0
  3. package/dist/dom.d.ts +10 -0
  4. package/dist/dom.js +70 -0
  5. package/dist/index.d.ts +9 -0
  6. package/dist/index.js +9 -0
  7. package/dist/input.d.ts +117 -0
  8. package/dist/input.js +699 -0
  9. package/dist/native_component_wrapper_shared.d.ts +74 -0
  10. package/dist/native_component_wrapper_shared.js +193 -0
  11. package/dist/nativecomponent.d.ts +6 -0
  12. package/dist/nativecomponent.js +82 -0
  13. package/dist/navigator.d.ts +83 -0
  14. package/dist/navigator.js +388 -0
  15. package/dist/picker-web.d.ts +2 -0
  16. package/dist/picker-web.js +727 -0
  17. package/dist/picker.d.ts +77 -0
  18. package/dist/picker.js +342 -0
  19. package/dist/platform.d.ts +6 -0
  20. package/dist/platform.js +24 -0
  21. package/dist/text_component_native_attrs.d.ts +53 -0
  22. package/dist/text_component_native_attrs.js +111 -0
  23. package/dist/text_component_shared.d.ts +7 -0
  24. package/dist/text_component_shared.js +148 -0
  25. package/dist/textarea.d.ts +129 -0
  26. package/dist/textarea.js +841 -0
  27. package/dist/types.d.ts +21 -0
  28. package/dist/types.js +1 -0
  29. package/dist/video.d.ts +134 -0
  30. package/dist/video.js +741 -0
  31. package/package.json +39 -0
  32. package/src/component.ts +164 -0
  33. package/src/dom.ts +83 -0
  34. package/src/index.ts +69 -0
  35. package/src/input.ts +799 -0
  36. package/src/native_component_wrapper_shared.ts +243 -0
  37. package/src/nativecomponent.ts +97 -0
  38. package/src/navigator.ts +533 -0
  39. package/src/picker-web.ts +719 -0
  40. package/src/picker.ts +418 -0
  41. package/src/platform.ts +25 -0
  42. package/src/text_component_native_attrs.ts +137 -0
  43. package/src/text_component_shared.ts +147 -0
  44. package/src/textarea.ts +959 -0
  45. package/src/types.ts +20 -0
  46. package/src/video.ts +855 -0
package/dist/video.js ADDED
@@ -0,0 +1,741 @@
1
+ import { sendNativeComponentMessage, registerNativeComponentHandler, addNativeComponentLayoutInvalidationListener } from "./nativecomponent.js";
2
+ import { measureElement } from "./dom.js";
3
+ import { ensureComponentId, NativeComponentUpdateState, iOSNativeComponentHelper } from "./component.js";
4
+ import { isAndroid, isHarmony, isIOS } from "./platform.js";
5
+ const HARMONY_PROPS_PREFIX = "data:application/json,";
6
+ // Component implementation
7
+ export class LxVideoElement extends HTMLElement {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.componentId = null;
11
+ this.mounted = false;
12
+ this.updateState = new NativeComponentUpdateState();
13
+ this.pendingLayoutFrame = null;
14
+ this.boundUpdatePosition = this.updatePosition.bind(this);
15
+ this._handlers = {};
16
+ this.pendingHarmonyRetryTimer = null;
17
+ this.pendingHarmonyRetryCount = 0;
18
+ this.iOSBootstrapFrame = null;
19
+ this.iOSBootstrapRemaining = 0;
20
+ this.forceHarmonyEmbedRecreate = false;
21
+ this.resizeEventListener = {
22
+ handleEvent: () => this.boundUpdatePosition(),
23
+ };
24
+ this.iOSScrollEventListener = {
25
+ handleEvent: () => this.boundUpdatePosition(),
26
+ };
27
+ this.rawHandlers = {};
28
+ this._pageBindings = {};
29
+ }
30
+ static get observedAttributes() {
31
+ return [
32
+ "id",
33
+ "src",
34
+ "poster",
35
+ "object-fit",
36
+ "rotate",
37
+ "autoplay",
38
+ "loop",
39
+ "muted",
40
+ "controls",
41
+ "progress-bar",
42
+ "live",
43
+ "volume",
44
+ "qualities",
45
+ "playback-rates"
46
+ ];
47
+ }
48
+ parseRotateValue(value) {
49
+ const parsed = (() => {
50
+ if (typeof value === "number") {
51
+ return Number.isInteger(value) ? value : NaN;
52
+ }
53
+ if (typeof value === "string") {
54
+ const normalized = value.trim();
55
+ if (!/^(0|90|180|270)$/.test(normalized))
56
+ return NaN;
57
+ return Number(normalized);
58
+ }
59
+ return NaN;
60
+ })();
61
+ if (parsed === 0 || parsed === 90 || parsed === 180 || parsed === 270) {
62
+ return parsed;
63
+ }
64
+ return undefined;
65
+ }
66
+ parseObjectFitValue(value) {
67
+ if (typeof value !== "string")
68
+ return undefined;
69
+ const normalized = value.trim().toLowerCase();
70
+ if (normalized === "cover" ||
71
+ normalized === "contain" ||
72
+ normalized === "fill" ||
73
+ normalized === "fit") {
74
+ return normalized;
75
+ }
76
+ return undefined;
77
+ }
78
+ set pageBindings(bindings) {
79
+ this._pageBindings = bindings !== null && bindings !== void 0 ? bindings : {};
80
+ if (this.isConnected) {
81
+ this.mountOrUpdate(true);
82
+ }
83
+ }
84
+ get pageBindings() {
85
+ return this._pageBindings;
86
+ }
87
+ dataAttrToDatasetKey(attr) {
88
+ const raw = attr.slice(5).trim();
89
+ if (!raw)
90
+ return "";
91
+ const parts = raw.split("-").filter(Boolean);
92
+ if (parts.length === 0)
93
+ return "";
94
+ return parts
95
+ .map((segment, index) => {
96
+ if (index === 0)
97
+ return segment.toLowerCase();
98
+ return segment.charAt(0).toUpperCase() + segment.slice(1);
99
+ })
100
+ .join("");
101
+ }
102
+ collectDataset() {
103
+ const dataset = {};
104
+ const attrs = this.getAttributeNames();
105
+ for (const attr of attrs) {
106
+ if (!attr.startsWith("data-"))
107
+ continue;
108
+ const key = this.dataAttrToDatasetKey(attr);
109
+ if (!key)
110
+ continue;
111
+ const value = this.getAttribute(attr);
112
+ if (value == null)
113
+ continue;
114
+ dataset[key] = value;
115
+ }
116
+ return dataset;
117
+ }
118
+ upgradeProperty(propName) {
119
+ const self = this;
120
+ if (!Object.prototype.hasOwnProperty.call(self, propName))
121
+ return;
122
+ const value = self[propName];
123
+ delete self[propName];
124
+ self[propName] = value;
125
+ }
126
+ set rotate(value) {
127
+ const normalized = this.parseRotateValue(value);
128
+ if (normalized === undefined) {
129
+ this.removeAttribute("rotate");
130
+ return;
131
+ }
132
+ this.setAttribute("rotate", String(normalized));
133
+ }
134
+ get rotate() {
135
+ return this.parseRotateValue(this.getAttribute("rotate"));
136
+ }
137
+ connectedCallback() {
138
+ // React may set custom-element properties before upgrade; replay through setters.
139
+ this.upgradeProperty("pageBindings");
140
+ this.upgradeProperty("rotate");
141
+ this.upgradeProperty("onplayrequest");
142
+ this.upgradeProperty("onplay");
143
+ this.upgradeProperty("onplaying");
144
+ this.upgradeProperty("onpause");
145
+ this.upgradeProperty("onstop");
146
+ this.upgradeProperty("onended");
147
+ this.upgradeProperty("ontimeupdate");
148
+ this.upgradeProperty("onloadedmetadata");
149
+ this.upgradeProperty("onfullscreenchange");
150
+ this.upgradeProperty("onwaiting");
151
+ this.upgradeProperty("onqualitychange");
152
+ this.upgradeProperty("onratechange");
153
+ this.componentId = ensureComponentId(this, "lx-video", this.componentId);
154
+ if (!this.componentId) {
155
+ return;
156
+ }
157
+ this.unregister = registerNativeComponentHandler(this.componentId, (message) => {
158
+ // Handle component events from native
159
+ if (message.event) {
160
+ // Normalize detail based on event type
161
+ let detail = message.detail || message.payload || {};
162
+ // Ensure common state events have empty detail if not provided
163
+ if (['playrequest', 'play', 'playing', 'pause', 'stop', 'ended', 'waiting'].includes(message.event)) {
164
+ if (Object.keys(detail).length === 0)
165
+ detail = {};
166
+ }
167
+ const ev = new CustomEvent(message.event, {
168
+ detail: detail,
169
+ bubbles: true,
170
+ cancelable: false
171
+ });
172
+ this.dispatchEvent(ev);
173
+ }
174
+ });
175
+ this.ensurePlaceholderStyle();
176
+ // Setup iOS native component rendering helper (no-op on other platforms)
177
+ this.iOSHelper = new iOSNativeComponentHelper(this, this.componentId);
178
+ this.iOSHelper.setup();
179
+ this.startAttrObserver();
180
+ // iOS bootstrap:
181
+ // avoid hardcoded timeout; run an immediate mount plus a few frame-based refreshes.
182
+ if (isIOS()) {
183
+ this.mountOrUpdate(true);
184
+ this.startTracking();
185
+ this.scheduleIOSBootstrapFrames();
186
+ }
187
+ else {
188
+ this.mountOrUpdate();
189
+ this.startTracking();
190
+ }
191
+ }
192
+ disconnectedCallback() {
193
+ var _a;
194
+ this.updateState.reset();
195
+ if (this.componentId && !isHarmony()) {
196
+ sendNativeComponentMessage({
197
+ action: "component.unmount",
198
+ id: this.componentId
199
+ });
200
+ }
201
+ this.stopTracking();
202
+ if (this.unregister) {
203
+ this.unregister();
204
+ this.unregister = undefined;
205
+ }
206
+ if (this.harmonyEmbed && this.contains(this.harmonyEmbed)) {
207
+ this.removeChild(this.harmonyEmbed);
208
+ }
209
+ this.harmonyEmbed = undefined;
210
+ this.lastHarmonyProps = undefined;
211
+ // Cleanup iOS helper
212
+ (_a = this.iOSHelper) === null || _a === void 0 ? void 0 : _a.cleanup();
213
+ this.iOSHelper = undefined;
214
+ this.stopAttrObserver();
215
+ // Cleanup manually attached property handlers
216
+ Object.keys(this._handlers).forEach(name => {
217
+ this.removeEventListener(name, this._handlers[name]);
218
+ });
219
+ this._handlers = {};
220
+ this.rawHandlers = {};
221
+ }
222
+ attributeChangedCallback(name) {
223
+ if (name === "id") {
224
+ const prev = this.componentId;
225
+ this.componentId = ensureComponentId(this, "lx-video", this.componentId);
226
+ if (prev && this.componentId !== prev) {
227
+ this.mounted = false;
228
+ this.updateState.reset();
229
+ }
230
+ }
231
+ if (!this.isConnected)
232
+ return;
233
+ // Keep rotate/object-fit updates immediate across all native platforms.
234
+ const forcePropsUpdate = name === "rotate" || name === "object-fit" || name === "objectfit";
235
+ if (isHarmony() && forcePropsUpdate) {
236
+ // Bridge callbacks can race on Harmony; force a deterministic embed recreate for visual props.
237
+ this.forceHarmonyEmbedRecreate = true;
238
+ }
239
+ this.mountOrUpdate(forcePropsUpdate);
240
+ }
241
+ isEventListener(value) {
242
+ if (typeof value === "function")
243
+ return true;
244
+ if (!value || typeof value !== "object")
245
+ return false;
246
+ return typeof value.handleEvent === "function";
247
+ }
248
+ setEventHandler(name, value) {
249
+ const current = this._handlers[name];
250
+ if (current) {
251
+ this.removeEventListener(name, current);
252
+ }
253
+ if (this.isEventListener(value)) {
254
+ const listener = typeof value === "function"
255
+ ? { handleEvent: value }
256
+ : value;
257
+ this._handlers[name] = listener;
258
+ this.rawHandlers[name] = value;
259
+ this.addEventListener(name, listener);
260
+ }
261
+ else {
262
+ delete this._handlers[name];
263
+ delete this.rawHandlers[name];
264
+ }
265
+ }
266
+ getEventHandler(name) {
267
+ return (this.rawHandlers[name] || null);
268
+ }
269
+ // Standard Media Events for React Props (e.g. <lx-video onPlay={...} />)
270
+ set onplayrequest(cb) { this.setEventHandler('playrequest', cb); }
271
+ get onplayrequest() { return this.getEventHandler('playrequest'); }
272
+ set onplay(cb) { this.setEventHandler('play', cb); }
273
+ get onplay() { return this.getEventHandler('play'); }
274
+ set onplaying(cb) { this.setEventHandler('playing', cb); }
275
+ get onplaying() { return this.getEventHandler('playing'); }
276
+ set onpause(cb) { this.setEventHandler('pause', cb); }
277
+ get onpause() { return this.getEventHandler('pause'); }
278
+ set onstop(cb) { this.setEventHandler('stop', cb); }
279
+ get onstop() { return this.getEventHandler('stop'); }
280
+ set onended(cb) { this.setEventHandler('ended', cb); }
281
+ get onended() { return this.getEventHandler('ended'); }
282
+ set ontimeupdate(cb) { this.setEventHandler('timeupdate', cb); }
283
+ get ontimeupdate() { return this.getEventHandler('timeupdate'); }
284
+ // Note: onerror is inherited from HTMLElement with OnErrorEventHandler type.
285
+ // Use addEventListener('error', handler) for error events instead.
286
+ set onloadedmetadata(cb) { this.setEventHandler('loadedmetadata', cb); }
287
+ get onloadedmetadata() { return this.getEventHandler('loadedmetadata'); }
288
+ set onfullscreenchange(cb) { this.setEventHandler('fullscreenchange', cb); }
289
+ get onfullscreenchange() { return this.getEventHandler('fullscreenchange'); }
290
+ set onwaiting(cb) { this.setEventHandler('waiting', cb); }
291
+ get onwaiting() { return this.getEventHandler('waiting'); }
292
+ set onqualitychange(cb) { this.setEventHandler('qualitychange', cb); }
293
+ get onqualitychange() { return this.getEventHandler('qualitychange'); }
294
+ set onratechange(cb) { this.setEventHandler('ratechange', cb); }
295
+ get onratechange() { return this.getEventHandler('ratechange'); }
296
+ // Internal
297
+ ensurePlaceholderStyle() {
298
+ if (!this.style.display)
299
+ this.style.display = "block";
300
+ if (!this.style.position)
301
+ this.style.position = "relative"; // Needed for iOS scroll container
302
+ if (!this.style.backgroundColor)
303
+ this.style.backgroundColor = "black";
304
+ if (!this.style.aspectRatio)
305
+ this.style.aspectRatio = "16 / 9";
306
+ }
307
+ collectProps() {
308
+ var _a;
309
+ const volumeAttr = this.getAttribute("volume");
310
+ const volume = volumeAttr != null ? parseFloat(volumeAttr) : undefined;
311
+ const rawObjectFit = (_a = this.getAttribute("object-fit")) !== null && _a !== void 0 ? _a : this.getAttribute("objectfit");
312
+ const objectFit = this.parseObjectFitValue(rawObjectFit);
313
+ // JSON-encoded arrays (React wrapper sets these via JSON.stringify)
314
+ const qualitiesAttr = this.getAttribute("qualities");
315
+ let qualities;
316
+ if (qualitiesAttr) {
317
+ try {
318
+ const parsed = JSON.parse(qualitiesAttr);
319
+ if (Array.isArray(parsed)) {
320
+ qualities = parsed.filter((q) => q && typeof q === "object" && typeof q.label === "string");
321
+ }
322
+ }
323
+ catch { }
324
+ }
325
+ // Parse playbackRates array from JSON attribute
326
+ const playbackRatesAttr = this.getAttribute("playback-rates");
327
+ let playbackRates;
328
+ if (playbackRatesAttr) {
329
+ try {
330
+ const parsed = JSON.parse(playbackRatesAttr);
331
+ if (Array.isArray(parsed)) {
332
+ playbackRates = parsed.filter((n) => typeof n === "number");
333
+ }
334
+ }
335
+ catch { }
336
+ }
337
+ const isLive = this.hasAttribute("live");
338
+ // When live, default progressBar to false unless explicitly enabled.
339
+ const progressBarAttr = this.getAttribute("progress-bar");
340
+ const progressBar = isLive
341
+ ? (progressBarAttr !== null && progressBarAttr !== "false")
342
+ : (progressBarAttr !== "false");
343
+ const rawRotate = this.getAttribute("rotate");
344
+ const validRotate = this.parseRotateValue(rawRotate);
345
+ const pageFuncBindings = this._pageBindings;
346
+ const dataset = this.collectDataset();
347
+ const clearProps = [];
348
+ if (rawRotate === null || validRotate === undefined) {
349
+ clearProps.push("rotate");
350
+ }
351
+ if (rawObjectFit == null || objectFit === undefined) {
352
+ clearProps.push("objectFit");
353
+ }
354
+ return {
355
+ src: this.getAttribute("src") || undefined,
356
+ poster: this.getAttribute("poster") || undefined,
357
+ autoplay: this.hasAttribute("autoplay"),
358
+ loop: this.hasAttribute("loop"),
359
+ muted: this.hasAttribute("muted"),
360
+ controls: this.hasAttribute("controls"),
361
+ progressBar,
362
+ live: isLive,
363
+ volume: !Number.isNaN(volume !== null && volume !== void 0 ? volume : NaN) ? volume : undefined,
364
+ objectFit,
365
+ rotate: validRotate,
366
+ ...(clearProps.length > 0 ? { __clearProps: clearProps } : {}),
367
+ qualities,
368
+ playbackRates,
369
+ dataset,
370
+ datasetJson: JSON.stringify(dataset),
371
+ pageFuncBindings,
372
+ pageFuncBindingsJson: JSON.stringify(pageFuncBindings)
373
+ };
374
+ }
375
+ shouldRefreshForAttribute(name) {
376
+ const normalized = name.trim().toLowerCase();
377
+ return normalized.startsWith("data-");
378
+ }
379
+ startAttrObserver() {
380
+ if (typeof MutationObserver === "undefined" || this.attrObserver) {
381
+ return;
382
+ }
383
+ this.attrObserver = new MutationObserver((mutations) => {
384
+ for (const mutation of mutations) {
385
+ const attrName = mutation.attributeName;
386
+ if (!attrName || !this.shouldRefreshForAttribute(attrName)) {
387
+ continue;
388
+ }
389
+ this.mountOrUpdate(true);
390
+ break;
391
+ }
392
+ });
393
+ this.attrObserver.observe(this, { attributes: true });
394
+ }
395
+ stopAttrObserver() {
396
+ if (!this.attrObserver) {
397
+ return;
398
+ }
399
+ this.attrObserver.disconnect();
400
+ this.attrObserver = undefined;
401
+ }
402
+ measureForNative() {
403
+ const measured = measureElement(this);
404
+ if (!isIOS()) {
405
+ return measured;
406
+ }
407
+ const rect = this.getBoundingClientRect();
408
+ return {
409
+ rect: {
410
+ x: rect.left,
411
+ y: rect.top,
412
+ width: rect.width,
413
+ height: rect.height
414
+ },
415
+ cornerRadius: measured.cornerRadius
416
+ };
417
+ }
418
+ mountOrUpdate(forceUpdate = false) {
419
+ var _a;
420
+ if (isHarmony()) {
421
+ this.mountOrUpdateHarmony(forceUpdate);
422
+ return;
423
+ }
424
+ if (!this.componentId)
425
+ return;
426
+ const { rect, cornerRadius } = this.measureForNative();
427
+ const hasSize = rect.width > 0 && rect.height > 0;
428
+ if (!hasSize) {
429
+ // Defer and try again once layout stabilizes; only needed before first mount
430
+ if (!this.mounted) {
431
+ this.scheduleMountRetry();
432
+ }
433
+ return;
434
+ }
435
+ const props = this.collectProps();
436
+ if (cornerRadius !== undefined) {
437
+ props.cornerRadius = cornerRadius;
438
+ }
439
+ const zIndex = parseFloat(this.style.zIndex || "0") || 0;
440
+ const decision = this.updateState.decide(rect, props, zIndex, !this.mounted || forceUpdate);
441
+ if (!decision.shouldSend)
442
+ return;
443
+ const payload = {
444
+ action: this.mounted ? "component.update" : "component.mount",
445
+ id: this.componentId,
446
+ type: "video.native",
447
+ rect,
448
+ zIndex,
449
+ ...(cornerRadius !== undefined ? { cornerRadius } : {})
450
+ };
451
+ // Enhance payload with platform-specific data (iOS scroll container rect)
452
+ (_a = this.iOSHelper) === null || _a === void 0 ? void 0 : _a.enhancePayload(payload);
453
+ // Only include props when they changed or on first mount
454
+ if (decision.propsChanged || !this.mounted) {
455
+ payload.props = props;
456
+ }
457
+ sendNativeComponentMessage(payload);
458
+ this.mounted = true;
459
+ }
460
+ updatePosition() {
461
+ var _a;
462
+ if (isHarmony()) {
463
+ this.mountOrUpdateHarmony();
464
+ return;
465
+ }
466
+ if (!this.mounted || !this.componentId)
467
+ return;
468
+ const { rect, cornerRadius } = this.measureForNative();
469
+ if (!rect.width || !rect.height)
470
+ return;
471
+ const zIndex = parseFloat(this.style.zIndex || "0") || 0;
472
+ const decision = this.updateState.decide(rect, null, zIndex);
473
+ if (!decision.shouldSend)
474
+ return;
475
+ const payload = {
476
+ action: "component.update",
477
+ id: this.componentId,
478
+ rect,
479
+ zIndex
480
+ };
481
+ // Keep iOS scroll-container metadata on update so native side can still
482
+ // promote fallback overlay into WKChildScrollView when it becomes available.
483
+ (_a = this.iOSHelper) === null || _a === void 0 ? void 0 : _a.enhancePayload(payload);
484
+ if (cornerRadius !== undefined) {
485
+ payload.cornerRadius = cornerRadius;
486
+ }
487
+ sendNativeComponentMessage(payload);
488
+ }
489
+ /**
490
+ * Start tracking component size changes.
491
+ */
492
+ startTracking() {
493
+ window.addEventListener("resize", this.resizeEventListener);
494
+ if (isIOS()) {
495
+ window.addEventListener("scroll", this.iOSScrollEventListener, { passive: true });
496
+ }
497
+ if (isAndroid()) {
498
+ this.removeLayoutInvalidationListener = addNativeComponentLayoutInvalidationListener(this.resizeEventListener);
499
+ }
500
+ this.startSizeObserver();
501
+ this.updatePosition();
502
+ }
503
+ stopTracking() {
504
+ var _a;
505
+ window.removeEventListener("resize", this.resizeEventListener);
506
+ if (isIOS()) {
507
+ window.removeEventListener("scroll", this.iOSScrollEventListener);
508
+ }
509
+ (_a = this.removeLayoutInvalidationListener) === null || _a === void 0 ? void 0 : _a.call(this);
510
+ this.removeLayoutInvalidationListener = undefined;
511
+ this.stopSizeObserver();
512
+ if (this.pendingLayoutFrame !== null) {
513
+ cancelAnimationFrame(this.pendingLayoutFrame);
514
+ this.pendingLayoutFrame = null;
515
+ }
516
+ if (this.pendingHarmonyRetryTimer !== null) {
517
+ clearTimeout(this.pendingHarmonyRetryTimer);
518
+ this.pendingHarmonyRetryTimer = null;
519
+ }
520
+ if (this.iOSBootstrapFrame !== null) {
521
+ cancelAnimationFrame(this.iOSBootstrapFrame);
522
+ this.iOSBootstrapFrame = null;
523
+ }
524
+ this.iOSBootstrapRemaining = 0;
525
+ this.pendingHarmonyProps = undefined;
526
+ this.pendingHarmonyRetryCount = 0;
527
+ }
528
+ startSizeObserver() {
529
+ if (typeof ResizeObserver === "undefined") {
530
+ // ResizeObserver unavailable: retry mount on next frame.
531
+ this.scheduleMountRetry();
532
+ return;
533
+ }
534
+ if (this.resizeObserver)
535
+ return;
536
+ this.resizeObserver = new ResizeObserver((entries) => {
537
+ for (const entry of entries) {
538
+ if (entry.target !== this)
539
+ continue;
540
+ const { width, height } = entry.contentRect;
541
+ if (!width || !height)
542
+ continue;
543
+ if (this.mounted) {
544
+ this.updatePosition();
545
+ }
546
+ else {
547
+ this.mountOrUpdate();
548
+ }
549
+ }
550
+ });
551
+ this.resizeObserver.observe(this);
552
+ }
553
+ stopSizeObserver() {
554
+ if (this.resizeObserver) {
555
+ this.resizeObserver.disconnect();
556
+ this.resizeObserver = undefined;
557
+ }
558
+ }
559
+ scheduleMountRetry() {
560
+ if (this.pendingLayoutFrame !== null || this.mounted)
561
+ return;
562
+ this.pendingLayoutFrame = requestAnimationFrame(() => {
563
+ this.pendingLayoutFrame = null;
564
+ this.mountOrUpdate();
565
+ });
566
+ }
567
+ scheduleIOSBootstrapFrames() {
568
+ if (!isIOS())
569
+ return;
570
+ // Keep a short frame-based bootstrap window so late-created WKChildScrollView
571
+ // can still receive mount/update without relying on hardcoded millisecond delay.
572
+ this.iOSBootstrapRemaining = 8;
573
+ const tick = () => {
574
+ if (!this.isConnected) {
575
+ this.iOSBootstrapFrame = null;
576
+ this.iOSBootstrapRemaining = 0;
577
+ return;
578
+ }
579
+ this.mountOrUpdate(true);
580
+ this.iOSBootstrapRemaining -= 1;
581
+ if (this.iOSBootstrapRemaining <= 0) {
582
+ this.iOSBootstrapFrame = null;
583
+ return;
584
+ }
585
+ this.iOSBootstrapFrame = requestAnimationFrame(tick);
586
+ };
587
+ this.iOSBootstrapFrame = requestAnimationFrame(tick);
588
+ }
589
+ mountOrUpdateHarmony(forceUpdate = false) {
590
+ if (!this.componentId)
591
+ return;
592
+ const { rect, cornerRadius } = measureElement(this);
593
+ const hasSize = rect.width > 0 && rect.height > 0;
594
+ if (!hasSize) {
595
+ if (!this.mounted) {
596
+ this.scheduleMountRetry();
597
+ }
598
+ return;
599
+ }
600
+ const props = this.collectProps();
601
+ if (cornerRadius !== undefined) {
602
+ props.cornerRadius = cornerRadius;
603
+ }
604
+ const zIndex = parseFloat(this.style.zIndex || "0") || 0;
605
+ const decision = this.updateState.decide(rect, props, zIndex, !this.mounted || forceUpdate);
606
+ if (!decision.shouldSend)
607
+ return;
608
+ this.ensureHarmonyEmbed(rect, props, cornerRadius, this.forceHarmonyEmbedRecreate);
609
+ this.forceHarmonyEmbedRecreate = false;
610
+ this.mounted = true;
611
+ }
612
+ ensureHarmonyEmbed(rect, props, cornerRadius, forceRecreate = false) {
613
+ const createEmbed = () => {
614
+ const embed = document.createElement("embed");
615
+ embed.setAttribute("type", "native/video");
616
+ embed.setAttribute("width", `${rect.width}`);
617
+ embed.setAttribute("height", `${rect.height}`);
618
+ embed.setAttribute("id", this.componentId);
619
+ embed.setAttribute("data-lx-component-id", this.componentId);
620
+ embed.style.display = "block";
621
+ embed.style.width = "100%";
622
+ embed.style.height = "100%";
623
+ embed.style.border = "none";
624
+ if (cornerRadius !== undefined) {
625
+ embed.style.borderRadius = `${cornerRadius}px`;
626
+ embed.style.overflow = "hidden";
627
+ }
628
+ const encodedProps = this.encodeHarmonyProps(props);
629
+ embed.setAttribute("src", encodedProps);
630
+ this.lastHarmonyProps = encodedProps;
631
+ return embed;
632
+ };
633
+ if (forceRecreate && this.harmonyEmbed) {
634
+ const oldEmbed = this.harmonyEmbed;
635
+ this.harmonyEmbed = undefined;
636
+ if (this.contains(oldEmbed)) {
637
+ this.removeChild(oldEmbed);
638
+ }
639
+ this.lastHarmonyProps = undefined;
640
+ const recreated = createEmbed();
641
+ this.appendChild(recreated);
642
+ this.harmonyEmbed = recreated;
643
+ this.pushHarmonyPropsUpdate(props);
644
+ this.pendingHarmonyRetryCount = 0;
645
+ return;
646
+ }
647
+ // Create embed element only once - ArkWeb triggers DESTROY+CREATE on attribute changes
648
+ if (!this.harmonyEmbed) {
649
+ const embed = createEmbed();
650
+ this.pushHarmonyPropsUpdate(props);
651
+ this.appendChild(embed);
652
+ this.harmonyEmbed = embed;
653
+ return;
654
+ }
655
+ // Only update props when they changed.
656
+ const encodedProps = this.encodeHarmonyProps(props);
657
+ if (encodedProps !== this.lastHarmonyProps) {
658
+ const pushed = this.pushHarmonyPropsUpdate(props);
659
+ if (pushed) {
660
+ this.lastHarmonyProps = encodedProps;
661
+ this.pendingHarmonyProps = undefined;
662
+ this.pendingHarmonyRetryCount = 0;
663
+ }
664
+ else {
665
+ this.scheduleHarmonyPropsRetry(props);
666
+ }
667
+ }
668
+ // Ensure embed element matches latest corner radius for clipping
669
+ if (cornerRadius !== undefined && this.harmonyEmbed.style.borderRadius !== `${cornerRadius}px`) {
670
+ this.harmonyEmbed.style.borderRadius = `${cornerRadius}px`;
671
+ this.harmonyEmbed.style.overflow = "hidden";
672
+ }
673
+ }
674
+ encodeHarmonyProps(props) {
675
+ try {
676
+ const payload = { componentId: this.componentId, ...props };
677
+ return `${HARMONY_PROPS_PREFIX}${encodeURIComponent(JSON.stringify(payload))}`;
678
+ }
679
+ catch (_e) {
680
+ return `${HARMONY_PROPS_PREFIX}%7B%7D`;
681
+ }
682
+ }
683
+ pushHarmonyPropsUpdate(props) {
684
+ if (!this.componentId || !isHarmony())
685
+ return false;
686
+ try {
687
+ const proxy = window.LingXiaProxy;
688
+ const updateFn = proxy === null || proxy === void 0 ? void 0 : proxy.nativeComponentUpdate;
689
+ if (typeof updateFn !== "function") {
690
+ return false;
691
+ }
692
+ const payload = JSON.stringify({ componentId: this.componentId, ...props });
693
+ try {
694
+ updateFn.call(proxy, payload);
695
+ return true;
696
+ }
697
+ catch {
698
+ try {
699
+ updateFn.call(proxy, this.componentId, payload);
700
+ return true;
701
+ }
702
+ catch {
703
+ return false;
704
+ }
705
+ }
706
+ }
707
+ catch (error) {
708
+ return false;
709
+ }
710
+ }
711
+ scheduleHarmonyPropsRetry(props) {
712
+ this.pendingHarmonyProps = { ...props };
713
+ if (this.pendingHarmonyRetryTimer !== null) {
714
+ return;
715
+ }
716
+ this.pendingHarmonyRetryTimer = window.setTimeout(() => {
717
+ this.pendingHarmonyRetryTimer = null;
718
+ if (!this.isConnected || !this.pendingHarmonyProps) {
719
+ return;
720
+ }
721
+ const pendingProps = this.pendingHarmonyProps;
722
+ const pushed = this.pushHarmonyPropsUpdate(pendingProps);
723
+ if (pushed) {
724
+ this.lastHarmonyProps = this.encodeHarmonyProps(pendingProps);
725
+ this.pendingHarmonyProps = undefined;
726
+ this.pendingHarmonyRetryCount = 0;
727
+ }
728
+ else {
729
+ this.pendingHarmonyRetryCount += 1;
730
+ if (this.pendingHarmonyRetryCount < 20) {
731
+ this.scheduleHarmonyPropsRetry(pendingProps);
732
+ }
733
+ }
734
+ }, 100);
735
+ }
736
+ }
737
+ export function registerVideoComponent() {
738
+ if (!customElements.get("lx-video")) {
739
+ customElements.define("lx-video", LxVideoElement);
740
+ }
741
+ }