@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.
@@ -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(reportSchemaState).catch((error) => reportSchemaState(schemaFailureState(error)));
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
- configureBackground({
116
- clientId: auth.clientId,
117
- endpoint: config.background.endpoint,
118
- intervalMinutes: config.background.intervalMinutes,
119
- issuer: auth.issuer,
120
- namespace
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,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
+ export {
259
+ closeAbsoluteMobileSheet,
260
+ installAbsoluteMobileUiPrimitives,
261
+ openAbsoluteMobileSheet,
262
+ readAbsoluteMobileLinkIntent,
263
+ requestAbsoluteMobileBack
264
+ };
@@ -1,3 +1,4 @@
1
1
  export * from './client';
2
2
  export * from './pageProtocol';
3
3
  export * from './syncRemediation';
4
+ export * from './uiPrimitives';
@@ -39,3 +39,4 @@ export * from './routeMetadataTransform';
39
39
  export * from './routeMatcher';
40
40
  export * from './shellHttp';
41
41
  export * from './transport';
42
+ export * from './uiPrimitives';
@@ -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;
@@ -0,0 +1,53 @@
1
+ export type AbsoluteMobileHistoryEntry = {
2
+ absoluteMobile: true;
3
+ entryId: string;
4
+ index: number;
5
+ path: string;
6
+ };
7
+ type ElementLocator = {
8
+ kind: 'id';
9
+ value: string;
10
+ } | {
11
+ kind: 'index';
12
+ index: number;
13
+ } | {
14
+ kind: 'name';
15
+ index: number;
16
+ value: string;
17
+ };
18
+ type ControlSnapshot = {
19
+ checked?: boolean;
20
+ locator: ElementLocator;
21
+ open?: boolean;
22
+ selectionEnd?: number;
23
+ selectionStart?: number;
24
+ tag: string;
25
+ value?: string;
26
+ values?: string[];
27
+ };
28
+ type ScrollSnapshot = {
29
+ left: number;
30
+ locator: ElementLocator;
31
+ top: number;
32
+ };
33
+ export type AbsoluteMobileDocumentSnapshot = {
34
+ controls: ControlSnapshot[];
35
+ focus?: ElementLocator;
36
+ scroll: ScrollSnapshot[];
37
+ window: {
38
+ x: number;
39
+ y: number;
40
+ };
41
+ };
42
+ export declare const createAbsoluteMobileHistoryEntry: (path: string, index: number, entryId?: string) => {
43
+ absoluteMobile: true;
44
+ entryId: string;
45
+ index: number;
46
+ path: string;
47
+ };
48
+ export declare const readAbsoluteMobileHistoryEntry: (value: unknown) => AbsoluteMobileHistoryEntry | undefined;
49
+ /** Captures route-local state in memory. Sensitive credential fields are omitted. */
50
+ export declare const captureAbsoluteMobileDocumentState: () => AbsoluteMobileDocumentSnapshot;
51
+ export declare const restoreAbsoluteMobileDocumentState: (snapshot: AbsoluteMobileDocumentSnapshot) => void;
52
+ export declare const resetAbsoluteMobileDocumentState: () => void;
53
+ export {};
@@ -34,6 +34,7 @@ export type AbsoluteMobileClientManifest = {
34
34
  export type AbsoluteMobileTransportOptions = {
35
35
  fetch?: AbsoluteMobileFetch;
36
36
  headers?: HeadersInit;
37
+ signal?: AbortSignal;
37
38
  };
38
39
  export declare const createAbsoluteMobilePageRequest: (manifest: AbsoluteMobileClientManifest, path: string, options?: AbsoluteMobileTransportOptions) => Request;
39
40
  export declare const fetchAbsoluteMobilePage: (manifest: AbsoluteMobileClientManifest, path: string, options?: AbsoluteMobileTransportOptions) => Promise<unknown>;
@@ -0,0 +1,37 @@
1
+ export type AbsoluteMobileNavigationDirection = 'back' | 'forward' | 'replace';
2
+ export type AbsoluteMobileNavigationDetail = {
3
+ direction: AbsoluteMobileNavigationDirection;
4
+ from: string;
5
+ to: string;
6
+ };
7
+ export type AbsoluteMobileSheetDetail = {
8
+ id: string;
9
+ open: boolean;
10
+ };
11
+ export type AbsoluteMobileLinkIntent = {
12
+ kind: 'back';
13
+ } | {
14
+ kind: 'external';
15
+ } | {
16
+ kind: 'navigate';
17
+ replace: boolean;
18
+ };
19
+ declare global {
20
+ interface WindowEventMap {
21
+ 'absolute:back-request': CustomEvent;
22
+ 'absolute:navigation-change': CustomEvent<AbsoluteMobileNavigationDetail>;
23
+ 'absolute:sheet-change': CustomEvent<AbsoluteMobileSheetDetail>;
24
+ }
25
+ }
26
+ export type AbsoluteMobileUiPrimitives = {
27
+ dispose(): void;
28
+ navigate(detail: AbsoluteMobileNavigationDetail): void;
29
+ refreshDocument(path?: string): void;
30
+ requestBack(): boolean;
31
+ };
32
+ export declare const closeAbsoluteMobileSheet: (target: HTMLDialogElement | string) => boolean;
33
+ export declare const openAbsoluteMobileSheet: (target: HTMLDialogElement | string, opener?: HTMLElement) => boolean;
34
+ export declare const readAbsoluteMobileLinkIntent: (anchor: HTMLAnchorElement) => AbsoluteMobileLinkIntent;
35
+ export declare const requestAbsoluteMobileBack: () => boolean;
36
+ /** Installs optional semantic-HTML mobile layout and interaction primitives. */
37
+ export declare const installAbsoluteMobileUiPrimitives: () => AbsoluteMobileUiPrimitives;
package/package.json CHANGED
@@ -152,6 +152,10 @@
152
152
  "import": "./dist/mobile/index.js",
153
153
  "types": "./dist/src/mobile/index.d.ts"
154
154
  },
155
+ "./mobile/ui": {
156
+ "import": "./dist/mobile/uiPrimitives.js",
157
+ "types": "./dist/src/mobile/uiPrimitives.d.ts"
158
+ },
155
159
  "./react": {
156
160
  "browser": "./dist/react/browser.js",
157
161
  "import": "./dist/react/index.js",
@@ -289,12 +293,12 @@
289
293
  "main": "./dist/index.js",
290
294
  "name": "@absolutejs/absolute",
291
295
  "optionalDependencies": {
292
- "@absolutejs/native-darwin-arm64": "0.20.0-beta.57",
293
- "@absolutejs/native-darwin-x64": "0.20.0-beta.57",
294
- "@absolutejs/native-linux-arm64": "0.20.0-beta.57",
295
- "@absolutejs/native-linux-x64": "0.20.0-beta.57",
296
- "@absolutejs/native-windows-arm64": "0.20.0-beta.57",
297
- "@absolutejs/native-windows-x64": "0.20.0-beta.57"
296
+ "@absolutejs/native-darwin-arm64": "0.20.0-beta.59",
297
+ "@absolutejs/native-darwin-x64": "0.20.0-beta.59",
298
+ "@absolutejs/native-linux-arm64": "0.20.0-beta.59",
299
+ "@absolutejs/native-linux-x64": "0.20.0-beta.59",
300
+ "@absolutejs/native-windows-arm64": "0.20.0-beta.59",
301
+ "@absolutejs/native-windows-x64": "0.20.0-beta.59"
298
302
  },
299
303
  "overrides": {
300
304
  "@sinclair/typebox": "0.34.52",
@@ -510,7 +514,7 @@
510
514
  ]
511
515
  }
512
516
  },
513
- "version": "0.20.0-beta.57",
517
+ "version": "0.20.0-beta.59",
514
518
  "workspaces": [
515
519
  "tests/fixtures/*",
516
520
  "tests/fixtures/_packages/*"