@lingxia/html 0.18.0 → 0.20.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/global.d.ts +2 -0
- package/dist/global.es2020.js +555 -56
- package/dist/global.es5.js +630 -64
- package/dist/global.js +5 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/page.d.ts +19 -0
- package/dist/page.js +20 -0
- package/package.json +4 -4
package/dist/global.es2020.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var LingXiaPage = (function(exports) {
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
//#region dist/
|
|
3
|
+
//#region ../lingxia-page-runtime/dist/shared/runtime.js
|
|
4
4
|
let snapshot = {};
|
|
5
5
|
let stateInfo = {
|
|
6
6
|
rev: -1,
|
|
@@ -8,7 +8,16 @@ var LingXiaPage = (function(exports) {
|
|
|
8
8
|
};
|
|
9
9
|
let subscribed = false;
|
|
10
10
|
let subscribeRetryTimer = null;
|
|
11
|
+
let snapshotRetryTimer = null;
|
|
12
|
+
let snapshotRetries = 0;
|
|
13
|
+
/**
|
|
14
|
+
* The host pushes a page's first state at bridge-ready, so the request is only
|
|
15
|
+
* a fallback: a few tries, backing off, then stop. A page with no Logic never
|
|
16
|
+
* answers, and must not be asked for the life of the page.
|
|
17
|
+
*/
|
|
18
|
+
const MAX_SNAPSHOT_RETRIES = 3;
|
|
11
19
|
let initialSnapshotResolved = false;
|
|
20
|
+
let actions = null;
|
|
12
21
|
let snapshotRequestInFlight = false;
|
|
13
22
|
const listeners = /* @__PURE__ */ new Set();
|
|
14
23
|
function notifyListeners() {
|
|
@@ -18,8 +27,22 @@ var LingXiaPage = (function(exports) {
|
|
|
18
27
|
} catch {}
|
|
19
28
|
});
|
|
20
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* In a dev session the page data is frozen, so a View that writes to it fails
|
|
32
|
+
* at the write instead of drifting from Logic until the next update. Each
|
|
33
|
+
* push is a fresh copy, so nothing else holds these objects.
|
|
34
|
+
*/
|
|
35
|
+
function isDevSession() {
|
|
36
|
+
return typeof window !== "undefined" && window.__LX_BRIDGE_CFG?.dev === true;
|
|
37
|
+
}
|
|
38
|
+
function deepFreeze(value) {
|
|
39
|
+
if (!value || typeof value !== "object" || Object.isFrozen(value)) return;
|
|
40
|
+
Object.freeze(value);
|
|
41
|
+
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
42
|
+
}
|
|
21
43
|
function updateSnapshot(next, info) {
|
|
22
44
|
snapshot = next && typeof next === "object" ? next : {};
|
|
45
|
+
if (isDevSession()) deepFreeze(snapshot);
|
|
23
46
|
stateInfo = info;
|
|
24
47
|
notifyListeners();
|
|
25
48
|
}
|
|
@@ -30,14 +53,31 @@ var LingXiaPage = (function(exports) {
|
|
|
30
53
|
ensurePageBridgeSubscription();
|
|
31
54
|
}, 10);
|
|
32
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Retry the snapshot request itself. The subscription is already in place, so
|
|
58
|
+
* retrying it (as this used to) returned at once and never asked again; the
|
|
59
|
+
* page then relied on the host's own push at bridge-ready.
|
|
60
|
+
*/
|
|
61
|
+
function scheduleSnapshotRetry() {
|
|
62
|
+
if (snapshotRetryTimer !== null || snapshotRetries >= MAX_SNAPSHOT_RETRIES) return;
|
|
63
|
+
snapshotRetries += 1;
|
|
64
|
+
snapshotRetryTimer = setTimeout(() => {
|
|
65
|
+
snapshotRetryTimer = null;
|
|
66
|
+
requestInitialSnapshot(window.LingXiaBridge);
|
|
67
|
+
}, 250 * 2 ** (snapshotRetries - 1));
|
|
68
|
+
}
|
|
33
69
|
function requestInitialSnapshot(bridge) {
|
|
70
|
+
if (stateInfo.rev >= 0) initialSnapshotResolved = true;
|
|
34
71
|
if (initialSnapshotResolved || snapshotRequestInFlight) return;
|
|
35
|
-
if (!bridge?.raw?.call)
|
|
72
|
+
if (!bridge?.raw?.call) {
|
|
73
|
+
scheduleSnapshotRetry();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
36
76
|
snapshotRequestInFlight = true;
|
|
37
77
|
bridge.raw.call("state.getSnapshot", { scope: "page" }).then(() => {
|
|
38
78
|
initialSnapshotResolved = true;
|
|
39
79
|
}).catch(() => {
|
|
40
|
-
|
|
80
|
+
scheduleSnapshotRetry();
|
|
41
81
|
}).finally(() => {
|
|
42
82
|
snapshotRequestInFlight = false;
|
|
43
83
|
});
|
|
@@ -63,35 +103,58 @@ var LingXiaPage = (function(exports) {
|
|
|
63
103
|
listeners.delete(listener);
|
|
64
104
|
};
|
|
65
105
|
}
|
|
66
|
-
|
|
67
|
-
|
|
106
|
+
/** Whether the page's first state has arrived. Flips once per document. */
|
|
107
|
+
function isPageReady() {
|
|
68
108
|
ensurePageBridgeSubscription();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
109
|
+
return stateInfo.rev >= 0;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolves once the page's first state has arrived — the host pushes it at
|
|
113
|
+
* bridge-ready, before `onLoad`, carrying `Page({ data })`'s defaults — so a
|
|
114
|
+
* page can mount with its data whole. With `timeoutMs`, rejects if Logic has
|
|
115
|
+
* not delivered it by then (it failed to load, or threw first); `null` waits
|
|
116
|
+
* without limit.
|
|
117
|
+
*/
|
|
118
|
+
function whenPageReady(options = {}) {
|
|
119
|
+
if (isPageReady()) return Promise.resolve();
|
|
120
|
+
const timeoutMs = options.timeoutMs === void 0 ? 1e4 : options.timeoutMs;
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
let timer = null;
|
|
123
|
+
const check = () => {
|
|
124
|
+
if (stateInfo.rev < 0) return;
|
|
125
|
+
if (timer !== null) clearTimeout(timer);
|
|
126
|
+
listeners.delete(check);
|
|
127
|
+
resolve();
|
|
128
|
+
};
|
|
129
|
+
if (timeoutMs !== null) timer = setTimeout(() => {
|
|
130
|
+
listeners.delete(check);
|
|
131
|
+
reject(/* @__PURE__ */ new Error(`Page Logic did not deliver the page state within ${timeoutMs} ms`));
|
|
132
|
+
}, timeoutMs);
|
|
133
|
+
listeners.add(check);
|
|
75
134
|
});
|
|
76
135
|
}
|
|
77
136
|
function getPageSnapshot() {
|
|
78
137
|
ensurePageBridgeSubscription();
|
|
79
138
|
return snapshot;
|
|
80
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* The page's actions: one object for the whole page, so it is a stable
|
|
142
|
+
* dependency. Built on first use — the page's action names arrive with its
|
|
143
|
+
* bridge metadata, after the page module has evaluated.
|
|
144
|
+
*/
|
|
81
145
|
function getPageActions() {
|
|
82
|
-
|
|
146
|
+
if (actions) return actions;
|
|
83
147
|
const bridge = window.__pageBridge;
|
|
84
|
-
if (!bridge?.__names)
|
|
148
|
+
if (!bridge?.__names) throw new Error("Page actions are not ready. Read useLxPage() during component setup/render, or getPage() after pageReady(); pass actions into shared helpers.");
|
|
149
|
+
const built = {};
|
|
85
150
|
for (const name of bridge.__names) {
|
|
86
151
|
if (typeof name !== "string") continue;
|
|
87
152
|
const fn = getOrCreatePageAction(bridge, name);
|
|
88
|
-
if (typeof fn === "function")
|
|
153
|
+
if (typeof fn === "function") built[name] = fn;
|
|
89
154
|
}
|
|
155
|
+
actions = built;
|
|
90
156
|
return actions;
|
|
91
157
|
}
|
|
92
|
-
function getPageStateInfo() {
|
|
93
|
-
return stateInfo;
|
|
94
|
-
}
|
|
95
158
|
function getOrCreatePageAction(bridge, name) {
|
|
96
159
|
const existing = bridge[name];
|
|
97
160
|
if (typeof existing === "function") return existing;
|
|
@@ -101,7 +164,8 @@ var LingXiaPage = (function(exports) {
|
|
|
101
164
|
}
|
|
102
165
|
function resolvePageActionMode(bridge, name) {
|
|
103
166
|
const mode = bridge.__modes?.[name];
|
|
104
|
-
|
|
167
|
+
if (mode === "call" || mode === "stream") return mode;
|
|
168
|
+
throw new Error(`Invalid bridge mode for page action '${name}'; regenerate the page with the current CLI`);
|
|
105
169
|
}
|
|
106
170
|
function definePageBridgeAction(name, mode) {
|
|
107
171
|
function action(...args) {
|
|
@@ -116,13 +180,13 @@ var LingXiaPage = (function(exports) {
|
|
|
116
180
|
return handle;
|
|
117
181
|
}
|
|
118
182
|
if (mode === "call") {
|
|
119
|
-
const promise = bridge
|
|
183
|
+
const promise = callUnaryPageAction(bridge, name, payload);
|
|
120
184
|
if (promise && typeof promise.catch === "function") promise.catch((err) => {
|
|
121
185
|
console.warn(`[PageFunc] ${name} failed:`, err instanceof Error ? err.message : err);
|
|
122
186
|
});
|
|
123
187
|
return promise;
|
|
124
188
|
}
|
|
125
|
-
bridge
|
|
189
|
+
throw new Error(`Invalid bridge mode for page action '${name}'`);
|
|
126
190
|
}
|
|
127
191
|
Object.assign(action, {
|
|
128
192
|
__logicFunc: true,
|
|
@@ -131,31 +195,450 @@ var LingXiaPage = (function(exports) {
|
|
|
131
195
|
});
|
|
132
196
|
return action;
|
|
133
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* The one wire path of a unary action. It has no bridge deadline: an action
|
|
200
|
+
* may wait for a person or poll for minutes, and settles when Logic settles
|
|
201
|
+
* or the document goes away.
|
|
202
|
+
*/
|
|
203
|
+
async function callUnaryPageAction(bridge, name, payload, dropWhenGone = true) {
|
|
204
|
+
if (!isPageReady()) await whenPageReady({ timeoutMs: null });
|
|
205
|
+
try {
|
|
206
|
+
return await bridge.raw.call(name, payload, { timeoutMs: 0 });
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (dropWhenGone && error?.code === "BRIDGE_NOT_READY") return new Promise(() => {});
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* A DOM or framework event, in this realm or another. A JSON payload never
|
|
214
|
+
* carries methods, so event methods tell the two apart.
|
|
215
|
+
*/
|
|
216
|
+
function isEventLike(value) {
|
|
217
|
+
if (typeof Event !== "undefined" && value instanceof Event) return true;
|
|
218
|
+
if (!value || typeof value !== "object") return false;
|
|
219
|
+
const event = value;
|
|
220
|
+
return typeof event.preventDefault === "function" && typeof event.stopPropagation === "function";
|
|
221
|
+
}
|
|
222
|
+
const PAGE_ACTIONS_NOT_READY = "PAGE_ACTIONS_NOT_READY";
|
|
223
|
+
function pageActionError(code, message) {
|
|
224
|
+
return Object.assign(new Error(message), { code });
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Automation entry (`PageDriver.action`): invoke one unary action of this
|
|
228
|
+
* page with a JSON payload, as the View would. The payload is passed as is —
|
|
229
|
+
* no DOM-event repackaging.
|
|
230
|
+
*/
|
|
231
|
+
function invokePageActionForAutomation(name, payload) {
|
|
232
|
+
if (typeof name !== "string" || name === "") return Promise.reject(pageActionError("BRIDGE_MALFORMED_MESSAGE", "Page action name must be a non-empty string"));
|
|
233
|
+
const metadata = window.__pageBridge;
|
|
234
|
+
const bridge = window.LingXiaBridge;
|
|
235
|
+
if (!Array.isArray(metadata?.__names) || !bridge?.raw) return Promise.reject(pageActionError(PAGE_ACTIONS_NOT_READY, "Page actions are not ready yet"));
|
|
236
|
+
if (!metadata.__names.includes(name)) {
|
|
237
|
+
const known = metadata.__names.length ? metadata.__names.join(", ") : "none";
|
|
238
|
+
return Promise.reject(pageActionError("BRIDGE_METHOD_NOT_FOUND", `'${name}' is not an action of this page (actions: ${known})`));
|
|
239
|
+
}
|
|
240
|
+
if (metadata.__modes?.[name] !== "call") return Promise.reject(pageActionError("PAGE_ACTION_NOT_UNARY", `Page action '${name}' is a stream action; only unary actions can be invoked`));
|
|
241
|
+
return callUnaryPageAction(bridge, name, payload, false);
|
|
242
|
+
}
|
|
243
|
+
if (typeof window !== "undefined") Object.defineProperty(window, "__lxInvokePageAction", {
|
|
244
|
+
value: invokePageActionForAutomation,
|
|
245
|
+
configurable: true
|
|
246
|
+
});
|
|
134
247
|
function filterPayload(name, args) {
|
|
135
248
|
const clean = [];
|
|
136
249
|
for (const value of args) {
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
250
|
+
if (isEventLike(value)) {
|
|
251
|
+
const event = value;
|
|
252
|
+
if (typeof event.type === "string" && "detail" in event) clean.push({
|
|
253
|
+
type: event.type,
|
|
254
|
+
detail: event.detail
|
|
141
255
|
});
|
|
142
256
|
continue;
|
|
143
257
|
}
|
|
144
|
-
const maybeEvent = value;
|
|
145
|
-
if (maybeEvent && typeof maybeEvent === "object" && typeof maybeEvent.type === "string" && "detail" in maybeEvent) {
|
|
146
|
-
clean.push({
|
|
147
|
-
type: maybeEvent.type,
|
|
148
|
-
detail: maybeEvent.detail
|
|
149
|
-
});
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
if (value instanceof Event) continue;
|
|
153
|
-
if (value && typeof value === "object" && "stopPropagation" in value && typeof value.stopPropagation === "function") continue;
|
|
154
258
|
clean.push(value);
|
|
155
259
|
}
|
|
156
260
|
if (clean.length > 1) throw new Error(`Page action '${name}' accepts at most one payload argument`);
|
|
157
261
|
return clean[0];
|
|
158
262
|
}
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region ../lingxia-bridge/dist/es2020/error.js
|
|
265
|
+
const ERROR_STYLES = `
|
|
266
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
267
|
+
body {
|
|
268
|
+
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', Roboto, sans-serif;
|
|
269
|
+
display: flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
justify-content: center;
|
|
272
|
+
min-height: 100vh;
|
|
273
|
+
background: #f5f5f7;
|
|
274
|
+
color: #1d1d1f;
|
|
275
|
+
padding: 20px;
|
|
276
|
+
-webkit-font-smoothing: antialiased;
|
|
277
|
+
}
|
|
278
|
+
.lx-error {
|
|
279
|
+
text-align: center;
|
|
280
|
+
max-width: 400px;
|
|
281
|
+
}
|
|
282
|
+
.lx-error-code {
|
|
283
|
+
font-size: 120px;
|
|
284
|
+
font-weight: 700;
|
|
285
|
+
color: #d1d1d6;
|
|
286
|
+
line-height: 1;
|
|
287
|
+
letter-spacing: -4px;
|
|
288
|
+
}
|
|
289
|
+
.lx-error-title {
|
|
290
|
+
font-size: 21px;
|
|
291
|
+
font-weight: 600;
|
|
292
|
+
margin: 16px 0 8px;
|
|
293
|
+
color: #1d1d1f;
|
|
294
|
+
}
|
|
295
|
+
.lx-error-desc {
|
|
296
|
+
font-size: 15px;
|
|
297
|
+
color: #86868b;
|
|
298
|
+
line-height: 1.5;
|
|
299
|
+
}
|
|
300
|
+
.lx-error-path {
|
|
301
|
+
display: inline-block;
|
|
302
|
+
margin-top: 20px;
|
|
303
|
+
padding: 10px 16px;
|
|
304
|
+
background: rgba(0,0,0,0.04);
|
|
305
|
+
border-radius: 8px;
|
|
306
|
+
font-family: 'SF Mono', ui-monospace, Menlo, Monaco, monospace;
|
|
307
|
+
font-size: 13px;
|
|
308
|
+
color: #1d1d1f;
|
|
309
|
+
word-break: break-all;
|
|
310
|
+
max-width: 100%;
|
|
311
|
+
}
|
|
312
|
+
@media (prefers-color-scheme: dark) {
|
|
313
|
+
body { background: #000; color: #f5f5f7; }
|
|
314
|
+
.lx-error-code { color: #3a3a3c; }
|
|
315
|
+
.lx-error-title { color: #f5f5f7; }
|
|
316
|
+
.lx-error-desc { color: #86868b; }
|
|
317
|
+
.lx-error-path { background: rgba(255,255,255,0.08); color: #f5f5f7; }
|
|
318
|
+
}
|
|
319
|
+
`;
|
|
320
|
+
function escapeHtml(str) {
|
|
321
|
+
const div = document.createElement("div");
|
|
322
|
+
div.textContent = str;
|
|
323
|
+
return div.innerHTML;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* A page whose Logic has not delivered its first state in time — it failed to
|
|
327
|
+
* load, threw before the page could start, or is very slow. Shown over the page
|
|
328
|
+
* instead of a blank screen, naming the page; returns a function that removes
|
|
329
|
+
* it, for when the state does arrive after all.
|
|
330
|
+
*/
|
|
331
|
+
function renderPageFault(pagePath, reason) {
|
|
332
|
+
const styleEl = document.createElement("style");
|
|
333
|
+
styleEl.textContent = `
|
|
334
|
+
.lx-page-fault { position: fixed; inset: 0; z-index: 2147483647; overflow: auto;
|
|
335
|
+
background: #fff; color: #1d1d1f; }
|
|
336
|
+
@media (prefers-color-scheme: dark) { .lx-page-fault { background: #000; color: #f5f5f7; } }
|
|
337
|
+
${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
|
|
338
|
+
document.head.appendChild(styleEl);
|
|
339
|
+
const overlay = document.createElement("div");
|
|
340
|
+
overlay.className = "lx-page-fault";
|
|
341
|
+
overlay.setAttribute("role", "alert");
|
|
342
|
+
const container = document.createElement("div");
|
|
343
|
+
container.className = "lx-error";
|
|
344
|
+
let html = "<div class=\"lx-error-code\">Page</div>";
|
|
345
|
+
html += `<h1 class="lx-error-title">${escapeHtml("This page couldn't start")}</h1>`;
|
|
346
|
+
html += `<p class="lx-error-desc">${escapeHtml("Its Logic has not delivered the page state. Check the Logic log for an error.")}</p>`;
|
|
347
|
+
if (pagePath) html += `<div class="lx-error-path">${escapeHtml(pagePath)}</div>`;
|
|
348
|
+
html += `<p class="lx-error-desc" style="margin-top:12px">${escapeHtml(reason)}</p>`;
|
|
349
|
+
container.innerHTML = html;
|
|
350
|
+
overlay.appendChild(container);
|
|
351
|
+
document.body.appendChild(overlay);
|
|
352
|
+
return () => {
|
|
353
|
+
overlay.remove();
|
|
354
|
+
styleEl.remove();
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region ../lingxia-bridge/dist/es2020/runtime-env.js
|
|
359
|
+
const BRIDGE_CONFIG = typeof window !== "undefined" && window.__LX_BRIDGE_CFG || {};
|
|
360
|
+
/**
|
|
361
|
+
* One store per document, deliberately on `window`.
|
|
362
|
+
*
|
|
363
|
+
* This module is present twice in a page: once as the global bridge runtime
|
|
364
|
+
* the host injects, once bundled into the page's own JS. Module-local state
|
|
365
|
+
* would give each copy its own value — the host would push the change into
|
|
366
|
+
* whichever copy won the race to install the hook, and the other, which is the
|
|
367
|
+
* one the framework hooks read, would answer with the boot value forever.
|
|
368
|
+
*/
|
|
369
|
+
const fallbackStore$1 = {
|
|
370
|
+
value: BRIDGE_CONFIG.displayLanguage?.trim() || "en-US",
|
|
371
|
+
listeners: /* @__PURE__ */ new Set()
|
|
372
|
+
};
|
|
373
|
+
function store$1() {
|
|
374
|
+
if (typeof window === "undefined") return fallbackStore$1;
|
|
375
|
+
if (!window.__lxDisplayLanguage) window.__lxDisplayLanguage = {
|
|
376
|
+
value: BRIDGE_CONFIG.displayLanguage?.trim() || "en-US",
|
|
377
|
+
listeners: /* @__PURE__ */ new Set()
|
|
378
|
+
};
|
|
379
|
+
return window.__lxDisplayLanguage;
|
|
380
|
+
}
|
|
381
|
+
/** Primary subtags of languages written right to left, for engines without `Intl.Locale#getTextInfo`. */
|
|
382
|
+
const RTL_LANGUAGES = /* @__PURE__ */ new Set([
|
|
383
|
+
"ar",
|
|
384
|
+
"ckb",
|
|
385
|
+
"dv",
|
|
386
|
+
"fa",
|
|
387
|
+
"he",
|
|
388
|
+
"ps",
|
|
389
|
+
"sd",
|
|
390
|
+
"ug",
|
|
391
|
+
"ur",
|
|
392
|
+
"yi"
|
|
393
|
+
]);
|
|
394
|
+
/** The writing direction of a BCP-47 tag. */
|
|
395
|
+
function textDirection(tag) {
|
|
396
|
+
try {
|
|
397
|
+
const locale = new Intl.Locale(tag);
|
|
398
|
+
const info = typeof locale.getTextInfo === "function" ? locale.getTextInfo() : locale.textInfo;
|
|
399
|
+
if (info?.direction === "rtl" || info?.direction === "ltr") return info.direction;
|
|
400
|
+
} catch {}
|
|
401
|
+
return RTL_LANGUAGES.has(tag.split(/[-_]/)[0].toLowerCase()) ? "rtl" : "ltr";
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* `lang` and `dir` on `<html>` follow the display language, so a page never
|
|
405
|
+
* sets either: CSS logical properties and `:dir()` just work.
|
|
406
|
+
*/
|
|
407
|
+
function stampDocumentLanguage() {
|
|
408
|
+
if (typeof document !== "undefined" && document.documentElement) {
|
|
409
|
+
const language = store$1().value;
|
|
410
|
+
document.documentElement.lang = language;
|
|
411
|
+
document.documentElement.dir = textDirection(language);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
stampDocumentLanguage();
|
|
415
|
+
/**
|
|
416
|
+
* Host entry point for a language the user changed while this document was
|
|
417
|
+
* open. Bootstrap alone would leave a live page in the language it started in,
|
|
418
|
+
* with the native chrome around it already switched.
|
|
419
|
+
*
|
|
420
|
+
* The host pushes each change once, and again — the current value — when a
|
|
421
|
+
* document's bridge reports ready, since a change made while it loaded found
|
|
422
|
+
* no hook to call. Those two can land in either order, so a push carries the
|
|
423
|
+
* host revision it took effect at and an older one never overwrites a newer
|
|
424
|
+
* one. A push without a revision applies as it always did.
|
|
425
|
+
*/
|
|
426
|
+
function applyDisplayLanguage(next, revision) {
|
|
427
|
+
const normalized = typeof next === "string" ? next.trim() : "";
|
|
428
|
+
const current = store$1();
|
|
429
|
+
if (typeof revision === "number" && Number.isFinite(revision)) {
|
|
430
|
+
if (revision <= (current.revision ?? 0)) return;
|
|
431
|
+
current.revision = revision;
|
|
432
|
+
}
|
|
433
|
+
if (!normalized || normalized === current.value) return;
|
|
434
|
+
current.value = normalized;
|
|
435
|
+
stampDocumentLanguage();
|
|
436
|
+
for (const listener of [...current.listeners]) listener(normalized);
|
|
437
|
+
}
|
|
438
|
+
if (typeof window !== "undefined" && !window.__lingxiaApplyDisplayLanguage) Object.defineProperty(window, "__lingxiaApplyDisplayLanguage", {
|
|
439
|
+
configurable: false,
|
|
440
|
+
enumerable: false,
|
|
441
|
+
value: applyDisplayLanguage
|
|
442
|
+
});
|
|
443
|
+
function getDisplayLanguage() {
|
|
444
|
+
return store$1().value;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Subscribe to host display-language changes. Returns an unsubscribe.
|
|
448
|
+
*
|
|
449
|
+
* Change-only, so that this composes with `useSyncExternalStore`: the listener
|
|
450
|
+
* runs when the language changes, never on subscribe. Read the current value
|
|
451
|
+
* with `getDisplayLanguage()`. Logic's `lx.host.displayLanguage.watch` differs
|
|
452
|
+
* deliberately — it has no render loop to feed, so it delivers immediately.
|
|
453
|
+
*/
|
|
454
|
+
function subscribeDisplayLanguage(listener) {
|
|
455
|
+
const listeners = store$1().listeners;
|
|
456
|
+
listeners.add(listener);
|
|
457
|
+
return () => {
|
|
458
|
+
listeners.delete(listener);
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function getPlatformOS() {
|
|
462
|
+
return BRIDGE_CONFIG.os || "unknown";
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Which kind of machine this is, mobile or desktop. Read through `isMobile()`
|
|
466
|
+
* and `isDesktop()`; the class itself is host vocabulary, not lxapp API.
|
|
467
|
+
*
|
|
468
|
+
* Fixed for the life of the document. A shipped host is one machine; the
|
|
469
|
+
* Runner re-serves the page when its simulated device changes class, so this
|
|
470
|
+
* never has to change under a page that is already rendering.
|
|
471
|
+
*
|
|
472
|
+
* Hosts from before this config key shipped send nothing, so fall back to the
|
|
473
|
+
* OS: every one of them is the machine it names.
|
|
474
|
+
*/
|
|
475
|
+
function hostClass() {
|
|
476
|
+
if (BRIDGE_CONFIG.hostClass === "mobile" || BRIDGE_CONFIG.hostClass === "desktop") return BRIDGE_CONFIG.hostClass;
|
|
477
|
+
return BRIDGE_CONFIG.os === "macOS" || BRIDGE_CONFIG.os === "Windows" ? "desktop" : "mobile";
|
|
478
|
+
}
|
|
479
|
+
function isDesktop() {
|
|
480
|
+
return hostClass() === "desktop";
|
|
481
|
+
}
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region ../lingxia-bridge/dist/es2020/surface-context.js
|
|
484
|
+
/** What a host that sends nothing — one older than this API — is taken to say. */
|
|
485
|
+
const UNREPORTED = {
|
|
486
|
+
sizeClass: "compact",
|
|
487
|
+
width: 0,
|
|
488
|
+
height: 0,
|
|
489
|
+
aside: false
|
|
490
|
+
};
|
|
491
|
+
/**
|
|
492
|
+
* One store per document, on `window`, for the same reason as the display
|
|
493
|
+
* language: the bridge module is present twice in a page, and the host must
|
|
494
|
+
* reach the copy the framework hooks read.
|
|
495
|
+
*/
|
|
496
|
+
function store() {
|
|
497
|
+
if (typeof window === "undefined") return fallbackStore;
|
|
498
|
+
if (!window.__lxSurfaceContext) {
|
|
499
|
+
const config = window.__LX_BRIDGE_CFG;
|
|
500
|
+
window.__lxSurfaceContext = {
|
|
501
|
+
value: parse(config?.surfaceContext) ?? UNREPORTED,
|
|
502
|
+
revision: typeof config?.surfaceContextRevision === "number" ? config.surfaceContextRevision : 0,
|
|
503
|
+
listeners: /* @__PURE__ */ new Set()
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return window.__lxSurfaceContext;
|
|
507
|
+
}
|
|
508
|
+
const fallbackStore = {
|
|
509
|
+
value: UNREPORTED,
|
|
510
|
+
revision: 0,
|
|
511
|
+
listeners: /* @__PURE__ */ new Set()
|
|
512
|
+
};
|
|
513
|
+
function parse(next) {
|
|
514
|
+
if (typeof next !== "object" || next === null) return null;
|
|
515
|
+
const { sizeClass, width, height, aside } = next;
|
|
516
|
+
if (sizeClass !== "compact" && sizeClass !== "regular") return null;
|
|
517
|
+
if (typeof width !== "number" || typeof height !== "number") return null;
|
|
518
|
+
return {
|
|
519
|
+
sizeClass,
|
|
520
|
+
width,
|
|
521
|
+
height,
|
|
522
|
+
aside: aside === true
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function same$1(a, b) {
|
|
526
|
+
return a.sizeClass === b.sizeClass && a.width === b.width && a.height === b.height && a.aside === b.aside;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Host entry point: once the page's bridge is up, and on every change. Pushes
|
|
530
|
+
* can run out of order across host threads, so each carries a revision and an
|
|
531
|
+
* older one than the store holds is dropped.
|
|
532
|
+
*/
|
|
533
|
+
function applySurfaceContext(next, revision) {
|
|
534
|
+
const context = parse(next);
|
|
535
|
+
const current = store();
|
|
536
|
+
if (!context) return;
|
|
537
|
+
if (typeof revision === "number") {
|
|
538
|
+
if (revision <= current.revision) return;
|
|
539
|
+
current.revision = revision;
|
|
540
|
+
}
|
|
541
|
+
if (same$1(current.value, context)) return;
|
|
542
|
+
current.value = context;
|
|
543
|
+
for (const listener of [...current.listeners]) listener();
|
|
544
|
+
}
|
|
545
|
+
if (typeof window !== "undefined" && !window.__lingxiaApplySurfaceContext) Object.defineProperty(window, "__lingxiaApplySurfaceContext", {
|
|
546
|
+
configurable: false,
|
|
547
|
+
enumerable: false,
|
|
548
|
+
value: applySurfaceContext
|
|
549
|
+
});
|
|
550
|
+
/**
|
|
551
|
+
* The lxapp's adaptive context. The host seeds it into every page before any
|
|
552
|
+
* script runs and pushes each change, so there is always a value; a host older
|
|
553
|
+
* than this API reports `compact` with a 0×0 viewport.
|
|
554
|
+
*/
|
|
555
|
+
function getSurfaceContext() {
|
|
556
|
+
return store().value;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Follow the adaptive context. Change-only, like `subscribeDisplayLanguage`,
|
|
560
|
+
* so it composes with `useSyncExternalStore`. Returns an unsubscribe.
|
|
561
|
+
*/
|
|
562
|
+
function subscribeSurfaceContext(listener) {
|
|
563
|
+
const listeners = store().listeners;
|
|
564
|
+
listeners.add(listener);
|
|
565
|
+
return () => {
|
|
566
|
+
listeners.delete(listener);
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region ../lingxia-bridge/dist/es2020/host.js
|
|
571
|
+
let current = null;
|
|
572
|
+
function read() {
|
|
573
|
+
const surface = getSurfaceContext();
|
|
574
|
+
return {
|
|
575
|
+
sizeClass: surface.sizeClass,
|
|
576
|
+
aside: surface.aside,
|
|
577
|
+
displayLanguage: getDisplayLanguage(),
|
|
578
|
+
formFactor: isDesktop() ? "desktop" : "mobile",
|
|
579
|
+
os: getPlatformOS(),
|
|
580
|
+
runner: BRIDGE_CONFIG.runner === true
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function same(a, b) {
|
|
584
|
+
return a.sizeClass === b.sizeClass && a.aside === b.aside && a.displayLanguage === b.displayLanguage && a.formFactor === b.formFactor && a.os === b.os && a.runner === b.runner;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* The host facts for this page. The same object is returned until a field
|
|
588
|
+
* changes, so it composes with `useSyncExternalStore` and with memoization.
|
|
589
|
+
*/
|
|
590
|
+
function getHost() {
|
|
591
|
+
const next = read();
|
|
592
|
+
if (current === null || !same(current, next)) current = next;
|
|
593
|
+
return current;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Follow the host facts. Change-only: the listener runs when a field of
|
|
597
|
+
* `getHost()` changes — not on subscribe, and not when only the viewport's
|
|
598
|
+
* size moves within its size class. Returns an unsubscribe.
|
|
599
|
+
*/
|
|
600
|
+
function subscribeHost(listener) {
|
|
601
|
+
let last = getHost();
|
|
602
|
+
const check = () => {
|
|
603
|
+
const next = getHost();
|
|
604
|
+
if (next === last) return;
|
|
605
|
+
last = next;
|
|
606
|
+
listener();
|
|
607
|
+
};
|
|
608
|
+
const stopLanguage = subscribeDisplayLanguage(check);
|
|
609
|
+
const stopSurface = subscribeSurfaceContext(check);
|
|
610
|
+
return () => {
|
|
611
|
+
stopLanguage();
|
|
612
|
+
stopSurface();
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region ../lingxia-page-runtime/dist/shared/startup.js
|
|
617
|
+
let pending;
|
|
618
|
+
/** Mount with complete initial data; a delayed startup stays recoverable. */
|
|
619
|
+
function waitForPageState() {
|
|
620
|
+
if (isPageReady()) return Promise.resolve();
|
|
621
|
+
return pending ?? (pending = wait().finally(() => {
|
|
622
|
+
pending = void 0;
|
|
623
|
+
}));
|
|
624
|
+
}
|
|
625
|
+
async function wait() {
|
|
626
|
+
const delay = 1e4;
|
|
627
|
+
const panel = {};
|
|
628
|
+
const timer = setTimeout(() => {
|
|
629
|
+
const reason = `Page Logic has not delivered the page state after ${delay} ms`;
|
|
630
|
+
console.error(`[LingXia] ${location.pathname}: ${reason}`);
|
|
631
|
+
panel.dismiss = renderPageFault(location.pathname, reason);
|
|
632
|
+
}, delay);
|
|
633
|
+
try {
|
|
634
|
+
await whenPageReady({ timeoutMs: null });
|
|
635
|
+
} finally {
|
|
636
|
+
clearTimeout(timer);
|
|
637
|
+
panel.dismiss?.();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
//#endregion
|
|
641
|
+
//#region ../lingxia-page-runtime/dist/page-chrome.js
|
|
159
642
|
const initialLayout = Object.freeze({
|
|
160
643
|
revision: 0,
|
|
161
644
|
topInset: 0,
|
|
@@ -171,21 +654,15 @@ var LingXiaPage = (function(exports) {
|
|
|
171
654
|
root?.style.setProperty("--lx-page-chrome-top-inset", `${layout.topInset}px`);
|
|
172
655
|
root?.style.setProperty("--lx-page-chrome-bottom-inset", `${layout.bottomInset}px`);
|
|
173
656
|
root?.style.setProperty("--lx-page-chrome-capsule-inline-end-inset", `${layout.capsuleInlineEndInset}px`);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
installPageChromeRuntime();
|
|
184
|
-
const handleChange = (event) => {
|
|
185
|
-
listener(event.detail);
|
|
186
|
-
};
|
|
187
|
-
window.addEventListener("lxpagechromechange", handleChange);
|
|
188
|
-
return () => window.removeEventListener("lxpagechromechange", handleChange);
|
|
657
|
+
const capsule = layout.capsuleRect;
|
|
658
|
+
for (const edge of [
|
|
659
|
+
"top",
|
|
660
|
+
"right",
|
|
661
|
+
"bottom",
|
|
662
|
+
"left",
|
|
663
|
+
"width",
|
|
664
|
+
"height"
|
|
665
|
+
]) root?.style.setProperty(`--lx-page-chrome-capsule-${edge}`, `${capsule ? capsule[edge] : 0}px`);
|
|
189
666
|
}
|
|
190
667
|
/** Ensure browser previews have the same synchronous contract as native pages. */
|
|
191
668
|
function installPageChromeRuntime() {
|
|
@@ -224,12 +701,34 @@ var LingXiaPage = (function(exports) {
|
|
|
224
701
|
}
|
|
225
702
|
installPageChromeRuntime();
|
|
226
703
|
//#endregion
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
704
|
+
//#region dist/page.js
|
|
705
|
+
/**
|
|
706
|
+
* Resolves once the first state arrives. By default a delayed startup shows
|
|
707
|
+
* a fault panel and keeps waiting, just like React/Vue. An explicit timeout
|
|
708
|
+
* rejects instead; `null` waits without the panel. Await before `getPage()`.
|
|
709
|
+
*/
|
|
710
|
+
function pageReady(options) {
|
|
711
|
+
return options?.timeoutMs === void 0 ? waitForPageState() : whenPageReady(options);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* This page's Logic state and actions — `this.data` and the page's methods.
|
|
715
|
+
* `data` is readonly: Logic owns it (in a dev session a write throws).
|
|
716
|
+
*/
|
|
717
|
+
function getPage() {
|
|
718
|
+
return {
|
|
719
|
+
data: getPageSnapshot(),
|
|
720
|
+
actions: getPageActions()
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/** Follow the page's state. Change-only; read it with `getPage()`. Returns an unsubscribe. */
|
|
724
|
+
function subscribePage(listener) {
|
|
725
|
+
return subscribePageSnapshot(listener);
|
|
726
|
+
}
|
|
727
|
+
//#endregion
|
|
728
|
+
exports.getHost = getHost;
|
|
729
|
+
exports.getPage = getPage;
|
|
730
|
+
exports.pageReady = pageReady;
|
|
731
|
+
exports.subscribeHost = subscribeHost;
|
|
732
|
+
exports.subscribePage = subscribePage;
|
|
234
733
|
return exports;
|
|
235
734
|
})({});
|