@absolutejs/absolute 0.20.0-beta.57 → 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 +50 -7
- package/dist/index.js +5 -4
- package/dist/index.js.map +3 -3
- package/dist/mobile/browser.js +264 -2
- package/dist/mobile/browser.js.map +5 -4
- package/dist/mobile/index.js +284 -6
- package/dist/mobile/index.js.map +9 -8
- package/dist/mobile/remoteMacAgentEntry.js +13 -3
- package/dist/mobile/shellBootstrap.js +763 -54
- package/dist/mobile/shellSync.js +18 -7
- package/dist/mobile/uiPrimitives.js +264 -0
- package/dist/src/mobile/browser.d.ts +1 -0
- package/dist/src/mobile/index.d.ts +1 -0
- 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/dist/src/mobile/uiPrimitives.d.ts +37 -0
- package/package.json +11 -7
|
@@ -1,5 +1,264 @@
|
|
|
1
|
+
// src/mobile/uiPrimitives.ts
|
|
2
|
+
var STYLE_ID = "absolute-mobile-ui-primitives";
|
|
3
|
+
var NAVIGATION_EVENT = "absolute:navigation-change";
|
|
4
|
+
var SHEET_EVENT = "absolute:sheet-change";
|
|
5
|
+
var BACK_EVENT = "absolute:back-request";
|
|
6
|
+
var ACTIVE_SHEET_SELECTOR = "dialog[data-absolute-sheet][open]";
|
|
7
|
+
var TAB_LINK_SELECTOR = "[data-absolute-tab-bar] a[href]";
|
|
8
|
+
var UI_STYLE = `
|
|
9
|
+
[data-absolute-app-shell] {
|
|
10
|
+
display: grid;
|
|
11
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
min-height: var(--absolute-available-height, 100dvh);
|
|
14
|
+
padding-inline: var(--absolute-safe-area-inset-left, 0px) var(--absolute-safe-area-inset-right, 0px);
|
|
15
|
+
}
|
|
16
|
+
[data-absolute-app-header] {
|
|
17
|
+
padding-top: var(--absolute-safe-area-inset-top, 0px);
|
|
18
|
+
}
|
|
19
|
+
[data-absolute-app-main], [data-absolute-navigation-stack] {
|
|
20
|
+
min-width: 0;
|
|
21
|
+
min-height: 0;
|
|
22
|
+
}
|
|
23
|
+
[data-absolute-app-main] {
|
|
24
|
+
overflow: auto;
|
|
25
|
+
overscroll-behavior-y: contain;
|
|
26
|
+
}
|
|
27
|
+
[data-absolute-navigation-stack] {
|
|
28
|
+
view-transition-name: absolute-mobile-stack;
|
|
29
|
+
}
|
|
30
|
+
[data-absolute-tab-bar] {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: stretch;
|
|
33
|
+
justify-content: space-around;
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
padding-bottom: var(--absolute-safe-area-inset-bottom, 0px);
|
|
36
|
+
background: Canvas;
|
|
37
|
+
color: CanvasText;
|
|
38
|
+
}
|
|
39
|
+
[data-absolute-tab-bar] > a {
|
|
40
|
+
display: grid;
|
|
41
|
+
flex: 1 1 0;
|
|
42
|
+
min-width: 0;
|
|
43
|
+
min-height: 2.75rem;
|
|
44
|
+
place-items: center;
|
|
45
|
+
padding: 0.375rem 0.5rem;
|
|
46
|
+
color: inherit;
|
|
47
|
+
text-align: center;
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
touch-action: manipulation;
|
|
50
|
+
}
|
|
51
|
+
[data-absolute-tab-bar] > a[aria-current="page"] {
|
|
52
|
+
font-weight: 700;
|
|
53
|
+
}
|
|
54
|
+
dialog[data-absolute-sheet] {
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
width: min(100%, var(--absolute-sheet-max-width, 42rem));
|
|
57
|
+
max-height: min(90dvh, var(--absolute-available-height, 90dvh));
|
|
58
|
+
margin: auto auto 0;
|
|
59
|
+
padding: 1rem 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));
|
|
60
|
+
overflow: auto;
|
|
61
|
+
border: 1px solid color-mix(in srgb, CanvasText 18%, transparent);
|
|
62
|
+
border-radius: 1rem 1rem 0 0;
|
|
63
|
+
background: Canvas;
|
|
64
|
+
color: CanvasText;
|
|
65
|
+
box-shadow: 0 -0.5rem 2rem color-mix(in srgb, CanvasText 18%, transparent);
|
|
66
|
+
}
|
|
67
|
+
dialog[data-absolute-sheet]::backdrop {
|
|
68
|
+
background: color-mix(in srgb, CanvasText 38%, transparent);
|
|
69
|
+
}
|
|
70
|
+
@keyframes absolute-mobile-forward-old { to { opacity: 0; transform: translateX(-12%); } }
|
|
71
|
+
@keyframes absolute-mobile-forward-new { from { opacity: 0; transform: translateX(12%); } }
|
|
72
|
+
@keyframes absolute-mobile-back-old { to { opacity: 0; transform: translateX(12%); } }
|
|
73
|
+
@keyframes absolute-mobile-back-new { from { opacity: 0; transform: translateX(-12%); } }
|
|
74
|
+
html[data-absolute-navigation-direction="forward"]::view-transition-old(absolute-mobile-stack) { animation: 180ms ease both absolute-mobile-forward-old; }
|
|
75
|
+
html[data-absolute-navigation-direction="forward"]::view-transition-new(absolute-mobile-stack) { animation: 180ms ease both absolute-mobile-forward-new; }
|
|
76
|
+
html[data-absolute-navigation-direction="back"]::view-transition-old(absolute-mobile-stack) { animation: 180ms ease both absolute-mobile-back-old; }
|
|
77
|
+
html[data-absolute-navigation-direction="back"]::view-transition-new(absolute-mobile-stack) { animation: 180ms ease both absolute-mobile-back-new; }
|
|
78
|
+
html[data-absolute-reduced-motion="reduce"]::view-transition-old(absolute-mobile-stack),
|
|
79
|
+
html[data-absolute-reduced-motion="reduce"]::view-transition-new(absolute-mobile-stack) { animation: none; }
|
|
80
|
+
`;
|
|
81
|
+
var currentPath = () => `${location.pathname}${location.search}${location.hash}`;
|
|
82
|
+
var ensureStyle = () => {
|
|
83
|
+
let style = document.getElementById(STYLE_ID);
|
|
84
|
+
if (!(style instanceof HTMLStyleElement)) {
|
|
85
|
+
style = document.createElement("style");
|
|
86
|
+
style.id = STYLE_ID;
|
|
87
|
+
style.textContent = UI_STYLE;
|
|
88
|
+
document.head.append(style);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var sheetById = (id) => {
|
|
92
|
+
const target = document.getElementById(id);
|
|
93
|
+
return target instanceof HTMLDialogElement && target.dataset.absoluteSheet !== undefined ? target : undefined;
|
|
94
|
+
};
|
|
95
|
+
var dispatchSheet = (sheet, open) => dispatchEvent(new CustomEvent(SHEET_EVENT, {
|
|
96
|
+
detail: { id: sheet.id, open }
|
|
97
|
+
}));
|
|
98
|
+
var focusSheet = (sheet) => {
|
|
99
|
+
const target = sheet.querySelector('[autofocus], button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
100
|
+
(target ?? sheet).focus();
|
|
101
|
+
};
|
|
102
|
+
var sheetOpeners = new WeakMap;
|
|
103
|
+
var closeAbsoluteMobileSheet = (target) => {
|
|
104
|
+
const sheet = typeof target === "string" ? sheetById(target) : target;
|
|
105
|
+
if (!sheet || !sheet.open)
|
|
106
|
+
return false;
|
|
107
|
+
if (typeof sheet.close === "function")
|
|
108
|
+
sheet.close();
|
|
109
|
+
else
|
|
110
|
+
sheet.removeAttribute("open");
|
|
111
|
+
sheet.removeAttribute("aria-modal");
|
|
112
|
+
sheetOpeners.get(sheet)?.focus();
|
|
113
|
+
sheetOpeners.delete(sheet);
|
|
114
|
+
dispatchSheet(sheet, false);
|
|
115
|
+
return true;
|
|
116
|
+
};
|
|
117
|
+
var openAbsoluteMobileSheet = (target, opener) => {
|
|
118
|
+
const sheet = typeof target === "string" ? sheetById(target) : target;
|
|
119
|
+
if (!sheet || sheet.dataset.absoluteSheet === undefined)
|
|
120
|
+
return false;
|
|
121
|
+
const active = document.querySelector(ACTIVE_SHEET_SELECTOR);
|
|
122
|
+
if (active && active !== sheet)
|
|
123
|
+
closeAbsoluteMobileSheet(active);
|
|
124
|
+
if (opener)
|
|
125
|
+
sheetOpeners.set(sheet, opener);
|
|
126
|
+
if (!sheet.open) {
|
|
127
|
+
if (typeof sheet.showModal === "function")
|
|
128
|
+
sheet.showModal();
|
|
129
|
+
else
|
|
130
|
+
sheet.setAttribute("open", "");
|
|
131
|
+
}
|
|
132
|
+
sheet.setAttribute("aria-modal", "true");
|
|
133
|
+
focusSheet(sheet);
|
|
134
|
+
dispatchSheet(sheet, true);
|
|
135
|
+
return true;
|
|
136
|
+
};
|
|
137
|
+
var readAbsoluteMobileLinkIntent = (anchor) => {
|
|
138
|
+
const mode = anchor.dataset.absoluteLink;
|
|
139
|
+
if (mode === "back")
|
|
140
|
+
return { kind: "back" };
|
|
141
|
+
if (mode === "external")
|
|
142
|
+
return { kind: "external" };
|
|
143
|
+
return { kind: "navigate", replace: mode === "replace" };
|
|
144
|
+
};
|
|
145
|
+
var requestAbsoluteMobileBack = () => {
|
|
146
|
+
const event = new CustomEvent(BACK_EVENT, { cancelable: true });
|
|
147
|
+
return !dispatchEvent(event);
|
|
148
|
+
};
|
|
149
|
+
var normalizePathname = (value) => {
|
|
150
|
+
try {
|
|
151
|
+
return new URL(value, location.href).pathname.replace(/\/$/u, "") || "/";
|
|
152
|
+
} catch {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
var syncTabLinks = (path) => {
|
|
157
|
+
const activePath = normalizePathname(path);
|
|
158
|
+
if (!activePath)
|
|
159
|
+
return;
|
|
160
|
+
document.querySelectorAll(TAB_LINK_SELECTOR).forEach((anchor) => {
|
|
161
|
+
const candidate = normalizePathname(anchor.href);
|
|
162
|
+
const prefix = anchor.dataset.absoluteTabMatch === "prefix";
|
|
163
|
+
const active = candidate !== undefined && (prefix ? activePath === candidate || candidate !== "/" && activePath.startsWith(`${candidate}/`) : activePath === candidate);
|
|
164
|
+
if (active)
|
|
165
|
+
anchor.setAttribute("aria-current", "page");
|
|
166
|
+
else
|
|
167
|
+
anchor.removeAttribute("aria-current");
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
var installAbsoluteMobileUiPrimitives = () => {
|
|
171
|
+
let disposed = false;
|
|
172
|
+
let scheduled = false;
|
|
173
|
+
let path = currentPath();
|
|
174
|
+
const refreshDocument = (nextPath = path) => {
|
|
175
|
+
if (disposed || !document.head || !document.body)
|
|
176
|
+
return;
|
|
177
|
+
path = nextPath;
|
|
178
|
+
ensureStyle();
|
|
179
|
+
syncTabLinks(path);
|
|
180
|
+
};
|
|
181
|
+
const scheduleRefresh = () => {
|
|
182
|
+
if (scheduled || disposed)
|
|
183
|
+
return;
|
|
184
|
+
scheduled = true;
|
|
185
|
+
queueMicrotask(() => {
|
|
186
|
+
scheduled = false;
|
|
187
|
+
refreshDocument();
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const handleClick = (event) => {
|
|
191
|
+
if (!(event.target instanceof Element))
|
|
192
|
+
return;
|
|
193
|
+
const opener = event.target.closest("[data-absolute-sheet-open]");
|
|
194
|
+
if (opener?.dataset.absoluteSheetOpen) {
|
|
195
|
+
if (openAbsoluteMobileSheet(opener.dataset.absoluteSheetOpen, opener))
|
|
196
|
+
event.preventDefault();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const closer = event.target.closest("[data-absolute-sheet-close]");
|
|
200
|
+
const sheet = closer?.closest("dialog[data-absolute-sheet]");
|
|
201
|
+
if (sheet && closeAbsoluteMobileSheet(sheet)) {
|
|
202
|
+
event.preventDefault();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (event.target instanceof HTMLDialogElement && event.target.dataset.absoluteSheet !== undefined) {
|
|
206
|
+
const rect = event.target.getBoundingClientRect();
|
|
207
|
+
const outside = event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom;
|
|
208
|
+
if (outside && closeAbsoluteMobileSheet(event.target))
|
|
209
|
+
event.preventDefault();
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const handleCancel = (event) => {
|
|
213
|
+
if (!(event.target instanceof HTMLDialogElement))
|
|
214
|
+
return;
|
|
215
|
+
if (event.target.dataset.absoluteSheet === undefined)
|
|
216
|
+
return;
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
closeAbsoluteMobileSheet(event.target);
|
|
219
|
+
};
|
|
220
|
+
const handleBack = (event) => {
|
|
221
|
+
const active = document.querySelector(ACTIVE_SHEET_SELECTOR);
|
|
222
|
+
if (!active)
|
|
223
|
+
return;
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
closeAbsoluteMobileSheet(active);
|
|
226
|
+
};
|
|
227
|
+
const observer = new MutationObserver(scheduleRefresh);
|
|
228
|
+
observer.observe(document.documentElement, {
|
|
229
|
+
childList: true,
|
|
230
|
+
subtree: true
|
|
231
|
+
});
|
|
232
|
+
addEventListener("click", handleClick);
|
|
233
|
+
addEventListener("cancel", handleCancel, true);
|
|
234
|
+
addEventListener(BACK_EVENT, handleBack);
|
|
235
|
+
refreshDocument();
|
|
236
|
+
return {
|
|
237
|
+
refreshDocument,
|
|
238
|
+
requestBack: requestAbsoluteMobileBack,
|
|
239
|
+
dispose: () => {
|
|
240
|
+
if (disposed)
|
|
241
|
+
return;
|
|
242
|
+
disposed = true;
|
|
243
|
+
observer.disconnect();
|
|
244
|
+
removeEventListener("click", handleClick);
|
|
245
|
+
removeEventListener("cancel", handleCancel, true);
|
|
246
|
+
removeEventListener(BACK_EVENT, handleBack);
|
|
247
|
+
},
|
|
248
|
+
navigate: (detail) => {
|
|
249
|
+
path = detail.to;
|
|
250
|
+
document.documentElement.dataset.absoluteNavigationDirection = detail.direction;
|
|
251
|
+
refreshDocument(path);
|
|
252
|
+
dispatchEvent(new CustomEvent(NAVIGATION_EVENT, {
|
|
253
|
+
detail
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
|
|
1
259
|
// src/mobile/shellBootstrap.ts
|
|
2
260
|
import { App } from "@capacitor/app";
|
|
261
|
+
import { Browser } from "@capacitor/browser";
|
|
3
262
|
|
|
4
263
|
// src/mobile/producerContextState.ts
|
|
5
264
|
var ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY = Symbol.for("absolutejs.mobileProducerAsyncLocalStorage");
|
|
@@ -199,7 +458,11 @@ var createAbsoluteMobilePageRequest = (manifest, path, options = {}) => {
|
|
|
199
458
|
headers.set(MOBILE_PAGE_REQUEST_HEADERS.pageId, page.pageId);
|
|
200
459
|
headers.set(MOBILE_PAGE_REQUEST_HEADERS.protocol, String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION));
|
|
201
460
|
headers.set(MOBILE_PAGE_REQUEST_HEADERS.runtime, manifest.runtime);
|
|
202
|
-
return new Request(url, {
|
|
461
|
+
return new Request(url, {
|
|
462
|
+
headers,
|
|
463
|
+
method: "GET",
|
|
464
|
+
...options.signal ? { signal: options.signal } : {}
|
|
465
|
+
});
|
|
203
466
|
};
|
|
204
467
|
var fetchAbsoluteMobilePage = async (manifest, path, options = {}) => {
|
|
205
468
|
const request = createAbsoluteMobilePageRequest(manifest, path, options);
|
|
@@ -643,7 +906,7 @@ import {
|
|
|
643
906
|
platform,
|
|
644
907
|
systemBars
|
|
645
908
|
} from "@absolutejs/devices";
|
|
646
|
-
var
|
|
909
|
+
var STYLE_ID2 = "absolute-mobile-adaptive-shell";
|
|
647
910
|
var ANNOUNCER_ID = "absolute-mobile-announcer";
|
|
648
911
|
var HOST_METRICS_EVENT = "absolute:native-host-metrics";
|
|
649
912
|
var STATUS_STYLE = `
|
|
@@ -691,11 +954,11 @@ var viewportSize = () => ({
|
|
|
691
954
|
width: finitePixels(globalThis.visualViewport?.width ?? innerWidth)
|
|
692
955
|
});
|
|
693
956
|
var setPixels = (name, value) => document.documentElement.style.setProperty(name, `${finitePixels(value)}px`);
|
|
694
|
-
var
|
|
695
|
-
let style = document.getElementById(
|
|
957
|
+
var ensureStyle2 = () => {
|
|
958
|
+
let style = document.getElementById(STYLE_ID2);
|
|
696
959
|
if (!(style instanceof HTMLStyleElement)) {
|
|
697
960
|
style = document.createElement("style");
|
|
698
|
-
style.id =
|
|
961
|
+
style.id = STYLE_ID2;
|
|
699
962
|
style.textContent = STATUS_STYLE;
|
|
700
963
|
document.head.append(style);
|
|
701
964
|
}
|
|
@@ -773,7 +1036,7 @@ var installAbsoluteMobileAdaptiveShell = async (devices = defaultDevices) => {
|
|
|
773
1036
|
if (disposed || !document.head || !document.body)
|
|
774
1037
|
return;
|
|
775
1038
|
ensureViewport();
|
|
776
|
-
|
|
1039
|
+
ensureStyle2();
|
|
777
1040
|
const root = document.documentElement;
|
|
778
1041
|
const current = state();
|
|
779
1042
|
root.dataset.absoluteMobile = "";
|
|
@@ -884,9 +1147,268 @@ var installAbsoluteMobileAdaptiveShell = async (devices = defaultDevices) => {
|
|
|
884
1147
|
};
|
|
885
1148
|
};
|
|
886
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
|
+
|
|
887
1408
|
// src/mobile/shellBootstrap.ts
|
|
888
1409
|
var MANIFEST_PATH = "./absolute-mobile-manifest.json";
|
|
889
1410
|
var STATUS_ID = "absolute-mobile-status";
|
|
1411
|
+
var NAVIGATION_ERROR_ID = "absolute-mobile-navigation-error";
|
|
890
1412
|
var initialNavigationPath = (entry) => {
|
|
891
1413
|
const expoPath = new URLSearchParams(location.search).get("absolutePath");
|
|
892
1414
|
if (expoPath?.startsWith("/") && !expoPath.startsWith("//"))
|
|
@@ -898,20 +1420,19 @@ var currentNavigationPath = () => {
|
|
|
898
1420
|
const expoPath = new URLSearchParams(location.search).get("absolutePath");
|
|
899
1421
|
return expoPath?.startsWith("/") && !expoPath.startsWith("//") ? expoPath : `${location.pathname}${location.search}${location.hash}`;
|
|
900
1422
|
};
|
|
901
|
-
var
|
|
1423
|
+
var historyUrl = (path) => {
|
|
902
1424
|
if (!Reflect.get(globalThis, "__absoluteExpoBridge")) {
|
|
903
|
-
|
|
904
|
-
return;
|
|
1425
|
+
return path;
|
|
905
1426
|
}
|
|
906
1427
|
const url = new URL(location.href);
|
|
907
1428
|
url.search = "";
|
|
908
1429
|
url.hash = "";
|
|
909
1430
|
url.searchParams.set("absolutePath", path);
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1431
|
+
return url.href;
|
|
1432
|
+
};
|
|
1433
|
+
var writeNavigationHistory = (entry, mode) => {
|
|
1434
|
+
history[mode === "push" ? "pushState" : "replaceState"](entry, "", historyUrl(entry.path));
|
|
1435
|
+
notifyExpoNavigationPath(entry.path);
|
|
915
1436
|
};
|
|
916
1437
|
var notifyExpoNavigationPath = (path) => {
|
|
917
1438
|
const bridge = Reflect.get(globalThis, "__absoluteExpoBridge");
|
|
@@ -921,6 +1442,7 @@ var notifyExpoNavigationPath = (path) => {
|
|
|
921
1442
|
};
|
|
922
1443
|
var navigationGeneration = 0;
|
|
923
1444
|
var adaptiveShell;
|
|
1445
|
+
var uiPrimitives;
|
|
924
1446
|
var notifyCapacitorSystemBarsDomReady = () => {
|
|
925
1447
|
const provider = Reflect.get(globalThis, "CapacitorSystemBarsAndroidInterface");
|
|
926
1448
|
if (typeof provider !== "object" || provider === null)
|
|
@@ -949,6 +1471,34 @@ var renderStatus = (message, kind = "loading") => {
|
|
|
949
1471
|
status.textContent = message;
|
|
950
1472
|
adaptiveShell?.refreshDocument();
|
|
951
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
|
+
};
|
|
952
1502
|
var renderPageTarget = () => {
|
|
953
1503
|
document.title = "AbsoluteJS";
|
|
954
1504
|
document.head.innerHTML = '<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">';
|
|
@@ -974,47 +1524,89 @@ var installLocalPageStyle = (manifest, pageId, contract) => {
|
|
|
974
1524
|
link.dataset.absoluteMobilePageStyle = page.bundleHash;
|
|
975
1525
|
document.head.appendChild(link);
|
|
976
1526
|
};
|
|
977
|
-
var
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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) => {
|
|
1535
|
+
if (!hadActivePage)
|
|
1536
|
+
renderStatus("Loading…");
|
|
1537
|
+
let activation;
|
|
1538
|
+
const commit = async () => {
|
|
1539
|
+
await disposeAbsoluteMobilePage();
|
|
1540
|
+
activation = await activateAbsoluteMobilePage(envelope, {
|
|
1541
|
+
loadPage: ({ contract, pageId }) => {
|
|
1542
|
+
const page = manifest.pages.find((candidate) => candidate.pageId === pageId && candidate.contract === contract);
|
|
1543
|
+
if (!page) {
|
|
1544
|
+
throw new TypeError(`The embedded app does not contain ${pageId} contract ${contract}.`);
|
|
1545
|
+
}
|
|
1546
|
+
if (page.framework === "html" || page.framework === "htmx") {
|
|
1547
|
+
return installAbsoluteMobileStaticDocument(manifest, page, localBundleUrl(manifest, pageId, contract));
|
|
1548
|
+
}
|
|
1549
|
+
renderPageTarget();
|
|
1550
|
+
installLocalPageStyle(manifest, pageId, contract);
|
|
1551
|
+
return import(localBundleUrl(manifest, pageId, contract));
|
|
991
1552
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
const transitionDocument = document;
|
|
1556
|
+
if (hadActivePage && document.documentElement.dataset.absoluteReducedMotion !== "reduce" && transitionDocument.startViewTransition) {
|
|
1557
|
+
const transition = transitionDocument.startViewTransition(commit);
|
|
1558
|
+
await transition.updateCallbackDone;
|
|
1559
|
+
await transition.finished.catch(() => {
|
|
1560
|
+
return;
|
|
1561
|
+
});
|
|
1562
|
+
} else
|
|
1563
|
+
await commit();
|
|
1564
|
+
if (!activation)
|
|
1565
|
+
throw new TypeError("The mobile page did not produce an activation.");
|
|
997
1566
|
if (activation.kind === "upgrade-required") {
|
|
998
1567
|
renderStatus("This app version must be updated to continue.", "update");
|
|
999
|
-
return;
|
|
1568
|
+
return false;
|
|
1000
1569
|
}
|
|
1570
|
+
await waitForDocumentPaint();
|
|
1571
|
+
document.body.dataset.absoluteMobilePageActive = "";
|
|
1001
1572
|
adaptiveShell?.refreshDocument();
|
|
1002
|
-
|
|
1003
|
-
pushNavigationHistory(path);
|
|
1573
|
+
return true;
|
|
1004
1574
|
};
|
|
1005
|
-
var
|
|
1575
|
+
var openExternalLink = (anchor, event) => {
|
|
1576
|
+
if (Reflect.get(globalThis, "__absoluteExpoBridge"))
|
|
1577
|
+
return;
|
|
1578
|
+
try {
|
|
1579
|
+
const url = new URL(anchor.href);
|
|
1580
|
+
if (url.username || url.password || url.protocol !== "http:" && url.protocol !== "https:")
|
|
1581
|
+
return;
|
|
1582
|
+
event.preventDefault();
|
|
1583
|
+
Browser.open({ url: url.href }).catch(() => location.assign(url.href));
|
|
1584
|
+
} catch {}
|
|
1585
|
+
};
|
|
1586
|
+
var installAnchorNavigation = (manifest, onNavigate, onBack) => {
|
|
1006
1587
|
const handleClick = (event) => {
|
|
1007
1588
|
if (!(event.target instanceof Element))
|
|
1008
1589
|
return;
|
|
1009
1590
|
const anchor = event.target.closest("a[href]");
|
|
1010
1591
|
if (!(anchor instanceof HTMLAnchorElement) || anchor.target)
|
|
1011
1592
|
return;
|
|
1593
|
+
const intent = readAbsoluteMobileLinkIntent(anchor);
|
|
1594
|
+
if (intent.kind === "back") {
|
|
1595
|
+
event.preventDefault();
|
|
1596
|
+
if (!uiPrimitives?.requestBack() && !onBack())
|
|
1597
|
+
history.back();
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (intent.kind === "external") {
|
|
1601
|
+
openExternalLink(anchor, event);
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1012
1604
|
try {
|
|
1013
1605
|
const path = resolveAbsoluteMobileNavigation(manifest, anchor.href, location.origin);
|
|
1014
1606
|
if (!path)
|
|
1015
1607
|
return;
|
|
1016
1608
|
event.preventDefault();
|
|
1017
|
-
onNavigate(path);
|
|
1609
|
+
onNavigate(path, intent.kind === "navigate" && intent.replace ? "replace" : "forward", intent.kind === "navigate" && intent.replace);
|
|
1018
1610
|
} catch {}
|
|
1019
1611
|
};
|
|
1020
1612
|
addEventListener("click", handleClick);
|
|
@@ -1041,21 +1633,19 @@ var installDeepLinks = async (manifest, onNavigate, authRedirectUri) => {
|
|
|
1041
1633
|
} catch {}
|
|
1042
1634
|
return listener;
|
|
1043
1635
|
};
|
|
1044
|
-
var navigateWithFailureState = async (manifest, path, pushHistory, reinstallBrowserNavigation, fetchImpl) => {
|
|
1045
|
-
try {
|
|
1046
|
-
await navigate(manifest, path, pushHistory, fetchImpl);
|
|
1047
|
-
} catch (error) {
|
|
1048
|
-
console.error("[Absolute Mobile] Navigation failed:", error);
|
|
1049
|
-
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");
|
|
1050
|
-
} finally {
|
|
1051
|
-
reinstallBrowserNavigation();
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
1636
|
var startAbsoluteMobileShell = async (options = {}) => {
|
|
1055
1637
|
notifyCapacitorSystemBarsDomReady();
|
|
1056
1638
|
await adaptiveShell?.dispose();
|
|
1639
|
+
uiPrimitives?.dispose();
|
|
1057
1640
|
adaptiveShell = await installAbsoluteMobileAdaptiveShell();
|
|
1641
|
+
uiPrimitives = installAbsoluteMobileUiPrimitives();
|
|
1058
1642
|
const manifest = await readManifest();
|
|
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");
|
|
1059
1649
|
const auth = manifest.auth && options.createAuth ? await options.createAuth(manifest.auth, {
|
|
1060
1650
|
beforeSignOut: options.beforeSignOut
|
|
1061
1651
|
}) : undefined;
|
|
@@ -1065,25 +1655,144 @@ var startAbsoluteMobileShell = async (options = {}) => {
|
|
|
1065
1655
|
options.connectPush?.(auth);
|
|
1066
1656
|
if (auth && manifest.sync?.socketTickets)
|
|
1067
1657
|
options.installSync?.(auth, manifest.sync);
|
|
1658
|
+
const snapshots = new Map;
|
|
1659
|
+
const targetEntries = new WeakMap;
|
|
1660
|
+
let suppressNextPop = false;
|
|
1661
|
+
let hasActivePage = false;
|
|
1068
1662
|
let removeAnchorNavigation = () => {
|
|
1069
1663
|
return;
|
|
1070
1664
|
};
|
|
1071
|
-
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) => {
|
|
1072
1740
|
const path = currentNavigationPath();
|
|
1741
|
+
if (suppressNextPop) {
|
|
1742
|
+
suppressNextPop = false;
|
|
1743
|
+
notifyExpoNavigationPath(activePath);
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
const targetEntry = readAbsoluteMobileHistoryEntry(event.state) ?? createAbsoluteMobileHistoryEntry(path, activeEntry.index - 1);
|
|
1073
1747
|
notifyExpoNavigationPath(path);
|
|
1074
|
-
|
|
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);
|
|
1075
1756
|
};
|
|
1076
1757
|
const reinstallBrowserNavigation = () => {
|
|
1077
1758
|
removeAnchorNavigation();
|
|
1078
1759
|
removeEventListener("popstate", handlePopState);
|
|
1079
|
-
|
|
1760
|
+
uiPrimitives?.dispose();
|
|
1761
|
+
uiPrimitives = installAbsoluteMobileUiPrimitives();
|
|
1762
|
+
uiPrimitives.refreshDocument(activePath);
|
|
1763
|
+
removeAnchorNavigation = installAnchorNavigation(manifest, onNavigate, cancelPendingNavigation);
|
|
1080
1764
|
addEventListener("popstate", handlePopState);
|
|
1081
1765
|
};
|
|
1082
|
-
const onNavigate = (path) => {
|
|
1083
|
-
|
|
1766
|
+
const onNavigate = (path, direction = "forward", replace = false) => {
|
|
1767
|
+
if (path === activePath && !replace && hasActivePage)
|
|
1768
|
+
return;
|
|
1769
|
+
coordinator.navigate({
|
|
1770
|
+
direction,
|
|
1771
|
+
from: activePath,
|
|
1772
|
+
historyMode: replace ? "replace" : "push",
|
|
1773
|
+
path
|
|
1774
|
+
});
|
|
1084
1775
|
};
|
|
1085
|
-
|
|
1776
|
+
reinstallBrowserNavigation();
|
|
1777
|
+
await coordinator.navigate({
|
|
1778
|
+
direction: "replace",
|
|
1779
|
+
from: activePath,
|
|
1780
|
+
historyMode: "none",
|
|
1781
|
+
path: activePath
|
|
1782
|
+
});
|
|
1086
1783
|
await installDeepLinks(manifest, onNavigate, auth?.redirectUri);
|
|
1784
|
+
try {
|
|
1785
|
+
await App.addListener("backButton", ({ canGoBack }) => {
|
|
1786
|
+
if (uiPrimitives?.requestBack())
|
|
1787
|
+
return;
|
|
1788
|
+
if (cancelPendingNavigation())
|
|
1789
|
+
return;
|
|
1790
|
+
if (canGoBack)
|
|
1791
|
+
history.back();
|
|
1792
|
+
else
|
|
1793
|
+
App.exitApp();
|
|
1794
|
+
});
|
|
1795
|
+
} catch {}
|
|
1087
1796
|
};
|
|
1088
1797
|
export {
|
|
1089
1798
|
startAbsoluteMobileShell
|