@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
@@ -0,0 +1,533 @@
1
+ // LxNavigator - Navigation component for LingXia apps
2
+ // Similar to WeChat mini program navigator component
3
+
4
+ export type NavigatorOpenType =
5
+ | 'navigate' // Push new page (default)
6
+ | 'redirect' // Replace current page
7
+ | 'navigateBack' // Go back
8
+ | 'reLaunch' // Restart app with new page
9
+ | 'switchTab' // Switch to tab page
10
+ | 'exit' // Exit current lxapp
11
+ | 'openUrl' // Open external URL or lxapp
12
+ | 'tel'; // Make a phone call
13
+
14
+ export type NavigatorTarget =
15
+ | 'self' // Navigate within current lxapp (default)
16
+ | 'lxapp' // Open another lxapp
17
+ | 'browser'; // Open in external browser
18
+
19
+ export interface LxNavigatorEventDetail {
20
+ success?: boolean;
21
+ errMsg?: string;
22
+ }
23
+
24
+ export interface LxNavigatorEvent extends CustomEvent<LxNavigatorEventDetail> {
25
+ detail: LxNavigatorEventDetail;
26
+ }
27
+
28
+ export type LxNavigatorAttributes = {
29
+ // Navigation
30
+ url?: string; // Target URL or path
31
+ 'open-type'?: NavigatorOpenType; // Navigation type
32
+ target?: NavigatorTarget; // Navigation target (auto-inferred if not specified)
33
+ delta?: number; // Pages to go back (for navigateBack)
34
+
35
+ // Open external lxapp
36
+ 'app-id'?: string; // Target lxapp ID
37
+ path?: string; // Path in target lxapp (supports query string)
38
+
39
+ // Phone call
40
+ 'phone-number'?: string; // Phone number for tel open-type
41
+
42
+ // Hover effect
43
+ 'hover-class'?: string; // CSS class for hover state
44
+ 'hover-stop-propagation'?: boolean; // Prevent hover propagation
45
+ 'hover-start-time'?: number; // Hover start delay (ms)
46
+ 'hover-stay-time'?: number; // Hover stay duration (ms)
47
+
48
+ // Styling
49
+ className?: string;
50
+ style?: any;
51
+
52
+ // Events
53
+ onSuccess?: (e: LxNavigatorEvent) => void;
54
+ onFail?: (e: LxNavigatorEvent) => void;
55
+ onComplete?: (e: LxNavigatorEvent) => void;
56
+
57
+ // React
58
+ ref?: any;
59
+ children?: any;
60
+ };
61
+
62
+ declare global {
63
+ namespace JSX {
64
+ interface IntrinsicElements {
65
+ "lx-navigator": LxNavigatorAttributes;
66
+ }
67
+ }
68
+ }
69
+
70
+ // Component implementation
71
+ export class LxNavigatorElement extends HTMLElement {
72
+ static get observedAttributes() {
73
+ return [
74
+ "url",
75
+ "open-type",
76
+ "target",
77
+ "delta",
78
+ "app-id",
79
+ "path",
80
+ "phone-number",
81
+ "hover-class",
82
+ "hover-stop-propagation",
83
+ "hover-start-time",
84
+ "hover-stay-time"
85
+ ];
86
+ }
87
+
88
+ private hoverClass: string = 'navigator-hover';
89
+ private hoverStopPropagation: boolean = false;
90
+ private hoverStartTime: number = 20;
91
+ private hoverStayTime: number = 70;
92
+ private hoverTimer: number | null = null;
93
+ private isHovering: boolean = false;
94
+ private readonly onClick = (e: MouseEvent) => this.handleClick(e);
95
+ private readonly onTouchStart = (e: TouchEvent) => this.handleTouchStart(e);
96
+ private readonly onTouchEnd = (e: TouchEvent) => this.handleTouchEnd(e);
97
+ private readonly onTouchCancel = (e: TouchEvent) => this.handleTouchCancel(e);
98
+ private readonly onMouseEnter = (e: MouseEvent) => this.handleMouseEnter(e);
99
+ private readonly onMouseLeave = (e: MouseEvent) => this.handleMouseLeave(e);
100
+ private readonly clickListener: EventListenerObject = {
101
+ handleEvent: (event: Event): void => this.onClick(event as MouseEvent),
102
+ };
103
+ private readonly touchStartListener: EventListenerObject = {
104
+ handleEvent: (event: Event): void => this.onTouchStart(event as TouchEvent),
105
+ };
106
+ private readonly touchEndListener: EventListenerObject = {
107
+ handleEvent: (event: Event): void => this.onTouchEnd(event as TouchEvent),
108
+ };
109
+ private readonly touchCancelListener: EventListenerObject = {
110
+ handleEvent: (event: Event): void => this.onTouchCancel(event as TouchEvent),
111
+ };
112
+ private readonly mouseEnterListener: EventListenerObject = {
113
+ handleEvent: (event: Event): void => this.onMouseEnter(event as MouseEvent),
114
+ };
115
+ private readonly mouseLeaveListener: EventListenerObject = {
116
+ handleEvent: (event: Event): void => this.onMouseLeave(event as MouseEvent),
117
+ };
118
+
119
+ connectedCallback() {
120
+ this.setupHoverEffect();
121
+ this.setupClickHandler();
122
+ this.applyDefaultStyles();
123
+ }
124
+
125
+ disconnectedCallback() {
126
+ this.clearHoverTimer();
127
+ this.removeEventListener('click', this.clickListener);
128
+ this.removeEventListener('touchstart', this.touchStartListener);
129
+ this.removeEventListener('touchend', this.touchEndListener);
130
+ this.removeEventListener('touchcancel', this.touchCancelListener);
131
+ this.removeEventListener('mouseenter', this.mouseEnterListener);
132
+ this.removeEventListener('mouseleave', this.mouseLeaveListener);
133
+ }
134
+
135
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
136
+ if (oldValue === newValue) return;
137
+
138
+ switch (name) {
139
+ case 'hover-class':
140
+ this.hoverClass = newValue || 'navigator-hover';
141
+ break;
142
+ case 'hover-stop-propagation':
143
+ this.hoverStopPropagation = newValue === 'true';
144
+ break;
145
+ case 'hover-start-time':
146
+ this.hoverStartTime = parseInt(newValue || '20', 10);
147
+ break;
148
+ case 'hover-stay-time':
149
+ this.hoverStayTime = parseInt(newValue || '70', 10);
150
+ break;
151
+ }
152
+ }
153
+
154
+ private applyDefaultStyles() {
155
+ if (!this.hasAttribute('style') && !this.className) {
156
+ this.style.display = 'inline-block';
157
+ this.style.cursor = 'pointer';
158
+ this.style.userSelect = 'none';
159
+ (this.style as any).webkitTapHighlightColor = 'transparent';
160
+ }
161
+ }
162
+
163
+ private setupHoverEffect() {
164
+ this.addEventListener('touchstart', this.touchStartListener, { passive: true });
165
+ this.addEventListener('touchend', this.touchEndListener, { passive: true });
166
+ this.addEventListener('touchcancel', this.touchCancelListener, { passive: true });
167
+
168
+ // Desktop hover
169
+ this.addEventListener('mouseenter', this.mouseEnterListener);
170
+ this.addEventListener('mouseleave', this.mouseLeaveListener);
171
+ }
172
+
173
+ private setupClickHandler() {
174
+ this.addEventListener('click', this.clickListener);
175
+ }
176
+
177
+ private handleTouchStart(e: TouchEvent) {
178
+ if (this.hoverStopPropagation) {
179
+ e.stopPropagation();
180
+ }
181
+
182
+ this.clearHoverTimer();
183
+ this.hoverTimer = window.setTimeout(() => {
184
+ this.addHoverClass();
185
+ }, this.hoverStartTime);
186
+ }
187
+
188
+ private handleTouchEnd(_e: TouchEvent) {
189
+ this.clearHoverTimer();
190
+ if (this.isHovering) {
191
+ window.setTimeout(() => {
192
+ this.removeHoverClass();
193
+ }, this.hoverStayTime);
194
+ }
195
+ }
196
+
197
+ private handleTouchCancel(_e: TouchEvent) {
198
+ this.clearHoverTimer();
199
+ this.removeHoverClass();
200
+ }
201
+
202
+ private handleMouseEnter(_e: MouseEvent) {
203
+ this.clearHoverTimer();
204
+ this.hoverTimer = window.setTimeout(() => {
205
+ this.addHoverClass();
206
+ }, this.hoverStartTime);
207
+ }
208
+
209
+ private handleMouseLeave(_e: MouseEvent) {
210
+ this.clearHoverTimer();
211
+ if (this.isHovering) {
212
+ window.setTimeout(() => {
213
+ this.removeHoverClass();
214
+ }, this.hoverStayTime);
215
+ }
216
+ }
217
+
218
+ private addHoverClass() {
219
+ if (this.hoverClass && this.hoverClass !== 'none') {
220
+ this.classList.add(this.hoverClass);
221
+ this.isHovering = true;
222
+ }
223
+ }
224
+
225
+ private removeHoverClass() {
226
+ if (this.hoverClass && this.hoverClass !== 'none') {
227
+ this.classList.remove(this.hoverClass);
228
+ this.isHovering = false;
229
+ }
230
+ }
231
+
232
+ private clearHoverTimer() {
233
+ if (this.hoverTimer !== null) {
234
+ window.clearTimeout(this.hoverTimer);
235
+ this.hoverTimer = null;
236
+ }
237
+ }
238
+
239
+ private handleClick(e: MouseEvent) {
240
+ e.preventDefault();
241
+
242
+ const url = this.getAttribute('url') || '';
243
+ const openType = (this.getAttribute('open-type') || 'navigate') as NavigatorOpenType;
244
+ const explicitTarget = this.getAttribute('target') as NavigatorTarget | null;
245
+ const delta = parseInt(this.getAttribute('delta') || '1', 10);
246
+ const appId = this.getAttribute('app-id');
247
+ const path = this.getAttribute('path');
248
+ const phoneNumber = this.getAttribute('phone-number');
249
+
250
+ // Auto-infer target if not explicitly specified
251
+ const target = this.inferTarget(explicitTarget, url, appId);
252
+
253
+ void this.navigate({
254
+ url,
255
+ openType,
256
+ target,
257
+ delta,
258
+ appId,
259
+ path,
260
+ phoneNumber
261
+ });
262
+ }
263
+
264
+ /**
265
+ * Auto-infer navigation target based on context
266
+ * Priority: explicit target > appId > HTTPS URL > default (self)
267
+ */
268
+ private inferTarget(
269
+ explicitTarget: NavigatorTarget | null,
270
+ url: string,
271
+ appId: string | null
272
+ ): NavigatorTarget {
273
+ // 1. Explicit target has highest priority
274
+ if (explicitTarget) {
275
+ return explicitTarget;
276
+ }
277
+
278
+ // 2. If appId is specified, target is another lxapp
279
+ if (appId) {
280
+ return 'lxapp';
281
+ }
282
+
283
+ // 3. If URL starts with http:// or https://, open in browser
284
+ if (/^https?:\/\//i.test(url)) {
285
+ return 'browser';
286
+ }
287
+
288
+ // 4. Default: navigate within current lxapp
289
+ return 'self';
290
+ }
291
+
292
+ private async navigate(options: {
293
+ url: string;
294
+ openType: NavigatorOpenType;
295
+ target: NavigatorTarget;
296
+ delta: number;
297
+ appId?: string | null;
298
+ path?: string | null;
299
+ phoneNumber?: string | null;
300
+ }) {
301
+ try {
302
+ await this.performNavigation(options);
303
+ this.dispatchSuccess();
304
+ } catch (error) {
305
+ this.dispatchFail(error);
306
+ }
307
+ }
308
+
309
+ private dispatchSuccess() {
310
+ const successEvent = new CustomEvent('success', {
311
+ detail: {
312
+ success: true
313
+ },
314
+ bubbles: true,
315
+ composed: true
316
+ });
317
+ this.dispatchEvent(successEvent);
318
+
319
+ const completeEvent = new CustomEvent('complete', {
320
+ detail: {
321
+ success: true
322
+ },
323
+ bubbles: true,
324
+ composed: true
325
+ });
326
+ this.dispatchEvent(completeEvent);
327
+ }
328
+
329
+ private resolveErrorMessage(error: unknown): string {
330
+ if (error instanceof Error) {
331
+ const message = error.message.trim();
332
+ return message || 'Unknown error';
333
+ }
334
+
335
+ if (typeof error === 'string') {
336
+ const message = error.trim();
337
+ return message || 'Unknown error';
338
+ }
339
+
340
+ if (error && typeof error === 'object') {
341
+ const message = (error as { message?: unknown }).message;
342
+ if (typeof message === 'string' && message.trim()) {
343
+ return message;
344
+ }
345
+ }
346
+
347
+ return 'Unknown error';
348
+ }
349
+
350
+ private dispatchFail(error: unknown) {
351
+ const errMsg = this.resolveErrorMessage(error);
352
+ const failEvent = new CustomEvent('fail', {
353
+ detail: {
354
+ success: false,
355
+ errMsg
356
+ },
357
+ bubbles: true,
358
+ composed: true
359
+ });
360
+ this.dispatchEvent(failEvent);
361
+
362
+ const completeEvent = new CustomEvent('complete', {
363
+ detail: {
364
+ success: false,
365
+ errMsg
366
+ },
367
+ bubbles: true,
368
+ composed: true
369
+ });
370
+ this.dispatchEvent(completeEvent);
371
+ }
372
+
373
+ private getHostApi() {
374
+ const host = (window as any).host;
375
+ if (!host || typeof host !== 'object') {
376
+ return null;
377
+ }
378
+ return host as {
379
+ navigation?: {
380
+ navigateTo?: (params: { url: string }) => Promise<void>;
381
+ navigateBack?: (params: { delta: number }) => Promise<void>;
382
+ redirectTo?: (params: { url: string }) => Promise<void>;
383
+ switchTab?: (params: { url: string }) => Promise<void>;
384
+ reLaunch?: (params: { url: string }) => Promise<void>;
385
+ };
386
+ navigator?: {
387
+ navigateToLxApp?: (params: { appId: string; path?: string }) => Promise<void>;
388
+ navigateBackLxApp?: () => Promise<void>;
389
+ };
390
+ device?: {
391
+ makePhoneCall?: (params: { phoneNumber: string }) => Promise<void>;
392
+ openUrl?: (params: { url: string; target?: 'external' | 'self' | 'new_browser_tab' }) => Promise<void>;
393
+ };
394
+ };
395
+ }
396
+
397
+ private async performNavigation(options: {
398
+ url: string;
399
+ openType: NavigatorOpenType;
400
+ target: NavigatorTarget;
401
+ delta: number;
402
+ appId?: string | null;
403
+ path?: string | null;
404
+ phoneNumber?: string | null;
405
+ }) {
406
+ const host = this.getHostApi();
407
+ if (!host) {
408
+ throw new Error('LingXia bridge not available');
409
+ }
410
+
411
+ const url = options.url || '';
412
+ const delta = Number.isFinite(options.delta) && options.delta > 0 ? options.delta : 1;
413
+
414
+ if (options.openType === 'tel') {
415
+ if (!options.phoneNumber) {
416
+ throw new Error('tel requires phone-number attribute');
417
+ }
418
+ if (typeof host.device?.makePhoneCall !== 'function') {
419
+ throw new Error('host.device.makePhoneCall is not available');
420
+ }
421
+ await host.device.makePhoneCall({ phoneNumber: options.phoneNumber });
422
+ return;
423
+ }
424
+
425
+ if (options.openType === 'exit') {
426
+ if (typeof host.navigator?.navigateBackLxApp !== 'function') {
427
+ throw new Error('host.navigator.navigateBackLxApp is not available');
428
+ }
429
+ await host.navigator.navigateBackLxApp();
430
+ return;
431
+ }
432
+
433
+ if (options.openType === 'navigateBack') {
434
+ if (options.target === 'lxapp') {
435
+ if (typeof host.navigator?.navigateBackLxApp !== 'function') {
436
+ throw new Error('host.navigator.navigateBackLxApp is not available');
437
+ }
438
+ await host.navigator.navigateBackLxApp();
439
+ return;
440
+ }
441
+ if (options.target === 'browser') {
442
+ throw new Error('navigateBack is not supported for browser target');
443
+ }
444
+ if (typeof host.navigation?.navigateBack !== 'function') {
445
+ throw new Error('host.navigation.navigateBack is not available');
446
+ }
447
+ await host.navigation.navigateBack({ delta });
448
+ return;
449
+ }
450
+
451
+ if (options.target === 'browser') {
452
+ if (!url) {
453
+ throw new Error('openUrl requires url');
454
+ }
455
+ if (typeof host.device?.openUrl !== 'function') {
456
+ throw new Error('host.device.openUrl is not available');
457
+ }
458
+ await host.device.openUrl({ url, target: 'external' });
459
+ return;
460
+ }
461
+
462
+ if (options.target === 'lxapp') {
463
+ if (!options.appId) {
464
+ throw new Error('navigateToLxApp requires app-id');
465
+ }
466
+ const lxappPath = options.path || '';
467
+ if (typeof host.navigator?.navigateToLxApp !== 'function') {
468
+ throw new Error('host.navigator.navigateToLxApp is not available');
469
+ }
470
+ await host.navigator.navigateToLxApp({ appId: options.appId, path: lxappPath });
471
+ return;
472
+ }
473
+
474
+ if (options.target === 'self' && /^https?:\/\//i.test(url)) {
475
+ if (!url) {
476
+ throw new Error('openUrl requires url');
477
+ }
478
+ if (typeof host.device?.openUrl !== 'function') {
479
+ throw new Error('host.device.openUrl is not available');
480
+ }
481
+ await host.device.openUrl({ url, target: 'self' });
482
+ return;
483
+ }
484
+
485
+ if (!url) {
486
+ throw new Error(`${options.openType} requires url`);
487
+ }
488
+
489
+ switch (options.openType) {
490
+ case 'navigate':
491
+ if (typeof host.navigation?.navigateTo !== 'function') {
492
+ throw new Error('host.navigation.navigateTo is not available');
493
+ }
494
+ await host.navigation.navigateTo({ url });
495
+ break;
496
+ case 'redirect':
497
+ if (typeof host.navigation?.redirectTo !== 'function') {
498
+ throw new Error('host.navigation.redirectTo is not available');
499
+ }
500
+ await host.navigation.redirectTo({ url });
501
+ break;
502
+ case 'switchTab':
503
+ if (typeof host.navigation?.switchTab !== 'function') {
504
+ throw new Error('host.navigation.switchTab is not available');
505
+ }
506
+ await host.navigation.switchTab({ url });
507
+ break;
508
+ case 'reLaunch':
509
+ if (typeof host.navigation?.reLaunch !== 'function') {
510
+ throw new Error('host.navigation.reLaunch is not available');
511
+ }
512
+ await host.navigation.reLaunch({ url });
513
+ break;
514
+ case 'openUrl':
515
+ if (typeof host.device?.openUrl !== 'function') {
516
+ throw new Error('host.device.openUrl is not available');
517
+ }
518
+ await host.device.openUrl({
519
+ url,
520
+ target: options.target === 'self' ? 'self' : 'external'
521
+ });
522
+ break;
523
+ default:
524
+ throw new Error(`Unsupported openType: ${options.openType}`);
525
+ }
526
+ }
527
+
528
+ }
529
+
530
+ // Register custom element
531
+ if (typeof customElements !== 'undefined' && !customElements.get('lx-navigator')) {
532
+ customElements.define('lx-navigator', LxNavigatorElement);
533
+ }