@lingxia/elements 0.9.0 → 0.11.1

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/dist/index.d.ts CHANGED
@@ -1,10 +1,7 @@
1
1
  export { registerVideoComponent, LxVideoElement, type LxVideoAttributes, } from "./video.js";
2
2
  export { registerMediaSwiperComponent, LxMediaSwiperElement, type LxMediaSwiperAttributes, type LxMediaSwiperItem, type LxMediaSwiperChangeEvent, type LxMediaSwiperChangeEventDetail, type LxMediaSwiperTransitionEndEvent, type LxMediaSwiperTransitionEndEventDetail, type LxMediaSwiperItemEvent, type LxMediaSwiperEventDetail, type LxMediaSwiperEndReachedEvent, type LxMediaSwiperEndReachedEventDetail, type LxMediaSwiperErrorEvent, type LxMediaSwiperErrorEventDetail, type LxMediaSwiperChangeSource, type LxMediaSwiperErrorCode, } from "./media_swiper.js";
3
3
  export { registerPickerComponent, LxPickerElement, type LxPickerAttributes, type LxPickerColumn, type LxPickerCascadingColumns, type LxPickerEvent, type LxPickerEventDetail, } from "./picker.js";
4
- export { registerInputComponent, LxInputElement, type LxInputAttributes, type LxInputEvent, type LxInputEventDetail, } from "./input.js";
5
- export { registerTextareaComponent, LxTextareaElement, type LxTextareaAttributes, type LxTextareaEvent, type LxTextareaEventDetail, } from "./textarea.js";
6
4
  export { LxNavigatorElement, type LxNavigatorAttributes, type LxNavigatorEvent, type NavigatorEnvVersion, type NavigatorOpenType, type NavigatorQuery, type NavigatorQueryValue, type NavigatorTarget, } from "./navigator.js";
7
5
  export { buildVideoNativeAttrs, buildMediaSwiperNativeAttrs, buildNavigatorNativeAttrs, buildPickerNativeAttrs, getPickerDisplayText, getPickerValueFromIndex, VIDEO_DOM_EVENT_MAP, MEDIA_SWIPER_DOM_EVENT_MAP, } from "./native_component_wrapper_shared.js";
8
- export { buildInputNativeAttrs, buildTextareaNativeAttrs, } from "./text_component_native_attrs.js";
9
6
  export { isHarmony, isIOS, isAndroid, isMacOS, isDesktop, getOS, } from "./platform.js";
10
7
  export { ensureComponentId, rectEquals, NativeComponentUpdateState, type Rect, } from "./component.js";
package/dist/index.js CHANGED
@@ -1,10 +1,7 @@
1
1
  export { registerVideoComponent, LxVideoElement, } from "./video.js";
2
2
  export { registerMediaSwiperComponent, LxMediaSwiperElement, } from "./media_swiper.js";
3
3
  export { registerPickerComponent, LxPickerElement, } from "./picker.js";
4
- export { registerInputComponent, LxInputElement, } from "./input.js";
5
- export { registerTextareaComponent, LxTextareaElement, } from "./textarea.js";
6
4
  export { LxNavigatorElement, } from "./navigator.js";
7
5
  export { buildVideoNativeAttrs, buildMediaSwiperNativeAttrs, buildNavigatorNativeAttrs, buildPickerNativeAttrs, getPickerDisplayText, getPickerValueFromIndex, VIDEO_DOM_EVENT_MAP, MEDIA_SWIPER_DOM_EVENT_MAP, } from "./native_component_wrapper_shared.js";
8
- export { buildInputNativeAttrs, buildTextareaNativeAttrs, } from "./text_component_native_attrs.js";
9
6
  export { isHarmony, isIOS, isAndroid, isMacOS, isDesktop, getOS, } from "./platform.js";
10
7
  export { ensureComponentId, rectEquals, NativeComponentUpdateState, } from "./component.js";
