@lingxia/elements 0.8.0 → 0.10.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/dist/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/platform.d.ts +1 -0
- package/dist/platform.js +4 -0
- package/dist/video.d.ts +3 -0
- package/dist/video.js +77 -1
- package/package.json +1 -1
- package/src/index.ts +0 -20
- package/src/platform.ts +4 -0
- package/src/video.ts +77 -1
- package/dist/input.d.ts +0 -117
- package/dist/input.js +0 -699
- package/dist/text_component_native_attrs.d.ts +0 -53
- package/dist/text_component_native_attrs.js +0 -111
- package/dist/text_component_shared.d.ts +0 -7
- package/dist/text_component_shared.js +0 -148
- package/dist/textarea.d.ts +0 -129
- package/dist/textarea.js +0 -841
- package/src/input.ts +0 -799
- package/src/text_component_native_attrs.ts +0 -137
- package/src/text_component_shared.ts +0 -147
- package/src/textarea.ts +0 -959
package/src/textarea.ts
DELETED
|
@@ -1,959 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
sendNativeComponentMessage,
|
|
3
|
-
registerNativeComponentHandler,
|
|
4
|
-
addNativeComponentLayoutInvalidationListener,
|
|
5
|
-
invalidateNativeComponentLayout
|
|
6
|
-
} from "./nativecomponent.js";
|
|
7
|
-
import { ensureComponentId, NativeComponentUpdateState } from "./component.js";
|
|
8
|
-
import { measureElement } from "./dom.js";
|
|
9
|
-
import { isHarmony, isDesktop, isMacOS, isAndroid, isIOS } from "./platform.js";
|
|
10
|
-
import {
|
|
11
|
-
getPropOrAttr as getSharedPropOrAttr,
|
|
12
|
-
getBoolAttr as getSharedBoolAttr,
|
|
13
|
-
getNumAttr as getSharedNumAttr,
|
|
14
|
-
parseNumberLike,
|
|
15
|
-
shouldRefreshForDataAttribute as shouldRefreshSharedDataAttribute,
|
|
16
|
-
collectDataset as collectSharedDataset,
|
|
17
|
-
ensureElementVisibleForKeyboard
|
|
18
|
-
} from "./text_component_shared.js";
|
|
19
|
-
|
|
20
|
-
const HARMONY_PROPS_PREFIX = "data:application/json,";
|
|
21
|
-
|
|
22
|
-
// Type definitions
|
|
23
|
-
export interface LxTextareaEventDetail {
|
|
24
|
-
value?: string;
|
|
25
|
-
cursor?: number;
|
|
26
|
-
lineCount?: number;
|
|
27
|
-
height?: number;
|
|
28
|
-
duration?: number;
|
|
29
|
-
selectionStart?: number;
|
|
30
|
-
selectionEnd?: number;
|
|
31
|
-
heightRpx?: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface LxTextareaEvent extends CustomEvent<LxTextareaEventDetail> {
|
|
35
|
-
detail: LxTextareaEventDetail;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type LxTextareaAttributes = {
|
|
39
|
-
id?: string;
|
|
40
|
-
value?: string;
|
|
41
|
-
placeholder?: string;
|
|
42
|
-
'placeholder-style'?: string;
|
|
43
|
-
'placeholder-class'?: string;
|
|
44
|
-
maxlength?: number | string;
|
|
45
|
-
disabled?: boolean | string;
|
|
46
|
-
'auto-focus'?: boolean | string;
|
|
47
|
-
focus?: boolean | string;
|
|
48
|
-
'auto-height'?: boolean | string;
|
|
49
|
-
'cursor-spacing'?: number | string;
|
|
50
|
-
'show-confirm-bar'?: boolean | string;
|
|
51
|
-
'adjust-position'?: boolean | string;
|
|
52
|
-
'hold-keyboard'?: boolean | string;
|
|
53
|
-
'disable-default-padding'?: boolean | string;
|
|
54
|
-
'confirm-type'?: 'send' | 'search' | 'next' | 'go' | 'done' | 'return';
|
|
55
|
-
'confirm-hold'?: boolean | string;
|
|
56
|
-
fixed?: boolean | string;
|
|
57
|
-
'adjust-keyboard-to'?: 'cursor' | 'bottom';
|
|
58
|
-
cursor?: number | string;
|
|
59
|
-
'selection-start'?: number | string;
|
|
60
|
-
'selection-end'?: number | string;
|
|
61
|
-
className?: string;
|
|
62
|
-
style?: any;
|
|
63
|
-
ref?: any;
|
|
64
|
-
onInput?: (e: LxTextareaEvent) => void;
|
|
65
|
-
onChange?: (e: LxTextareaEvent) => void;
|
|
66
|
-
onFocus?: (e: LxTextareaEvent) => void;
|
|
67
|
-
onBlur?: (e: LxTextareaEvent) => void;
|
|
68
|
-
onConfirm?: (e: LxTextareaEvent) => void;
|
|
69
|
-
onLinechange?: (e: LxTextareaEvent) => void;
|
|
70
|
-
onKeyboardheightchange?: (e: LxTextareaEvent) => void;
|
|
71
|
-
pageBindings?: Record<string, string>;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
declare global {
|
|
75
|
-
namespace JSX {
|
|
76
|
-
interface IntrinsicElements {
|
|
77
|
-
"lx-textarea": LxTextareaAttributes;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Component implementation
|
|
83
|
-
export class LxTextareaElement extends HTMLElement {
|
|
84
|
-
static get observedAttributes() {
|
|
85
|
-
return [
|
|
86
|
-
"id", "value", "placeholder", "placeholder-style",
|
|
87
|
-
"placeholder-class", "maxlength", "disabled", "auto-focus", "focus",
|
|
88
|
-
"auto-height", "cursor-spacing", "show-confirm-bar", "adjust-position",
|
|
89
|
-
"hold-keyboard", "disable-default-padding", "confirm-type",
|
|
90
|
-
"confirm-hold", "fixed", "adjust-keyboard-to", "cursor",
|
|
91
|
-
"selection-start", "selection-end"
|
|
92
|
-
];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
private componentId: string | null = null;
|
|
96
|
-
private mounted = false;
|
|
97
|
-
private unregister?: () => void;
|
|
98
|
-
private _handlers: Record<string, EventListenerOrEventListenerObject> = {};
|
|
99
|
-
private rawHandlers: Record<string, EventListenerOrEventListenerObject> = {};
|
|
100
|
-
private harmonyEmbed?: HTMLEmbedElement;
|
|
101
|
-
private lastHarmonyProps?: string;
|
|
102
|
-
private pendingHarmonyProps?: Record<string, unknown>;
|
|
103
|
-
private pendingHarmonyRetryTimer: number | null = null;
|
|
104
|
-
private pendingHarmonyRetryCount = 0;
|
|
105
|
-
private attrObserver?: MutationObserver;
|
|
106
|
-
private shadow?: ShadowRoot;
|
|
107
|
-
private innerTextarea?: HTMLTextAreaElement;
|
|
108
|
-
private lastNativeAutoHeight?: number;
|
|
109
|
-
private autoHeightMinHeight?: number;
|
|
110
|
-
private lastKeyboardHeight = 0;
|
|
111
|
-
private readonly updateState = new NativeComponentUpdateState();
|
|
112
|
-
private resizeObserver?: ResizeObserver;
|
|
113
|
-
private pendingLayoutFrame: number | null = null;
|
|
114
|
-
private focusLayoutSyncTimer: number | null = null;
|
|
115
|
-
private readonly layoutEventListener: EventListenerObject = {
|
|
116
|
-
handleEvent: () => this.schedulePositionSync()
|
|
117
|
-
};
|
|
118
|
-
private removeLayoutInvalidationListener?: () => void;
|
|
119
|
-
private _pageBindings: Record<string, string> = {};
|
|
120
|
-
|
|
121
|
-
set pageBindings(bindings: Record<string, string>) {
|
|
122
|
-
this._pageBindings = bindings ?? {};
|
|
123
|
-
if (this.isConnected) {
|
|
124
|
-
this.mountTextarea();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
get pageBindings(): Record<string, string> {
|
|
129
|
-
return this._pageBindings;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private upgradeProperty(propName: string): void {
|
|
133
|
-
const self = this as unknown as Record<string, unknown>;
|
|
134
|
-
if (!Object.prototype.hasOwnProperty.call(self, propName)) return;
|
|
135
|
-
const value = self[propName];
|
|
136
|
-
delete self[propName];
|
|
137
|
-
self[propName] = value;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private registerNativeHandler(): void {
|
|
141
|
-
if (!this.componentId) return;
|
|
142
|
-
if (this.unregister) {
|
|
143
|
-
this.unregister();
|
|
144
|
-
this.unregister = undefined;
|
|
145
|
-
}
|
|
146
|
-
this.unregister = registerNativeComponentHandler(this.componentId, (message) => {
|
|
147
|
-
if (typeof message.event !== "string") return;
|
|
148
|
-
const detail = this.normalizeNativeDetail(message.detail) as LxTextareaEventDetail;
|
|
149
|
-
if (isHarmony()) {
|
|
150
|
-
}
|
|
151
|
-
if (message.event === "linechange") {
|
|
152
|
-
this.applyNativeAutoHeightIfNeeded(detail);
|
|
153
|
-
} else if (message.event === "keyboardheightchange") {
|
|
154
|
-
const nextKeyboardHeight = this.parseHeight(detail.height);
|
|
155
|
-
if (nextKeyboardHeight !== undefined) {
|
|
156
|
-
this.lastKeyboardHeight = Math.max(0, nextKeyboardHeight);
|
|
157
|
-
}
|
|
158
|
-
this.resyncHarmonyLayoutAfterFocus();
|
|
159
|
-
this.ensureVisibleForKeyboard(this.lastKeyboardHeight, false);
|
|
160
|
-
} else if (message.event === "focus") {
|
|
161
|
-
this.startFocusLayoutSync();
|
|
162
|
-
this.resyncHarmonyLayoutAfterFocus();
|
|
163
|
-
} else if (message.event === "blur") {
|
|
164
|
-
this.lastKeyboardHeight = 0;
|
|
165
|
-
this.stopFocusLayoutSync();
|
|
166
|
-
}
|
|
167
|
-
this.dispatchEvent(new CustomEvent(message.event, { detail, bubbles: true }));
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private normalizeNativeDetail(detail: unknown): unknown {
|
|
172
|
-
if (!detail || typeof detail !== "object") {
|
|
173
|
-
return detail ?? {};
|
|
174
|
-
}
|
|
175
|
-
const raw = detail as Record<string, unknown>;
|
|
176
|
-
if (raw.data && typeof raw.data === "object") {
|
|
177
|
-
const data = raw.data as Record<string, unknown>;
|
|
178
|
-
if (
|
|
179
|
-
"value" in data ||
|
|
180
|
-
"cursor" in data ||
|
|
181
|
-
"selectionStart" in data ||
|
|
182
|
-
"selectionEnd" in data ||
|
|
183
|
-
"lineCount" in data ||
|
|
184
|
-
"height" in data ||
|
|
185
|
-
"heightRpx" in data ||
|
|
186
|
-
"duration" in data
|
|
187
|
-
) {
|
|
188
|
-
return data;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return raw;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private unmountNativeComponentById(componentId: string): void {
|
|
195
|
-
const useDesktopFallback = isDesktop() && !isMacOS();
|
|
196
|
-
if (!componentId || isHarmony() || useDesktopFallback) return;
|
|
197
|
-
sendNativeComponentMessage({
|
|
198
|
-
action: "component.unmount",
|
|
199
|
-
id: componentId
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private teardownHarmonyEmbed(): void {
|
|
204
|
-
if (this.harmonyEmbed && this.contains(this.harmonyEmbed)) {
|
|
205
|
-
this.removeChild(this.harmonyEmbed);
|
|
206
|
-
}
|
|
207
|
-
this.harmonyEmbed = undefined;
|
|
208
|
-
this.lastHarmonyProps = undefined;
|
|
209
|
-
if (this.pendingHarmonyRetryTimer !== null) {
|
|
210
|
-
clearTimeout(this.pendingHarmonyRetryTimer);
|
|
211
|
-
this.pendingHarmonyRetryTimer = null;
|
|
212
|
-
}
|
|
213
|
-
this.pendingHarmonyProps = undefined;
|
|
214
|
-
this.pendingHarmonyRetryCount = 0;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
connectedCallback() {
|
|
218
|
-
this.upgradeProperty("pageBindings");
|
|
219
|
-
this.upgradeProperty("oninput");
|
|
220
|
-
this.upgradeProperty("onchange");
|
|
221
|
-
this.upgradeProperty("onfocus");
|
|
222
|
-
this.upgradeProperty("onblur");
|
|
223
|
-
this.upgradeProperty("onconfirm");
|
|
224
|
-
this.upgradeProperty("onlinechange");
|
|
225
|
-
this.upgradeProperty("onkeyboardheightchange");
|
|
226
|
-
|
|
227
|
-
this.componentId = ensureComponentId(this, "lx-textarea", this.componentId);
|
|
228
|
-
if (!this.componentId) return;
|
|
229
|
-
this.registerNativeHandler();
|
|
230
|
-
|
|
231
|
-
this.startAttrObserver();
|
|
232
|
-
this.startTracking();
|
|
233
|
-
this.mountTextarea();
|
|
234
|
-
queueMicrotask(() => {
|
|
235
|
-
if (!this.isConnected) return;
|
|
236
|
-
this.mountTextarea();
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
disconnectedCallback() {
|
|
241
|
-
if (this.shadow) {
|
|
242
|
-
this.innerTextarea = undefined;
|
|
243
|
-
this.shadow = undefined;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (this.componentId) {
|
|
247
|
-
this.unmountNativeComponentById(this.componentId);
|
|
248
|
-
}
|
|
249
|
-
if (this.unregister) {
|
|
250
|
-
this.unregister();
|
|
251
|
-
this.unregister = undefined;
|
|
252
|
-
}
|
|
253
|
-
this.teardownHarmonyEmbed();
|
|
254
|
-
this.lastNativeAutoHeight = undefined;
|
|
255
|
-
this.autoHeightMinHeight = undefined;
|
|
256
|
-
this.lastKeyboardHeight = 0;
|
|
257
|
-
this.stopFocusLayoutSync();
|
|
258
|
-
this.stopTracking();
|
|
259
|
-
this.mounted = false;
|
|
260
|
-
this.updateState.reset();
|
|
261
|
-
this.stopAttrObserver();
|
|
262
|
-
Object.keys(this._handlers).forEach(name => {
|
|
263
|
-
this.removeEventListener(name, this._handlers[name]);
|
|
264
|
-
});
|
|
265
|
-
this._handlers = {};
|
|
266
|
-
this.rawHandlers = {};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
attributeChangedCallback(name: string) {
|
|
270
|
-
if (name === "id") {
|
|
271
|
-
const prev = this.componentId;
|
|
272
|
-
this.componentId = ensureComponentId(this, "lx-textarea", this.componentId);
|
|
273
|
-
if (prev && this.componentId !== prev) {
|
|
274
|
-
this.mounted = false;
|
|
275
|
-
this.updateState.reset();
|
|
276
|
-
if (this.isConnected) {
|
|
277
|
-
this.unmountNativeComponentById(prev);
|
|
278
|
-
this.teardownHarmonyEmbed();
|
|
279
|
-
this.registerNativeHandler();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
if (!this.isConnected) return;
|
|
284
|
-
this.mountTextarea();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
private getAttr(name: string, fallback = "") {
|
|
288
|
-
const raw = this.getPropOrAttr(name);
|
|
289
|
-
if (raw === undefined || raw === null) return fallback;
|
|
290
|
-
return String(raw);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private getBoolAttr(name: string): boolean {
|
|
294
|
-
return getSharedBoolAttr(this, name);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
private getNumAttr(name: string): number | undefined {
|
|
298
|
-
return getSharedNumAttr(this, name);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
private getPropOrAttr(name: string): unknown {
|
|
302
|
-
return getSharedPropOrAttr(this, name);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
private shouldTrackNativeLayout(): boolean {
|
|
306
|
-
return isAndroid() || isIOS() || isHarmony();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
private startTracking(): void {
|
|
310
|
-
if (!this.shouldTrackNativeLayout()) return;
|
|
311
|
-
window.addEventListener("resize", this.layoutEventListener);
|
|
312
|
-
if (isAndroid()) {
|
|
313
|
-
// Android overlay can drift inside nested scrolling containers; capture phase catches element scroll events.
|
|
314
|
-
window.addEventListener("scroll", this.layoutEventListener, { capture: true, passive: true });
|
|
315
|
-
} else if (isHarmony()) {
|
|
316
|
-
// Harmony native embed coordinates need continuous sync while page scrolls.
|
|
317
|
-
window.addEventListener("scroll", this.layoutEventListener, { capture: true, passive: true });
|
|
318
|
-
} else if (isIOS()) {
|
|
319
|
-
// Match video behavior on iOS: track top-level page scrolling only.
|
|
320
|
-
window.addEventListener("scroll", this.layoutEventListener, { passive: true });
|
|
321
|
-
}
|
|
322
|
-
window.visualViewport?.addEventListener("resize", this.layoutEventListener);
|
|
323
|
-
window.visualViewport?.addEventListener("scroll", this.layoutEventListener);
|
|
324
|
-
if (isAndroid()) {
|
|
325
|
-
this.removeLayoutInvalidationListener = addNativeComponentLayoutInvalidationListener(this.layoutEventListener);
|
|
326
|
-
}
|
|
327
|
-
this.startSizeObserver();
|
|
328
|
-
this.schedulePositionSync();
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
private stopTracking(): void {
|
|
332
|
-
window.removeEventListener("resize", this.layoutEventListener);
|
|
333
|
-
window.removeEventListener("scroll", this.layoutEventListener, true);
|
|
334
|
-
window.removeEventListener("scroll", this.layoutEventListener);
|
|
335
|
-
window.visualViewport?.removeEventListener("resize", this.layoutEventListener);
|
|
336
|
-
window.visualViewport?.removeEventListener("scroll", this.layoutEventListener);
|
|
337
|
-
this.removeLayoutInvalidationListener?.();
|
|
338
|
-
this.removeLayoutInvalidationListener = undefined;
|
|
339
|
-
this.stopSizeObserver();
|
|
340
|
-
if (this.pendingLayoutFrame !== null) {
|
|
341
|
-
cancelAnimationFrame(this.pendingLayoutFrame);
|
|
342
|
-
this.pendingLayoutFrame = null;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
private startSizeObserver(): void {
|
|
347
|
-
if (typeof ResizeObserver === "undefined" || this.resizeObserver) return;
|
|
348
|
-
this.resizeObserver = new ResizeObserver((entries) => {
|
|
349
|
-
for (const entry of entries) {
|
|
350
|
-
if (entry.target !== this) continue;
|
|
351
|
-
this.schedulePositionSync();
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
this.resizeObserver.observe(this);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
private stopSizeObserver(): void {
|
|
359
|
-
if (!this.resizeObserver) return;
|
|
360
|
-
this.resizeObserver.disconnect();
|
|
361
|
-
this.resizeObserver = undefined;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
private schedulePositionSync(): void {
|
|
365
|
-
if (!this.shouldTrackNativeLayout() || !this.isConnected) return;
|
|
366
|
-
if (this.pendingLayoutFrame !== null) return;
|
|
367
|
-
this.pendingLayoutFrame = requestAnimationFrame(() => {
|
|
368
|
-
this.pendingLayoutFrame = null;
|
|
369
|
-
if (!this.isConnected) return;
|
|
370
|
-
this.mountTextarea();
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
private applyNativeAutoHeightIfNeeded(detail: LxTextareaEventDetail): void {
|
|
375
|
-
if (!this.getBoolAttr("auto-height")) {
|
|
376
|
-
this.autoHeightMinHeight = undefined;
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
const height = this.parseHeight(detail.height ?? detail.heightRpx);
|
|
380
|
-
if (height === undefined || height <= 0) return;
|
|
381
|
-
let nextHeight = height;
|
|
382
|
-
if (this.autoHeightMinHeight === undefined) {
|
|
383
|
-
// Preserve the initial CSS/layout height as the auto-height floor.
|
|
384
|
-
// Native runtimes may report content height for each extra line, but the
|
|
385
|
-
// component should only grow after it has consumed the configured height.
|
|
386
|
-
// Prefer inline style height over getBoundingClientRect() because the
|
|
387
|
-
// rect may still be 0 if linechange arrives before the first layout pass.
|
|
388
|
-
const styleHeight = Number.parseFloat(this.style.height);
|
|
389
|
-
const rectHeight = this.getBoundingClientRect().height;
|
|
390
|
-
const initialHeight = Number.isFinite(styleHeight) && styleHeight > 1
|
|
391
|
-
? styleHeight
|
|
392
|
-
: rectHeight;
|
|
393
|
-
if (Number.isFinite(initialHeight) && initialHeight > 1) {
|
|
394
|
-
this.autoHeightMinHeight = initialHeight;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (this.autoHeightMinHeight !== undefined) {
|
|
398
|
-
nextHeight = Math.max(nextHeight, this.autoHeightMinHeight);
|
|
399
|
-
}
|
|
400
|
-
if (this.lastNativeAutoHeight !== undefined && Math.abs(this.lastNativeAutoHeight - nextHeight) < 0.5) return;
|
|
401
|
-
this.lastNativeAutoHeight = nextHeight;
|
|
402
|
-
this.style.height = `${nextHeight}px`;
|
|
403
|
-
if (isAndroid()) {
|
|
404
|
-
invalidateNativeComponentLayout();
|
|
405
|
-
}
|
|
406
|
-
this.mountTextarea();
|
|
407
|
-
requestAnimationFrame(() => {
|
|
408
|
-
if (!this.isConnected) return;
|
|
409
|
-
this.mountTextarea();
|
|
410
|
-
});
|
|
411
|
-
setTimeout(() => {
|
|
412
|
-
if (!this.isConnected) return;
|
|
413
|
-
this.mountTextarea();
|
|
414
|
-
}, 32);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
private parseHeight(value: unknown): number | undefined {
|
|
418
|
-
return parseNumberLike(value);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
private collectProps() {
|
|
422
|
-
const autoFocus = this.getBoolAttr("auto-focus");
|
|
423
|
-
const value = this.getPropOrAttr("value");
|
|
424
|
-
const props: Record<string, any> = {
|
|
425
|
-
value: value === undefined || value === null ? undefined : String(value),
|
|
426
|
-
placeholder: this.getAttr("placeholder"),
|
|
427
|
-
placeholderStyle: this.getAttr("placeholder-style"),
|
|
428
|
-
disabled: this.getBoolAttr("disabled"),
|
|
429
|
-
// Use string values so "false" survives all bridge layers (some paths may drop falsy booleans).
|
|
430
|
-
focus: (this.getBoolAttr("focus") || autoFocus) ? "true" : "false",
|
|
431
|
-
autoFocus,
|
|
432
|
-
autoHeight: this.getBoolAttr("auto-height"),
|
|
433
|
-
showConfirmBar: this.getAttribute("show-confirm-bar") !== "false",
|
|
434
|
-
adjustPosition: this.getAttribute("adjust-position") !== "false",
|
|
435
|
-
holdKeyboard: this.getBoolAttr("hold-keyboard"),
|
|
436
|
-
disableDefaultPadding: this.getBoolAttr("disable-default-padding"),
|
|
437
|
-
confirmHold: this.getBoolAttr("confirm-hold"),
|
|
438
|
-
fixed: this.getBoolAttr("fixed"),
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
const textColor = this.getHostTextColor();
|
|
442
|
-
if (textColor) props.textColor = textColor;
|
|
443
|
-
|
|
444
|
-
const placeholderClass = this.getAttr("placeholder-class");
|
|
445
|
-
if (placeholderClass) props.placeholderClass = placeholderClass;
|
|
446
|
-
|
|
447
|
-
const maxlength = this.getNumAttr("maxlength");
|
|
448
|
-
if (maxlength !== undefined) props.maxlength = maxlength;
|
|
449
|
-
|
|
450
|
-
const cursorSpacing = this.getNumAttr("cursor-spacing");
|
|
451
|
-
if (cursorSpacing !== undefined) props.cursorSpacing = cursorSpacing;
|
|
452
|
-
|
|
453
|
-
const confirmType = this.getAttr("confirm-type");
|
|
454
|
-
if (confirmType) props.confirmType = confirmType;
|
|
455
|
-
|
|
456
|
-
const adjustKeyboardTo = this.getAttr("adjust-keyboard-to");
|
|
457
|
-
if (adjustKeyboardTo) props.adjustKeyboardTo = adjustKeyboardTo;
|
|
458
|
-
|
|
459
|
-
const cursor = this.getNumAttr("cursor");
|
|
460
|
-
if (cursor !== undefined) props.cursor = cursor;
|
|
461
|
-
|
|
462
|
-
const selectionStart = this.getNumAttr("selection-start");
|
|
463
|
-
if (selectionStart !== undefined) props.selectionStart = selectionStart;
|
|
464
|
-
|
|
465
|
-
const selectionEnd = this.getNumAttr("selection-end");
|
|
466
|
-
if (selectionEnd !== undefined) props.selectionEnd = selectionEnd;
|
|
467
|
-
|
|
468
|
-
return props;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
private getHostTextColor(): string | undefined {
|
|
472
|
-
const color = getComputedStyle(this).color?.trim();
|
|
473
|
-
if (!color) return undefined;
|
|
474
|
-
if (color === "transparent" || color === "rgba(0, 0, 0, 0)") return undefined;
|
|
475
|
-
return color;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private shouldAdjustPosition(): boolean {
|
|
479
|
-
return this.getAttribute("adjust-position") !== "false";
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
private ensureVisibleForKeyboard(explicitKeyboardHeight = 0, forceCenter = false): void {
|
|
483
|
-
if (isHarmony() || isAndroid() || isIOS()) return;
|
|
484
|
-
if (!this.shouldAdjustPosition()) return;
|
|
485
|
-
ensureElementVisibleForKeyboard(this, explicitKeyboardHeight, forceCenter, [120]);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
private resyncHarmonyLayoutAfterFocus(): void {
|
|
489
|
-
if (!isHarmony() || !this.isConnected) return;
|
|
490
|
-
this.mountTextarea();
|
|
491
|
-
setTimeout(() => {
|
|
492
|
-
if (!this.isConnected) return;
|
|
493
|
-
this.mountTextarea();
|
|
494
|
-
}, 32);
|
|
495
|
-
setTimeout(() => {
|
|
496
|
-
if (!this.isConnected) return;
|
|
497
|
-
this.mountTextarea();
|
|
498
|
-
}, 96);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
private startFocusLayoutSync(): void {
|
|
502
|
-
if (!isHarmony()) return;
|
|
503
|
-
if (this.focusLayoutSyncTimer !== null) return;
|
|
504
|
-
this.focusLayoutSyncTimer = window.setInterval(() => {
|
|
505
|
-
if (!this.isConnected) {
|
|
506
|
-
this.stopFocusLayoutSync();
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
this.mountTextarea();
|
|
510
|
-
}, 80);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
private stopFocusLayoutSync(): void {
|
|
514
|
-
if (this.focusLayoutSyncTimer === null) return;
|
|
515
|
-
clearInterval(this.focusLayoutSyncTimer);
|
|
516
|
-
this.focusLayoutSyncTimer = null;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
private shouldRefreshForAttribute(name: string): boolean {
|
|
520
|
-
return shouldRefreshSharedDataAttribute(name);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
private startAttrObserver(): void {
|
|
524
|
-
if (typeof MutationObserver === "undefined" || this.attrObserver) return;
|
|
525
|
-
this.attrObserver = new MutationObserver((mutations) => {
|
|
526
|
-
for (const mutation of mutations) {
|
|
527
|
-
const attrName = mutation.attributeName;
|
|
528
|
-
if (!attrName || !this.shouldRefreshForAttribute(attrName)) continue;
|
|
529
|
-
this.mountTextarea();
|
|
530
|
-
break;
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
this.attrObserver.observe(this, { attributes: true });
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
private stopAttrObserver(): void {
|
|
537
|
-
if (!this.attrObserver) return;
|
|
538
|
-
this.attrObserver.disconnect();
|
|
539
|
-
this.attrObserver = undefined;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
private collectDataset(): Record<string, string> {
|
|
543
|
-
return collectSharedDataset(this);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
private autoResizeTextarea(textarea: HTMLTextAreaElement): void {
|
|
548
|
-
textarea.style.height = "auto";
|
|
549
|
-
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
private getLineCount(textarea: HTMLTextAreaElement): number {
|
|
553
|
-
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight) || 20;
|
|
554
|
-
return Math.round(textarea.scrollHeight / lineHeight);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
private mountDesktopTextarea() {
|
|
558
|
-
const autoHeight = this.getBoolAttr("auto-height");
|
|
559
|
-
|
|
560
|
-
if (!this.shadow) {
|
|
561
|
-
this.shadow = this.attachShadow({ mode: "open" });
|
|
562
|
-
const style = document.createElement("style");
|
|
563
|
-
style.textContent = `
|
|
564
|
-
:host {
|
|
565
|
-
display: block;
|
|
566
|
-
width: 100%;
|
|
567
|
-
}
|
|
568
|
-
textarea {
|
|
569
|
-
display: block;
|
|
570
|
-
width: 100%;
|
|
571
|
-
box-sizing: border-box;
|
|
572
|
-
padding: 8px 12px;
|
|
573
|
-
border: 1px solid #d1d5db;
|
|
574
|
-
border-radius: 6px;
|
|
575
|
-
font-size: 14px;
|
|
576
|
-
line-height: 1.5;
|
|
577
|
-
color: #111827;
|
|
578
|
-
background: #fff;
|
|
579
|
-
outline: none;
|
|
580
|
-
transition: border-color 0.2s;
|
|
581
|
-
resize: vertical;
|
|
582
|
-
min-height: 80px;
|
|
583
|
-
font-family: inherit;
|
|
584
|
-
}
|
|
585
|
-
textarea.auto-height {
|
|
586
|
-
resize: none;
|
|
587
|
-
overflow: hidden;
|
|
588
|
-
}
|
|
589
|
-
textarea:focus {
|
|
590
|
-
border-color: #3b82f6;
|
|
591
|
-
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
|
|
592
|
-
}
|
|
593
|
-
textarea:disabled {
|
|
594
|
-
background: #f3f4f6;
|
|
595
|
-
color: #9ca3af;
|
|
596
|
-
cursor: not-allowed;
|
|
597
|
-
}
|
|
598
|
-
textarea::placeholder {
|
|
599
|
-
color: #9ca3af;
|
|
600
|
-
}
|
|
601
|
-
`;
|
|
602
|
-
this.shadow.appendChild(style);
|
|
603
|
-
|
|
604
|
-
const textarea = document.createElement("textarea");
|
|
605
|
-
this.innerTextarea = textarea;
|
|
606
|
-
this.shadow.appendChild(textarea);
|
|
607
|
-
|
|
608
|
-
let lastLineCount = 0;
|
|
609
|
-
|
|
610
|
-
// Stop native composed events from leaking through shadow boundary
|
|
611
|
-
// to avoid double-firing (native retarget + our CustomEvent).
|
|
612
|
-
textarea.addEventListener("input", (e) => {
|
|
613
|
-
e.stopPropagation();
|
|
614
|
-
const detail: LxTextareaEventDetail = { value: textarea.value };
|
|
615
|
-
this.dispatchEvent(new CustomEvent("input", { detail, bubbles: true }));
|
|
616
|
-
|
|
617
|
-
if (textarea.classList.contains("auto-height")) {
|
|
618
|
-
this.autoResizeTextarea(textarea);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const lineCount = this.getLineCount(textarea);
|
|
622
|
-
if (lineCount !== lastLineCount) {
|
|
623
|
-
lastLineCount = lineCount;
|
|
624
|
-
const lineDetail: LxTextareaEventDetail = { lineCount };
|
|
625
|
-
this.dispatchEvent(new CustomEvent("linechange", { detail: lineDetail, bubbles: true }));
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
textarea.addEventListener("change", (e) => {
|
|
629
|
-
e.stopPropagation();
|
|
630
|
-
const detail: LxTextareaEventDetail = { value: textarea.value };
|
|
631
|
-
this.dispatchEvent(new CustomEvent("change", { detail, bubbles: true }));
|
|
632
|
-
});
|
|
633
|
-
textarea.addEventListener("focus", (e) => {
|
|
634
|
-
e.stopPropagation();
|
|
635
|
-
const detail: LxTextareaEventDetail = { value: textarea.value };
|
|
636
|
-
this.dispatchEvent(new CustomEvent("focus", { detail, bubbles: true }));
|
|
637
|
-
});
|
|
638
|
-
textarea.addEventListener("blur", (e) => {
|
|
639
|
-
e.stopPropagation();
|
|
640
|
-
const detail: LxTextareaEventDetail = { value: textarea.value };
|
|
641
|
-
this.dispatchEvent(new CustomEvent("blur", { detail, bubbles: true }));
|
|
642
|
-
});
|
|
643
|
-
textarea.addEventListener("keydown", (e) => {
|
|
644
|
-
if (e.key !== "Enter" || e.shiftKey) return;
|
|
645
|
-
const confirmType = this.getAttr("confirm-type", "return").trim().toLowerCase();
|
|
646
|
-
if (confirmType === "return") return;
|
|
647
|
-
|
|
648
|
-
e.preventDefault();
|
|
649
|
-
const detail: LxTextareaEventDetail = { value: textarea.value };
|
|
650
|
-
this.dispatchEvent(new CustomEvent("confirm", { detail, bubbles: true }));
|
|
651
|
-
if (!this.getBoolAttr("confirm-hold")) {
|
|
652
|
-
textarea.blur();
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// Sync attributes to inner textarea
|
|
658
|
-
const textarea = this.innerTextarea!;
|
|
659
|
-
const props = this.collectProps();
|
|
660
|
-
textarea.placeholder = props.placeholder || "";
|
|
661
|
-
textarea.disabled = props.disabled;
|
|
662
|
-
if (props.maxlength !== undefined) {
|
|
663
|
-
textarea.maxLength = props.maxlength;
|
|
664
|
-
} else {
|
|
665
|
-
textarea.removeAttribute("maxlength");
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
if (autoHeight) {
|
|
669
|
-
textarea.classList.add("auto-height");
|
|
670
|
-
} else {
|
|
671
|
-
textarea.classList.remove("auto-height");
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Only sync value if it differs (avoid cursor jump)
|
|
675
|
-
if (props.value !== undefined && textarea.value !== props.value) {
|
|
676
|
-
textarea.value = props.value;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (
|
|
680
|
-
typeof props.selectionStart === "number" &&
|
|
681
|
-
typeof props.selectionEnd === "number" &&
|
|
682
|
-
props.selectionStart >= 0 &&
|
|
683
|
-
props.selectionEnd >= props.selectionStart
|
|
684
|
-
) {
|
|
685
|
-
textarea.setSelectionRange(props.selectionStart, props.selectionEnd);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// Apply placeholder style (replace on every update so dynamic changes work)
|
|
689
|
-
const placeholderStyle = props.placeholderStyle;
|
|
690
|
-
const styleEl = this.shadow!.querySelector("style")!;
|
|
691
|
-
const marker = "/* placeholder-style */";
|
|
692
|
-
const markerIdx = styleEl.textContent!.indexOf(marker);
|
|
693
|
-
if (placeholderStyle) {
|
|
694
|
-
const rule = `\n${marker}\ntextarea::placeholder { ${placeholderStyle} }`;
|
|
695
|
-
if (markerIdx >= 0) {
|
|
696
|
-
styleEl.textContent = styleEl.textContent!.substring(0, markerIdx) + rule;
|
|
697
|
-
} else {
|
|
698
|
-
styleEl.textContent += rule;
|
|
699
|
-
}
|
|
700
|
-
} else if (markerIdx >= 0) {
|
|
701
|
-
styleEl.textContent = styleEl.textContent!.substring(0, markerIdx);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
if (autoHeight) {
|
|
705
|
-
this.autoResizeTextarea(textarea);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
if (props.focus && document.activeElement !== textarea) {
|
|
709
|
-
textarea.focus();
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
private mountTextarea() {
|
|
714
|
-
if (!this.componentId) return;
|
|
715
|
-
this.ensureHostLayout();
|
|
716
|
-
|
|
717
|
-
const props = this.collectProps();
|
|
718
|
-
const dataset = this.collectDataset();
|
|
719
|
-
props.dataset = dataset;
|
|
720
|
-
props.datasetJson = JSON.stringify(dataset);
|
|
721
|
-
const pageFuncBindings = this._pageBindings;
|
|
722
|
-
props.pageFuncBindings = pageFuncBindings;
|
|
723
|
-
props.pageFuncBindingsJson = JSON.stringify(pageFuncBindings);
|
|
724
|
-
const { rect, cornerRadius } = measureElement(this);
|
|
725
|
-
props.__layoutX = rect.x;
|
|
726
|
-
props.__layoutY = rect.y;
|
|
727
|
-
props.__layoutWidth = rect.width;
|
|
728
|
-
props.__layoutHeight = rect.height;
|
|
729
|
-
|
|
730
|
-
if (this.getBoolAttr("auto-height") && this.autoHeightMinHeight === undefined && rect.height > 1) {
|
|
731
|
-
this.autoHeightMinHeight = rect.height;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
if (isHarmony()) {
|
|
735
|
-
this.ensureHarmonyEmbed(rect, props, cornerRadius);
|
|
736
|
-
this.mounted = true;
|
|
737
|
-
return;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
if (!this.getBoolAttr("auto-height")) {
|
|
741
|
-
this.autoHeightMinHeight = undefined;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
if (isDesktop() && !isMacOS()) {
|
|
745
|
-
this.mountDesktopTextarea();
|
|
746
|
-
this.mounted = true;
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
const zIndex = 9999;
|
|
751
|
-
const decision = this.updateState.decide(rect, props, zIndex, !this.mounted);
|
|
752
|
-
if (!decision.shouldSend) {
|
|
753
|
-
this.mounted = true;
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
const payload: any = {
|
|
758
|
-
action: this.mounted ? "component.update" : "component.mount",
|
|
759
|
-
id: this.componentId,
|
|
760
|
-
type: "textarea.native",
|
|
761
|
-
rect,
|
|
762
|
-
zIndex
|
|
763
|
-
};
|
|
764
|
-
if (!this.mounted || decision.propsChanged) {
|
|
765
|
-
payload.props = props;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
if (cornerRadius !== undefined) {
|
|
769
|
-
payload.cornerRadius = cornerRadius;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
sendNativeComponentMessage(payload);
|
|
773
|
-
this.mounted = true;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// Force a full native sync when host frameworks mutate properties without
|
|
777
|
-
// triggering observed attribute callbacks on custom elements.
|
|
778
|
-
syncNativeProps(): void {
|
|
779
|
-
if (!this.isConnected) return;
|
|
780
|
-
this.mountTextarea();
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
private ensureHostLayout(): void {
|
|
784
|
-
const computed = getComputedStyle(this);
|
|
785
|
-
if (computed.display === "inline") {
|
|
786
|
-
this.style.display = "block";
|
|
787
|
-
}
|
|
788
|
-
const height = Number.parseFloat(computed.height);
|
|
789
|
-
if (!Number.isFinite(height) || height <= 1) {
|
|
790
|
-
this.style.height = this.getBoolAttr("auto-height") ? "24px" : "96px";
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
// Event handler getter/setter for 'input' event
|
|
795
|
-
set oninput(cb: EventListener | null) {
|
|
796
|
-
const raw = cb as unknown;
|
|
797
|
-
const current = this._handlers['input'];
|
|
798
|
-
if (current) this.removeEventListener('input', current);
|
|
799
|
-
|
|
800
|
-
if (
|
|
801
|
-
typeof raw === "function" ||
|
|
802
|
-
(!!raw && typeof raw === "object" && typeof (raw as EventListenerObject).handleEvent === "function")
|
|
803
|
-
) {
|
|
804
|
-
const listener =
|
|
805
|
-
typeof raw === "function"
|
|
806
|
-
? ({ handleEvent: raw } as EventListenerObject)
|
|
807
|
-
: (raw as EventListenerOrEventListenerObject);
|
|
808
|
-
this._handlers['input'] = listener;
|
|
809
|
-
this.rawHandlers['input'] = raw as EventListenerOrEventListenerObject;
|
|
810
|
-
this.addEventListener('input', listener);
|
|
811
|
-
} else {
|
|
812
|
-
delete this._handlers['input'];
|
|
813
|
-
delete this.rawHandlers['input'];
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
get oninput() { return (this.rawHandlers['input'] as EventListener) || null; }
|
|
817
|
-
|
|
818
|
-
// Event handler getter/setter for 'change' event
|
|
819
|
-
set onchange(cb: EventListener | null) {
|
|
820
|
-
const raw = cb as unknown;
|
|
821
|
-
const current = this._handlers['change'];
|
|
822
|
-
if (current) this.removeEventListener('change', current);
|
|
823
|
-
|
|
824
|
-
if (
|
|
825
|
-
typeof raw === "function" ||
|
|
826
|
-
(!!raw && typeof raw === "object" && typeof (raw as EventListenerObject).handleEvent === "function")
|
|
827
|
-
) {
|
|
828
|
-
const listener =
|
|
829
|
-
typeof raw === "function"
|
|
830
|
-
? ({ handleEvent: raw } as EventListenerObject)
|
|
831
|
-
: (raw as EventListenerOrEventListenerObject);
|
|
832
|
-
this._handlers['change'] = listener;
|
|
833
|
-
this.rawHandlers['change'] = raw as EventListenerOrEventListenerObject;
|
|
834
|
-
this.addEventListener('change', listener);
|
|
835
|
-
} else {
|
|
836
|
-
delete this._handlers['change'];
|
|
837
|
-
delete this.rawHandlers['change'];
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
get onchange() { return (this.rawHandlers['change'] as EventListener) || null; }
|
|
841
|
-
|
|
842
|
-
private ensureHarmonyEmbed(
|
|
843
|
-
rect: { width: number; height: number },
|
|
844
|
-
props: Record<string, unknown>,
|
|
845
|
-
cornerRadius?: number
|
|
846
|
-
) {
|
|
847
|
-
if (!this.harmonyEmbed) {
|
|
848
|
-
const embed = document.createElement("embed");
|
|
849
|
-
embed.setAttribute("type", "native/textarea");
|
|
850
|
-
embed.setAttribute("width", `${rect.width}`);
|
|
851
|
-
embed.setAttribute("height", `${rect.height}`);
|
|
852
|
-
embed.setAttribute("id", this.componentId!);
|
|
853
|
-
embed.setAttribute("data-lx-component-id", this.componentId!);
|
|
854
|
-
embed.style.display = "block";
|
|
855
|
-
embed.style.width = "100%";
|
|
856
|
-
embed.style.height = "100%";
|
|
857
|
-
embed.style.border = "none";
|
|
858
|
-
if (cornerRadius !== undefined) {
|
|
859
|
-
embed.style.borderRadius = `${cornerRadius}px`;
|
|
860
|
-
embed.style.overflow = "hidden";
|
|
861
|
-
}
|
|
862
|
-
const encodedProps = this.encodeHarmonyProps(props);
|
|
863
|
-
embed.setAttribute("src", encodedProps);
|
|
864
|
-
this.lastHarmonyProps = encodedProps;
|
|
865
|
-
this.appendChild(embed);
|
|
866
|
-
this.harmonyEmbed = embed;
|
|
867
|
-
this.pendingHarmonyRetryCount = 0;
|
|
868
|
-
return;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
const encodedProps = this.encodeHarmonyProps(props);
|
|
872
|
-
if (encodedProps !== this.lastHarmonyProps) {
|
|
873
|
-
const pushed = this.pushHarmonyPropsUpdate(props);
|
|
874
|
-
if (pushed) {
|
|
875
|
-
this.lastHarmonyProps = encodedProps;
|
|
876
|
-
this.pendingHarmonyProps = undefined;
|
|
877
|
-
this.pendingHarmonyRetryCount = 0;
|
|
878
|
-
} else {
|
|
879
|
-
// Fallback to src update path when proxy bridge is unavailable.
|
|
880
|
-
this.harmonyEmbed.setAttribute("src", encodedProps);
|
|
881
|
-
this.lastHarmonyProps = encodedProps;
|
|
882
|
-
this.scheduleHarmonyPropsRetry(props);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
if (cornerRadius !== undefined && this.harmonyEmbed.style.borderRadius !== `${cornerRadius}px`) {
|
|
887
|
-
this.harmonyEmbed.style.borderRadius = `${cornerRadius}px`;
|
|
888
|
-
this.harmonyEmbed.style.overflow = "hidden";
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
private encodeHarmonyProps(props: Record<string, unknown>): string {
|
|
893
|
-
try {
|
|
894
|
-
const payload = { componentId: this.componentId, ...props };
|
|
895
|
-
return `${HARMONY_PROPS_PREFIX}${encodeURIComponent(JSON.stringify(payload))}`;
|
|
896
|
-
} catch (_e) {
|
|
897
|
-
return `${HARMONY_PROPS_PREFIX}%7B%7D`;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
private pushHarmonyPropsUpdate(props: Record<string, unknown>): boolean {
|
|
902
|
-
if (!this.componentId || !isHarmony()) return false;
|
|
903
|
-
try {
|
|
904
|
-
const proxy = (window as unknown as Record<string, unknown>).LingXiaProxy as
|
|
905
|
-
| { nativeComponentUpdate?: ((componentId: string, payload: string) => void) | ((payload: string) => void) }
|
|
906
|
-
| undefined;
|
|
907
|
-
const updateFn = proxy?.nativeComponentUpdate;
|
|
908
|
-
if (typeof updateFn !== "function") {
|
|
909
|
-
return false;
|
|
910
|
-
}
|
|
911
|
-
const payload = JSON.stringify({ componentId: this.componentId, ...props });
|
|
912
|
-
try {
|
|
913
|
-
(updateFn as (payload: string) => void).call(proxy, payload);
|
|
914
|
-
return true;
|
|
915
|
-
} catch {
|
|
916
|
-
try {
|
|
917
|
-
(updateFn as (componentId: string, payload: string) => void).call(proxy, this.componentId, payload);
|
|
918
|
-
return true;
|
|
919
|
-
} catch {
|
|
920
|
-
return false;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
} catch {
|
|
924
|
-
return false;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
private scheduleHarmonyPropsRetry(props: Record<string, unknown>): void {
|
|
929
|
-
this.pendingHarmonyProps = { ...props };
|
|
930
|
-
if (this.pendingHarmonyRetryTimer !== null) {
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
this.pendingHarmonyRetryTimer = window.setTimeout(() => {
|
|
934
|
-
this.pendingHarmonyRetryTimer = null;
|
|
935
|
-
if (!this.isConnected || !this.pendingHarmonyProps) {
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
const pendingProps = this.pendingHarmonyProps;
|
|
939
|
-
const pushed = this.pushHarmonyPropsUpdate(pendingProps);
|
|
940
|
-
if (pushed) {
|
|
941
|
-
this.lastHarmonyProps = this.encodeHarmonyProps(pendingProps);
|
|
942
|
-
this.pendingHarmonyProps = undefined;
|
|
943
|
-
this.pendingHarmonyRetryCount = 0;
|
|
944
|
-
} else {
|
|
945
|
-
this.pendingHarmonyRetryCount += 1;
|
|
946
|
-
if (this.pendingHarmonyRetryCount < 20) {
|
|
947
|
-
this.scheduleHarmonyPropsRetry(pendingProps);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}, 100);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// Register the custom element
|
|
955
|
-
export function registerTextareaComponent() {
|
|
956
|
-
if (typeof window !== "undefined" && !customElements.get("lx-textarea")) {
|
|
957
|
-
customElements.define("lx-textarea", LxTextareaElement);
|
|
958
|
-
}
|
|
959
|
-
}
|