@absolutejs/absolute 0.20.0-beta.58 → 0.20.0-beta.59
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/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/build.js +5 -4
- package/dist/build.js.map +3 -3
- package/dist/cli/index.js +29 -4
- package/dist/index.js +5 -4
- package/dist/index.js.map +3 -3
- package/dist/mobile/index.js +9 -3
- package/dist/mobile/index.js.map +4 -4
- package/dist/mobile/shellBootstrap.js +427 -57
- package/dist/mobile/shellSync.js +18 -7
- package/dist/src/mobile/navigationLifecycle.d.ts +36 -0
- package/dist/src/mobile/navigationState.d.ts +53 -0
- package/dist/src/mobile/transport.d.ts +1 -0
- package/package.json +7 -7
|
@@ -458,7 +458,11 @@ var createAbsoluteMobilePageRequest = (manifest, path, options = {}) => {
|
|
|
458
458
|
headers.set(MOBILE_PAGE_REQUEST_HEADERS.pageId, page.pageId);
|
|
459
459
|
headers.set(MOBILE_PAGE_REQUEST_HEADERS.protocol, String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION));
|
|
460
460
|
headers.set(MOBILE_PAGE_REQUEST_HEADERS.runtime, manifest.runtime);
|
|
461
|
-
return new Request(url, {
|
|
461
|
+
return new Request(url, {
|
|
462
|
+
headers,
|
|
463
|
+
method: "GET",
|
|
464
|
+
...options.signal ? { signal: options.signal } : {}
|
|
465
|
+
});
|
|
462
466
|
};
|
|
463
467
|
var fetchAbsoluteMobilePage = async (manifest, path, options = {}) => {
|
|
464
468
|
const request = createAbsoluteMobilePageRequest(manifest, path, options);
|
|
@@ -1143,9 +1147,268 @@ var installAbsoluteMobileAdaptiveShell = async (devices = defaultDevices) => {
|
|
|
1143
1147
|
};
|
|
1144
1148
|
};
|
|
1145
1149
|
|
|
1150
|
+
// src/mobile/navigationLifecycle.ts
|
|
1151
|
+
var isAbortError = (error) => error instanceof DOMException ? error.name === "AbortError" : error instanceof Error && error.name === "AbortError";
|
|
1152
|
+
var cancelledResult = { kind: "cancelled" };
|
|
1153
|
+
var committedResult = { kind: "committed" };
|
|
1154
|
+
var failedResult = (error, phase) => ({ error, kind: "failed", phase });
|
|
1155
|
+
var createAbsoluteMobileNavigationCoordinator = (options) => {
|
|
1156
|
+
let activeLoad;
|
|
1157
|
+
let commitQueue = Promise.resolve();
|
|
1158
|
+
let disposed = false;
|
|
1159
|
+
let generation = 0;
|
|
1160
|
+
let lifecyclePhase = "idle";
|
|
1161
|
+
const navigate = async (request) => {
|
|
1162
|
+
if (disposed)
|
|
1163
|
+
return cancelledResult;
|
|
1164
|
+
const ownGeneration = generation += 1;
|
|
1165
|
+
activeLoad?.abort();
|
|
1166
|
+
const controller = new AbortController;
|
|
1167
|
+
activeLoad = controller;
|
|
1168
|
+
lifecyclePhase = "loading";
|
|
1169
|
+
options.onStart?.(request);
|
|
1170
|
+
let payload;
|
|
1171
|
+
try {
|
|
1172
|
+
payload = await options.load(request, controller.signal);
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
if (controller.signal.aborted || isAbortError(error) || ownGeneration !== generation || disposed) {
|
|
1175
|
+
if (ownGeneration === generation)
|
|
1176
|
+
lifecyclePhase = "idle";
|
|
1177
|
+
return cancelledResult;
|
|
1178
|
+
}
|
|
1179
|
+
lifecyclePhase = "idle";
|
|
1180
|
+
options.onFailure?.(error, "load", request);
|
|
1181
|
+
return failedResult(error, "load");
|
|
1182
|
+
}
|
|
1183
|
+
if (ownGeneration !== generation || disposed)
|
|
1184
|
+
return cancelledResult;
|
|
1185
|
+
if (activeLoad === controller)
|
|
1186
|
+
activeLoad = undefined;
|
|
1187
|
+
lifecyclePhase = "queued";
|
|
1188
|
+
const execute = async () => {
|
|
1189
|
+
if (ownGeneration !== generation || disposed) {
|
|
1190
|
+
if (ownGeneration === generation)
|
|
1191
|
+
lifecyclePhase = "idle";
|
|
1192
|
+
return cancelledResult;
|
|
1193
|
+
}
|
|
1194
|
+
lifecyclePhase = "committing";
|
|
1195
|
+
try {
|
|
1196
|
+
await options.commit(payload, request);
|
|
1197
|
+
options.onCommit?.(request);
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
if (ownGeneration === generation) {
|
|
1200
|
+
lifecyclePhase = "idle";
|
|
1201
|
+
options.onFailure?.(error, "commit", request);
|
|
1202
|
+
}
|
|
1203
|
+
return failedResult(error, "commit");
|
|
1204
|
+
}
|
|
1205
|
+
if (ownGeneration !== generation || disposed) {
|
|
1206
|
+
return cancelledResult;
|
|
1207
|
+
}
|
|
1208
|
+
lifecyclePhase = "idle";
|
|
1209
|
+
options.onSuccess?.(request);
|
|
1210
|
+
return committedResult;
|
|
1211
|
+
};
|
|
1212
|
+
const result = commitQueue.then(execute, execute);
|
|
1213
|
+
commitQueue = result.then(() => {
|
|
1214
|
+
return;
|
|
1215
|
+
}, () => {
|
|
1216
|
+
return;
|
|
1217
|
+
});
|
|
1218
|
+
return result;
|
|
1219
|
+
};
|
|
1220
|
+
return {
|
|
1221
|
+
cancelPending: () => {
|
|
1222
|
+
if (disposed || lifecyclePhase !== "loading" && lifecyclePhase !== "queued")
|
|
1223
|
+
return false;
|
|
1224
|
+
generation += 1;
|
|
1225
|
+
activeLoad?.abort();
|
|
1226
|
+
activeLoad = undefined;
|
|
1227
|
+
lifecyclePhase = "idle";
|
|
1228
|
+
return true;
|
|
1229
|
+
},
|
|
1230
|
+
dispose: () => {
|
|
1231
|
+
if (disposed)
|
|
1232
|
+
return;
|
|
1233
|
+
disposed = true;
|
|
1234
|
+
generation += 1;
|
|
1235
|
+
activeLoad?.abort();
|
|
1236
|
+
activeLoad = undefined;
|
|
1237
|
+
lifecyclePhase = "idle";
|
|
1238
|
+
},
|
|
1239
|
+
navigate: (request) => navigate(request),
|
|
1240
|
+
phase: () => lifecyclePhase
|
|
1241
|
+
};
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// src/mobile/navigationState.ts
|
|
1245
|
+
var CONTROL_SELECTOR = 'input, textarea, select, details, [contenteditable="true"]';
|
|
1246
|
+
var FOCUS_SELECTOR = '[data-absolute-navigation-focus], button, a[href], input, textarea, select, [tabindex]:not([tabindex="-1"])';
|
|
1247
|
+
var SCROLL_SELECTOR = "[data-absolute-app-main], [data-absolute-scroll-restoration]";
|
|
1248
|
+
var SENSITIVE_AUTOCOMPLETE = new Set([
|
|
1249
|
+
"cc-csc",
|
|
1250
|
+
"cc-number",
|
|
1251
|
+
"current-password",
|
|
1252
|
+
"new-password",
|
|
1253
|
+
"one-time-code"
|
|
1254
|
+
]);
|
|
1255
|
+
var locatorFor = (element, elements) => {
|
|
1256
|
+
if (element.id)
|
|
1257
|
+
return { kind: "id", value: element.id };
|
|
1258
|
+
const name = element.getAttribute("name");
|
|
1259
|
+
if (name) {
|
|
1260
|
+
const matching = elements.filter((candidate) => candidate.getAttribute("name") === name);
|
|
1261
|
+
return { index: matching.indexOf(element), kind: "name", value: name };
|
|
1262
|
+
}
|
|
1263
|
+
return { index: elements.indexOf(element), kind: "index" };
|
|
1264
|
+
};
|
|
1265
|
+
var resolveLocator = (locator, selector) => {
|
|
1266
|
+
if (locator.kind === "id") {
|
|
1267
|
+
return document.getElementById(locator.value) ?? undefined;
|
|
1268
|
+
}
|
|
1269
|
+
if (locator.kind === "name") {
|
|
1270
|
+
return [...document.querySelectorAll(selector)].filter((element) => element.getAttribute("name") === locator.value)[locator.index];
|
|
1271
|
+
}
|
|
1272
|
+
return document.querySelectorAll(selector)[locator.index];
|
|
1273
|
+
};
|
|
1274
|
+
var shouldCaptureControl = (element) => {
|
|
1275
|
+
if (element.closest('[data-absolute-navigation-preserve="off"]'))
|
|
1276
|
+
return false;
|
|
1277
|
+
if (!(element instanceof HTMLInputElement))
|
|
1278
|
+
return true;
|
|
1279
|
+
if (element.type === "file" || element.type === "hidden" || element.type === "password")
|
|
1280
|
+
return false;
|
|
1281
|
+
return !SENSITIVE_AUTOCOMPLETE.has(element.autocomplete.toLowerCase());
|
|
1282
|
+
};
|
|
1283
|
+
var captureControl = (element, locator) => {
|
|
1284
|
+
const snapshot = {
|
|
1285
|
+
locator,
|
|
1286
|
+
tag: element.tagName.toLowerCase()
|
|
1287
|
+
};
|
|
1288
|
+
if (element instanceof HTMLInputElement) {
|
|
1289
|
+
if (element.type === "checkbox" || element.type === "radio") {
|
|
1290
|
+
snapshot.checked = element.checked;
|
|
1291
|
+
} else {
|
|
1292
|
+
snapshot.value = element.value;
|
|
1293
|
+
if (element.selectionStart !== null)
|
|
1294
|
+
snapshot.selectionStart = element.selectionStart;
|
|
1295
|
+
if (element.selectionEnd !== null)
|
|
1296
|
+
snapshot.selectionEnd = element.selectionEnd;
|
|
1297
|
+
}
|
|
1298
|
+
} else if (element instanceof HTMLTextAreaElement) {
|
|
1299
|
+
snapshot.value = element.value;
|
|
1300
|
+
snapshot.selectionStart = element.selectionStart;
|
|
1301
|
+
snapshot.selectionEnd = element.selectionEnd;
|
|
1302
|
+
} else if (element instanceof HTMLSelectElement) {
|
|
1303
|
+
snapshot.values = [...element.selectedOptions].map(({ value }) => value);
|
|
1304
|
+
} else if (element instanceof HTMLDetailsElement) {
|
|
1305
|
+
snapshot.open = element.open;
|
|
1306
|
+
} else if (element.getAttribute("contenteditable") === "true") {
|
|
1307
|
+
snapshot.value = element.textContent ?? "";
|
|
1308
|
+
}
|
|
1309
|
+
return snapshot;
|
|
1310
|
+
};
|
|
1311
|
+
var restoreControl = (snapshot) => {
|
|
1312
|
+
const element = resolveLocator(snapshot.locator, CONTROL_SELECTOR);
|
|
1313
|
+
if (!element || element.tagName.toLowerCase() !== snapshot.tag)
|
|
1314
|
+
return;
|
|
1315
|
+
if (element instanceof HTMLInputElement) {
|
|
1316
|
+
if (snapshot.checked !== undefined)
|
|
1317
|
+
element.checked = snapshot.checked;
|
|
1318
|
+
else if (snapshot.value !== undefined)
|
|
1319
|
+
element.value = snapshot.value;
|
|
1320
|
+
if (snapshot.selectionStart !== undefined && snapshot.selectionEnd !== undefined) {
|
|
1321
|
+
try {
|
|
1322
|
+
element.setSelectionRange(snapshot.selectionStart, snapshot.selectionEnd);
|
|
1323
|
+
} catch {}
|
|
1324
|
+
}
|
|
1325
|
+
} else if (element instanceof HTMLTextAreaElement) {
|
|
1326
|
+
if (snapshot.value !== undefined)
|
|
1327
|
+
element.value = snapshot.value;
|
|
1328
|
+
if (snapshot.selectionStart !== undefined && snapshot.selectionEnd !== undefined)
|
|
1329
|
+
element.setSelectionRange(snapshot.selectionStart, snapshot.selectionEnd);
|
|
1330
|
+
} else if (element instanceof HTMLSelectElement && snapshot.values) {
|
|
1331
|
+
for (const option of element.options) {
|
|
1332
|
+
option.selected = snapshot.values.includes(option.value);
|
|
1333
|
+
}
|
|
1334
|
+
} else if (element instanceof HTMLDetailsElement && snapshot.open !== undefined) {
|
|
1335
|
+
element.open = snapshot.open;
|
|
1336
|
+
} else if (element.getAttribute("contenteditable") === "true" && snapshot.value !== undefined) {
|
|
1337
|
+
element.textContent = snapshot.value;
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
var focusElement = (element) => {
|
|
1341
|
+
if (!(element instanceof HTMLElement))
|
|
1342
|
+
return false;
|
|
1343
|
+
if (!element.matches(FOCUS_SELECTOR))
|
|
1344
|
+
element.tabIndex = -1;
|
|
1345
|
+
element.focus({ preventScroll: true });
|
|
1346
|
+
return document.activeElement === element;
|
|
1347
|
+
};
|
|
1348
|
+
var createAbsoluteMobileHistoryEntry = (path, index, entryId = crypto.randomUUID()) => ({
|
|
1349
|
+
absoluteMobile: true,
|
|
1350
|
+
entryId,
|
|
1351
|
+
index,
|
|
1352
|
+
path
|
|
1353
|
+
});
|
|
1354
|
+
var readAbsoluteMobileHistoryEntry = (value) => {
|
|
1355
|
+
if (typeof value !== "object" || value === null)
|
|
1356
|
+
return;
|
|
1357
|
+
if (Reflect.get(value, "absoluteMobile") !== true || typeof Reflect.get(value, "entryId") !== "string" || !Number.isSafeInteger(Reflect.get(value, "index")) || typeof Reflect.get(value, "path") !== "string")
|
|
1358
|
+
return;
|
|
1359
|
+
return {
|
|
1360
|
+
absoluteMobile: true,
|
|
1361
|
+
entryId: Reflect.get(value, "entryId"),
|
|
1362
|
+
index: Reflect.get(value, "index"),
|
|
1363
|
+
path: Reflect.get(value, "path")
|
|
1364
|
+
};
|
|
1365
|
+
};
|
|
1366
|
+
var captureAbsoluteMobileDocumentState = () => {
|
|
1367
|
+
const controls = [
|
|
1368
|
+
...document.querySelectorAll(CONTROL_SELECTOR)
|
|
1369
|
+
].filter(shouldCaptureControl);
|
|
1370
|
+
const focusable = [...document.querySelectorAll(FOCUS_SELECTOR)];
|
|
1371
|
+
const scrollable = [...document.querySelectorAll(SCROLL_SELECTOR)];
|
|
1372
|
+
const active = document.activeElement;
|
|
1373
|
+
return {
|
|
1374
|
+
controls: controls.map((element) => captureControl(element, locatorFor(element, controls))),
|
|
1375
|
+
...active instanceof Element && focusable.includes(active) ? { focus: locatorFor(active, focusable) } : {},
|
|
1376
|
+
scroll: scrollable.map((element) => ({
|
|
1377
|
+
left: element.scrollLeft,
|
|
1378
|
+
locator: locatorFor(element, scrollable),
|
|
1379
|
+
top: element.scrollTop
|
|
1380
|
+
})),
|
|
1381
|
+
window: { x: window.scrollX, y: window.scrollY }
|
|
1382
|
+
};
|
|
1383
|
+
};
|
|
1384
|
+
var restoreAbsoluteMobileDocumentState = (snapshot) => {
|
|
1385
|
+
for (const control of snapshot.controls)
|
|
1386
|
+
restoreControl(control);
|
|
1387
|
+
const focus = snapshot.focus ? resolveLocator(snapshot.focus, FOCUS_SELECTOR) : undefined;
|
|
1388
|
+
focusElement(focus);
|
|
1389
|
+
for (const scroll of snapshot.scroll) {
|
|
1390
|
+
const element = resolveLocator(scroll.locator, SCROLL_SELECTOR);
|
|
1391
|
+
if (element)
|
|
1392
|
+
element.scrollTo(scroll.left, scroll.top);
|
|
1393
|
+
}
|
|
1394
|
+
window.scrollTo(snapshot.window.x, snapshot.window.y);
|
|
1395
|
+
};
|
|
1396
|
+
var resetAbsoluteMobileDocumentState = () => {
|
|
1397
|
+
for (const element of document.querySelectorAll(SCROLL_SELECTOR)) {
|
|
1398
|
+
element.scrollTo(0, 0);
|
|
1399
|
+
}
|
|
1400
|
+
window.scrollTo(0, 0);
|
|
1401
|
+
const autofocus = document.querySelector("[autofocus]");
|
|
1402
|
+
if (autofocus && focusElement(autofocus))
|
|
1403
|
+
return;
|
|
1404
|
+
const target = ["[data-absolute-navigation-focus]", "main h1", "h1", "main"].map((selector) => document.querySelector(selector)).find((element) => element !== null);
|
|
1405
|
+
focusElement(target ?? undefined);
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1146
1408
|
// src/mobile/shellBootstrap.ts
|
|
1147
1409
|
var MANIFEST_PATH = "./absolute-mobile-manifest.json";
|
|
1148
1410
|
var STATUS_ID = "absolute-mobile-status";
|
|
1411
|
+
var NAVIGATION_ERROR_ID = "absolute-mobile-navigation-error";
|
|
1149
1412
|
var initialNavigationPath = (entry) => {
|
|
1150
1413
|
const expoPath = new URLSearchParams(location.search).get("absolutePath");
|
|
1151
1414
|
if (expoPath?.startsWith("/") && !expoPath.startsWith("//"))
|
|
@@ -1157,33 +1420,19 @@ var currentNavigationPath = () => {
|
|
|
1157
1420
|
const expoPath = new URLSearchParams(location.search).get("absolutePath");
|
|
1158
1421
|
return expoPath?.startsWith("/") && !expoPath.startsWith("//") ? expoPath : `${location.pathname}${location.search}${location.hash}`;
|
|
1159
1422
|
};
|
|
1160
|
-
var
|
|
1423
|
+
var historyUrl = (path) => {
|
|
1161
1424
|
if (!Reflect.get(globalThis, "__absoluteExpoBridge")) {
|
|
1162
|
-
|
|
1163
|
-
return;
|
|
1425
|
+
return path;
|
|
1164
1426
|
}
|
|
1165
1427
|
const url = new URL(location.href);
|
|
1166
1428
|
url.search = "";
|
|
1167
1429
|
url.hash = "";
|
|
1168
1430
|
url.searchParams.set("absolutePath", path);
|
|
1169
|
-
|
|
1170
|
-
const bridge = Reflect.get(globalThis, "__absoluteExpoBridge");
|
|
1171
|
-
if (typeof bridge === "object" && bridge !== null && typeof Reflect.get(bridge, "setPath") === "function") {
|
|
1172
|
-
Reflect.get(bridge, "setPath").call(bridge, path);
|
|
1173
|
-
}
|
|
1431
|
+
return url.href;
|
|
1174
1432
|
};
|
|
1175
|
-
var
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
history.replaceState(state, "", path);
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
const url = new URL(location.href);
|
|
1182
|
-
url.search = "";
|
|
1183
|
-
url.hash = "";
|
|
1184
|
-
url.searchParams.set("absolutePath", path);
|
|
1185
|
-
history.replaceState(state, "", url.href);
|
|
1186
|
-
notifyExpoNavigationPath(path);
|
|
1433
|
+
var writeNavigationHistory = (entry, mode) => {
|
|
1434
|
+
history[mode === "push" ? "pushState" : "replaceState"](entry, "", historyUrl(entry.path));
|
|
1435
|
+
notifyExpoNavigationPath(entry.path);
|
|
1187
1436
|
};
|
|
1188
1437
|
var notifyExpoNavigationPath = (path) => {
|
|
1189
1438
|
const bridge = Reflect.get(globalThis, "__absoluteExpoBridge");
|
|
@@ -1222,6 +1471,34 @@ var renderStatus = (message, kind = "loading") => {
|
|
|
1222
1471
|
status.textContent = message;
|
|
1223
1472
|
adaptiveShell?.refreshDocument();
|
|
1224
1473
|
};
|
|
1474
|
+
var clearNavigationFailure = () => {
|
|
1475
|
+
document.getElementById(NAVIGATION_ERROR_ID)?.remove();
|
|
1476
|
+
};
|
|
1477
|
+
var renderNavigationFailure = (message, retry) => {
|
|
1478
|
+
clearNavigationFailure();
|
|
1479
|
+
const error = document.createElement("aside");
|
|
1480
|
+
error.id = NAVIGATION_ERROR_ID;
|
|
1481
|
+
error.dataset.absoluteMobileNavigationError = "";
|
|
1482
|
+
error.setAttribute("role", "alert");
|
|
1483
|
+
error.style.cssText = "position:fixed;z-index:2147483646;inset:auto max(1rem,var(--absolute-safe-area-inset-right,0px)) max(1rem,var(--absolute-safe-area-inset-bottom,0px)) max(1rem,var(--absolute-safe-area-inset-left,0px));display:flex;align-items:center;justify-content:space-between;gap:1rem;box-sizing:border-box;padding:.75rem 1rem;border:1px solid color-mix(in srgb,CanvasText 24%,transparent);border-radius:.75rem;background:Canvas;color:CanvasText;box-shadow:0 .5rem 2rem color-mix(in srgb,CanvasText 20%,transparent)";
|
|
1484
|
+
const text = document.createElement("span");
|
|
1485
|
+
text.textContent = message;
|
|
1486
|
+
const button = document.createElement("button");
|
|
1487
|
+
button.type = "button";
|
|
1488
|
+
button.textContent = "Retry";
|
|
1489
|
+
button.addEventListener("click", retry, { once: true });
|
|
1490
|
+
error.append(text, button);
|
|
1491
|
+
document.body.append(error);
|
|
1492
|
+
};
|
|
1493
|
+
var markNavigationPending = (pending) => {
|
|
1494
|
+
if (pending) {
|
|
1495
|
+
document.body.dataset.absoluteMobileNavigationPending = "";
|
|
1496
|
+
document.body.setAttribute("aria-busy", "true");
|
|
1497
|
+
} else {
|
|
1498
|
+
delete document.body.dataset.absoluteMobileNavigationPending;
|
|
1499
|
+
document.body.removeAttribute("aria-busy");
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1225
1502
|
var renderPageTarget = () => {
|
|
1226
1503
|
document.title = "AbsoluteJS";
|
|
1227
1504
|
document.head.innerHTML = '<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">';
|
|
@@ -1247,13 +1524,16 @@ var installLocalPageStyle = (manifest, pageId, contract) => {
|
|
|
1247
1524
|
link.dataset.absoluteMobilePageStyle = page.bundleHash;
|
|
1248
1525
|
document.head.appendChild(link);
|
|
1249
1526
|
};
|
|
1250
|
-
var
|
|
1251
|
-
|
|
1527
|
+
var waitForDocumentPaint = () => new Promise((resolve) => {
|
|
1528
|
+
if (typeof requestAnimationFrame !== "function") {
|
|
1529
|
+
setTimeout(resolve, 0);
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
1533
|
+
});
|
|
1534
|
+
var commitNavigation = async (manifest, envelope, hadActivePage) => {
|
|
1252
1535
|
if (!hadActivePage)
|
|
1253
1536
|
renderStatus("Loading…");
|
|
1254
|
-
const envelope = await fetchAbsoluteMobilePage(manifest, path, {
|
|
1255
|
-
...fetchImpl ? { fetch: fetchImpl } : {}
|
|
1256
|
-
});
|
|
1257
1537
|
let activation;
|
|
1258
1538
|
const commit = async () => {
|
|
1259
1539
|
await disposeAbsoluteMobilePage();
|
|
@@ -1285,14 +1565,12 @@ var navigate = async (manifest, path, historyMode, fetchImpl) => {
|
|
|
1285
1565
|
throw new TypeError("The mobile page did not produce an activation.");
|
|
1286
1566
|
if (activation.kind === "upgrade-required") {
|
|
1287
1567
|
renderStatus("This app version must be updated to continue.", "update");
|
|
1288
|
-
return;
|
|
1568
|
+
return false;
|
|
1289
1569
|
}
|
|
1570
|
+
await waitForDocumentPaint();
|
|
1290
1571
|
document.body.dataset.absoluteMobilePageActive = "";
|
|
1291
1572
|
adaptiveShell?.refreshDocument();
|
|
1292
|
-
|
|
1293
|
-
pushNavigationHistory(path);
|
|
1294
|
-
else if (historyMode === "replace")
|
|
1295
|
-
replaceNavigationHistory(path);
|
|
1573
|
+
return true;
|
|
1296
1574
|
};
|
|
1297
1575
|
var openExternalLink = (anchor, event) => {
|
|
1298
1576
|
if (Reflect.get(globalThis, "__absoluteExpoBridge"))
|
|
@@ -1305,7 +1583,7 @@ var openExternalLink = (anchor, event) => {
|
|
|
1305
1583
|
Browser.open({ url: url.href }).catch(() => location.assign(url.href));
|
|
1306
1584
|
} catch {}
|
|
1307
1585
|
};
|
|
1308
|
-
var installAnchorNavigation = (manifest, onNavigate) => {
|
|
1586
|
+
var installAnchorNavigation = (manifest, onNavigate, onBack) => {
|
|
1309
1587
|
const handleClick = (event) => {
|
|
1310
1588
|
if (!(event.target instanceof Element))
|
|
1311
1589
|
return;
|
|
@@ -1315,7 +1593,7 @@ var installAnchorNavigation = (manifest, onNavigate) => {
|
|
|
1315
1593
|
const intent = readAbsoluteMobileLinkIntent(anchor);
|
|
1316
1594
|
if (intent.kind === "back") {
|
|
1317
1595
|
event.preventDefault();
|
|
1318
|
-
if (!uiPrimitives?.requestBack())
|
|
1596
|
+
if (!uiPrimitives?.requestBack() && !onBack())
|
|
1319
1597
|
history.back();
|
|
1320
1598
|
return;
|
|
1321
1599
|
}
|
|
@@ -1355,21 +1633,6 @@ var installDeepLinks = async (manifest, onNavigate, authRedirectUri) => {
|
|
|
1355
1633
|
} catch {}
|
|
1356
1634
|
return listener;
|
|
1357
1635
|
};
|
|
1358
|
-
var navigateWithFailureState = async (manifest, path, historyMode, direction, from, reinstallBrowserNavigation, fetchImpl) => {
|
|
1359
|
-
let completed = false;
|
|
1360
|
-
try {
|
|
1361
|
-
document.documentElement.dataset.absoluteNavigationDirection = direction;
|
|
1362
|
-
await navigate(manifest, path, historyMode, fetchImpl);
|
|
1363
|
-
completed = true;
|
|
1364
|
-
} catch (error) {
|
|
1365
|
-
console.error("[Absolute Mobile] Navigation failed:", error);
|
|
1366
|
-
renderStatus(document.documentElement.dataset.absoluteNetwork === "offline" ? "You are offline. Reconnect to load this page." : "Unable to load this page. Check your connection and retry.", "error");
|
|
1367
|
-
} finally {
|
|
1368
|
-
reinstallBrowserNavigation();
|
|
1369
|
-
}
|
|
1370
|
-
if (completed)
|
|
1371
|
-
uiPrimitives?.navigate({ direction, from, to: path });
|
|
1372
|
-
};
|
|
1373
1636
|
var startAbsoluteMobileShell = async (options = {}) => {
|
|
1374
1637
|
notifyCapacitorSystemBarsDomReady();
|
|
1375
1638
|
await adaptiveShell?.dispose();
|
|
@@ -1378,6 +1641,11 @@ var startAbsoluteMobileShell = async (options = {}) => {
|
|
|
1378
1641
|
uiPrimitives = installAbsoluteMobileUiPrimitives();
|
|
1379
1642
|
const manifest = await readManifest();
|
|
1380
1643
|
let activePath = initialNavigationPath(manifest.entry);
|
|
1644
|
+
let activeEntry = readAbsoluteMobileHistoryEntry(history.state) ?? createAbsoluteMobileHistoryEntry(activePath, 0);
|
|
1645
|
+
if (activeEntry.path !== activePath) {
|
|
1646
|
+
activeEntry = createAbsoluteMobileHistoryEntry(activePath, 0);
|
|
1647
|
+
}
|
|
1648
|
+
writeNavigationHistory(activeEntry, "replace");
|
|
1381
1649
|
const auth = manifest.auth && options.createAuth ? await options.createAuth(manifest.auth, {
|
|
1382
1650
|
beforeSignOut: options.beforeSignOut
|
|
1383
1651
|
}) : undefined;
|
|
@@ -1387,15 +1655,104 @@ var startAbsoluteMobileShell = async (options = {}) => {
|
|
|
1387
1655
|
options.connectPush?.(auth);
|
|
1388
1656
|
if (auth && manifest.sync?.socketTickets)
|
|
1389
1657
|
options.installSync?.(auth, manifest.sync);
|
|
1658
|
+
const snapshots = new Map;
|
|
1659
|
+
const targetEntries = new WeakMap;
|
|
1660
|
+
let suppressNextPop = false;
|
|
1661
|
+
let hasActivePage = false;
|
|
1390
1662
|
let removeAnchorNavigation = () => {
|
|
1391
1663
|
return;
|
|
1392
1664
|
};
|
|
1393
|
-
const
|
|
1665
|
+
const coordinator = createAbsoluteMobileNavigationCoordinator({
|
|
1666
|
+
commit: async (envelope, request) => {
|
|
1667
|
+
if (hasActivePage) {
|
|
1668
|
+
snapshots.set(activeEntry.entryId, captureAbsoluteMobileDocumentState());
|
|
1669
|
+
}
|
|
1670
|
+
document.documentElement.dataset.absoluteNavigationDirection = request.direction;
|
|
1671
|
+
try {
|
|
1672
|
+
const previouslyActive = hasActivePage;
|
|
1673
|
+
hasActivePage = false;
|
|
1674
|
+
hasActivePage = await commitNavigation(manifest, envelope, previouslyActive);
|
|
1675
|
+
} finally {
|
|
1676
|
+
reinstallBrowserNavigation();
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
load: (request, signal) => fetchAbsoluteMobilePage(manifest, request.path, {
|
|
1680
|
+
fetch: applicationFetch,
|
|
1681
|
+
signal
|
|
1682
|
+
}),
|
|
1683
|
+
onFailure: (error, phase, request) => {
|
|
1684
|
+
console.error("[Absolute Mobile] Navigation failed:", error);
|
|
1685
|
+
markNavigationPending(false);
|
|
1686
|
+
const targetEntry = targetEntries.get(request);
|
|
1687
|
+
if (targetEntry && targetEntry.index !== activeEntry.index) {
|
|
1688
|
+
suppressNextPop = true;
|
|
1689
|
+
history.go(activeEntry.index - targetEntry.index);
|
|
1690
|
+
}
|
|
1691
|
+
const message = document.documentElement.dataset.absoluteNetwork === "offline" ? "You are offline. Reconnect to load this page." : "Unable to load this page. Check your connection and retry.";
|
|
1692
|
+
if (phase === "load" && hasActivePage) {
|
|
1693
|
+
renderNavigationFailure(message, () => {
|
|
1694
|
+
if (targetEntry) {
|
|
1695
|
+
history.go(targetEntry.index - activeEntry.index);
|
|
1696
|
+
} else
|
|
1697
|
+
coordinator.navigate(request);
|
|
1698
|
+
});
|
|
1699
|
+
} else
|
|
1700
|
+
renderStatus(message, "error");
|
|
1701
|
+
},
|
|
1702
|
+
onStart: () => {
|
|
1703
|
+
clearNavigationFailure();
|
|
1704
|
+
markNavigationPending(true);
|
|
1705
|
+
},
|
|
1706
|
+
onSuccess: (request) => {
|
|
1707
|
+
let nextEntry = targetEntries.get(request);
|
|
1708
|
+
if (request.historyMode === "push") {
|
|
1709
|
+
nextEntry = createAbsoluteMobileHistoryEntry(request.path, activeEntry.index + 1);
|
|
1710
|
+
writeNavigationHistory(nextEntry, "push");
|
|
1711
|
+
} else if (request.historyMode === "replace") {
|
|
1712
|
+
nextEntry = createAbsoluteMobileHistoryEntry(request.path, activeEntry.index);
|
|
1713
|
+
writeNavigationHistory(nextEntry, "replace");
|
|
1714
|
+
}
|
|
1715
|
+
nextEntry ??= activeEntry;
|
|
1716
|
+
activeEntry = nextEntry;
|
|
1717
|
+
activePath = request.path;
|
|
1718
|
+
markNavigationPending(false);
|
|
1719
|
+
clearNavigationFailure();
|
|
1720
|
+
const snapshot = snapshots.get(activeEntry.entryId);
|
|
1721
|
+
if (snapshot)
|
|
1722
|
+
restoreAbsoluteMobileDocumentState(snapshot);
|
|
1723
|
+
else if (request.historyMode !== "none")
|
|
1724
|
+
resetAbsoluteMobileDocumentState();
|
|
1725
|
+
uiPrimitives?.navigate({
|
|
1726
|
+
direction: request.direction,
|
|
1727
|
+
from: request.from,
|
|
1728
|
+
to: request.path
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
const cancelPendingNavigation = () => {
|
|
1733
|
+
if (!coordinator.cancelPending())
|
|
1734
|
+
return false;
|
|
1735
|
+
markNavigationPending(false);
|
|
1736
|
+
clearNavigationFailure();
|
|
1737
|
+
return true;
|
|
1738
|
+
};
|
|
1739
|
+
const handlePopState = (event) => {
|
|
1394
1740
|
const path = currentNavigationPath();
|
|
1395
|
-
|
|
1396
|
-
|
|
1741
|
+
if (suppressNextPop) {
|
|
1742
|
+
suppressNextPop = false;
|
|
1743
|
+
notifyExpoNavigationPath(activePath);
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
const targetEntry = readAbsoluteMobileHistoryEntry(event.state) ?? createAbsoluteMobileHistoryEntry(path, activeEntry.index - 1);
|
|
1397
1747
|
notifyExpoNavigationPath(path);
|
|
1398
|
-
|
|
1748
|
+
const request = {
|
|
1749
|
+
direction: targetEntry.index > activeEntry.index ? "forward" : "back",
|
|
1750
|
+
from: activePath,
|
|
1751
|
+
historyMode: "none",
|
|
1752
|
+
path
|
|
1753
|
+
};
|
|
1754
|
+
targetEntries.set(request, targetEntry);
|
|
1755
|
+
coordinator.navigate(request);
|
|
1399
1756
|
};
|
|
1400
1757
|
const reinstallBrowserNavigation = () => {
|
|
1401
1758
|
removeAnchorNavigation();
|
|
@@ -1403,20 +1760,33 @@ var startAbsoluteMobileShell = async (options = {}) => {
|
|
|
1403
1760
|
uiPrimitives?.dispose();
|
|
1404
1761
|
uiPrimitives = installAbsoluteMobileUiPrimitives();
|
|
1405
1762
|
uiPrimitives.refreshDocument(activePath);
|
|
1406
|
-
removeAnchorNavigation = installAnchorNavigation(manifest, onNavigate);
|
|
1763
|
+
removeAnchorNavigation = installAnchorNavigation(manifest, onNavigate, cancelPendingNavigation);
|
|
1407
1764
|
addEventListener("popstate", handlePopState);
|
|
1408
1765
|
};
|
|
1409
1766
|
const onNavigate = (path, direction = "forward", replace = false) => {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1767
|
+
if (path === activePath && !replace && hasActivePage)
|
|
1768
|
+
return;
|
|
1769
|
+
coordinator.navigate({
|
|
1770
|
+
direction,
|
|
1771
|
+
from: activePath,
|
|
1772
|
+
historyMode: replace ? "replace" : "push",
|
|
1773
|
+
path
|
|
1774
|
+
});
|
|
1413
1775
|
};
|
|
1414
|
-
|
|
1776
|
+
reinstallBrowserNavigation();
|
|
1777
|
+
await coordinator.navigate({
|
|
1778
|
+
direction: "replace",
|
|
1779
|
+
from: activePath,
|
|
1780
|
+
historyMode: "none",
|
|
1781
|
+
path: activePath
|
|
1782
|
+
});
|
|
1415
1783
|
await installDeepLinks(manifest, onNavigate, auth?.redirectUri);
|
|
1416
1784
|
try {
|
|
1417
1785
|
await App.addListener("backButton", ({ canGoBack }) => {
|
|
1418
1786
|
if (uiPrimitives?.requestBack())
|
|
1419
1787
|
return;
|
|
1788
|
+
if (cancelPendingNavigation())
|
|
1789
|
+
return;
|
|
1420
1790
|
if (canGoBack)
|
|
1421
1791
|
history.back();
|
|
1422
1792
|
else
|
package/dist/mobile/shellSync.js
CHANGED
|
@@ -107,17 +107,28 @@ var installAbsoluteMobileShellSync = (auth, config, options = {}) => {
|
|
|
107
107
|
const reportSchemaState = options.reportSchemaState ?? reportShellSchemaState;
|
|
108
108
|
const configureBackground = options.configureBackground ?? configureCapacitorBackgroundSync;
|
|
109
109
|
const clearBackground = options.clearBackground ?? (() => AbsoluteBackgroundSync.clear());
|
|
110
|
+
let schemaReady = Promise.resolve(true);
|
|
110
111
|
if (store?.getSchemaStatus) {
|
|
111
112
|
reportSchemaState({ state: "preparing" });
|
|
112
|
-
store.getSchemaStatus().then(
|
|
113
|
+
schemaReady = store.getSchemaStatus().then((state) => {
|
|
114
|
+
reportSchemaState(state);
|
|
115
|
+
return true;
|
|
116
|
+
}).catch((error) => {
|
|
117
|
+
reportSchemaState(schemaFailureState(error));
|
|
118
|
+
return false;
|
|
119
|
+
});
|
|
113
120
|
}
|
|
114
121
|
if (namespace && config) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
schemaReady.then((ready) => {
|
|
123
|
+
if (!ready)
|
|
124
|
+
return;
|
|
125
|
+
return configureBackground({
|
|
126
|
+
clientId: auth.clientId,
|
|
127
|
+
endpoint: config.background.endpoint,
|
|
128
|
+
intervalMinutes: config.background.intervalMinutes,
|
|
129
|
+
issuer: auth.issuer,
|
|
130
|
+
namespace
|
|
131
|
+
});
|
|
121
132
|
}).catch((error) => console.error("[Absolute Mobile] Background Sync configuration failed:", error));
|
|
122
133
|
} else {
|
|
123
134
|
clearBackground().catch(() => {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type AbsoluteMobileNavigationHistoryMode = 'none' | 'push' | 'replace';
|
|
2
|
+
export type AbsoluteMobileNavigationRequest = {
|
|
3
|
+
direction: 'back' | 'forward' | 'replace';
|
|
4
|
+
from: string;
|
|
5
|
+
historyMode: AbsoluteMobileNavigationHistoryMode;
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
export type AbsoluteMobileNavigationFailurePhase = 'commit' | 'load';
|
|
9
|
+
export type AbsoluteMobileNavigationResult = {
|
|
10
|
+
kind: 'cancelled';
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'committed';
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'failed';
|
|
15
|
+
error: unknown;
|
|
16
|
+
phase: AbsoluteMobileNavigationFailurePhase;
|
|
17
|
+
};
|
|
18
|
+
export type AbsoluteMobileNavigationCoordinatorOptions<Payload> = {
|
|
19
|
+
commit: (payload: Payload, request: AbsoluteMobileNavigationRequest) => Promise<void>;
|
|
20
|
+
load: (request: AbsoluteMobileNavigationRequest, signal: AbortSignal) => Promise<Payload>;
|
|
21
|
+
onCommit?: (request: AbsoluteMobileNavigationRequest) => void;
|
|
22
|
+
onFailure?: (error: unknown, phase: AbsoluteMobileNavigationFailurePhase, request: AbsoluteMobileNavigationRequest) => void;
|
|
23
|
+
onStart?: (request: AbsoluteMobileNavigationRequest) => void;
|
|
24
|
+
onSuccess?: (request: AbsoluteMobileNavigationRequest) => void;
|
|
25
|
+
};
|
|
26
|
+
export type AbsoluteMobileNavigationCoordinator = {
|
|
27
|
+
cancelPending(): boolean;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
navigate(request: AbsoluteMobileNavigationRequest): Promise<AbsoluteMobileNavigationResult>;
|
|
30
|
+
phase(): 'committing' | 'idle' | 'loading' | 'queued';
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Coordinates native-shell route loads. Loads may overlap, but only the latest
|
|
34
|
+
* completed load may enter the serialized document commit boundary.
|
|
35
|
+
*/
|
|
36
|
+
export declare const createAbsoluteMobileNavigationCoordinator: <Payload>(options: AbsoluteMobileNavigationCoordinatorOptions<Payload>) => AbsoluteMobileNavigationCoordinator;
|