@@ -2,5 +2,6 @@ export declare function isHarmony(): boolean;
2
2
  export declare function isIOS(): boolean;
3
3
  export declare function isAndroid(): boolean;
4
4
  export declare function isMacOS(): boolean;
5
+ export declare function isWindows(): boolean;
5
6
  export declare function isDesktop(): boolean;
6
7
  export declare function getOS(): string;
package/dist/platform.js CHANGED
@@ -14,6 +14,10 @@ export function isMacOS() {
14
14
  var _a, _b, _c;
15
15
  return ((_c = (_b = (_a = window.LingXiaBridge) === null || _a === void 0 ? void 0 : _a.platform) === null || _b === void 0 ? void 0 : _b.isMacOS) === null || _c === void 0 ? void 0 : _c.call(_b)) === true;
16
16
  }
17
+ export function isWindows() {
18
+ var _a, _b, _c;
19
+ return ((_c = (_b = (_a = window.LingXiaBridge) === null || _a === void 0 ? void 0 : _a.platform) === null || _b === void 0 ? void 0 : _b.isWindows) === null || _c === void 0 ? void 0 : _c.call(_b)) === true;
20
+ }
17
21
  export function isDesktop() {
18
22
  var _a, _b, _c;
19
23
  return ((_c = (_b = (_a = window.LingXiaBridge) === null || _a === void 0 ? void 0 : _a.platform) === null || _b === void 0 ? void 0 : _b.isDesktop) === null || _c === void 0 ? void 0 : _c.call(_b)) === true;
package/dist/video.d.ts CHANGED
@@ -50,6 +50,7 @@ export declare class LxVideoElement extends HTMLElement {
50
50
  private updateState;
51
51
  private unregister?;
52
52
  private resizeObserver?;
53
+ private winSettleFrame;
53
54
  private pendingLayoutFrame;
54
55
  private boundUpdatePosition;
55
56
  private _handlers;
@@ -66,6 +67,8 @@ export declare class LxVideoElement extends HTMLElement {
66
67
  private attrObserver?;
67
68
  private resizeEventListener;
68
69
  private iOSScrollEventListener;
70
+ private scrollSettleFrame;
71
+ private containerScrollListener;
69
72
  private rawHandlers;
70
73
  private _pageBindings;
71
74
  private parseRotateValue;
@@ -110,6 +113,7 @@ export declare class LxVideoElement extends HTMLElement {
110
113
  set onratechange(cb: EventListener);
111
114
  get onratechange(): EventListener;
112
115
  private ensurePlaceholderStyle;
116
+ private syncPosterPlaceholder;
113
117
  private collectProps;
114
118
  private shouldRefreshForAttribute;
115
119
  private startAttrObserver;
@@ -125,6 +129,7 @@ export declare class LxVideoElement extends HTMLElement {
125
129
  private startSizeObserver;
126
130
  private stopSizeObserver;
127
131
  private scheduleMountRetry;
132
+ private scheduleWindowsMountSettle;
128
133
  private scheduleIOSBootstrapFrames;
129
134
  private mountOrUpdateHarmony;
130
135
  private ensureHarmonyEmbed;
package/dist/video.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { sendNativeComponentMessage, registerNativeComponentHandler, addNativeComponentLayoutInvalidationListener } from "./nativecomponent.js";
2
2
  import { measureElement } from "./dom.js";
3
3
  import { ensureComponentId, NativeComponentUpdateState, iOSNativeComponentHelper } from "./component.js";
4
- import { isAndroid, isHarmony, isIOS } from "./platform.js";
4
+ import { isAndroid, isHarmony, isIOS, isWindows } from "./platform.js";
5
5
  const HARMONY_PROPS_PREFIX = "data:application/json,";
6
6
  // Component implementation
7
7
  export class LxVideoElement extends HTMLElement {
@@ -10,6 +10,7 @@ export class LxVideoElement extends HTMLElement {
10
10
  this.componentId = null;
11
11
  this.mounted = false;
12
12
  this.updateState = new NativeComponentUpdateState();
13
+ this.winSettleFrame = null;
13
14
  this.pendingLayoutFrame = null;
14
15
  this.boundUpdatePosition = this.updatePosition.bind(this);
15
16
  this._handlers = {};
@@ -24,6 +25,21 @@ export class LxVideoElement extends HTMLElement {
24
25
  this.iOSScrollEventListener = {
25
26
  handleEvent: () => this.boundUpdatePosition(),
26
27
  };
28
+ this.scrollSettleFrame = null;
29
+ // Scroll events do not bubble, so a plain window listener misses inner
30
+ // scroll containers; the capture phase sees them all. Any ancestor
31
+ // scrolling moves the element's document rect, so re-measure (coalesced
32
+ // to one update per frame; decide() dedups unchanged rects).
33
+ this.containerScrollListener = {
34
+ handleEvent: () => {
35
+ if (this.scrollSettleFrame !== null)
36
+ return;
37
+ this.scrollSettleFrame = requestAnimationFrame(() => {
38
+ this.scrollSettleFrame = null;
39
+ this.updatePosition();
40
+ });
41
+ },
42
+ };
27
43
  this.rawHandlers = {};
28
44
  this._pageBindings = {};
29
45
  }
@@ -198,6 +214,17 @@ export class LxVideoElement extends HTMLElement {
198
214
  else {
199
215
  this.mountOrUpdate();
200
216
  this.startTracking();
217
+ // Windows only: the WebView2 surface is created at an intermediate size
218
+ // and resized to its final content bounds shortly after this element
219
+ // mounts, which reflows the page and moves the element. Unlike the other
220
+ // platforms' webviews, WebView2 doesn't reliably fire the resize /
221
+ // ResizeObserver that would make us re-report, and an early update can be
222
+ // lost in the bridge transport during page load — so the native overlay
223
+ // can stay pinned at the mount-time rect. Re-measure + re-report across the
224
+ // settle window to land the final position. No-op on every other platform.
225
+ if (isWindows()) {
226
+ this.scheduleWindowsMountSettle();
227
+ }
201
228
  }
202
229
  }
203
230
  disconnectedCallback() {
@@ -241,6 +268,9 @@ export class LxVideoElement extends HTMLElement {
241
268
  }
242
269
  if (!this.isConnected)
243
270
  return;
271
+ if (name === "poster" || name === "object-fit" || name === "objectfit") {
272
+ this.syncPosterPlaceholder();
273
+ }
244
274
  // Keep content-rotate/object-fit updates immediate across all native platforms.
245
275
  const forcePropsUpdate = name === "content-rotate" || name === "object-fit" || name === "objectfit";
246
276
  if (isHarmony() && forcePropsUpdate) {
@@ -314,6 +344,24 @@ export class LxVideoElement extends HTMLElement {
314
344
  this.style.backgroundColor = "black";
315
345
  if (!this.style.aspectRatio)
316
346
  this.style.aspectRatio = "16 / 9";
347
+ this.syncPosterPlaceholder();
348
+ }
349
+ /// The poster doubles as the element's CSS background: on platforms
350
+ /// where the native layer hides while no media is presented (Windows:
351
+ /// initial state, after stop, on error), the placeholder shows it.
352
+ syncPosterPlaceholder() {
353
+ var _a;
354
+ const poster = this.getAttribute("poster");
355
+ if (!poster) {
356
+ this.style.backgroundImage = "";
357
+ return;
358
+ }
359
+ const objectFit = this.parseObjectFitValue((_a = this.getAttribute("object-fit")) !== null && _a !== void 0 ? _a : this.getAttribute("objectfit"));
360
+ const size = objectFit === "cover" ? "cover" : objectFit === "fill" ? "100% 100%" : "contain";
361
+ this.style.backgroundImage = `url("${poster.replace(/"/g, '\\"')}")`;
362
+ this.style.backgroundSize = size;
363
+ this.style.backgroundPosition = "center";
364
+ this.style.backgroundRepeat = "no-repeat";
317
365
  }
318
366
  collectProps() {
319
367
  var _a;
@@ -505,6 +553,12 @@ export class LxVideoElement extends HTMLElement {
505
553
  if (isIOS()) {
506
554
  window.addEventListener("scroll", this.iOSScrollEventListener, { passive: true });
507
555
  }
556
+ else if (isWindows()) {
557
+ window.addEventListener("scroll", this.containerScrollListener, {
558
+ capture: true,
559
+ passive: true,
560
+ });
561
+ }
508
562
  if (isAndroid()) {
509
563
  this.removeLayoutInvalidationListener = addNativeComponentLayoutInvalidationListener(this.resizeEventListener);
510
564
  }
@@ -517,9 +571,20 @@ export class LxVideoElement extends HTMLElement {
517
571
  if (isIOS()) {
518
572
  window.removeEventListener("scroll", this.iOSScrollEventListener);
519
573
  }
574
+ else if (isWindows()) {
575
+ window.removeEventListener("scroll", this.containerScrollListener, { capture: true });
576
+ }
520
577
  (_a = this.removeLayoutInvalidationListener) === null || _a === void 0 ? void 0 : _a.call(this);
521
578
  this.removeLayoutInvalidationListener = undefined;
522
579
  this.stopSizeObserver();
580
+ if (this.scrollSettleFrame !== null) {
581
+ cancelAnimationFrame(this.scrollSettleFrame);
582
+ this.scrollSettleFrame = null;
583
+ }
584
+ if (this.winSettleFrame !== null) {
585
+ cancelAnimationFrame(this.winSettleFrame);
586
+ this.winSettleFrame = null;
587
+ }
523
588
  if (this.pendingLayoutFrame !== null) {
524
589
  cancelAnimationFrame(this.pendingLayoutFrame);
525
590
  this.pendingLayoutFrame = null;
@@ -575,6 +640,45 @@ export class LxVideoElement extends HTMLElement {
575
640
  this.mountOrUpdate();
576
641
  });
577
642
  }
643
+ scheduleWindowsMountSettle() {
644
+ if (typeof requestAnimationFrame === "undefined")
645
+ return;
646
+ if (this.winSettleFrame !== null)
647
+ return;
648
+ // Re-measure across the page-load / WebView2-resize settle window. The
649
+ // reflow that moves this element can land well after mount, so keep checking
650
+ // until the rect has been stable for a while; decide() dedups, so updates
651
+ // only fire when the rect changes.
652
+ let lastKey = "";
653
+ let stable = 0;
654
+ let elapsed = 0;
655
+ const tick = () => {
656
+ this.winSettleFrame = null;
657
+ if (!this.isConnected)
658
+ return;
659
+ // Force the re-send (don't dedup): an early update can be lost in the
660
+ // bridge transport during page load, and once the element has settled a
661
+ // dedup'd re-measure sees "no change" and never re-delivers it. Forcing
662
+ // each frame re-delivers the current rect until it lands.
663
+ this.mountOrUpdate(true);
664
+ const r = this.getBoundingClientRect();
665
+ const key = `${Math.round(r.top)}:${Math.round(r.width)}`;
666
+ if (key === lastKey) {
667
+ stable += 1;
668
+ }
669
+ else {
670
+ stable = 0;
671
+ lastKey = key;
672
+ }
673
+ elapsed += 1;
674
+ // Keep re-sending until the layout has held steady for a while (so a late
675
+ // settle is still caught), capped so it never runs unbounded.
676
+ if (stable < 12 && elapsed < 180) {
677
+ this.winSettleFrame = requestAnimationFrame(tick);
678
+ }
679
+ };
680
+ this.winSettleFrame = requestAnimationFrame(tick);
681
+ }
578
682
  scheduleIOSBootstrapFrames() {
579
683
  if (!isIOS())
580
684
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingxia/elements",
3
- "version": "0.9.0",
3
+ "version": "0.11.1",
4
4
  "private": false,
5
5
  "description": "LingXia custom elements — native-backed web components for LxApp pages",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -33,21 +33,6 @@ export {
33
33
  type LxPickerEventDetail,
34
34
  } from "./picker.js";
35
35
 
36
- export {
37
- registerInputComponent,
38
- LxInputElement,
39
- type LxInputAttributes,
40
- type LxInputEvent,
41
- type LxInputEventDetail,
42
- } from "./input.js";
43
-
44
- export {
45
- registerTextareaComponent,
46
- LxTextareaElement,
47
- type LxTextareaAttributes,
48
- type LxTextareaEvent,
49
- type LxTextareaEventDetail,
50
- } from "./textarea.js";
51
36
 
52
37
  export {
53
38
  LxNavigatorElement,
@@ -71,11 +56,6 @@ export {
71
56
  MEDIA_SWIPER_DOM_EVENT_MAP,
72
57
  } from "./native_component_wrapper_shared.js";
73
58
 
74
- export {
75
- buildInputNativeAttrs,
76
- buildTextareaNativeAttrs,
77
- } from "./text_component_native_attrs.js";
78
-
79
59
  export {
80
60
  isHarmony,
81
61
  isIOS,
package/src/platform.ts CHANGED
@@ -16,6 +16,10 @@ export function isMacOS(): boolean {
16
16
  return window.LingXiaBridge?.platform?.isMacOS?.() === true;
17
17
  }
18
18
 
19
+ export function isWindows(): boolean {
20
+ return window.LingXiaBridge?.platform?.isWindows?.() === true;
21
+ }
22
+
19
23
  export function isDesktop(): boolean {
20
24
  return window.LingXiaBridge?.platform?.isDesktop?.() === true;
21
25
  }
package/src/video.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from "./nativecomponent.js";
6
6
  import { measureElement } from "./dom.js";
7
7
  import { ensureComponentId, NativeComponentUpdateState, iOSNativeComponentHelper } from "./component.js";
8
- import { isAndroid, isHarmony, isIOS } from "./platform.js";
8
+ import { isAndroid, isHarmony, isIOS, isWindows } from "./platform.js";
9
9
 
10
10
  const HARMONY_PROPS_PREFIX = "data:application/json,";
11
11
 
@@ -85,6 +85,7 @@ export class LxVideoElement extends HTMLElement {
85
85
  private updateState = new NativeComponentUpdateState();
86
86
  private unregister?: () => void;
87
87
  private resizeObserver?: ResizeObserver;
88
+ private winSettleFrame: number | null = null;
88
89
  private pendingLayoutFrame: number | null = null;
89
90
  private boundUpdatePosition = this.updatePosition.bind(this);
90
91
  private _handlers: Record<string, EventListenerOrEventListenerObject> = {};
@@ -105,6 +106,20 @@ export class LxVideoElement extends HTMLElement {
105
106
  private iOSScrollEventListener: EventListenerObject = {
106
107
  handleEvent: () => this.boundUpdatePosition(),
107
108
  };
109
+ private scrollSettleFrame: number | null = null;
110
+ // Scroll events do not bubble, so a plain window listener misses inner
111
+ // scroll containers; the capture phase sees them all. Any ancestor
112
+ // scrolling moves the element's document rect, so re-measure (coalesced
113
+ // to one update per frame; decide() dedups unchanged rects).
114
+ private containerScrollListener: EventListenerObject = {
115
+ handleEvent: () => {
116
+ if (this.scrollSettleFrame !== null) return;
117
+ this.scrollSettleFrame = requestAnimationFrame(() => {
118
+ this.scrollSettleFrame = null;
119
+ this.updatePosition();
120
+ });
121
+ },
122
+ };
108
123
  private rawHandlers: Record<string, EventListenerOrEventListenerObject> = {};
109
124
  private _pageBindings: Record<string, string> = {};
110
125
 
@@ -268,6 +283,17 @@ export class LxVideoElement extends HTMLElement {
268
283
  } else {
269
284
  this.mountOrUpdate();
270
285
  this.startTracking();
286
+ // Windows only: the WebView2 surface is created at an intermediate size
287
+ // and resized to its final content bounds shortly after this element
288
+ // mounts, which reflows the page and moves the element. Unlike the other
289
+ // platforms' webviews, WebView2 doesn't reliably fire the resize /
290
+ // ResizeObserver that would make us re-report, and an early update can be
291
+ // lost in the bridge transport during page load — so the native overlay
292
+ // can stay pinned at the mount-time rect. Re-measure + re-report across the
293
+ // settle window to land the final position. No-op on every other platform.
294
+ if (isWindows()) {
295
+ this.scheduleWindowsMountSettle();
296
+ }
271
297
  }
272
298
  }
273
299
 
@@ -314,6 +340,9 @@ export class LxVideoElement extends HTMLElement {
314
340
  }
315
341
  }
316
342
  if (!this.isConnected) return;
343
+ if (name === "poster" || name === "object-fit" || name === "objectfit") {
344
+ this.syncPosterPlaceholder();
345
+ }
317
346
  // Keep content-rotate/object-fit updates immediate across all native platforms.
318
347
  const forcePropsUpdate = name === "content-rotate" || name === "object-fit" || name === "objectfit";
319
348
  if (isHarmony() && forcePropsUpdate) {
@@ -399,6 +428,27 @@ export class LxVideoElement extends HTMLElement {
399
428
  if (!this.style.position) this.style.position = "relative"; // Needed for iOS scroll container
400
429
  if (!this.style.backgroundColor) this.style.backgroundColor = "black";
401
430
  if (!this.style.aspectRatio) this.style.aspectRatio = "16 / 9";
431
+ this.syncPosterPlaceholder();
432
+ }
433
+
434
+ /// The poster doubles as the element's CSS background: on platforms
435
+ /// where the native layer hides while no media is presented (Windows:
436
+ /// initial state, after stop, on error), the placeholder shows it.
437
+ private syncPosterPlaceholder() {
438
+ const poster = this.getAttribute("poster");
439
+ if (!poster) {
440
+ this.style.backgroundImage = "";
441
+ return;
442
+ }
443
+ const objectFit = this.parseObjectFitValue(
444
+ this.getAttribute("object-fit") ?? this.getAttribute("objectfit")
445
+ );
446
+ const size =
447
+ objectFit === "cover" ? "cover" : objectFit === "fill" ? "100% 100%" : "contain";
448
+ this.style.backgroundImage = `url("${poster.replace(/"/g, '\\"')}")`;
449
+ this.style.backgroundSize = size;
450
+ this.style.backgroundPosition = "center";
451
+ this.style.backgroundRepeat = "no-repeat";
402
452
  }
403
453
 
404
454
  private collectProps() {
@@ -605,6 +655,11 @@ export class LxVideoElement extends HTMLElement {
605
655
  window.addEventListener("resize", this.resizeEventListener);
606
656
  if (isIOS()) {
607
657
  window.addEventListener("scroll", this.iOSScrollEventListener, { passive: true });
658
+ } else if (isWindows()) {
659
+ window.addEventListener("scroll", this.containerScrollListener, {
660
+ capture: true,
661
+ passive: true,
662
+ });
608
663
  }
609
664
  if (isAndroid()) {
610
665
  this.removeLayoutInvalidationListener = addNativeComponentLayoutInvalidationListener(this.resizeEventListener);
@@ -617,10 +672,20 @@ export class LxVideoElement extends HTMLElement {
617
672
  window.removeEventListener("resize", this.resizeEventListener);
618
673
  if (isIOS()) {
619
674
  window.removeEventListener("scroll", this.iOSScrollEventListener);
675
+ } else if (isWindows()) {
676
+ window.removeEventListener("scroll", this.containerScrollListener, { capture: true });
620
677
  }
621
678
  this.removeLayoutInvalidationListener?.();
622
679
  this.removeLayoutInvalidationListener = undefined;
623
680
  this.stopSizeObserver();
681
+ if (this.scrollSettleFrame !== null) {
682
+ cancelAnimationFrame(this.scrollSettleFrame);
683
+ this.scrollSettleFrame = null;
684
+ }
685
+ if (this.winSettleFrame !== null) {
686
+ cancelAnimationFrame(this.winSettleFrame);
687
+ this.winSettleFrame = null;
688
+ }
624
689
  if (this.pendingLayoutFrame !== null) {
625
690
  cancelAnimationFrame(this.pendingLayoutFrame);
626
691
  this.pendingLayoutFrame = null;
@@ -675,6 +740,42 @@ export class LxVideoElement extends HTMLElement {
675
740
  });
676
741
  }
677
742
 
743
+ private scheduleWindowsMountSettle(): void {
744
+ if (typeof requestAnimationFrame === "undefined") return;
745
+ if (this.winSettleFrame !== null) return;
746
+ // Re-measure across the page-load / WebView2-resize settle window. The
747
+ // reflow that moves this element can land well after mount, so keep checking
748
+ // until the rect has been stable for a while; decide() dedups, so updates
749
+ // only fire when the rect changes.
750
+ let lastKey = "";
751
+ let stable = 0;
752
+ let elapsed = 0;
753
+ const tick = () => {
754
+ this.winSettleFrame = null;
755
+ if (!this.isConnected) return;
756
+ // Force the re-send (don't dedup): an early update can be lost in the
757
+ // bridge transport during page load, and once the element has settled a
758
+ // dedup'd re-measure sees "no change" and never re-delivers it. Forcing
759
+ // each frame re-delivers the current rect until it lands.
760
+ this.mountOrUpdate(true);
761
+ const r = this.getBoundingClientRect();
762
+ const key = `${Math.round(r.top)}:${Math.round(r.width)}`;
763
+ if (key === lastKey) {
764
+ stable += 1;
765
+ } else {
766
+ stable = 0;
767
+ lastKey = key;
768
+ }
769
+ elapsed += 1;
770
+ // Keep re-sending until the layout has held steady for a while (so a late
771
+ // settle is still caught), capped so it never runs unbounded.
772
+ if (stable < 12 && elapsed < 180) {
773
+ this.winSettleFrame = requestAnimationFrame(tick);
774
+ }
775
+ };
776
+ this.winSettleFrame = requestAnimationFrame(tick);
777
+ }
778
+
678
779
  private scheduleIOSBootstrapFrames(): void {
679
780
  if (!isIOS()) return;
680
781
  // Keep a short frame-based bootstrap window so late-created WKChildScrollView
package/dist/input.d.ts DELETED
@@ -1,117 +0,0 @@
1
- export interface LxInputEventDetail {
2
- value?: string;
3
- cursor?: number;
4
- keyCode?: number;
5
- height?: number;
6
- duration?: number;
7
- encryptedValue?: string;
8
- encryptError?: string;
9
- pass?: boolean;
10
- timeout?: boolean;
11
- }
12
- export interface LxInputEvent extends CustomEvent<LxInputEventDetail> {
13
- detail: LxInputEventDetail;
14
- }
15
- export type LxInputAttributes = {
16
- id?: string;
17
- value?: string;
18
- type?: 'text' | 'number' | 'password' | 'digit';
19
- password?: boolean | string;
20
- placeholder?: string;
21
- 'placeholder-style'?: string;
22
- 'placeholder-class'?: string;
23
- maxlength?: number | string;
24
- 'cursor-spacing'?: number | string;
25
- 'auto-focus'?: boolean | string;
26
- disabled?: boolean | string;
27
- focus?: boolean | string;
28
- 'confirm-type'?: 'send' | 'search' | 'next' | 'go' | 'done';
29
- 'always-embed'?: boolean | string;
30
- 'confirm-hold'?: boolean | string;
31
- cursor?: number | string;
32
- 'cursor-color'?: string;
33
- 'selection-start'?: number | string;
34
- 'selection-end'?: number | string;
35
- 'adjust-position'?: boolean | string;
36
- 'hold-keyboard'?: boolean | string;
37
- className?: string;
38
- style?: any;
39
- ref?: any;
40
- onInput?: (e: LxInputEvent) => void;
41
- onChange?: (e: LxInputEvent) => void;
42
- onFocus?: (e: LxInputEvent) => void;
43
- onBlur?: (e: LxInputEvent) => void;
44
- onConfirm?: (e: LxInputEvent) => void;
45
- onKeyboardheightchange?: (e: LxInputEvent) => void;
46
- onNicknamereview?: (e: LxInputEvent) => void;
47
- pageBindings?: Record<string, string>;
48
- };
49
- declare global {
50
- namespace JSX {
51
- interface IntrinsicElements {
52
- "lx-input": LxInputAttributes;
53
- }
54
- }
55
- }
56
- export declare class LxInputElement extends HTMLElement {
57
- static get observedAttributes(): string[];
58
- private componentId;
59
- private mounted;
60
- private unregister?;
61
- private _handlers;
62
- private rawHandlers;
63
- private harmonyEmbed?;
64
- private lastHarmonyProps?;
65
- private pendingHarmonyProps?;
66
- private pendingHarmonyRetryTimer;
67
- private pendingHarmonyRetryCount;
68
- private attrObserver?;
69
- private shadow?;
70
- private innerInput?;
71
- private readonly updateState;
72
- private resizeObserver?;
73
- private pendingLayoutFrame;
74
- private focusLayoutSyncTimer;
75
- private readonly layoutEventListener;
76
- private _pageBindings;
77
- set pageBindings(bindings: Record<string, string>);
78
- get pageBindings(): Record<string, string>;
79
- private upgradeProperty;
80
- private registerNativeHandler;
81
- private unmountNativeComponentById;
82
- private teardownHarmonyEmbed;
83
- connectedCallback(): void;
84
- disconnectedCallback(): void;
85
- attributeChangedCallback(name: string): void;
86
- private getAttr;
87
- private getBoolAttr;
88
- private getNumAttr;
89
- private getPropOrAttr;
90
- private shouldTrackNativeLayout;
91
- private startTracking;
92
- private stopTracking;
93
- private startSizeObserver;
94
- private stopSizeObserver;
95
- private schedulePositionSync;
96
- private collectProps;
97
- private getHostTextColor;
98
- private resyncHarmonyLayoutAfterFocus;
99
- private startFocusLayoutSync;
100
- private stopFocusLayoutSync;
101
- private shouldRefreshForAttribute;
102
- private startAttrObserver;
103
- private stopAttrObserver;
104
- private collectDataset;
105
- private getInputType;
106
- private mountDesktopInput;
107
- private mountInput;
108
- syncNativeProps(): void;
109
- private ensureHostLayout;
110
- set oninput(cb: EventListener | null);
111
- get oninput(): EventListener | null;
112
- private ensureHarmonyEmbed;
113
- private encodeHarmonyProps;
114
- private pushHarmonyPropsUpdate;
115
- private scheduleHarmonyPropsRetry;
116
- }
117
- export declare function registerInputComponent(): void;