@lingxia/elements 0.17.0 → 0.18.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 +1 -1
- package/dist/native_component_wrapper_shared.d.ts +7 -1
- package/dist/native_component_wrapper_shared.js +12 -0
- package/dist/navigator.d.ts +27 -7
- package/dist/navigator.js +102 -82
- package/package.json +2 -2
- package/src/index.ts +9 -0
- package/src/native_component_wrapper_shared.ts +23 -1
- package/src/navigator.ts +149 -129
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { registerVideoComponent, LxVideoElement, type LxVideoAttributes, type LxVideoEventPayloads, type LxVideoEventHandlers, } 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 { LxNavigatorElement, type LxNavigatorAttributes, type LxNavigatorEvent, type NavigatorChannel, type NavigatorOpenType, type NavigatorQuery, type NavigatorQueryValue, type NavigatorTarget, } from "./navigator.js";
|
|
4
|
+
export { LxNavigatorElement, type LxNavigatorAttributes, type LxNavigatorEvent, type NavigatorChannel, type NavigatorEdge, type NavigatorFloatPosition, type NavigatorInteraction, type NavigatorPagePlacement, type NavigatorPlacement, type NavigatorSize, type NavigatorSizeValue, type NavigatorUrlPlacement, type NavigatorWindowChrome, type NavigatorOpenType, type NavigatorQuery, type NavigatorQueryValue, type NavigatorTarget, } from "./navigator.js";
|
|
5
5
|
export { buildVideoNativeAttrs, buildMediaSwiperNativeAttrs, buildNavigatorNativeAttrs, buildPickerNativeAttrs, getPickerDisplayText, getPickerValueFromIndex, VIDEO_DOM_EVENT_MAP, MEDIA_SWIPER_DOM_EVENT_MAP, } from "./native_component_wrapper_shared.js";
|
|
6
6
|
export { isHarmony, isIOS, isAndroid, isMacOS, isDesktop, getOS, } from "./platform.js";
|
|
7
7
|
export { ensureComponentId, rectEquals, NativeComponentUpdateState, type Rect, } from "./component.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NavigatorChannel, NavigatorOpenType, NavigatorTarget } from "./navigator.js";
|
|
1
|
+
import type { NavigatorChannel, NavigatorEdge, NavigatorFloatPosition, NavigatorInteraction, NavigatorOpenType, NavigatorPlacement, NavigatorSize, NavigatorTarget, NavigatorWindowChrome } from "./navigator.js";
|
|
2
2
|
import type { NavigatorQuery } from "./navigator.js";
|
|
3
3
|
import type { LxMediaSwiperItem } from "./media_swiper.js";
|
|
4
4
|
import type { LxVideoQuality } from "./video.js";
|
|
@@ -10,6 +10,12 @@ export interface NavigatorNativeAttrOptions {
|
|
|
10
10
|
openType?: NavigatorOpenType;
|
|
11
11
|
target?: NavigatorTarget;
|
|
12
12
|
delta?: number;
|
|
13
|
+
as?: NavigatorPlacement;
|
|
14
|
+
edge?: NavigatorEdge;
|
|
15
|
+
position?: NavigatorFloatPosition;
|
|
16
|
+
chrome?: NavigatorWindowChrome;
|
|
17
|
+
size?: NavigatorSize;
|
|
18
|
+
interaction?: NavigatorInteraction;
|
|
13
19
|
appId?: string;
|
|
14
20
|
query?: NavigatorQuery;
|
|
15
21
|
channel?: NavigatorChannel;
|
|
@@ -39,6 +39,18 @@ export function buildNavigatorNativeAttrs(options, extraAttrs = {}) {
|
|
|
39
39
|
result.page = options.page;
|
|
40
40
|
if (options.target)
|
|
41
41
|
result.target = options.target;
|
|
42
|
+
if (options.as)
|
|
43
|
+
result.as = options.as;
|
|
44
|
+
if (options.edge)
|
|
45
|
+
result.edge = options.edge;
|
|
46
|
+
if (options.position)
|
|
47
|
+
result.position = options.position;
|
|
48
|
+
if (options.chrome)
|
|
49
|
+
result.chrome = options.chrome;
|
|
50
|
+
if (options.size)
|
|
51
|
+
result.size = JSON.stringify(options.size);
|
|
52
|
+
if (options.interaction)
|
|
53
|
+
result.interaction = JSON.stringify(options.interaction);
|
|
42
54
|
if (options.appId)
|
|
43
55
|
result["app-id"] = options.appId;
|
|
44
56
|
if (options.query)
|
package/dist/navigator.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
export type NavigatorOpenType = 'navigate' | 'redirect' | 'navigateBack' | 'reLaunch' | 'switchTab' | 'exit' | '
|
|
2
|
-
export type NavigatorTarget = '
|
|
1
|
+
export type NavigatorOpenType = 'navigate' | 'redirect' | 'navigateBack' | 'reLaunch' | 'switchTab' | 'exit' | 'tel';
|
|
2
|
+
export type NavigatorTarget = 'page' | 'lxapp' | 'url';
|
|
3
|
+
export type NavigatorUrlPlacement = 'external' | 'tab' | 'aside';
|
|
4
|
+
export type NavigatorPagePlacement = 'float' | 'window';
|
|
5
|
+
export type NavigatorPlacement = NavigatorUrlPlacement | NavigatorPagePlacement;
|
|
6
|
+
export type NavigatorEdge = 'left' | 'right' | 'top' | 'bottom';
|
|
7
|
+
export type NavigatorFloatPosition = 'center' | 'top' | 'bottom' | 'left' | 'right';
|
|
8
|
+
export type NavigatorWindowChrome = 'system' | 'full';
|
|
9
|
+
export type NavigatorSizeValue = number | `${number}%`;
|
|
10
|
+
export type NavigatorSize = {
|
|
11
|
+
width?: NavigatorSizeValue;
|
|
12
|
+
height?: NavigatorSizeValue;
|
|
13
|
+
};
|
|
14
|
+
export type NavigatorInteraction = {
|
|
15
|
+
closeButton?: boolean;
|
|
16
|
+
dismiss?: 'tapOutside' | 'manual';
|
|
17
|
+
modal?: boolean;
|
|
18
|
+
};
|
|
3
19
|
export type NavigatorQueryValue = string | number | boolean | null | undefined;
|
|
4
20
|
export type NavigatorQuery = Record<string, NavigatorQueryValue>;
|
|
5
21
|
export type NavigatorChannel = 'release' | 'draft';
|
|
@@ -17,6 +33,12 @@ export type LxNavigatorAttributes = {
|
|
|
17
33
|
'open-type'?: NavigatorOpenType;
|
|
18
34
|
target?: NavigatorTarget;
|
|
19
35
|
delta?: number;
|
|
36
|
+
as?: NavigatorPlacement;
|
|
37
|
+
edge?: NavigatorEdge;
|
|
38
|
+
position?: NavigatorFloatPosition;
|
|
39
|
+
chrome?: NavigatorWindowChrome;
|
|
40
|
+
size?: string;
|
|
41
|
+
interaction?: string;
|
|
20
42
|
'app-id'?: string;
|
|
21
43
|
channel?: NavigatorChannel;
|
|
22
44
|
'target-version'?: string;
|
|
@@ -75,17 +97,15 @@ export declare class LxNavigatorElement extends HTMLElement {
|
|
|
75
97
|
private removeHoverClass;
|
|
76
98
|
private clearHoverTimer;
|
|
77
99
|
private handleClick;
|
|
78
|
-
/**
|
|
79
|
-
* Auto-infer navigation target based on context
|
|
80
|
-
* Priority: explicit target > appId > HTTPS URL > default (self)
|
|
81
|
-
*/
|
|
82
|
-
private inferTarget;
|
|
83
100
|
private navigate;
|
|
84
101
|
private dispatchSuccess;
|
|
85
102
|
private resolveErrorMessage;
|
|
86
103
|
private dispatchFail;
|
|
104
|
+
private bridge;
|
|
87
105
|
private callHost;
|
|
106
|
+
private callSurface;
|
|
88
107
|
private performNavigation;
|
|
108
|
+
private readJsonObject;
|
|
89
109
|
private readQuery;
|
|
90
110
|
private buildPageTarget;
|
|
91
111
|
private buildLxAppTarget;
|
package/dist/navigator.js
CHANGED
|
@@ -43,6 +43,12 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
43
43
|
"open-type",
|
|
44
44
|
"target",
|
|
45
45
|
"delta",
|
|
46
|
+
"as",
|
|
47
|
+
"edge",
|
|
48
|
+
"position",
|
|
49
|
+
"chrome",
|
|
50
|
+
"size",
|
|
51
|
+
"interaction",
|
|
46
52
|
"app-id",
|
|
47
53
|
"channel",
|
|
48
54
|
"target-version",
|
|
@@ -158,55 +164,33 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
158
164
|
}
|
|
159
165
|
}
|
|
160
166
|
handleClick(e) {
|
|
167
|
+
var _a;
|
|
161
168
|
e.preventDefault();
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const path = this.getAttribute('path');
|
|
167
|
-
const query = this.getAttribute('query');
|
|
168
|
-
const openType = (this.getAttribute('open-type') || 'navigate');
|
|
169
|
-
const explicitTarget = this.getAttribute('target');
|
|
170
|
-
const delta = parseInt(this.getAttribute('delta') || '1', 10);
|
|
171
|
-
const appId = this.getAttribute('app-id');
|
|
172
|
-
const channel = this.getAttribute('channel');
|
|
173
|
-
const targetVersion = this.getAttribute('target-version');
|
|
174
|
-
const phoneNumber = this.getAttribute('phone-number');
|
|
175
|
-
// Auto-infer target if not explicitly specified
|
|
176
|
-
const target = this.inferTarget(explicitTarget, url, appId);
|
|
177
|
-
void this.navigate({
|
|
169
|
+
const attr = (name) => this.getAttribute(name);
|
|
170
|
+
const url = attr('url') || '';
|
|
171
|
+
const appId = attr('app-id');
|
|
172
|
+
const options = {
|
|
178
173
|
url,
|
|
179
|
-
page,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
174
|
+
page: attr('page'),
|
|
175
|
+
// Read the removed attribute only to provide a forward-only runtime error
|
|
176
|
+
// for untyped HTML callers instead of silently ignoring it.
|
|
177
|
+
path: attr('path'),
|
|
178
|
+
query: attr('query'),
|
|
179
|
+
openType: (attr('open-type') || 'navigate'),
|
|
180
|
+
target: (_a = attr('target')) !== null && _a !== void 0 ? _a : (appId ? 'lxapp' : url ? 'url' : 'page'),
|
|
181
|
+
delta: parseInt(attr('delta') || '1', 10),
|
|
182
|
+
placement: attr('as'),
|
|
183
|
+
edge: attr('edge'),
|
|
184
|
+
position: attr('position'),
|
|
185
|
+
chrome: attr('chrome'),
|
|
186
|
+
size: attr('size'),
|
|
187
|
+
interaction: attr('interaction'),
|
|
185
188
|
appId,
|
|
186
|
-
channel,
|
|
187
|
-
targetVersion,
|
|
188
|
-
phoneNumber
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Auto-infer navigation target based on context
|
|
193
|
-
* Priority: explicit target > appId > HTTPS URL > default (self)
|
|
194
|
-
*/
|
|
195
|
-
inferTarget(explicitTarget, url, appId) {
|
|
196
|
-
// 1. Explicit target has highest priority
|
|
197
|
-
if (explicitTarget) {
|
|
198
|
-
return explicitTarget;
|
|
199
|
-
}
|
|
200
|
-
// 2. If appId is specified, target is another lxapp
|
|
201
|
-
if (appId) {
|
|
202
|
-
return 'lxapp';
|
|
203
|
-
}
|
|
204
|
-
// 3. If URL starts with http:// or https://, open in browser
|
|
205
|
-
if (/^https?:\/\//i.test(url)) {
|
|
206
|
-
return 'browser';
|
|
207
|
-
}
|
|
208
|
-
// 4. Default: navigate within current lxapp
|
|
209
|
-
return 'self';
|
|
189
|
+
channel: attr('channel'),
|
|
190
|
+
targetVersion: attr('target-version'),
|
|
191
|
+
phoneNumber: attr('phone-number'),
|
|
192
|
+
};
|
|
193
|
+
void this.navigate(options);
|
|
210
194
|
}
|
|
211
195
|
async navigate(options) {
|
|
212
196
|
try {
|
|
@@ -273,17 +257,24 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
273
257
|
});
|
|
274
258
|
this.dispatchEvent(completeEvent);
|
|
275
259
|
}
|
|
276
|
-
|
|
260
|
+
bridge() {
|
|
277
261
|
const bridge = window.LingXiaBridge;
|
|
278
262
|
if (!bridge || typeof bridge.invoke !== 'function') {
|
|
279
|
-
|
|
263
|
+
throw new Error('LingXiaBridge is not available');
|
|
280
264
|
}
|
|
281
|
-
return bridge
|
|
265
|
+
return bridge;
|
|
266
|
+
}
|
|
267
|
+
async callHost(route, params) {
|
|
268
|
+
await this.bridge().invoke(route, params);
|
|
269
|
+
}
|
|
270
|
+
// Placements run through Logic's lx.surface, which owns surface handles.
|
|
271
|
+
async callSurface(method, params) {
|
|
272
|
+
await this.bridge().raw.call(`surface.${method}`, params);
|
|
282
273
|
}
|
|
283
274
|
async performNavigation(options) {
|
|
284
|
-
const url = options
|
|
275
|
+
const { url, placement } = options;
|
|
285
276
|
const delta = Number.isFinite(options.delta) && options.delta > 0 ? options.delta : 1;
|
|
286
|
-
if (options.path !== null
|
|
277
|
+
if (options.path !== null) {
|
|
287
278
|
throw new Error('path is not supported; pass the configured page name in page');
|
|
288
279
|
}
|
|
289
280
|
if (options.openType === 'tel') {
|
|
@@ -300,39 +291,71 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
300
291
|
if (options.openType === 'navigateBack') {
|
|
301
292
|
if (options.target === 'lxapp') {
|
|
302
293
|
await this.callHost('navigator.navigateBackApp');
|
|
303
|
-
return;
|
|
304
294
|
}
|
|
305
|
-
|
|
306
|
-
|
|
295
|
+
else {
|
|
296
|
+
await this.callHost('navigation.navigateBack', { delta });
|
|
307
297
|
}
|
|
308
|
-
await this.callHost('navigation.navigateBack', { delta });
|
|
309
298
|
return;
|
|
310
299
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
300
|
+
switch (options.target) {
|
|
301
|
+
case 'url': {
|
|
302
|
+
if (!url) {
|
|
303
|
+
throw new Error('target url requires url');
|
|
304
|
+
}
|
|
305
|
+
if (!placement || placement === 'external') {
|
|
306
|
+
await this.callHost('device.openUrl', { url, target: 'external' });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (placement !== 'tab' && placement !== 'aside') {
|
|
310
|
+
throw new Error(`a url opens as external, tab, or aside; got ${placement}`);
|
|
311
|
+
}
|
|
312
|
+
const surfaceOptions = { as: placement };
|
|
313
|
+
if (options.edge)
|
|
314
|
+
surfaceOptions.edge = options.edge;
|
|
315
|
+
const size = this.readJsonObject('size', options.size);
|
|
316
|
+
if (size)
|
|
317
|
+
surfaceOptions.size = size;
|
|
318
|
+
await this.callSurface('openUrl', { url, options: surfaceOptions });
|
|
319
|
+
return;
|
|
328
320
|
}
|
|
329
|
-
|
|
330
|
-
|
|
321
|
+
case 'lxapp':
|
|
322
|
+
if (!options.appId) {
|
|
323
|
+
throw new Error('target lxapp requires app-id');
|
|
324
|
+
}
|
|
325
|
+
if (placement) {
|
|
326
|
+
throw new Error('as is not supported for target lxapp');
|
|
327
|
+
}
|
|
328
|
+
await this.callHost('navigator.navigateToApp', this.buildLxAppTarget(options));
|
|
329
|
+
return;
|
|
330
|
+
case 'page':
|
|
331
|
+
break;
|
|
332
|
+
default:
|
|
333
|
+
throw new Error(`Unsupported target: ${options.target}`);
|
|
331
334
|
}
|
|
332
335
|
const target = this.buildPageTarget(options);
|
|
333
336
|
if (!target) {
|
|
334
337
|
throw new Error(`${options.openType} requires page`);
|
|
335
338
|
}
|
|
339
|
+
if (placement) {
|
|
340
|
+
if (placement !== 'float' && placement !== 'window') {
|
|
341
|
+
throw new Error(`a page opens as float or window; got ${placement}`);
|
|
342
|
+
}
|
|
343
|
+
const surfaceOptions = { as: placement };
|
|
344
|
+
if (target.query)
|
|
345
|
+
surfaceOptions.query = target.query;
|
|
346
|
+
if (options.position)
|
|
347
|
+
surfaceOptions.position = options.position;
|
|
348
|
+
if (options.chrome)
|
|
349
|
+
surfaceOptions.chrome = options.chrome;
|
|
350
|
+
const size = this.readJsonObject('size', options.size);
|
|
351
|
+
if (size)
|
|
352
|
+
surfaceOptions.size = size;
|
|
353
|
+
const interaction = this.readJsonObject('interaction', options.interaction);
|
|
354
|
+
if (interaction)
|
|
355
|
+
surfaceOptions.interaction = interaction;
|
|
356
|
+
await this.callSurface('openPage', { page: target.page, options: surfaceOptions });
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
336
359
|
switch (options.openType) {
|
|
337
360
|
case 'navigate':
|
|
338
361
|
await this.callHost('navigation.navigateTo', target);
|
|
@@ -346,25 +369,22 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
346
369
|
case 'reLaunch':
|
|
347
370
|
await this.callHost('navigation.reLaunch', target);
|
|
348
371
|
break;
|
|
349
|
-
case 'openUrl':
|
|
350
|
-
await this.callHost('device.openUrl', {
|
|
351
|
-
url,
|
|
352
|
-
target: options.target === 'self' ? 'self' : 'external'
|
|
353
|
-
});
|
|
354
|
-
break;
|
|
355
372
|
default:
|
|
356
373
|
throw new Error(`Unsupported openType: ${options.openType}`);
|
|
357
374
|
}
|
|
358
375
|
}
|
|
359
|
-
|
|
376
|
+
readJsonObject(name, raw) {
|
|
360
377
|
if (!raw)
|
|
361
378
|
return undefined;
|
|
362
379
|
const parsed = JSON.parse(raw);
|
|
363
380
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
364
|
-
throw new Error(
|
|
381
|
+
throw new Error(`${name} must be an object`);
|
|
365
382
|
}
|
|
366
383
|
return parsed;
|
|
367
384
|
}
|
|
385
|
+
readQuery(raw) {
|
|
386
|
+
return this.readJsonObject('query', raw);
|
|
387
|
+
}
|
|
368
388
|
buildPageTarget(options) {
|
|
369
389
|
var _a;
|
|
370
390
|
const page = (_a = options.page) === null || _a === void 0 ? void 0 : _a.trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingxia/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LingXia custom elements — native-backed web components for LxApp pages",
|
|
6
6
|
"type": "module",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "npm --prefix ../lingxia-bridge run build:types && tsc -p tsconfig.json",
|
|
18
|
-
"test:inline-native": "npm run build && node tests/inline-native-structure.mjs && node tests/inline-native-unwrap.mjs && node tests/inline-native-runtime.mjs && node tests/inline-native-events.mjs && node tests/inline-native-style.mjs && node tests/video-events.mjs && node tests/inline-native-identity.mjs && node tests/inline-native-contract.mjs",
|
|
18
|
+
"test:inline-native": "npm run build && node tests/inline-native-structure.mjs && node tests/inline-native-unwrap.mjs && node tests/inline-native-runtime.mjs && node tests/inline-native-events.mjs && node tests/inline-native-style.mjs && node tests/video-events.mjs && node tests/navigator-routing.mjs && node tests/inline-native-identity.mjs && node tests/inline-native-contract.mjs",
|
|
19
19
|
"clean": "rm -rf dist",
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,15 @@ export {
|
|
|
41
41
|
type LxNavigatorAttributes,
|
|
42
42
|
type LxNavigatorEvent,
|
|
43
43
|
type NavigatorChannel,
|
|
44
|
+
type NavigatorEdge,
|
|
45
|
+
type NavigatorFloatPosition,
|
|
46
|
+
type NavigatorInteraction,
|
|
47
|
+
type NavigatorPagePlacement,
|
|
48
|
+
type NavigatorPlacement,
|
|
49
|
+
type NavigatorSize,
|
|
50
|
+
type NavigatorSizeValue,
|
|
51
|
+
type NavigatorUrlPlacement,
|
|
52
|
+
type NavigatorWindowChrome,
|
|
44
53
|
type NavigatorOpenType,
|
|
45
54
|
type NavigatorQuery,
|
|
46
55
|
type NavigatorQueryValue,
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
NavigatorChannel,
|
|
3
|
+
NavigatorEdge,
|
|
4
|
+
NavigatorFloatPosition,
|
|
5
|
+
NavigatorInteraction,
|
|
6
|
+
NavigatorOpenType,
|
|
7
|
+
NavigatorPlacement,
|
|
8
|
+
NavigatorSize,
|
|
9
|
+
NavigatorTarget,
|
|
10
|
+
NavigatorWindowChrome,
|
|
11
|
+
} from "./navigator.js";
|
|
2
12
|
import type { NavigatorQuery } from "./navigator.js";
|
|
3
13
|
import type { LxMediaSwiperItem } from "./media_swiper.js";
|
|
4
14
|
import type { LxVideoQuality, LxVideoEventPayloads } from "./video.js";
|
|
@@ -37,6 +47,12 @@ export interface NavigatorNativeAttrOptions {
|
|
|
37
47
|
openType?: NavigatorOpenType;
|
|
38
48
|
target?: NavigatorTarget;
|
|
39
49
|
delta?: number;
|
|
50
|
+
as?: NavigatorPlacement;
|
|
51
|
+
edge?: NavigatorEdge;
|
|
52
|
+
position?: NavigatorFloatPosition;
|
|
53
|
+
chrome?: NavigatorWindowChrome;
|
|
54
|
+
size?: NavigatorSize;
|
|
55
|
+
interaction?: NavigatorInteraction;
|
|
40
56
|
appId?: string;
|
|
41
57
|
query?: NavigatorQuery;
|
|
42
58
|
channel?: NavigatorChannel;
|
|
@@ -64,6 +80,12 @@ export function buildNavigatorNativeAttrs(
|
|
|
64
80
|
if (options.url) result.url = options.url;
|
|
65
81
|
if (options.page) result.page = options.page;
|
|
66
82
|
if (options.target) result.target = options.target;
|
|
83
|
+
if (options.as) result.as = options.as;
|
|
84
|
+
if (options.edge) result.edge = options.edge;
|
|
85
|
+
if (options.position) result.position = options.position;
|
|
86
|
+
if (options.chrome) result.chrome = options.chrome;
|
|
87
|
+
if (options.size) result.size = JSON.stringify(options.size);
|
|
88
|
+
if (options.interaction) result.interaction = JSON.stringify(options.interaction);
|
|
67
89
|
if (options.appId) result["app-id"] = options.appId;
|
|
68
90
|
if (options.query) result.query = JSON.stringify(options.query);
|
|
69
91
|
if (options.channel) result.channel = options.channel;
|
package/src/navigator.ts
CHANGED
|
@@ -8,13 +8,26 @@ export type NavigatorOpenType =
|
|
|
8
8
|
| 'reLaunch' // Restart app with new page
|
|
9
9
|
| 'switchTab' // Switch to tab page
|
|
10
10
|
| 'exit' // Exit current lxapp
|
|
11
|
-
| 'openUrl' // Open external URL or lxapp
|
|
12
11
|
| 'tel'; // Make a phone call
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
// What to open. Inferred when omitted: app-id → lxapp, url → url, else page.
|
|
14
|
+
export type NavigatorTarget = 'page' | 'lxapp' | 'url';
|
|
15
|
+
|
|
16
|
+
// Where to open it; mirrors `as` of lx.surface.openUrl / openPage.
|
|
17
|
+
// A url defaults to `external`; a page without `as` follows open-type.
|
|
18
|
+
export type NavigatorUrlPlacement = 'external' | 'tab' | 'aside';
|
|
19
|
+
export type NavigatorPagePlacement = 'float' | 'window';
|
|
20
|
+
export type NavigatorPlacement = NavigatorUrlPlacement | NavigatorPagePlacement;
|
|
21
|
+
export type NavigatorEdge = 'left' | 'right' | 'top' | 'bottom';
|
|
22
|
+
export type NavigatorFloatPosition = 'center' | 'top' | 'bottom' | 'left' | 'right';
|
|
23
|
+
export type NavigatorWindowChrome = 'system' | 'full';
|
|
24
|
+
export type NavigatorSizeValue = number | `${number}%`;
|
|
25
|
+
export type NavigatorSize = { width?: NavigatorSizeValue; height?: NavigatorSizeValue };
|
|
26
|
+
export type NavigatorInteraction = {
|
|
27
|
+
closeButton?: boolean;
|
|
28
|
+
dismiss?: 'tapOutside' | 'manual';
|
|
29
|
+
modal?: boolean;
|
|
30
|
+
};
|
|
18
31
|
|
|
19
32
|
export type NavigatorQueryValue = string | number | boolean | null | undefined;
|
|
20
33
|
export type NavigatorQuery = Record<string, NavigatorQueryValue>;
|
|
@@ -31,17 +44,46 @@ export interface LxNavigatorEvent extends CustomEvent<LxNavigatorEventDetail> {
|
|
|
31
44
|
|
|
32
45
|
type LingXiaBridgeCall = {
|
|
33
46
|
invoke(route: string, params?: unknown): Promise<unknown>;
|
|
47
|
+
raw: { call(method: string, params?: unknown): Promise<unknown> };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type NavigateOptions = {
|
|
51
|
+
url: string;
|
|
52
|
+
page: string | null;
|
|
53
|
+
path: string | null;
|
|
54
|
+
query: string | null;
|
|
55
|
+
openType: NavigatorOpenType;
|
|
56
|
+
target: NavigatorTarget;
|
|
57
|
+
delta: number;
|
|
58
|
+
placement: NavigatorPlacement | null;
|
|
59
|
+
edge: string | null;
|
|
60
|
+
position: string | null;
|
|
61
|
+
chrome: string | null;
|
|
62
|
+
size: string | null;
|
|
63
|
+
interaction: string | null;
|
|
64
|
+
appId: string | null;
|
|
65
|
+
channel: NavigatorChannel | null;
|
|
66
|
+
targetVersion: string | null;
|
|
67
|
+
phoneNumber: string | null;
|
|
34
68
|
};
|
|
35
69
|
|
|
36
70
|
export type LxNavigatorAttributes = {
|
|
37
71
|
// Navigation
|
|
38
|
-
url?: string; //
|
|
72
|
+
url?: string; // http(s) URL
|
|
39
73
|
page?: string; // Configured page name from lxapp.json; routes are unsupported
|
|
40
74
|
query?: string; // JSON-encoded page query params
|
|
41
75
|
'open-type'?: NavigatorOpenType; // Navigation type
|
|
42
|
-
target?: NavigatorTarget; //
|
|
76
|
+
target?: NavigatorTarget; // What to open (inferred if not specified)
|
|
43
77
|
delta?: number; // Pages to go back (for navigateBack)
|
|
44
78
|
|
|
79
|
+
// Placement
|
|
80
|
+
as?: NavigatorPlacement;
|
|
81
|
+
edge?: NavigatorEdge; // as="aside"
|
|
82
|
+
position?: NavigatorFloatPosition; // as="float"
|
|
83
|
+
chrome?: NavigatorWindowChrome; // as="window"
|
|
84
|
+
size?: string; // JSON-encoded NavigatorSize
|
|
85
|
+
interaction?: string; // JSON-encoded NavigatorInteraction (float/window)
|
|
86
|
+
|
|
45
87
|
// Open external lxapp
|
|
46
88
|
'app-id'?: string; // Target lxapp ID
|
|
47
89
|
channel?: NavigatorChannel; // Target lxapp channel
|
|
@@ -88,6 +130,12 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
88
130
|
"open-type",
|
|
89
131
|
"target",
|
|
90
132
|
"delta",
|
|
133
|
+
"as",
|
|
134
|
+
"edge",
|
|
135
|
+
"position",
|
|
136
|
+
"chrome",
|
|
137
|
+
"size",
|
|
138
|
+
"interaction",
|
|
91
139
|
"app-id",
|
|
92
140
|
"channel",
|
|
93
141
|
"target-version",
|
|
@@ -252,80 +300,34 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
252
300
|
|
|
253
301
|
private handleClick(e: MouseEvent) {
|
|
254
302
|
e.preventDefault();
|
|
255
|
-
|
|
256
|
-
const url =
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
// for untyped HTML callers instead of silently ignoring it.
|
|
260
|
-
const path = this.getAttribute('path');
|
|
261
|
-
const query = this.getAttribute('query');
|
|
262
|
-
const openType = (this.getAttribute('open-type') || 'navigate') as NavigatorOpenType;
|
|
263
|
-
const explicitTarget = this.getAttribute('target') as NavigatorTarget | null;
|
|
264
|
-
const delta = parseInt(this.getAttribute('delta') || '1', 10);
|
|
265
|
-
const appId = this.getAttribute('app-id');
|
|
266
|
-
const channel = this.getAttribute('channel') as NavigatorChannel | null;
|
|
267
|
-
const targetVersion = this.getAttribute('target-version');
|
|
268
|
-
const phoneNumber = this.getAttribute('phone-number');
|
|
269
|
-
|
|
270
|
-
// Auto-infer target if not explicitly specified
|
|
271
|
-
const target = this.inferTarget(explicitTarget, url, appId);
|
|
272
|
-
|
|
273
|
-
void this.navigate({
|
|
303
|
+
const attr = (name: string) => this.getAttribute(name);
|
|
304
|
+
const url = attr('url') || '';
|
|
305
|
+
const appId = attr('app-id');
|
|
306
|
+
const options: NavigateOptions = {
|
|
274
307
|
url,
|
|
275
|
-
page,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
308
|
+
page: attr('page'),
|
|
309
|
+
// Read the removed attribute only to provide a forward-only runtime error
|
|
310
|
+
// for untyped HTML callers instead of silently ignoring it.
|
|
311
|
+
path: attr('path'),
|
|
312
|
+
query: attr('query'),
|
|
313
|
+
openType: (attr('open-type') || 'navigate') as NavigatorOpenType,
|
|
314
|
+
target: (attr('target') as NavigatorTarget | null) ?? (appId ? 'lxapp' : url ? 'url' : 'page'),
|
|
315
|
+
delta: parseInt(attr('delta') || '1', 10),
|
|
316
|
+
placement: attr('as') as NavigatorPlacement | null,
|
|
317
|
+
edge: attr('edge'),
|
|
318
|
+
position: attr('position'),
|
|
319
|
+
chrome: attr('chrome'),
|
|
320
|
+
size: attr('size'),
|
|
321
|
+
interaction: attr('interaction'),
|
|
281
322
|
appId,
|
|
282
|
-
channel,
|
|
283
|
-
targetVersion,
|
|
284
|
-
phoneNumber
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Auto-infer navigation target based on context
|
|
290
|
-
* Priority: explicit target > appId > HTTPS URL > default (self)
|
|
291
|
-
*/
|
|
292
|
-
private inferTarget(
|
|
293
|
-
explicitTarget: NavigatorTarget | null,
|
|
294
|
-
url: string,
|
|
295
|
-
appId: string | null
|
|
296
|
-
): NavigatorTarget {
|
|
297
|
-
// 1. Explicit target has highest priority
|
|
298
|
-
if (explicitTarget) {
|
|
299
|
-
return explicitTarget;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// 2. If appId is specified, target is another lxapp
|
|
303
|
-
if (appId) {
|
|
304
|
-
return 'lxapp';
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// 3. If URL starts with http:// or https://, open in browser
|
|
308
|
-
if (/^https?:\/\//i.test(url)) {
|
|
309
|
-
return 'browser';
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// 4. Default: navigate within current lxapp
|
|
313
|
-
return 'self';
|
|
323
|
+
channel: attr('channel') as NavigatorChannel | null,
|
|
324
|
+
targetVersion: attr('target-version'),
|
|
325
|
+
phoneNumber: attr('phone-number'),
|
|
326
|
+
};
|
|
327
|
+
void this.navigate(options);
|
|
314
328
|
}
|
|
315
329
|
|
|
316
|
-
private async navigate(options: {
|
|
317
|
-
url: string;
|
|
318
|
-
page?: string | null;
|
|
319
|
-
path?: string | null;
|
|
320
|
-
query?: string | null;
|
|
321
|
-
openType: NavigatorOpenType;
|
|
322
|
-
target: NavigatorTarget;
|
|
323
|
-
delta: number;
|
|
324
|
-
appId?: string | null;
|
|
325
|
-
channel?: NavigatorChannel | null;
|
|
326
|
-
targetVersion?: string | null;
|
|
327
|
-
phoneNumber?: string | null;
|
|
328
|
-
}) {
|
|
330
|
+
private async navigate(options: NavigateOptions) {
|
|
329
331
|
try {
|
|
330
332
|
await this.performNavigation(options);
|
|
331
333
|
this.dispatchSuccess();
|
|
@@ -398,31 +400,28 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
398
400
|
this.dispatchEvent(completeEvent);
|
|
399
401
|
}
|
|
400
402
|
|
|
401
|
-
private
|
|
403
|
+
private bridge(): LingXiaBridgeCall {
|
|
402
404
|
const bridge = (window as unknown as { LingXiaBridge?: LingXiaBridgeCall }).LingXiaBridge;
|
|
403
405
|
if (!bridge || typeof bridge.invoke !== 'function') {
|
|
404
|
-
|
|
406
|
+
throw new Error('LingXiaBridge is not available');
|
|
405
407
|
}
|
|
406
|
-
return bridge
|
|
408
|
+
return bridge;
|
|
407
409
|
}
|
|
408
410
|
|
|
409
|
-
private async
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
phoneNumber?: string | null;
|
|
421
|
-
}) {
|
|
422
|
-
const url = options.url || '';
|
|
411
|
+
private async callHost(route: string, params?: unknown): Promise<void> {
|
|
412
|
+
await this.bridge().invoke(route, params);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Placements run through Logic's lx.surface, which owns surface handles.
|
|
416
|
+
private async callSurface(method: 'openPage' | 'openUrl', params: unknown): Promise<void> {
|
|
417
|
+
await this.bridge().raw.call(`surface.${method}`, params);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private async performNavigation(options: NavigateOptions) {
|
|
421
|
+
const { url, placement } = options;
|
|
423
422
|
const delta = Number.isFinite(options.delta) && options.delta > 0 ? options.delta : 1;
|
|
424
423
|
|
|
425
|
-
if (options.path !== null
|
|
424
|
+
if (options.path !== null) {
|
|
426
425
|
throw new Error('path is not supported; pass the configured page name in page');
|
|
427
426
|
}
|
|
428
427
|
|
|
@@ -442,44 +441,67 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
442
441
|
if (options.openType === 'navigateBack') {
|
|
443
442
|
if (options.target === 'lxapp') {
|
|
444
443
|
await this.callHost('navigator.navigateBackApp');
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (options.target === 'browser') {
|
|
448
|
-
throw new Error('navigateBack is not supported for browser target');
|
|
444
|
+
} else {
|
|
445
|
+
await this.callHost('navigation.navigateBack', { delta });
|
|
449
446
|
}
|
|
450
|
-
await this.callHost('navigation.navigateBack', { delta });
|
|
451
447
|
return;
|
|
452
448
|
}
|
|
453
449
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
450
|
+
switch (options.target) {
|
|
451
|
+
case 'url': {
|
|
452
|
+
if (!url) {
|
|
453
|
+
throw new Error('target url requires url');
|
|
454
|
+
}
|
|
455
|
+
if (!placement || placement === 'external') {
|
|
456
|
+
await this.callHost('device.openUrl', { url, target: 'external' });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (placement !== 'tab' && placement !== 'aside') {
|
|
460
|
+
throw new Error(`a url opens as external, tab, or aside; got ${placement}`);
|
|
461
|
+
}
|
|
462
|
+
const surfaceOptions: Record<string, unknown> = { as: placement };
|
|
463
|
+
if (options.edge) surfaceOptions.edge = options.edge;
|
|
464
|
+
const size = this.readJsonObject('size', options.size);
|
|
465
|
+
if (size) surfaceOptions.size = size;
|
|
466
|
+
await this.callSurface('openUrl', { url, options: surfaceOptions });
|
|
467
|
+
return;
|
|
457
468
|
}
|
|
458
|
-
|
|
459
|
-
|
|
469
|
+
case 'lxapp':
|
|
470
|
+
if (!options.appId) {
|
|
471
|
+
throw new Error('target lxapp requires app-id');
|
|
472
|
+
}
|
|
473
|
+
if (placement) {
|
|
474
|
+
throw new Error('as is not supported for target lxapp');
|
|
475
|
+
}
|
|
476
|
+
await this.callHost('navigator.navigateToApp', this.buildLxAppTarget(options));
|
|
477
|
+
return;
|
|
478
|
+
case 'page':
|
|
479
|
+
break;
|
|
480
|
+
default:
|
|
481
|
+
throw new Error(`Unsupported target: ${options.target}`);
|
|
460
482
|
}
|
|
461
483
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
await this.callHost('navigator.navigateToApp', this.buildLxAppTarget(options));
|
|
467
|
-
return;
|
|
484
|
+
const target = this.buildPageTarget(options);
|
|
485
|
+
if (!target) {
|
|
486
|
+
throw new Error(`${options.openType} requires page`);
|
|
468
487
|
}
|
|
469
488
|
|
|
470
|
-
if (
|
|
471
|
-
if (
|
|
472
|
-
throw new Error(
|
|
489
|
+
if (placement) {
|
|
490
|
+
if (placement !== 'float' && placement !== 'window') {
|
|
491
|
+
throw new Error(`a page opens as float or window; got ${placement}`);
|
|
473
492
|
}
|
|
474
|
-
|
|
493
|
+
const surfaceOptions: Record<string, unknown> = { as: placement };
|
|
494
|
+
if (target.query) surfaceOptions.query = target.query;
|
|
495
|
+
if (options.position) surfaceOptions.position = options.position;
|
|
496
|
+
if (options.chrome) surfaceOptions.chrome = options.chrome;
|
|
497
|
+
const size = this.readJsonObject('size', options.size);
|
|
498
|
+
if (size) surfaceOptions.size = size;
|
|
499
|
+
const interaction = this.readJsonObject('interaction', options.interaction);
|
|
500
|
+
if (interaction) surfaceOptions.interaction = interaction;
|
|
501
|
+
await this.callSurface('openPage', { page: target.page, options: surfaceOptions });
|
|
475
502
|
return;
|
|
476
503
|
}
|
|
477
504
|
|
|
478
|
-
const target = this.buildPageTarget(options);
|
|
479
|
-
if (!target) {
|
|
480
|
-
throw new Error(`${options.openType} requires page`);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
505
|
switch (options.openType) {
|
|
484
506
|
case 'navigate':
|
|
485
507
|
await this.callHost('navigation.navigateTo', target);
|
|
@@ -493,24 +515,22 @@ export class LxNavigatorElement extends HTMLElement {
|
|
|
493
515
|
case 'reLaunch':
|
|
494
516
|
await this.callHost('navigation.reLaunch', target);
|
|
495
517
|
break;
|
|
496
|
-
case 'openUrl':
|
|
497
|
-
await this.callHost('device.openUrl', {
|
|
498
|
-
url,
|
|
499
|
-
target: options.target === 'self' ? 'self' : 'external'
|
|
500
|
-
});
|
|
501
|
-
break;
|
|
502
518
|
default:
|
|
503
519
|
throw new Error(`Unsupported openType: ${options.openType}`);
|
|
504
520
|
}
|
|
505
521
|
}
|
|
506
522
|
|
|
507
|
-
private
|
|
523
|
+
private readJsonObject(name: string, raw?: string | null): Record<string, unknown> | undefined {
|
|
508
524
|
if (!raw) return undefined;
|
|
509
525
|
const parsed = JSON.parse(raw) as unknown;
|
|
510
526
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
511
|
-
throw new Error(
|
|
527
|
+
throw new Error(`${name} must be an object`);
|
|
512
528
|
}
|
|
513
|
-
return parsed as
|
|
529
|
+
return parsed as Record<string, unknown>;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private readQuery(raw?: string | null): NavigatorQuery | undefined {
|
|
533
|
+
return this.readJsonObject('query', raw) as NavigatorQuery | undefined;
|
|
514
534
|
}
|
|
515
535
|
|
|
516
536
|
private buildPageTarget(options: {
|