@multiplatform.one/router 6.1.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/cjs/hooks/useUrlState.cjs +30 -9
- package/dist/cjs/hooks/useUrlState.native.js +32 -9
- package/dist/cjs/hooks/useUrlState.native.js.map +1 -1
- package/dist/cjs/seam/DomNavigationHost.cjs +122 -0
- package/dist/cjs/seam/DomNavigationHost.native.js +186 -0
- package/dist/cjs/seam/DomNavigationHost.native.js.map +1 -0
- package/dist/cjs/seam/index.cjs +47 -0
- package/dist/cjs/seam/index.native.js +50 -0
- package/dist/cjs/seam/index.native.js.map +1 -0
- package/dist/cjs/seam/navigator.cjs +309 -0
- package/dist/cjs/seam/navigator.native.js +479 -0
- package/dist/cjs/seam/navigator.native.js.map +1 -0
- package/dist/cjs/seam/oneAdapter.cjs +128 -0
- package/dist/cjs/seam/oneAdapter.native.js +141 -0
- package/dist/cjs/seam/oneAdapter.native.js.map +1 -0
- package/dist/esm/hooks/useUrlState.mjs +31 -10
- package/dist/esm/hooks/useUrlState.mjs.map +1 -1
- package/dist/esm/hooks/useUrlState.native.js +33 -10
- package/dist/esm/hooks/useUrlState.native.js.map +1 -1
- package/dist/esm/seam/DomNavigationHost.mjs +96 -0
- package/dist/esm/seam/DomNavigationHost.mjs.map +1 -0
- package/dist/esm/seam/DomNavigationHost.native.js +157 -0
- package/dist/esm/seam/DomNavigationHost.native.js.map +1 -0
- package/dist/esm/seam/index.mjs +5 -0
- package/dist/esm/seam/index.mjs.map +1 -0
- package/dist/esm/seam/index.native.js +5 -0
- package/dist/esm/seam/index.native.js.map +1 -0
- package/dist/esm/seam/navigator.mjs +279 -0
- package/dist/esm/seam/navigator.mjs.map +1 -0
- package/dist/esm/seam/navigator.native.js +446 -0
- package/dist/esm/seam/navigator.native.js.map +1 -0
- package/dist/esm/seam/oneAdapter.mjs +90 -0
- package/dist/esm/seam/oneAdapter.mjs.map +1 -0
- package/dist/esm/seam/oneAdapter.native.js +100 -0
- package/dist/esm/seam/oneAdapter.native.js.map +1 -0
- package/dist/jsx/hooks/useUrlState.mjs +31 -10
- package/dist/jsx/hooks/useUrlState.mjs.map +1 -1
- package/dist/jsx/hooks/useUrlState.native.js +32 -9
- package/dist/jsx/hooks/useUrlState.native.js.map +1 -1
- package/dist/jsx/seam/DomNavigationHost.mjs +96 -0
- package/dist/jsx/seam/DomNavigationHost.mjs.map +1 -0
- package/dist/jsx/seam/DomNavigationHost.native.js +186 -0
- package/dist/jsx/seam/DomNavigationHost.native.js.map +1 -0
- package/dist/jsx/seam/index.mjs +5 -0
- package/dist/jsx/seam/index.mjs.map +1 -0
- package/dist/jsx/seam/index.native.js +50 -0
- package/dist/jsx/seam/index.native.js.map +1 -0
- package/dist/jsx/seam/navigator.mjs +279 -0
- package/dist/jsx/seam/navigator.mjs.map +1 -0
- package/dist/jsx/seam/navigator.native.js +479 -0
- package/dist/jsx/seam/navigator.native.js.map +1 -0
- package/dist/jsx/seam/oneAdapter.mjs +90 -0
- package/dist/jsx/seam/oneAdapter.mjs.map +1 -0
- package/dist/jsx/seam/oneAdapter.native.js +141 -0
- package/dist/jsx/seam/oneAdapter.native.js.map +1 -0
- package/package.json +26 -9
- package/src/hooks/urlStateEdges.spec.ts +98 -0
- package/src/hooks/useUrlState.ts +34 -8
- package/src/seam/DomNavigationHost.spec.tsx +78 -0
- package/src/seam/DomNavigationHost.tsx +150 -0
- package/src/seam/index.ts +41 -0
- package/src/seam/navigator.spec.ts +189 -0
- package/src/seam/navigator.ts +405 -0
- package/src/seam/oneAdapter.tsx +166 -0
- package/types/hooks/index.d.ts +2 -0
- package/types/hooks/index.d.ts.map +1 -0
- package/types/hooks/useUrlState.d.ts +36 -0
- package/types/hooks/useUrlState.d.ts.map +1 -0
- package/types/index.d.ts +4 -0
- package/types/index.d.ts.map +1 -0
- package/types/index.storybook.d.ts +4 -0
- package/types/index.storybook.d.ts.map +1 -0
- package/types/router/EmptyRouter.d.ts +7 -0
- package/types/router/EmptyRouter.d.ts.map +1 -0
- package/types/router/ReactRouter.d.ts +6 -0
- package/types/router/ReactRouter.d.ts.map +1 -0
- package/types/router/index.d.ts +2 -0
- package/types/router/index.d.ts.map +1 -0
- package/types/router/index.storybook-expo.d.ts +2 -0
- package/types/router/index.storybook-expo.d.ts.map +1 -0
- package/types/router/index.storybook.d.ts +2 -0
- package/types/router/index.storybook.d.ts.map +1 -0
- package/types/router/index.webext.d.ts +2 -0
- package/types/router/index.webext.d.ts.map +1 -0
- package/types/seam/DomNavigationHost.d.ts +33 -0
- package/types/seam/DomNavigationHost.d.ts.map +1 -0
- package/types/seam/index.d.ts +7 -0
- package/types/seam/index.d.ts.map +1 -0
- package/types/seam/navigator.d.ts +133 -0
- package/types/seam/navigator.d.ts.map +1 -0
- package/types/seam/oneAdapter.d.ts +57 -0
- package/types/seam/oneAdapter.d.ts.map +1 -0
- package/types/types.d.ts +40 -0
- package/types/types.d.ts.map +1 -0
- package/dist/cjs/hooks/useUrlState.spec.cjs +0 -280
- package/dist/cjs/hooks/useUrlState.spec.native.js +0 -315
- package/dist/cjs/hooks/useUrlState.spec.native.js.map +0 -1
- package/dist/esm/hooks/useUrlState.spec.mjs +0 -257
- package/dist/esm/hooks/useUrlState.spec.mjs.map +0 -1
- package/dist/esm/hooks/useUrlState.spec.native.js +0 -289
- package/dist/esm/hooks/useUrlState.spec.native.js.map +0 -1
- package/dist/jsx/hooks/useUrlState.spec.mjs +0 -257
- package/dist/jsx/hooks/useUrlState.spec.mjs.map +0 -1
- package/dist/jsx/hooks/useUrlState.spec.native.js +0 -315
- package/dist/jsx/hooks/useUrlState.spec.native.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all) __defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: true
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: () => from[key],
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
23
|
+
value: true
|
|
24
|
+
}), mod);
|
|
25
|
+
var seam_exports = {};
|
|
26
|
+
__export(seam_exports, {
|
|
27
|
+
DomNavigationHost: () => import_DomNavigationHost.DomNavigationHost,
|
|
28
|
+
DomStackAdapter: () => import_DomNavigationHost.DomStackAdapter,
|
|
29
|
+
Link: () => import_oneAdapter.Link,
|
|
30
|
+
LoadProgressBar: () => import_oneAdapter.LoadProgressBar,
|
|
31
|
+
RouteTagContext: () => import_navigator.RouteTagContext,
|
|
32
|
+
Slot: () => import_oneAdapter.Slot,
|
|
33
|
+
StackNavigator: () => import_navigator.StackNavigator,
|
|
34
|
+
hrefToString: () => import_navigator.hrefToString,
|
|
35
|
+
stackNavigator: () => import_navigator.stackNavigator,
|
|
36
|
+
useBlocker: () => import_oneAdapter.useBlocker,
|
|
37
|
+
useLoader: () => import_oneAdapter.useLoader,
|
|
38
|
+
useLocalSearchParams: () => import_oneAdapter.useLocalSearchParams,
|
|
39
|
+
useMatches: () => import_oneAdapter.useMatches,
|
|
40
|
+
useNavState: () => import_navigator.useNavState,
|
|
41
|
+
useParams: () => import_oneAdapter.useParams,
|
|
42
|
+
usePathname: () => import_oneAdapter.usePathname,
|
|
43
|
+
useRouteParams: () => import_navigator.useRouteParams,
|
|
44
|
+
useRouter: () => import_oneAdapter.useRouter
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(seam_exports);
|
|
47
|
+
var import_navigator = require("./navigator.native.js");
|
|
48
|
+
var import_DomNavigationHost = require("./DomNavigationHost.native.js");
|
|
49
|
+
var import_oneAdapter = require("./oneAdapter.native.js");
|
|
50
|
+
//# sourceMappingURL=index.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["__toCommonJS","mod","__copyProps","__defProp","value","seam_exports","__export","DomNavigationHost","import_DomNavigationHost","DomStackAdapter","Link","import_oneAdapter","LoadProgressBar","RouteTagContext","import_navigator","Slot","StackNavigator","hrefToString","stackNavigator","useBlocker","useLoader","useLocalSearchParams","useMatches","useNavState","useParams","usePathname","useRouteParams","useRouter","module","exports","require"],"sources":["../../../src/seam/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,GAAA,IAAAC,WAAA,CAAAC,SAAA;EAAAC,KAAA;AAAA,IAAAH,GAAA;AAAA,IAAAI,YAAA;AAAAC,QAAA,CAAAD,YAAA;EAAAE,iBAAA,EAAAA,CAAA,KAAAC,wBAAA,CAAAD,iBAAA;EAAAE,eAAA,EAAAA,CAAA,KAAAD,wBAAA,CAAAC,eAAA;EAAAC,IAAA,EAAAA,CAAA,KAAAC,iBAAA,CAAAD,IAAA;EAAAE,eAAA,EAAAA,CAAA,KAAAD,iBAAA,CAAAC,eAAA;EAAAC,eAAA,EAAAA,CAAA,KAAAC,gBAAA,CAAAD,eAAA;EAAAE,IAAA,EAAAA,CAAA,KAAAJ,iBAAA,CAAAI,IAAA;EAAAC,cAAA,EAAAA,CAAA,KAAAF,gBAAA,CAAAE,cAAA;EAAAC,YAAA,EAAAA,CAAA,KAAAH,gBAAA,CAAAG,YAAA;EAAAC,cAAA,EAAAA,CAAA,KAAAJ,gBAAA,CAAAI,cAAA;EAAAC,UAAA,EAAAA,CAAA,KAAAR,iBAAA,CAAAQ,UAAA;EAAAC,SAAA,EAAAA,CAAA,KAAAT,iBAAA,CAAAS,SAAA;EAAAC,oBAAA,EAAAA,CAAA,KAAAV,iBAAA,CAAAU,oBAAA;EAAAC,UAAA,EAAAA,CAAA,KAAAX,iBAAA,CAAAW,UAAA;EAAAC,WAAA,EAAAA,CAAA,KAAAT,gBAAA,CAAAS,WAAA;EAAAC,SAAA,EAAAA,CAAA,KAAAb,iBAAA,CAAAa,SAAA;EAAAC,WAAA,EAAAA,CAAA,KAAAd,iBAAA,CAAAc,WAAA;EAAAC,cAAA,EAAAA,CAAA,KAAAZ,gBAAA,CAAAY,cAAA;EAAAC,SAAA,EAAAA,CAAA,KAAAhB,iBAAA,CAAAgB;AAAA;AASAC,MAAA,CAAAC,OAAA,GAAA7B,YAOO,CAAAK,YAAA;AAUP,IAAAS,gBAAA,GAAAgB,OAAA,CAAmD;AAEnD,IAAAtB,wBAWO,GAAAsB,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all) __defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: () => from[key],
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
21
|
+
value: true
|
|
22
|
+
}), mod);
|
|
23
|
+
var navigator_exports = {};
|
|
24
|
+
__export(navigator_exports, {
|
|
25
|
+
RouteTagContext: () => RouteTagContext,
|
|
26
|
+
StackNavigator: () => StackNavigator,
|
|
27
|
+
hrefToString: () => hrefToString,
|
|
28
|
+
stackNavigator: () => stackNavigator,
|
|
29
|
+
useNavState: () => useNavState,
|
|
30
|
+
useRouteParams: () => useRouteParams
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(navigator_exports);
|
|
33
|
+
var import_react = require("react");
|
|
34
|
+
function compileRoute(def) {
|
|
35
|
+
const segments = def.pattern.split("/").filter(Boolean).map(seg => {
|
|
36
|
+
const m = seg.match(/^\[(\.{3})?(.+)\]$/);
|
|
37
|
+
return m ? {
|
|
38
|
+
param: m[2]
|
|
39
|
+
} : {
|
|
40
|
+
literal: seg
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
...def,
|
|
45
|
+
segments
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function parseQuery(query) {
|
|
49
|
+
const params = {};
|
|
50
|
+
if (!query) return params;
|
|
51
|
+
for (const pair of query.split("&")) {
|
|
52
|
+
if (!pair) continue;
|
|
53
|
+
const eq = pair.indexOf("=");
|
|
54
|
+
const key = decodeURIComponent(eq === -1 ? pair : pair.slice(0, eq));
|
|
55
|
+
const value = eq === -1 ? "" : decodeURIComponent(pair.slice(eq + 1));
|
|
56
|
+
const prev = params[key];
|
|
57
|
+
if (prev === void 0) params[key] = value;else if (Array.isArray(prev)) prev.push(value);else params[key] = [prev, value];
|
|
58
|
+
}
|
|
59
|
+
return params;
|
|
60
|
+
}
|
|
61
|
+
function hrefToString(href) {
|
|
62
|
+
if (typeof href === "string") return href;
|
|
63
|
+
let path = href.pathname;
|
|
64
|
+
const leftover = [];
|
|
65
|
+
for (const [key, raw] of Object.entries(href.params ?? {})) {
|
|
66
|
+
const value = String(raw);
|
|
67
|
+
const token = `[${key}]`;
|
|
68
|
+
if (path.includes(token)) path = path.replace(token, encodeURIComponent(value));else leftover.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
69
|
+
}
|
|
70
|
+
return leftover.length ? `${path}?${leftover.join("&")}` : path;
|
|
71
|
+
}
|
|
72
|
+
const EMPTY_PARAMS = Object.freeze({});
|
|
73
|
+
class StackNavigator {
|
|
74
|
+
routes = [];
|
|
75
|
+
host = null;
|
|
76
|
+
listeners = /* @__PURE__ */new Set();
|
|
77
|
+
/** Stack mirror rebuilt from the host on every pushed/popped/replaced
|
|
78
|
+
* signal. */
|
|
79
|
+
stackTags = [];
|
|
80
|
+
paramsByTag = /* @__PURE__ */new Map();
|
|
81
|
+
pathnameByTag = /* @__PURE__ */new Map();
|
|
82
|
+
/** Tags whose page content has been navigated to at least once. The host
|
|
83
|
+
* gates each page's React content on this (lazy mounting) — a tag enters
|
|
84
|
+
* the set right before its first host push and never leaves (backgrounded
|
|
85
|
+
* pages stay alive, same as One keeping stack screens mounted). */
|
|
86
|
+
visitedTags = /* @__PURE__ */new Set();
|
|
87
|
+
/** Route-loader results, per tag (the loader bridge's store). */
|
|
88
|
+
loaderDataByTag = /* @__PURE__ */new Map();
|
|
89
|
+
/** Monotonic navigation sequence — a loader that resolves after a NEWER
|
|
90
|
+
* navigation started must not perform its (stale) host action. */
|
|
91
|
+
navSeq = 0;
|
|
92
|
+
generation = 0;
|
|
93
|
+
snapshot = {
|
|
94
|
+
pathname: "/",
|
|
95
|
+
tag: "",
|
|
96
|
+
depth: 0,
|
|
97
|
+
canGoBack: false,
|
|
98
|
+
generation: 0
|
|
99
|
+
};
|
|
100
|
+
get isConfigured() {
|
|
101
|
+
return this.routes.length > 0;
|
|
102
|
+
}
|
|
103
|
+
/** Register the route manifest. The FIRST route is the root page. */
|
|
104
|
+
configure(defs) {
|
|
105
|
+
this.routes = defs.map(compileRoute);
|
|
106
|
+
this.visitedTags = /* @__PURE__ */new Set();
|
|
107
|
+
this.loaderDataByTag = /* @__PURE__ */new Map();
|
|
108
|
+
const root = defs[0];
|
|
109
|
+
if (root) {
|
|
110
|
+
this.stackTags = [root.tag];
|
|
111
|
+
this.visitedTags.add(root.tag);
|
|
112
|
+
this.pathnameByTag.set(root.tag, root.pattern);
|
|
113
|
+
this.rebuildSnapshot();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Has this route's page been navigated to (⇒ its content may mount)? */
|
|
117
|
+
isVisited(tag) {
|
|
118
|
+
return tag !== null && this.visitedTags.has(tag);
|
|
119
|
+
}
|
|
120
|
+
/** The route's last loader result (stable identity until the loader runs
|
|
121
|
+
* again). undefined when the route has no loader / hasn't resolved. */
|
|
122
|
+
loaderSnapshot(tag) {
|
|
123
|
+
if (!tag) return void 0;
|
|
124
|
+
return this.loaderDataByTag.get(tag);
|
|
125
|
+
}
|
|
126
|
+
/** Resolve a route's loader WITHOUT navigating. For the initial page:
|
|
127
|
+
* entries can `await stackNavigator.preload("/")` during top-level await
|
|
128
|
+
* (where GJS reliably drains promise jobs) so even the root page's first
|
|
129
|
+
* paint has loader data. */
|
|
130
|
+
async preload(href) {
|
|
131
|
+
const hit = this.match(href);
|
|
132
|
+
if (!hit?.route.loader) return;
|
|
133
|
+
try {
|
|
134
|
+
const data = await hit.route.loader({
|
|
135
|
+
path: hit.pathname,
|
|
136
|
+
params: hit.params
|
|
137
|
+
});
|
|
138
|
+
this.loaderDataByTag.set(hit.route.tag, data);
|
|
139
|
+
this.rebuildSnapshot();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(`[stack nav] preload loader for ${hit.route.tag} failed: ${String(err)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Bind the live host stack (an Adw.NavigationView widget on GTK, a
|
|
145
|
+
* DomStackAdapter on DOM targets). */
|
|
146
|
+
attach(host) {
|
|
147
|
+
this.host = host;
|
|
148
|
+
this.syncFromHost();
|
|
149
|
+
}
|
|
150
|
+
detach() {
|
|
151
|
+
this.host = null;
|
|
152
|
+
}
|
|
153
|
+
subscribe = fn => {
|
|
154
|
+
this.listeners.add(fn);
|
|
155
|
+
return () => this.listeners.delete(fn);
|
|
156
|
+
};
|
|
157
|
+
getSnapshot = () => this.snapshot;
|
|
158
|
+
paramsSnapshot(tag) {
|
|
159
|
+
if (!tag) return EMPTY_PARAMS;
|
|
160
|
+
return this.paramsByTag.get(tag) ?? EMPTY_PARAMS;
|
|
161
|
+
}
|
|
162
|
+
match(href) {
|
|
163
|
+
const str = hrefToString(href);
|
|
164
|
+
const [path = "", query] = str.split("?");
|
|
165
|
+
const parts = path.split("/").filter(Boolean);
|
|
166
|
+
for (const route of this.routes) {
|
|
167
|
+
if (route.segments.length !== parts.length) continue;
|
|
168
|
+
const params = parseQuery(query);
|
|
169
|
+
let ok = true;
|
|
170
|
+
for (let i = 0; i < parts.length; i++) {
|
|
171
|
+
const seg = route.segments[i];
|
|
172
|
+
const part = decodeURIComponent(parts[i]);
|
|
173
|
+
if (seg.literal !== void 0) {
|
|
174
|
+
if (seg.literal !== part) {
|
|
175
|
+
ok = false;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
} else if (seg.param !== void 0) {
|
|
179
|
+
params[seg.param] = part;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (ok) return {
|
|
183
|
+
route,
|
|
184
|
+
pathname: path === "" ? "/" : `/${parts.join("/")}`,
|
|
185
|
+
params
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
push = href => {
|
|
191
|
+
void this.navigate(href, "push");
|
|
192
|
+
};
|
|
193
|
+
replace = href => {
|
|
194
|
+
void this.navigate(href, "replace");
|
|
195
|
+
};
|
|
196
|
+
/** Shared push/replace flow, incl. the loader bridge. When the target
|
|
197
|
+
* route has no loader the whole body runs SYNCHRONOUSLY (an async
|
|
198
|
+
* function only yields at its first await), preserving the original
|
|
199
|
+
* timing. With a loader, the host action waits for the loader to resolve
|
|
200
|
+
* — same contract as One on web, where client navigation completes only
|
|
201
|
+
* after the route's loader data arrived — so the page's first paint has
|
|
202
|
+
* its data (and the lazily-mounted content mounts with it). */
|
|
203
|
+
async navigate(href, mode) {
|
|
204
|
+
const hit = this.match(href);
|
|
205
|
+
if (!hit) {
|
|
206
|
+
console.log(`[stack nav] ${mode} ${hrefToString(href)} \u2014 no page in the route manifest, ignored`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!this.host) {
|
|
210
|
+
console.log(`[stack nav] ${mode} ${hrefToString(href)} \u2014 no stack host attached, ignored`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const {
|
|
214
|
+
route,
|
|
215
|
+
pathname,
|
|
216
|
+
params
|
|
217
|
+
} = hit;
|
|
218
|
+
const seq = ++this.navSeq;
|
|
219
|
+
if (route.loader) {
|
|
220
|
+
try {
|
|
221
|
+
const data = await route.loader({
|
|
222
|
+
path: pathname,
|
|
223
|
+
params
|
|
224
|
+
});
|
|
225
|
+
if (seq !== this.navSeq) return;
|
|
226
|
+
this.loaderDataByTag.set(route.tag, data);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
if (seq !== this.navSeq) return;
|
|
229
|
+
console.error(`[stack nav] loader for ${route.tag} failed: ${String(err)}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
this.visitedTags.add(route.tag);
|
|
233
|
+
this.setRouteState(route.tag, pathname, params);
|
|
234
|
+
if (mode === "replace") {
|
|
235
|
+
const top = this.stackTags[this.stackTags.length - 1];
|
|
236
|
+
if (top === route.tag) return;
|
|
237
|
+
const tags = [...this.stackTags.slice(0, -1), route.tag];
|
|
238
|
+
this.host.replace_with_tags(tags);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const idx = this.stackTags.indexOf(route.tag);
|
|
242
|
+
if (idx === -1) {
|
|
243
|
+
this.host.push_by_tag(route.tag);
|
|
244
|
+
} else if (idx < this.stackTags.length - 1) {
|
|
245
|
+
this.host.pop_to_tag(route.tag);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
back = () => {
|
|
249
|
+
if (!this.host) {
|
|
250
|
+
console.log("[stack nav] back \u2014 no stack host attached, ignored");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.navSeq++;
|
|
254
|
+
this.host.pop();
|
|
255
|
+
};
|
|
256
|
+
setParams = params => {
|
|
257
|
+
const top = this.stackTags[this.stackTags.length - 1];
|
|
258
|
+
if (!top) return;
|
|
259
|
+
const prev = this.paramsByTag.get(top) ?? {};
|
|
260
|
+
this.paramsByTag.set(top, {
|
|
261
|
+
...prev,
|
|
262
|
+
...params
|
|
263
|
+
});
|
|
264
|
+
this.rebuildSnapshot();
|
|
265
|
+
};
|
|
266
|
+
/** Re-read the host navigation stack (called from pushed/popped/replaced
|
|
267
|
+
* signal handlers and attach). The host is the source of truth. */
|
|
268
|
+
syncFromHost = () => {
|
|
269
|
+
if (!this.host) return;
|
|
270
|
+
this.navSeq++;
|
|
271
|
+
const model = this.host.get_navigation_stack();
|
|
272
|
+
const tags = [];
|
|
273
|
+
const count = model.get_n_items();
|
|
274
|
+
for (let i = 0; i < count; i++) {
|
|
275
|
+
const tag = model.get_item(i)?.get_tag();
|
|
276
|
+
if (tag) tags.push(tag);
|
|
277
|
+
}
|
|
278
|
+
if (tags.length > 0) this.stackTags = tags;
|
|
279
|
+
for (const tag of this.stackTags) this.visitedTags.add(tag);
|
|
280
|
+
this.rebuildSnapshot();
|
|
281
|
+
};
|
|
282
|
+
setRouteState(tag, pathname, params) {
|
|
283
|
+
this.paramsByTag.set(tag, params);
|
|
284
|
+
this.pathnameByTag.set(tag, pathname);
|
|
285
|
+
this.rebuildSnapshot();
|
|
286
|
+
}
|
|
287
|
+
rebuildSnapshot() {
|
|
288
|
+
const tag = this.stackTags[this.stackTags.length - 1] ?? "";
|
|
289
|
+
const route = this.routes.find(r => r.tag === tag);
|
|
290
|
+
this.generation++;
|
|
291
|
+
this.snapshot = {
|
|
292
|
+
pathname: this.pathnameByTag.get(tag) ?? route?.pattern ?? "/",
|
|
293
|
+
tag,
|
|
294
|
+
depth: this.stackTags.length,
|
|
295
|
+
canGoBack: this.stackTags.length > 1,
|
|
296
|
+
generation: this.generation
|
|
297
|
+
};
|
|
298
|
+
for (const fn of this.listeners) fn();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const stackNavigator = new StackNavigator();
|
|
302
|
+
const RouteTagContext = (0, import_react.createContext)(null);
|
|
303
|
+
function useNavState() {
|
|
304
|
+
return (0, import_react.useSyncExternalStore)(stackNavigator.subscribe, stackNavigator.getSnapshot);
|
|
305
|
+
}
|
|
306
|
+
function useRouteParams() {
|
|
307
|
+
const tag = (0, import_react.useContext)(RouteTagContext);
|
|
308
|
+
return (0, import_react.useSyncExternalStore)(stackNavigator.subscribe, () => stackNavigator.paramsSnapshot(tag));
|
|
309
|
+
}
|