@lingxia/elements 0.5.1 → 0.6.3

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
@@ -2,7 +2,7 @@ export { registerVideoComponent, LxVideoElement, type LxVideoAttributes, } from
2
2
  export { registerPickerComponent, LxPickerElement, type LxPickerAttributes, type LxPickerColumn, type LxPickerCascadingColumns, type LxPickerEvent, type LxPickerEventDetail, } from "./picker.js";
3
3
  export { registerInputComponent, LxInputElement, type LxInputAttributes, type LxInputEvent, type LxInputEventDetail, } from "./input.js";
4
4
  export { registerTextareaComponent, LxTextareaElement, type LxTextareaAttributes, type LxTextareaEvent, type LxTextareaEventDetail, } from "./textarea.js";
5
- export { LxNavigatorElement, type LxNavigatorAttributes, type LxNavigatorEvent, type NavigatorOpenType, type NavigatorTarget, } from "./navigator.js";
5
+ export { LxNavigatorElement, type LxNavigatorAttributes, type LxNavigatorEvent, type NavigatorEnvVersion, type NavigatorOpenType, type NavigatorQuery, type NavigatorQueryValue, type NavigatorTarget, } from "./navigator.js";
6
6
  export { buildVideoNativeAttrs, buildNavigatorNativeAttrs, buildPickerNativeAttrs, getPickerDisplayText, getPickerValueFromIndex, VIDEO_DOM_EVENT_MAP, } from "./native_component_wrapper_shared.js";
7
7
  export { buildInputNativeAttrs, buildTextareaNativeAttrs, } from "./text_component_native_attrs.js";
8
8
  export { isHarmony, isIOS, isAndroid, isMacOS, isDesktop, getOS, } from "./platform.js";
@@ -1,14 +1,19 @@
1
- import type { NavigatorOpenType, NavigatorTarget } from "./navigator.js";
1
+ import type { NavigatorEnvVersion, NavigatorOpenType, NavigatorTarget } from "./navigator.js";
2
+ import type { NavigatorQuery } from "./navigator.js";
2
3
  import type { LxVideoQuality } from "./video.js";
3
4
  export declare function appendDataAttrs(attrs: Record<string, unknown>, target: Record<string, string>): void;
4
5
  export declare function appendPassthroughAttrs(attrs: Record<string, unknown>, target: Record<string, string>): void;
