@lingxia/bridge 0.18.0 → 0.19.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/bridge-runtime.es2020.js +1 -1
- package/dist/bridge-runtime.es5.js +1 -1
- package/dist/error.d.ts +7 -0
- package/dist/es2020/bridge.js +46 -6
- package/dist/es2020/error.js +48 -7
- package/dist/es2020/host.js +53 -0
- package/dist/es2020/index.js +2 -2
- package/dist/es2020/runtime-env.js +34 -2
- package/dist/es2020/surface-context.js +86 -0
- package/dist/es5/bridge.js +58 -6
- package/dist/es5/error.js +45 -7
- package/dist/es5/host.js +53 -0
- package/dist/es5/index.js +2 -2
- package/dist/es5/runtime-env.js +38 -5
- package/dist/es5/surface-context.js +135 -0
- package/dist/host.d.ts +33 -0
- package/dist/index.d.ts +2 -2
- package/dist/invocation.d.ts +12 -1
- package/dist/runtime-env.d.ts +2 -0
- package/dist/surface-context.d.ts +27 -0
- package/dist/types.d.ts +17 -4
- package/package.json +2 -2
package/dist/es2020/error.js
CHANGED
|
@@ -58,8 +58,7 @@ function escapeHtml(str) {
|
|
|
58
58
|
div.textContent = str;
|
|
59
59
|
return div.innerHTML;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
const { failedPath, reason } = errorInfo;
|
|
61
|
+
function renderPanel({ code, title, description, detail, reason }) {
|
|
63
62
|
const styleEl = document.createElement('style');
|
|
64
63
|
styleEl.textContent = ERROR_STYLES;
|
|
65
64
|
document.head.appendChild(styleEl);
|
|
@@ -67,11 +66,11 @@ export function renderErrorUI(errorInfo) {
|
|
|
67
66
|
document.body.style.cssText = '';
|
|
68
67
|
const container = document.createElement('div');
|
|
69
68
|
container.className = 'lx-error';
|
|
70
|
-
let html =
|
|
71
|
-
html +=
|
|
72
|
-
html +=
|
|
73
|
-
if (
|
|
74
|
-
html += `<div class="lx-error-path">${escapeHtml(
|
|
69
|
+
let html = `<div class="lx-error-code">${escapeHtml(code)}</div>`;
|
|
70
|
+
html += `<h1 class="lx-error-title">${escapeHtml(title)}</h1>`;
|
|
71
|
+
html += `<p class="lx-error-desc">${escapeHtml(description)}</p>`;
|
|
72
|
+
if (detail) {
|
|
73
|
+
html += `<div class="lx-error-path">${escapeHtml(detail)}</div>`;
|
|
75
74
|
}
|
|
76
75
|
if (reason) {
|
|
77
76
|
html += `<p class="lx-error-desc" style="margin-top:12px">${escapeHtml(reason)}</p>`;
|
|
@@ -79,6 +78,48 @@ export function renderErrorUI(errorInfo) {
|
|
|
79
78
|
container.innerHTML = html;
|
|
80
79
|
document.body.appendChild(container);
|
|
81
80
|
}
|
|
81
|
+
export function renderErrorUI(errorInfo) {
|
|
82
|
+
renderPanel({
|
|
83
|
+
code: '404',
|
|
84
|
+
title: 'Page Not Found',
|
|
85
|
+
description: "The page you're looking for doesn't exist.",
|
|
86
|
+
detail: errorInfo.failedPath,
|
|
87
|
+
reason: errorInfo.reason,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A page whose Logic has not delivered its first state in time — it failed to
|
|
92
|
+
* load, threw before the page could start, or is very slow. Shown over the page
|
|
93
|
+
* instead of a blank screen, naming the page; returns a function that removes
|
|
94
|
+
* it, for when the state does arrive after all.
|
|
95
|
+
*/
|
|
96
|
+
export function renderPageFault(pagePath, reason) {
|
|
97
|
+
const styleEl = document.createElement('style');
|
|
98
|
+
styleEl.textContent = `
|
|
99
|
+
.lx-page-fault { position: fixed; inset: 0; z-index: 2147483647; overflow: auto;
|
|
100
|
+
background: #fff; color: #1d1d1f; }
|
|
101
|
+
@media (prefers-color-scheme: dark) { .lx-page-fault { background: #000; color: #f5f5f7; } }
|
|
102
|
+
${ERROR_STYLES.replace(/\bbody\b/g, '.lx-page-fault')}`;
|
|
103
|
+
document.head.appendChild(styleEl);
|
|
104
|
+
const overlay = document.createElement('div');
|
|
105
|
+
overlay.className = 'lx-page-fault';
|
|
106
|
+
overlay.setAttribute('role', 'alert');
|
|
107
|
+
const container = document.createElement('div');
|
|
108
|
+
container.className = 'lx-error';
|
|
109
|
+
let html = '<div class="lx-error-code">Page</div>';
|
|
110
|
+
html += `<h1 class="lx-error-title">${escapeHtml("This page couldn't start")}</h1>`;
|
|
111
|
+
html += `<p class="lx-error-desc">${escapeHtml('Its Logic has not delivered the page state. Check the Logic log for an error.')}</p>`;
|
|
112
|
+
if (pagePath)
|
|
113
|
+
html += `<div class="lx-error-path">${escapeHtml(pagePath)}</div>`;
|
|
114
|
+
html += `<p class="lx-error-desc" style="margin-top:12px">${escapeHtml(reason)}</p>`;
|
|
115
|
+
container.innerHTML = html;
|
|
116
|
+
overlay.appendChild(container);
|
|
117
|
+
document.body.appendChild(overlay);
|
|
118
|
+
return () => {
|
|
119
|
+
overlay.remove();
|
|
120
|
+
styleEl.remove();
|
|
121
|
+
};
|
|
122
|
+
}
|
|
82
123
|
export function hasError() {
|
|
83
124
|
const config = window.__LX_RUNTIME_CONFIG;
|
|
84
125
|
return !!(config && config.error);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BRIDGE_CONFIG, getDisplayLanguage, getPlatformOS, isDesktop, subscribeDisplayLanguage, } from './runtime-env';
|
|
2
|
+
import { getSurfaceContext, subscribeSurfaceContext } from './surface-context';
|
|
3
|
+
let current = null;
|
|
4
|
+
function read() {
|
|
5
|
+
const surface = getSurfaceContext();
|
|
6
|
+
return {
|
|
7
|
+
sizeClass: surface.sizeClass,
|
|
8
|
+
aside: surface.aside,
|
|
9
|
+
displayLanguage: getDisplayLanguage(),
|
|
10
|
+
formFactor: isDesktop() ? 'desktop' : 'mobile',
|
|
11
|
+
os: getPlatformOS(),
|
|
12
|
+
runner: BRIDGE_CONFIG.runner === true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function same(a, b) {
|
|
16
|
+
return a.sizeClass === b.sizeClass
|
|
17
|
+
&& a.aside === b.aside
|
|
18
|
+
&& a.displayLanguage === b.displayLanguage
|
|
19
|
+
&& a.formFactor === b.formFactor
|
|
20
|
+
&& a.os === b.os
|
|
21
|
+
&& a.runner === b.runner;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The host facts for this page. The same object is returned until a field
|
|
25
|
+
* changes, so it composes with `useSyncExternalStore` and with memoization.
|
|
26
|
+
*/
|
|
27
|
+
export function getHost() {
|
|
28
|
+
const next = read();
|
|
29
|
+
if (current === null || !same(current, next))
|
|
30
|
+
current = next;
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Follow the host facts. Change-only: the listener runs when a field of
|
|
35
|
+
* `getHost()` changes — not on subscribe, and not when only the viewport's
|
|
36
|
+
* size moves within its size class. Returns an unsubscribe.
|
|
37
|
+
*/
|
|
38
|
+
export function subscribeHost(listener) {
|
|
39
|
+
let last = getHost();
|
|
40
|
+
const check = () => {
|
|
41
|
+
const next = getHost();
|
|
42
|
+
if (next === last)
|
|
43
|
+
return;
|
|
44
|
+
last = next;
|
|
45
|
+
listener();
|
|
46
|
+
};
|
|
47
|
+
const stopLanguage = subscribeDisplayLanguage(check);
|
|
48
|
+
const stopSurface = subscribeSurfaceContext(check);
|
|
49
|
+
return () => {
|
|
50
|
+
stopLanguage();
|
|
51
|
+
stopSurface();
|
|
52
|
+
};
|
|
53
|
+
}
|
package/dist/es2020/index.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { LingXiaBridge, channel, initBridge, invoke, notify, stream, } from './bridge';
|
|
9
9
|
export { isNativeError } from './invocation';
|
|
10
|
-
export {
|
|
11
|
-
export { renderErrorUI, hasError, getErrorInfo } from './error';
|
|
10
|
+
export { getHost, subscribeHost } from './host';
|
|
11
|
+
export { renderErrorUI, renderPageFault, hasError, getErrorInfo } from './error';
|
|
12
12
|
export { boot, bootWhenReady } from './boot';
|
|
13
13
|
import { bootWhenReady } from './boot';
|
|
14
14
|
import { registerUIHandlers } from './ui';
|
|
@@ -23,9 +23,30 @@ function store() {
|
|
|
23
23
|
}
|
|
24
24
|
return window.__lxDisplayLanguage;
|
|
25
25
|
}
|
|
26
|
+
/** Primary subtags of languages written right to left, for engines without `Intl.Locale#getTextInfo`. */
|
|
27
|
+
const RTL_LANGUAGES = new Set(['ar', 'ckb', 'dv', 'fa', 'he', 'ps', 'sd', 'ug', 'ur', 'yi']);
|
|
28
|
+
/** The writing direction of a BCP-47 tag. */
|
|
29
|
+
export function textDirection(tag) {
|
|
30
|
+
try {
|
|
31
|
+
const locale = new Intl.Locale(tag);
|
|
32
|
+
const info = typeof locale.getTextInfo === 'function' ? locale.getTextInfo() : locale.textInfo;
|
|
33
|
+
if (info?.direction === 'rtl' || info?.direction === 'ltr')
|
|
34
|
+
return info.direction;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Not a tag `Intl` accepts; fall through to the subtag list.
|
|
38
|
+
}
|
|
39
|
+
return RTL_LANGUAGES.has(tag.split(/[-_]/)[0].toLowerCase()) ? 'rtl' : 'ltr';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `lang` and `dir` on `<html>` follow the display language, so a page never
|
|
43
|
+
* sets either: CSS logical properties and `:dir()` just work.
|
|
44
|
+
*/
|
|
26
45
|
function stampDocumentLanguage() {
|
|
27
46
|
if (typeof document !== 'undefined' && document.documentElement) {
|
|
28
|
-
|
|
47
|
+
const language = store().value;
|
|
48
|
+
document.documentElement.lang = language;
|
|
49
|
+
document.documentElement.dir = textDirection(language);
|
|
29
50
|
}
|
|
30
51
|
}
|
|
31
52
|
stampDocumentLanguage();
|
|
@@ -33,10 +54,21 @@ stampDocumentLanguage();
|
|
|
33
54
|
* Host entry point for a language the user changed while this document was
|
|
34
55
|
* open. Bootstrap alone would leave a live page in the language it started in,
|
|
35
56
|
* with the native chrome around it already switched.
|
|
57
|
+
*
|
|
58
|
+
* The host pushes each change once, and again — the current value — when a
|
|
59
|
+
* document's bridge reports ready, since a change made while it loaded found
|
|
60
|
+
* no hook to call. Those two can land in either order, so a push carries the
|
|
61
|
+
* host revision it took effect at and an older one never overwrites a newer
|
|
62
|
+
* one. A push without a revision applies as it always did.
|
|
36
63
|
*/
|
|
37
|
-
function applyDisplayLanguage(next) {
|
|
64
|
+
function applyDisplayLanguage(next, revision) {
|
|
38
65
|
const normalized = typeof next === 'string' ? next.trim() : '';
|
|
39
66
|
const current = store();
|
|
67
|
+
if (typeof revision === 'number' && Number.isFinite(revision)) {
|
|
68
|
+
if (revision <= (current.revision ?? 0))
|
|
69
|
+
return;
|
|
70
|
+
current.revision = revision;
|
|
71
|
+
}
|
|
40
72
|
if (!normalized || normalized === current.value)
|
|
41
73
|
return;
|
|
42
74
|
current.value = normalized;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** What a host that sends nothing — one older than this API — is taken to say. */
|
|
2
|
+
const UNREPORTED = { sizeClass: 'compact', width: 0, height: 0, aside: false };
|
|
3
|
+
/**
|
|
4
|
+
* One store per document, on `window`, for the same reason as the display
|
|
5
|
+
* language: the bridge module is present twice in a page, and the host must
|
|
6
|
+
* reach the copy the framework hooks read.
|
|
7
|
+
*/
|
|
8
|
+
function store() {
|
|
9
|
+
if (typeof window === 'undefined')
|
|
10
|
+
return fallbackStore;
|
|
11
|
+
if (!window.__lxSurfaceContext) {
|
|
12
|
+
// Seeded by the host in this document's bridge config, before any script.
|
|
13
|
+
const config = window.__LX_BRIDGE_CFG;
|
|
14
|
+
window.__lxSurfaceContext = {
|
|
15
|
+
value: parse(config?.surfaceContext) ?? UNREPORTED,
|
|
16
|
+
revision: typeof config?.surfaceContextRevision === 'number' ? config.surfaceContextRevision : 0,
|
|
17
|
+
listeners: new Set(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return window.__lxSurfaceContext;
|
|
21
|
+
}
|
|
22
|
+
const fallbackStore = { value: UNREPORTED, revision: 0, listeners: new Set() };
|
|
23
|
+
function parse(next) {
|
|
24
|
+
if (typeof next !== 'object' || next === null)
|
|
25
|
+
return null;
|
|
26
|
+
const { sizeClass, width, height, aside } = next;
|
|
27
|
+
if (sizeClass !== 'compact' && sizeClass !== 'regular')
|
|
28
|
+
return null;
|
|
29
|
+
if (typeof width !== 'number' || typeof height !== 'number')
|
|
30
|
+
return null;
|
|
31
|
+
return { sizeClass, width, height, aside: aside === true };
|
|
32
|
+
}
|
|
33
|
+
function same(a, b) {
|
|
34
|
+
return a.sizeClass === b.sizeClass
|
|
35
|
+
&& a.width === b.width
|
|
36
|
+
&& a.height === b.height
|
|
37
|
+
&& a.aside === b.aside;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Host entry point: once the page's bridge is up, and on every change. Pushes
|
|
41
|
+
* can run out of order across host threads, so each carries a revision and an
|
|
42
|
+
* older one than the store holds is dropped.
|
|
43
|
+
*/
|
|
44
|
+
function applySurfaceContext(next, revision) {
|
|
45
|
+
const context = parse(next);
|
|
46
|
+
const current = store();
|
|
47
|
+
if (!context)
|
|
48
|
+
return;
|
|
49
|
+
if (typeof revision === 'number') {
|
|
50
|
+
if (revision <= current.revision)
|
|
51
|
+
return;
|
|
52
|
+
current.revision = revision;
|
|
53
|
+
}
|
|
54
|
+
if (same(current.value, context))
|
|
55
|
+
return;
|
|
56
|
+
// A new object per change, so `useSyncExternalStore` sees it changed.
|
|
57
|
+
current.value = context;
|
|
58
|
+
for (const listener of [...current.listeners])
|
|
59
|
+
listener();
|
|
60
|
+
}
|
|
61
|
+
if (typeof window !== 'undefined' && !window.__lingxiaApplySurfaceContext) {
|
|
62
|
+
Object.defineProperty(window, '__lingxiaApplySurfaceContext', {
|
|
63
|
+
configurable: false,
|
|
64
|
+
enumerable: false,
|
|
65
|
+
value: applySurfaceContext,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The lxapp's adaptive context. The host seeds it into every page before any
|
|
70
|
+
* script runs and pushes each change, so there is always a value; a host older
|
|
71
|
+
* than this API reports `compact` with a 0×0 viewport.
|
|
72
|
+
*/
|
|
73
|
+
export function getSurfaceContext() {
|
|
74
|
+
return store().value;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Follow the adaptive context. Change-only, like `subscribeDisplayLanguage`,
|
|
78
|
+
* so it composes with `useSyncExternalStore`. Returns an unsubscribe.
|
|
79
|
+
*/
|
|
80
|
+
export function subscribeSurfaceContext(listener) {
|
|
81
|
+
const listeners = store().listeners;
|
|
82
|
+
listeners.add(listener);
|
|
83
|
+
return () => {
|
|
84
|
+
listeners.delete(listener);
|
|
85
|
+
};
|
|
86
|
+
}
|
package/dist/es5/bridge.js
CHANGED
|
@@ -96,7 +96,8 @@ import { BRIDGE_ERROR } from "./types";
|
|
|
96
96
|
import { toBridgeError, toNativeError } from "./invocation";
|
|
97
97
|
import { installNativeComponentCoverageMonitor } from "./nativecomponents/coverage-monitor";
|
|
98
98
|
import { consumeV3Bootstrap, } from "./protocol-v3";
|
|
99
|
-
import {
|
|
99
|
+
import { getHost, subscribeHost } from "./host";
|
|
100
|
+
import { BRIDGE_CONFIG, getCommunicationMethod, getPlatformOS, isAndroid, isHarmony, isIOS, isMacOS, isWindows, isDesktop, isMobile, isApple, isDevSession, isRunner, } from "./runtime-env";
|
|
100
101
|
var NATIVE_HANDLER_NAME = "LingXia";
|
|
101
102
|
var GLOBAL_RECEIVER_NAME = "__LingXiaRecvMessage";
|
|
102
103
|
var DEFAULT_TIMEOUT_MS = 5000;
|
|
@@ -930,6 +931,16 @@ function createStreamHandle(id, cancelFn) {
|
|
|
930
931
|
pendingReads.push({ resolve: resolve, reject: reject });
|
|
931
932
|
});
|
|
932
933
|
},
|
|
934
|
+
// Leaving a `for await` early cancels the stream, as with a
|
|
935
|
+
// ReadableStream: nothing is left buffering for a gone reader.
|
|
936
|
+
return: function () {
|
|
937
|
+
iteratorRequested = false;
|
|
938
|
+
pendingData.length = 0;
|
|
939
|
+
// The cancel settles `result` with BRIDGE_CANCELED; the reader asked for it.
|
|
940
|
+
result.catch(function () { });
|
|
941
|
+
handle.cancel();
|
|
942
|
+
return Promise.resolve({ done: true, value: undefined });
|
|
943
|
+
},
|
|
933
944
|
};
|
|
934
945
|
},
|
|
935
946
|
_a.cancel = function () {
|
|
@@ -1031,6 +1042,7 @@ function createChannel(id, sendFn, closeFn) {
|
|
|
1031
1042
|
var open = false;
|
|
1032
1043
|
var outboundSeq = 0;
|
|
1033
1044
|
var closed = false;
|
|
1045
|
+
var closeEmitted = false;
|
|
1034
1046
|
// Only buffer for the async iterator or pre-first-listener replay;
|
|
1035
1047
|
// otherwise a listener-driven channel would accumulate payloads forever.
|
|
1036
1048
|
var iteratorRequested = false;
|
|
@@ -1102,13 +1114,20 @@ function createChannel(id, sendFn, closeFn) {
|
|
|
1102
1114
|
pendingReads.push({ resolve: resolve, reject: reject });
|
|
1103
1115
|
});
|
|
1104
1116
|
},
|
|
1117
|
+
// Leaving a `for await` early closes the channel, as with a
|
|
1118
|
+
// ReadableStream: nothing is left buffering for a gone reader.
|
|
1119
|
+
return: function () {
|
|
1120
|
+
iteratorRequested = false;
|
|
1121
|
+
pendingData.length = 0;
|
|
1122
|
+
channel.close();
|
|
1123
|
+
return Promise.resolve({ done: true, value: undefined });
|
|
1124
|
+
},
|
|
1105
1125
|
};
|
|
1106
1126
|
},
|
|
1107
1127
|
_a.close = function (code, reason) {
|
|
1108
1128
|
if (!open)
|
|
1109
1129
|
return;
|
|
1110
1130
|
open = false;
|
|
1111
|
-
closed = true;
|
|
1112
1131
|
closeFn(code, reason);
|
|
1113
1132
|
channel._emitClose(code, reason);
|
|
1114
1133
|
},
|
|
@@ -1143,7 +1162,12 @@ function createChannel(id, sendFn, closeFn) {
|
|
|
1143
1162
|
},
|
|
1144
1163
|
_a._emitClose = function (code, reason) {
|
|
1145
1164
|
var e_14, _a;
|
|
1165
|
+
// One close notification, whichever side closed first.
|
|
1166
|
+
open = false;
|
|
1146
1167
|
closed = true;
|
|
1168
|
+
if (closeEmitted)
|
|
1169
|
+
return;
|
|
1170
|
+
closeEmitted = true;
|
|
1147
1171
|
while (pendingReads.length > 0) {
|
|
1148
1172
|
pendingReads.shift().resolve({ done: true, value: undefined });
|
|
1149
1173
|
}
|
|
@@ -1168,6 +1192,7 @@ function createChannel(id, sendFn, closeFn) {
|
|
|
1168
1192
|
},
|
|
1169
1193
|
_a._reject = function (err) {
|
|
1170
1194
|
var e_15, _a;
|
|
1195
|
+
open = false;
|
|
1171
1196
|
closed = true;
|
|
1172
1197
|
while (pendingReads.length > 0) {
|
|
1173
1198
|
pendingReads.shift().reject(err);
|
|
@@ -2181,11 +2206,11 @@ export var LingXiaBridge = {
|
|
|
2181
2206
|
handleIncomingMessage(messageString);
|
|
2182
2207
|
},
|
|
2183
2208
|
debug: createDebugObject(debugFlags),
|
|
2184
|
-
// The host
|
|
2209
|
+
// The host facts, for documents that cannot bundle `@lingxia/bridge` —
|
|
2185
2210
|
// browser internal pages and plain-HTML lxapps load this runtime as a script.
|
|
2186
|
-
|
|
2187
|
-
get:
|
|
2188
|
-
subscribe:
|
|
2211
|
+
host: {
|
|
2212
|
+
get: getHost,
|
|
2213
|
+
subscribe: subscribeHost,
|
|
2189
2214
|
},
|
|
2190
2215
|
platform: {
|
|
2191
2216
|
isHarmony: isHarmony,
|
|
@@ -2308,8 +2333,19 @@ function wrapNativeStream(handle) {
|
|
|
2308
2333
|
function wrapNativeChannel(channel) {
|
|
2309
2334
|
var messageListeners = new Set();
|
|
2310
2335
|
var closeListeners = new Set();
|
|
2336
|
+
// Messages that arrive with the open ack, before the caller could call
|
|
2337
|
+
// `onMessage`, wait for it until the next task; a channel nobody listens
|
|
2338
|
+
// to buffers nothing after that.
|
|
2339
|
+
var early = [];
|
|
2340
|
+
setTimeout(function () {
|
|
2341
|
+
early = null;
|
|
2342
|
+
}, 0);
|
|
2311
2343
|
channel.on("data", function (message) {
|
|
2312
2344
|
var e_21, _a;
|
|
2345
|
+
if (messageListeners.size === 0) {
|
|
2346
|
+
early === null || early === void 0 ? void 0 : early.push(message);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2313
2349
|
try {
|
|
2314
2350
|
for (var messageListeners_1 = __values(messageListeners), messageListeners_1_1 = messageListeners_1.next(); !messageListeners_1_1.done; messageListeners_1_1 = messageListeners_1.next()) {
|
|
2315
2351
|
var listener = messageListeners_1_1.value;
|
|
@@ -2346,7 +2382,23 @@ function wrapNativeChannel(channel) {
|
|
|
2346
2382
|
channel.send(message);
|
|
2347
2383
|
},
|
|
2348
2384
|
onMessage: function (listener) {
|
|
2385
|
+
var e_23, _a;
|
|
2349
2386
|
messageListeners.add(listener);
|
|
2387
|
+
var replay = early;
|
|
2388
|
+
early = null;
|
|
2389
|
+
try {
|
|
2390
|
+
for (var _b = __values(replay !== null && replay !== void 0 ? replay : []), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
2391
|
+
var message = _c.value;
|
|
2392
|
+
listener(message);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
catch (e_23_1) { e_23 = { error: e_23_1 }; }
|
|
2396
|
+
finally {
|
|
2397
|
+
try {
|
|
2398
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
2399
|
+
}
|
|
2400
|
+
finally { if (e_23) throw e_23.error; }
|
|
2401
|
+
}
|
|
2350
2402
|
return function () {
|
|
2351
2403
|
messageListeners.delete(listener);
|
|
2352
2404
|
};
|
package/dist/es5/error.js
CHANGED
|
@@ -4,8 +4,8 @@ function escapeHtml(str) {
|
|
|
4
4
|
div.textContent = str;
|
|
5
5
|
return div.innerHTML;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
var
|
|
7
|
+
function renderPanel(_a) {
|
|
8
|
+
var code = _a.code, title = _a.title, description = _a.description, detail = _a.detail, reason = _a.reason;
|
|
9
9
|
var styleEl = document.createElement('style');
|
|
10
10
|
styleEl.textContent = ERROR_STYLES;
|
|
11
11
|
document.head.appendChild(styleEl);
|
|
@@ -13,11 +13,11 @@ export function renderErrorUI(errorInfo) {
|
|
|
13
13
|
document.body.style.cssText = '';
|
|
14
14
|
var container = document.createElement('div');
|
|
15
15
|
container.className = 'lx-error';
|
|
16
|
-
var html =
|
|
17
|
-
html +=
|
|
18
|
-
html +=
|
|
19
|
-
if (
|
|
20
|
-
html += "<div class=\"lx-error-path\">".concat(escapeHtml(
|
|
16
|
+
var html = "<div class=\"lx-error-code\">".concat(escapeHtml(code), "</div>");
|
|
17
|
+
html += "<h1 class=\"lx-error-title\">".concat(escapeHtml(title), "</h1>");
|
|
18
|
+
html += "<p class=\"lx-error-desc\">".concat(escapeHtml(description), "</p>");
|
|
19
|
+
if (detail) {
|
|
20
|
+
html += "<div class=\"lx-error-path\">".concat(escapeHtml(detail), "</div>");
|
|
21
21
|
}
|
|
22
22
|
if (reason) {
|
|
23
23
|
html += "<p class=\"lx-error-desc\" style=\"margin-top:12px\">".concat(escapeHtml(reason), "</p>");
|
|
@@ -25,6 +25,44 @@ export function renderErrorUI(errorInfo) {
|
|
|
25
25
|
container.innerHTML = html;
|
|
26
26
|
document.body.appendChild(container);
|
|
27
27
|
}
|
|
28
|
+
export function renderErrorUI(errorInfo) {
|
|
29
|
+
renderPanel({
|
|
30
|
+
code: '404',
|
|
31
|
+
title: 'Page Not Found',
|
|
32
|
+
description: "The page you're looking for doesn't exist.",
|
|
33
|
+
detail: errorInfo.failedPath,
|
|
34
|
+
reason: errorInfo.reason,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A page whose Logic has not delivered its first state in time — it failed to
|
|
39
|
+
* load, threw before the page could start, or is very slow. Shown over the page
|
|
40
|
+
* instead of a blank screen, naming the page; returns a function that removes
|
|
41
|
+
* it, for when the state does arrive after all.
|
|
42
|
+
*/
|
|
43
|
+
export function renderPageFault(pagePath, reason) {
|
|
44
|
+
var styleEl = document.createElement('style');
|
|
45
|
+
styleEl.textContent = "\n.lx-page-fault { position: fixed; inset: 0; z-index: 2147483647; overflow: auto;\n background: #fff; color: #1d1d1f; }\n@media (prefers-color-scheme: dark) { .lx-page-fault { background: #000; color: #f5f5f7; } }\n".concat(ERROR_STYLES.replace(/\bbody\b/g, '.lx-page-fault'));
|
|
46
|
+
document.head.appendChild(styleEl);
|
|
47
|
+
var overlay = document.createElement('div');
|
|
48
|
+
overlay.className = 'lx-page-fault';
|
|
49
|
+
overlay.setAttribute('role', 'alert');
|
|
50
|
+
var container = document.createElement('div');
|
|
51
|
+
container.className = 'lx-error';
|
|
52
|
+
var html = '<div class="lx-error-code">Page</div>';
|
|
53
|
+
html += "<h1 class=\"lx-error-title\">".concat(escapeHtml("This page couldn't start"), "</h1>");
|
|
54
|
+
html += "<p class=\"lx-error-desc\">".concat(escapeHtml('Its Logic has not delivered the page state. Check the Logic log for an error.'), "</p>");
|
|
55
|
+
if (pagePath)
|
|
56
|
+
html += "<div class=\"lx-error-path\">".concat(escapeHtml(pagePath), "</div>");
|
|
57
|
+
html += "<p class=\"lx-error-desc\" style=\"margin-top:12px\">".concat(escapeHtml(reason), "</p>");
|
|
58
|
+
container.innerHTML = html;
|
|
59
|
+
overlay.appendChild(container);
|
|
60
|
+
document.body.appendChild(overlay);
|
|
61
|
+
return function () {
|
|
62
|
+
overlay.remove();
|
|
63
|
+
styleEl.remove();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
28
66
|
export function hasError() {
|
|
29
67
|
var config = window.__LX_RUNTIME_CONFIG;
|
|
30
68
|
return !!(config && config.error);
|
package/dist/es5/host.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BRIDGE_CONFIG, getDisplayLanguage, getPlatformOS, isDesktop, subscribeDisplayLanguage, } from './runtime-env';
|
|
2
|
+
import { getSurfaceContext, subscribeSurfaceContext } from './surface-context';
|
|
3
|
+
var current = null;
|
|
4
|
+
function read() {
|
|
5
|
+
var surface = getSurfaceContext();
|
|
6
|
+
return {
|
|
7
|
+
sizeClass: surface.sizeClass,
|
|
8
|
+
aside: surface.aside,
|
|
9
|
+
displayLanguage: getDisplayLanguage(),
|
|
10
|
+
formFactor: isDesktop() ? 'desktop' : 'mobile',
|
|
11
|
+
os: getPlatformOS(),
|
|
12
|
+
runner: BRIDGE_CONFIG.runner === true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function same(a, b) {
|
|
16
|
+
return a.sizeClass === b.sizeClass
|
|
17
|
+
&& a.aside === b.aside
|
|
18
|
+
&& a.displayLanguage === b.displayLanguage
|
|
19
|
+
&& a.formFactor === b.formFactor
|
|
20
|
+
&& a.os === b.os
|
|
21
|
+
&& a.runner === b.runner;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The host facts for this page. The same object is returned until a field
|
|
25
|
+
* changes, so it composes with `useSyncExternalStore` and with memoization.
|
|
26
|
+
*/
|
|
27
|
+
export function getHost() {
|
|
28
|
+
var next = read();
|
|
29
|
+
if (current === null || !same(current, next))
|
|
30
|
+
current = next;
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Follow the host facts. Change-only: the listener runs when a field of
|
|
35
|
+
* `getHost()` changes — not on subscribe, and not when only the viewport's
|
|
36
|
+
* size moves within its size class. Returns an unsubscribe.
|
|
37
|
+
*/
|
|
38
|
+
export function subscribeHost(listener) {
|
|
39
|
+
var last = getHost();
|
|
40
|
+
var check = function () {
|
|
41
|
+
var next = getHost();
|
|
42
|
+
if (next === last)
|
|
43
|
+
return;
|
|
44
|
+
last = next;
|
|
45
|
+
listener();
|
|
46
|
+
};
|
|
47
|
+
var stopLanguage = subscribeDisplayLanguage(check);
|
|
48
|
+
var stopSurface = subscribeSurfaceContext(check);
|
|
49
|
+
return function () {
|
|
50
|
+
stopLanguage();
|
|
51
|
+
stopSurface();
|
|
52
|
+
};
|
|
53
|
+
}
|
package/dist/es5/index.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { LingXiaBridge, channel, initBridge, invoke, notify, stream, } from './bridge';
|
|
9
9
|
export { isNativeError } from './invocation';
|
|
10
|
-
export {
|
|
11
|
-
export { renderErrorUI, hasError, getErrorInfo } from './error';
|
|
10
|
+
export { getHost, subscribeHost } from './host';
|
|
11
|
+
export { renderErrorUI, renderPageFault, hasError, getErrorInfo } from './error';
|
|
12
12
|
export { boot, bootWhenReady } from './boot';
|
|
13
13
|
import { bootWhenReady } from './boot';
|
|
14
14
|
import { registerUIHandlers } from './ui';
|
package/dist/es5/runtime-env.js
CHANGED
|
@@ -61,9 +61,30 @@ function store() {
|
|
|
61
61
|
}
|
|
62
62
|
return window.__lxDisplayLanguage;
|
|
63
63
|
}
|
|
64
|
+
/** Primary subtags of languages written right to left, for engines without `Intl.Locale#getTextInfo`. */
|
|
65
|
+
var RTL_LANGUAGES = new Set(['ar', 'ckb', 'dv', 'fa', 'he', 'ps', 'sd', 'ug', 'ur', 'yi']);
|
|
66
|
+
/** The writing direction of a BCP-47 tag. */
|
|
67
|
+
export function textDirection(tag) {
|
|
68
|
+
try {
|
|
69
|
+
var locale = new Intl.Locale(tag);
|
|
70
|
+
var info = typeof locale.getTextInfo === 'function' ? locale.getTextInfo() : locale.textInfo;
|
|
71
|
+
if ((info === null || info === void 0 ? void 0 : info.direction) === 'rtl' || (info === null || info === void 0 ? void 0 : info.direction) === 'ltr')
|
|
72
|
+
return info.direction;
|
|
73
|
+
}
|
|
74
|
+
catch (_a) {
|
|
75
|
+
// Not a tag `Intl` accepts; fall through to the subtag list.
|
|
76
|
+
}
|
|
77
|
+
return RTL_LANGUAGES.has(tag.split(/[-_]/)[0].toLowerCase()) ? 'rtl' : 'ltr';
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `lang` and `dir` on `<html>` follow the display language, so a page never
|
|
81
|
+
* sets either: CSS logical properties and `:dir()` just work.
|
|
82
|
+
*/
|
|
64
83
|
function stampDocumentLanguage() {
|
|
65
84
|
if (typeof document !== 'undefined' && document.documentElement) {
|
|
66
|
-
|
|
85
|
+
var language = store().value;
|
|
86
|
+
document.documentElement.lang = language;
|
|
87
|
+
document.documentElement.dir = textDirection(language);
|
|
67
88
|
}
|
|
68
89
|
}
|
|
69
90
|
stampDocumentLanguage();
|
|
@@ -71,25 +92,37 @@ stampDocumentLanguage();
|
|
|
71
92
|
* Host entry point for a language the user changed while this document was
|
|
72
93
|
* open. Bootstrap alone would leave a live page in the language it started in,
|
|
73
94
|
* with the native chrome around it already switched.
|
|
95
|
+
*
|
|
96
|
+
* The host pushes each change once, and again — the current value — when a
|
|
97
|
+
* document's bridge reports ready, since a change made while it loaded found
|
|
98
|
+
* no hook to call. Those two can land in either order, so a push carries the
|
|
99
|
+
* host revision it took effect at and an older one never overwrites a newer
|
|
100
|
+
* one. A push without a revision applies as it always did.
|
|
74
101
|
*/
|
|
75
|
-
function applyDisplayLanguage(next) {
|
|
102
|
+
function applyDisplayLanguage(next, revision) {
|
|
76
103
|
var e_1, _a;
|
|
104
|
+
var _b;
|
|
77
105
|
var normalized = typeof next === 'string' ? next.trim() : '';
|
|
78
106
|
var current = store();
|
|
107
|
+
if (typeof revision === 'number' && Number.isFinite(revision)) {
|
|
108
|
+
if (revision <= ((_b = current.revision) !== null && _b !== void 0 ? _b : 0))
|
|
109
|
+
return;
|
|
110
|
+
current.revision = revision;
|
|
111
|
+
}
|
|
79
112
|
if (!normalized || normalized === current.value)
|
|
80
113
|
return;
|
|
81
114
|
current.value = normalized;
|
|
82
115
|
stampDocumentLanguage();
|
|
83
116
|
try {
|
|
84
|
-
for (var
|
|
85
|
-
var listener =
|
|
117
|
+
for (var _c = __values(__spreadArray([], __read(current.listeners), false)), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
118
|
+
var listener = _d.value;
|
|
86
119
|
listener(normalized);
|
|
87
120
|
}
|
|
88
121
|
}
|
|
89
122
|
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
90
123
|
finally {
|
|
91
124
|
try {
|
|
92
|
-
if (
|
|
125
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
93
126
|
}
|
|
94
127
|
finally { if (e_1) throw e_1.error; }
|
|
95
128
|
}
|