5
6
  export interface NavigatorNativeAttrOptions {
6
7
  url?: string;
8
+ page?: string;
7
9
  openType?: NavigatorOpenType;
8
10
  target?: NavigatorTarget;
9
11
  delta?: number;
10
12
  appId?: string;
11
13
  path?: string;
14
+ query?: NavigatorQuery;
15
+ envVersion?: NavigatorEnvVersion;
16
+ targetVersion?: string;
12
17
  phoneNumber?: string;
13
18
  hoverClass?: string;
14
19
  hoverStopPropagation?: boolean;
@@ -35,12 +35,20 @@ export function buildNavigatorNativeAttrs(options, extraAttrs = {}) {
35
35
  };
36
36
  if (options.url)
37
37
  result.url = options.url;
38
+ if (options.page)
39
+ result.page = options.page;
38
40
  if (options.target)
39
41
  result.target = options.target;
40
42
  if (options.appId)
41
43
  result["app-id"] = options.appId;
42
44
  if (options.path)
43
45
  result.path = options.path;
46
+ if (options.query)
47
+ result.query = JSON.stringify(options.query);
48
+ if (options.envVersion)
49
+ result["env-version"] = options.envVersion;
50
+ if (options.targetVersion)
51
+ result["target-version"] = options.targetVersion;
44
52
  if (options.phoneNumber)
45
53
  result["phone-number"] = options.phoneNumber;
46
54
  appendPassthroughAttrs(extraAttrs, result);
@@ -1,5 +1,8 @@
1
1
  export type NavigatorOpenType = 'navigate' | 'redirect' | 'navigateBack' | 'reLaunch' | 'switchTab' | 'exit' | 'openUrl' | 'tel';
2
2
  export type NavigatorTarget = 'self' | 'lxapp' | 'browser';
3
+ export type NavigatorQueryValue = string | number | boolean | null | undefined;
4
+ export type NavigatorQuery = Record<string, NavigatorQueryValue>;
5
+ export type NavigatorEnvVersion = 'release' | 'preview' | 'develop';
3
6
  export interface LxNavigatorEventDetail {
4
7
  success?: boolean;
5
8
  errMsg?: string;
@@ -9,11 +12,15 @@ export interface LxNavigatorEvent extends CustomEvent<LxNavigatorEventDetail> {
9
12
  }
10
13
  export type LxNavigatorAttributes = {
11
14
  url?: string;
15
+ page?: string;
16
+ path?: string;
17
+ query?: string;
12
18
  'open-type'?: NavigatorOpenType;
13
19
  target?: NavigatorTarget;
14
20
  delta?: number;
15
21
  'app-id'?: string;
16
- path?: string;
22
+ 'env-version'?: NavigatorEnvVersion;
23
+ 'target-version'?: string;
17
24
  'phone-number'?: string;
18
25
  'hover-class'?: string;
19
26
  'hover-stop-propagation'?: boolean;
@@ -78,6 +85,9 @@ export declare class LxNavigatorElement extends HTMLElement {
78
85
  private dispatchSuccess;
79
86
  private resolveErrorMessage;
80
87
  private dispatchFail;
81
- private getHostApi;
88
+ private callHost;
82
89
  private performNavigation;
90
+ private readQuery;
91
+ private buildPageTarget;
92
+ private buildLxAppTarget;
83
93
  }
package/dist/navigator.js CHANGED
@@ -38,11 +38,15 @@ export class LxNavigatorElement extends HTMLElement {
38
38
  static get observedAttributes() {
39
39
  return [
40
40
  "url",
41
+ "page",
42
+ "path",
43
+ "query",
41
44
  "open-type",
42
45
  "target",
43
46
  "delta",
44
47
  "app-id",
45
- "path",
48
+ "env-version",
49
+ "target-version",
46
50
  "phone-number",
47
51
  "hover-class",
48
52
  "hover-stop-propagation",
@@ -157,21 +161,29 @@ export class LxNavigatorElement extends HTMLElement {
157
161
  handleClick(e) {
158
162
  e.preventDefault();
159
163
  const url = this.getAttribute('url') || '';
164
+ const page = this.getAttribute('page');
165
+ const path = this.getAttribute('path');
166
+ const query = this.getAttribute('query');
160
167
  const openType = (this.getAttribute('open-type') || 'navigate');
161
168
  const explicitTarget = this.getAttribute('target');
162
169
  const delta = parseInt(this.getAttribute('delta') || '1', 10);
163
170
  const appId = this.getAttribute('app-id');
164
- const path = this.getAttribute('path');
171
+ const envVersion = this.getAttribute('env-version');
172
+ const targetVersion = this.getAttribute('target-version');
165
173
  const phoneNumber = this.getAttribute('phone-number');
166
174
  // Auto-infer target if not explicitly specified
167
175
  const target = this.inferTarget(explicitTarget, url, appId);
168
176
  void this.navigate({
169
177
  url,
178
+ page,
179
+ path,
180
+ query,
170
181
  openType,
171
182
  target,
172
183
  delta,
173
184
  appId,
174
- path,
185
+ envVersion,
186
+ targetVersion,
175
187
  phoneNumber
176
188
  });
177
189
  }
@@ -260,119 +272,78 @@ export class LxNavigatorElement extends HTMLElement {
260
272
  });
261
273
  this.dispatchEvent(completeEvent);
262
274
  }
263
- getHostApi() {
264
- const host = window.host;
265
- if (!host || typeof host !== 'object') {
266
- return null;
275
+ callHost(route, params) {
276
+ const bridge = window.LingXiaBridge;
277
+ if (!bridge || typeof bridge.invoke !== 'function') {
278
+ return Promise.reject(new Error('LingXiaBridge is not available'));
267
279
  }
268
- return host;
280
+ return bridge.invoke(route, params).then(() => undefined);
269
281
  }
270
282
  async performNavigation(options) {
271
- var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o;
272
- const host = this.getHostApi();
273
- if (!host) {
274
- throw new Error('LingXia bridge not available');
275
- }
276
283
  const url = options.url || '';
277
284
  const delta = Number.isFinite(options.delta) && options.delta > 0 ? options.delta : 1;
278
285
  if (options.openType === 'tel') {
279
286
  if (!options.phoneNumber) {
280
287
  throw new Error('tel requires phone-number attribute');
281
288
  }
282
- if (typeof ((_a = host.device) === null || _a === void 0 ? void 0 : _a.makePhoneCall) !== 'function') {
283
- throw new Error('host.device.makePhoneCall is not available');
284
- }
285
- await host.device.makePhoneCall({ phoneNumber: options.phoneNumber });
289
+ await this.callHost('device.makePhoneCall', { phoneNumber: options.phoneNumber });
286
290
  return;
287
291
  }
288
292
  if (options.openType === 'exit') {
289
- if (typeof ((_b = host.navigator) === null || _b === void 0 ? void 0 : _b.navigateBackLxApp) !== 'function') {
290
- throw new Error('host.navigator.navigateBackLxApp is not available');
291
- }
292
- await host.navigator.navigateBackLxApp();
293
+ await this.callHost('navigator.navigateBackLxApp');
293
294
  return;
294
295
  }
295
296
  if (options.openType === 'navigateBack') {
296
297
  if (options.target === 'lxapp') {
297
- if (typeof ((_c = host.navigator) === null || _c === void 0 ? void 0 : _c.navigateBackLxApp) !== 'function') {
298
- throw new Error('host.navigator.navigateBackLxApp is not available');
299
- }
300
- await host.navigator.navigateBackLxApp();
298
+ await this.callHost('navigator.navigateBackLxApp');
301
299
  return;
302
300
  }
303
301
  if (options.target === 'browser') {
304
302
  throw new Error('navigateBack is not supported for browser target');
305
303
  }
306
- if (typeof ((_d = host.navigation) === null || _d === void 0 ? void 0 : _d.navigateBack) !== 'function') {
307
- throw new Error('host.navigation.navigateBack is not available');
308
- }
309
- await host.navigation.navigateBack({ delta });
304
+ await this.callHost('navigation.navigateBack', { delta });
310
305
  return;
311
306
  }
312
307
  if (options.target === 'browser') {
313
308
  if (!url) {
314
309
  throw new Error('openUrl requires url');
315
310
  }
316
- if (typeof ((_f = host.device) === null || _f === void 0 ? void 0 : _f.openUrl) !== 'function') {
317
- throw new Error('host.device.openUrl is not available');
318
- }
319
- await host.device.openUrl({ url, target: 'external' });
311
+ await this.callHost('device.openUrl', { url, target: 'external' });
320
312
  return;
321
313
  }
322
314
  if (options.target === 'lxapp') {
323
315
  if (!options.appId) {
324
316
  throw new Error('navigateToLxApp requires app-id');
325
317
  }
326
- const lxappPath = options.path || '';
327
- if (typeof ((_g = host.navigator) === null || _g === void 0 ? void 0 : _g.navigateToLxApp) !== 'function') {
328
- throw new Error('host.navigator.navigateToLxApp is not available');
329
- }
330
- await host.navigator.navigateToLxApp({ appId: options.appId, path: lxappPath });
318
+ await this.callHost('navigator.navigateToLxApp', this.buildLxAppTarget(options));
331
319
  return;
332
320
  }
333
321
  if (options.target === 'self' && /^https?:\/\//i.test(url)) {
334
322
  if (!url) {
335
323
  throw new Error('openUrl requires url');
336
324
  }
337
- if (typeof ((_h = host.device) === null || _h === void 0 ? void 0 : _h.openUrl) !== 'function') {
338
- throw new Error('host.device.openUrl is not available');
339
- }
340
- await host.device.openUrl({ url, target: 'self' });
325
+ await this.callHost('device.openUrl', { url, target: 'self' });
341
326
  return;
342
327
  }
343
- if (!url) {
344
- throw new Error(`${options.openType} requires url`);
328
+ const target = this.buildPageTarget(options);
329
+ if (!target) {
330
+ throw new Error(`${options.openType} requires page or path`);
345
331
  }
346
332
  switch (options.openType) {
347
333
  case 'navigate':
348
- if (typeof ((_j = host.navigation) === null || _j === void 0 ? void 0 : _j.navigateTo) !== 'function') {
349
- throw new Error('host.navigation.navigateTo is not available');
350
- }
351
- await host.navigation.navigateTo({ url });
334
+ await this.callHost('navigation.navigateTo', target);
352
335
  break;
353
336
  case 'redirect':
354
- if (typeof ((_k = host.navigation) === null || _k === void 0 ? void 0 : _k.redirectTo) !== 'function') {
355
- throw new Error('host.navigation.redirectTo is not available');
356
- }
357
- await host.navigation.redirectTo({ url });
337
+ await this.callHost('navigation.redirectTo', target);
358
338
  break;
359
339
  case 'switchTab':
360
- if (typeof ((_l = host.navigation) === null || _l === void 0 ? void 0 : _l.switchTab) !== 'function') {
361
- throw new Error('host.navigation.switchTab is not available');
362
- }
363
- await host.navigation.switchTab({ url });
340
+ await this.callHost('navigation.switchTab', target);
364
341
  break;
365
342
  case 'reLaunch':
366
- if (typeof ((_m = host.navigation) === null || _m === void 0 ? void 0 : _m.reLaunch) !== 'function') {
367
- throw new Error('host.navigation.reLaunch is not available');
368
- }
369
- await host.navigation.reLaunch({ url });
343
+ await this.callHost('navigation.reLaunch', target);
370
344
  break;
371
345
  case 'openUrl':
372
- if (typeof ((_o = host.device) === null || _o === void 0 ? void 0 : _o.openUrl) !== 'function') {
373
- throw new Error('host.device.openUrl is not available');
374
- }
375
- await host.device.openUrl({
346
+ await this.callHost('device.openUrl', {
376
347
  url,
377
348
  target: options.target === 'self' ? 'self' : 'external'
378
349
  });
@@ -381,6 +352,41 @@ export class LxNavigatorElement extends HTMLElement {
381
352
  throw new Error(`Unsupported openType: ${options.openType}`);
382
353
  }
383
354
  }
355
+ readQuery(raw) {
356
+ if (!raw)
357
+ return undefined;
358
+ const parsed = JSON.parse(raw);
359
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
360
+ throw new Error('query must be an object');
361
+ }
362
+ return parsed;
363
+ }
364
+ buildPageTarget(options) {
365
+ var _a, _b;
366
+ const page = (_a = options.page) === null || _a === void 0 ? void 0 : _a.trim();
367
+ const path = (_b = options.path) === null || _b === void 0 ? void 0 : _b.trim();
368
+ if (page && path) {
369
+ throw new Error('pass either page or path, not both');
370
+ }
371
+ if (!page && !path)
372
+ return null;
373
+ const query = this.readQuery(options.query);
374
+ return {
375
+ ...(page ? { page } : { path: path }),
376
+ ...(query ? { query } : {}),
377
+ };
378
+ }
379
+ buildLxAppTarget(options) {
380
+ const target = { appId: options.appId };
381
+ const pageTarget = this.buildPageTarget(options);
382
+ if (pageTarget)
383
+ Object.assign(target, pageTarget);
384
+ if (options.envVersion)
385
+ target.envVersion = options.envVersion;
386
+ if (options.targetVersion)
387
+ target.targetVersion = options.targetVersion;
388
+ return target;
389
+ }
384
390
  }
385
391
  // Register custom element
386
392
  if (typeof customElements !== 'undefined' && !customElements.get('lx-navigator')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingxia/elements",
3
- "version": "0.5.1",
3
+ "version": "0.6.3",
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
@@ -34,7 +34,10 @@ export {
34
34
  LxNavigatorElement,
35
35
  type LxNavigatorAttributes,
36
36
  type LxNavigatorEvent,
37
+ type NavigatorEnvVersion,
37
38
  type NavigatorOpenType,
39
+ type NavigatorQuery,
40
+ type NavigatorQueryValue,
38
41
  type NavigatorTarget,
39
42
  } from "./navigator.js";
40
43
 
@@ -1,4 +1,5 @@
1
- import type { NavigatorOpenType, NavigatorTarget } from "./navigator.js";
1
+ import type { NavigatorEnvVersion, NavigatorOpenType, NavigatorTarget } from "./navigator.js";
2
+ import type { NavigatorQuery } from "./navigator.js";
2
3
  import type { LxVideoQuality } from "./video.js";
3
4
 
4
5
  export function appendDataAttrs(
@@ -31,11 +32,15 @@ export function appendPassthroughAttrs(
31
32
 
32
33
  export interface NavigatorNativeAttrOptions {
33
34
  url?: string;
35
+ page?: string;
34
36
  openType?: NavigatorOpenType;
35
37
  target?: NavigatorTarget;
36
38
  delta?: number;
37
39
  appId?: string;
38
40
  path?: string;
41
+ query?: NavigatorQuery;
42
+ envVersion?: NavigatorEnvVersion;
43
+ targetVersion?: string;
39
44
  phoneNumber?: string;
40
45
  hoverClass?: string;
41
46
  hoverStopPropagation?: boolean;
@@ -57,9 +62,13 @@ export function buildNavigatorNativeAttrs(
57
62
  };
58
63
 
59
64
  if (options.url) result.url = options.url;
65
+ if (options.page) result.page = options.page;
60
66
  if (options.target) result.target = options.target;
61
67
  if (options.appId) result["app-id"] = options.appId;
62
68
  if (options.path) result.path = options.path;
69
+ if (options.query) result.query = JSON.stringify(options.query);
70
+ if (options.envVersion) result["env-version"] = options.envVersion;
71
+ if (options.targetVersion) result["target-version"] = options.targetVersion;
63
72
  if (options.phoneNumber) result["phone-number"] = options.phoneNumber;
64
73
 
65
74
  appendPassthroughAttrs(extraAttrs, result);
package/src/navigator.ts CHANGED
@@ -16,6 +16,10 @@ export type NavigatorTarget =
16
16
  | 'lxapp' // Open another lxapp
17
17
  | 'browser'; // Open in external browser
18
18
 
19
+ export type NavigatorQueryValue = string | number | boolean | null | undefined;
20
+ export type NavigatorQuery = Record<string, NavigatorQueryValue>;
21
+ export type NavigatorEnvVersion = 'release' | 'preview' | 'develop';
22
+
19
23
  export interface LxNavigatorEventDetail {
20
24
  success?: boolean;
21
25
  errMsg?: string;
@@ -25,16 +29,24 @@ export interface LxNavigatorEvent extends CustomEvent<LxNavigatorEventDetail> {
25
29
  detail: LxNavigatorEventDetail;
26
30
  }
27
31
 
32
+ type LingXiaBridgeCall = {
33
+ invoke(route: string, params?: unknown): Promise<unknown>;
34
+ };
35
+
28
36
  export type LxNavigatorAttributes = {
29
37
  // Navigation
30
- url?: string; // Target URL or path
38
+ url?: string; // Browser URL for openUrl/browser target
39
+ page?: string; // Named page in lxapp.json
40
+ path?: string; // Raw page path, supports query string
41
+ query?: string; // JSON-encoded page query params
31
42
  'open-type'?: NavigatorOpenType; // Navigation type
32
43
  target?: NavigatorTarget; // Navigation target (auto-inferred if not specified)
33
44
  delta?: number; // Pages to go back (for navigateBack)
34
45
 
35
46
  // Open external lxapp
36
47
  'app-id'?: string; // Target lxapp ID
37
- path?: string; // Path in target lxapp (supports query string)
48
+ 'env-version'?: NavigatorEnvVersion; // Target lxapp envVersion
49
+ 'target-version'?: string; // Exact target lxapp version
38
50
 
39
51
  // Phone call
40
52
  'phone-number'?: string; // Phone number for tel open-type
@@ -72,11 +84,15 @@ export class LxNavigatorElement extends HTMLElement {
72
84
  static get observedAttributes() {
73
85
  return [
74
86
  "url",
87
+ "page",
88
+ "path",
89
+ "query",
75
90
  "open-type",
76
91
  "target",
77
92
  "delta",
78
93
  "app-id",
79
- "path",
94
+ "env-version",
95
+ "target-version",
80
96
  "phone-number",
81
97
  "hover-class",
82
98
  "hover-stop-propagation",
@@ -240,11 +256,15 @@ export class LxNavigatorElement extends HTMLElement {
240
256
  e.preventDefault();
241
257
 
242
258
  const url = this.getAttribute('url') || '';
259
+ const page = this.getAttribute('page');
260
+ const path = this.getAttribute('path');
261
+ const query = this.getAttribute('query');
243
262
  const openType = (this.getAttribute('open-type') || 'navigate') as NavigatorOpenType;
244
263
  const explicitTarget = this.getAttribute('target') as NavigatorTarget | null;
245
264
  const delta = parseInt(this.getAttribute('delta') || '1', 10);
246
265
  const appId = this.getAttribute('app-id');
247
- const path = this.getAttribute('path');
266
+ const envVersion = this.getAttribute('env-version') as NavigatorEnvVersion | null;
267
+ const targetVersion = this.getAttribute('target-version');
248
268
  const phoneNumber = this.getAttribute('phone-number');
249
269
 
250
270
  // Auto-infer target if not explicitly specified
@@ -252,11 +272,15 @@ export class LxNavigatorElement extends HTMLElement {
252
272
 
253
273
  void this.navigate({
254
274
  url,
275
+ page,
276
+ path,
277
+ query,
255
278
  openType,
256
279
  target,
257
280
  delta,
258
281
  appId,
259
- path,
282
+ envVersion,
283
+ targetVersion,
260
284
  phoneNumber
261
285
  });
262
286
  }
@@ -291,11 +315,15 @@ export class LxNavigatorElement extends HTMLElement {
291
315
 
292
316
  private async navigate(options: {
293
317
  url: string;
318
+ page?: string | null;
319
+ path?: string | null;
320
+ query?: string | null;
294
321
  openType: NavigatorOpenType;
295
322
  target: NavigatorTarget;
296
323
  delta: number;
297
324
  appId?: string | null;
298
- path?: string | null;
325
+ envVersion?: NavigatorEnvVersion | null;
326
+ targetVersion?: string | null;
299
327
  phoneNumber?: string | null;
300
328
  }) {
301
329
  try {
@@ -370,44 +398,27 @@ export class LxNavigatorElement extends HTMLElement {
370
398
  this.dispatchEvent(completeEvent);
371
399
  }
372
400
 
373
- private getHostApi() {
374
- const host = (window as any).host;
375
- if (!host || typeof host !== 'object') {
376
- return null;
401
+ private callHost(route: string, params?: unknown): Promise<void> {
402
+ const bridge = (window as unknown as { LingXiaBridge?: LingXiaBridgeCall }).LingXiaBridge;
403
+ if (!bridge || typeof bridge.invoke !== 'function') {
404
+ return Promise.reject(new Error('LingXiaBridge is not available'));
377
405
  }
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
- };
406
+ return bridge.invoke(route, params).then(() => undefined);
395
407
  }
396
408
 
397
409
  private async performNavigation(options: {
398
410
  url: string;
411
+ page?: string | null;
412
+ path?: string | null;
413
+ query?: string | null;
399
414
  openType: NavigatorOpenType;
400
415
  target: NavigatorTarget;
401
416
  delta: number;
402
417
  appId?: string | null;
403
- path?: string | null;
418
+ envVersion?: NavigatorEnvVersion | null;
419
+ targetVersion?: string | null;
404
420
  phoneNumber?: string | null;
405
421
  }) {
406
- const host = this.getHostApi();
407
- if (!host) {
408
- throw new Error('LingXia bridge not available');
409
- }
410
-
411
422
  const url = options.url || '';
412
423
  const delta = Number.isFinite(options.delta) && options.delta > 0 ? options.delta : 1;
413
424
 
@@ -415,36 +426,24 @@ export class LxNavigatorElement extends HTMLElement {
415
426
  if (!options.phoneNumber) {
416
427
  throw new Error('tel requires phone-number attribute');
417
428
  }
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 });
429
+ await this.callHost('device.makePhoneCall', { phoneNumber: options.phoneNumber });
422
430
  return;
423
431
  }
424
432
 
425
433
  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();
434
+ await this.callHost('navigator.navigateBackLxApp');
430
435
  return;
431
436
  }
432
437
 
433
438
  if (options.openType === 'navigateBack') {
434
439
  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();
440
+ await this.callHost('navigator.navigateBackLxApp');
439
441
  return;
440
442
  }
441
443
  if (options.target === 'browser') {
442
444
  throw new Error('navigateBack is not supported for browser target');
443
445
  }
444
- if (typeof host.navigation?.navigateBack !== 'function') {
445
- throw new Error('host.navigation.navigateBack is not available');
446
- }
447
- await host.navigation.navigateBack({ delta });
446
+ await this.callHost('navigation.navigateBack', { delta });
448
447
  return;
449
448
  }
450
449
 
@@ -452,10 +451,7 @@ export class LxNavigatorElement extends HTMLElement {
452
451
  if (!url) {
453
452
  throw new Error('openUrl requires url');
454
453
  }
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' });
454
+ await this.callHost('device.openUrl', { url, target: 'external' });
459
455
  return;
460
456
  }
461
457
 
@@ -463,11 +459,7 @@ export class LxNavigatorElement extends HTMLElement {
463
459
  if (!options.appId) {
464
460
  throw new Error('navigateToLxApp requires app-id');
465
461
  }
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 });
462
+ await this.callHost('navigator.navigateToLxApp', this.buildLxAppTarget(options));
471
463
  return;
472
464
  }
473
465
 
@@ -475,47 +467,30 @@ export class LxNavigatorElement extends HTMLElement {
475
467
  if (!url) {
476
468
  throw new Error('openUrl requires url');
477
469
  }
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' });
470
+ await this.callHost('device.openUrl', { url, target: 'self' });
482
471
  return;
483
472
  }
484
473
 
485
- if (!url) {
486
- throw new Error(`${options.openType} requires url`);
474
+ const target = this.buildPageTarget(options);
475
+ if (!target) {
476
+ throw new Error(`${options.openType} requires page or path`);
487
477
  }
488
478
 
489
479
  switch (options.openType) {
490
480
  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 });
481
+ await this.callHost('navigation.navigateTo', target);
495
482
  break;
496
483
  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 });
484
+ await this.callHost('navigation.redirectTo', target);
501
485
  break;
502
486
  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 });
487
+ await this.callHost('navigation.switchTab', target);
507
488
  break;
508
489
  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 });
490
+ await this.callHost('navigation.reLaunch', target);
513
491
  break;
514
492
  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({
493
+ await this.callHost('device.openUrl', {
519
494
  url,
520
495
  target: options.target === 'self' ? 'self' : 'external'
521
496
  });
@@ -525,6 +500,49 @@ export class LxNavigatorElement extends HTMLElement {
525
500
  }
526
501
  }
527
502
 
503
+ private readQuery(raw?: string | null): NavigatorQuery | undefined {
504
+ if (!raw) return undefined;
505
+ const parsed = JSON.parse(raw) as unknown;
506
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
507
+ throw new Error('query must be an object');
508
+ }
509
+ return parsed as NavigatorQuery;
510
+ }
511
+
512
+ private buildPageTarget(options: {
513
+ page?: string | null;
514
+ path?: string | null;
515
+ query?: string | null;
516
+ }): { page?: string; path?: string; query?: NavigatorQuery } | null {
517
+ const page = options.page?.trim();
518
+ const path = options.path?.trim();
519
+ if (page && path) {
520
+ throw new Error('pass either page or path, not both');
521
+ }
522
+ if (!page && !path) return null;
523
+ const query = this.readQuery(options.query);
524
+ return {
525
+ ...(page ? { page } : { path: path! }),
526
+ ...(query ? { query } : {}),
527
+ };
528
+ }
529
+
530
+ private buildLxAppTarget(options: {
531
+ appId?: string | null;
532
+ page?: string | null;
533
+ path?: string | null;
534
+ query?: string | null;
535
+ envVersion?: NavigatorEnvVersion | null;
536
+ targetVersion?: string | null;
537
+ }): Record<string, unknown> {
538
+ const target: Record<string, unknown> = { appId: options.appId };
539
+ const pageTarget = this.buildPageTarget(options);
540
+ if (pageTarget) Object.assign(target, pageTarget);
541
+ if (options.envVersion) target.envVersion = options.envVersion;
542
+ if (options.targetVersion) target.targetVersion = options.targetVersion;
543
+ return target;
544
+ }
545
+
528
546
  }
529
547
 
530
548
  // Register custom element