@echojs-ecosystem/router 0.1.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 +80 -0
- package/dist/index.d.ts +455 -0
- package/dist/index.js +1546 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1546 @@
|
|
|
1
|
+
import { signal, computed } from '@echojs-ecosystem/reactivity';
|
|
2
|
+
import { attachRouterQueryParams } from '@echojs-ecosystem/url-state';
|
|
3
|
+
import { h, createView, cx } from '@echojs-ecosystem/hyperdom';
|
|
4
|
+
|
|
5
|
+
// src/core/route.ts
|
|
6
|
+
|
|
7
|
+
// src/core/path.ts
|
|
8
|
+
var normalizePathname = (pathname) => {
|
|
9
|
+
let path = pathname;
|
|
10
|
+
const queryIndex = path.indexOf("?");
|
|
11
|
+
if (queryIndex !== -1) path = path.slice(0, queryIndex);
|
|
12
|
+
const hashIndex = path.indexOf("#");
|
|
13
|
+
if (hashIndex !== -1) path = path.slice(0, hashIndex);
|
|
14
|
+
if (!path || path === "/") return "/";
|
|
15
|
+
path = path.replace(/\/+$/, "");
|
|
16
|
+
return path || "/";
|
|
17
|
+
};
|
|
18
|
+
var joinRoutePaths = (parentPath, childPath) => {
|
|
19
|
+
const parent = normalizePathname(parentPath || "/");
|
|
20
|
+
const child = childPath.trim();
|
|
21
|
+
if (!child || child === "/") return parent;
|
|
22
|
+
if (child.startsWith("/")) {
|
|
23
|
+
return normalizePathname(child);
|
|
24
|
+
}
|
|
25
|
+
const base = parent === "/" ? "" : parent;
|
|
26
|
+
return normalizePathname(`${base}/${child}`);
|
|
27
|
+
};
|
|
28
|
+
var splitSegments = (path) => {
|
|
29
|
+
const normalized = normalizePathname(path);
|
|
30
|
+
if (normalized === "/") return [];
|
|
31
|
+
return normalized.slice(1).split("/");
|
|
32
|
+
};
|
|
33
|
+
var decodeParam = (value) => {
|
|
34
|
+
try {
|
|
35
|
+
return decodeURIComponent(value);
|
|
36
|
+
} catch {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var encodeParam = (value) => encodeURIComponent(value);
|
|
41
|
+
var matchPath = (pattern, pathname) => {
|
|
42
|
+
const patternSegments = splitSegments(pattern);
|
|
43
|
+
const pathSegments = splitSegments(pathname);
|
|
44
|
+
if (patternSegments.length === 0) {
|
|
45
|
+
return { matched: pathSegments.length === 0, params: {} };
|
|
46
|
+
}
|
|
47
|
+
const params = {};
|
|
48
|
+
let pi = 0;
|
|
49
|
+
let si = 0;
|
|
50
|
+
while (pi < patternSegments.length) {
|
|
51
|
+
const segment = patternSegments[pi];
|
|
52
|
+
if (segment === "*") {
|
|
53
|
+
if (si > pathSegments.length) {
|
|
54
|
+
return { matched: false, params: {} };
|
|
55
|
+
}
|
|
56
|
+
const rest = pathSegments.slice(si).map(decodeParam).join("/");
|
|
57
|
+
params["*"] = rest;
|
|
58
|
+
return { matched: true, params };
|
|
59
|
+
}
|
|
60
|
+
if (si >= pathSegments.length) {
|
|
61
|
+
return { matched: false, params: {} };
|
|
62
|
+
}
|
|
63
|
+
if (segment.startsWith(":")) {
|
|
64
|
+
const key = segment.slice(1);
|
|
65
|
+
params[key] = decodeParam(pathSegments[si]);
|
|
66
|
+
pi += 1;
|
|
67
|
+
si += 1;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (segment !== pathSegments[si]) {
|
|
71
|
+
return { matched: false, params: {} };
|
|
72
|
+
}
|
|
73
|
+
pi += 1;
|
|
74
|
+
si += 1;
|
|
75
|
+
}
|
|
76
|
+
if (si !== pathSegments.length) {
|
|
77
|
+
return { matched: false, params: {} };
|
|
78
|
+
}
|
|
79
|
+
return { matched: true, params };
|
|
80
|
+
};
|
|
81
|
+
var buildPath = (pattern, params = {}) => {
|
|
82
|
+
const segments = splitSegments(pattern);
|
|
83
|
+
if (segments.length === 0) return "/";
|
|
84
|
+
const built = segments.map((segment) => {
|
|
85
|
+
if (segment === "*") {
|
|
86
|
+
const value = params["*"] ?? "";
|
|
87
|
+
return value.split("/").map(encodeParam).join("/");
|
|
88
|
+
}
|
|
89
|
+
if (segment.startsWith(":")) {
|
|
90
|
+
const key = segment.slice(1);
|
|
91
|
+
const value = params[key];
|
|
92
|
+
if (value === void 0) {
|
|
93
|
+
throw new Error(`Missing path param "${key}" for pattern "${pattern}"`);
|
|
94
|
+
}
|
|
95
|
+
return encodeParam(value);
|
|
96
|
+
}
|
|
97
|
+
return segment;
|
|
98
|
+
});
|
|
99
|
+
return `/${built.join("/")}`;
|
|
100
|
+
};
|
|
101
|
+
var splitLocation = (location) => {
|
|
102
|
+
let path = location;
|
|
103
|
+
let search = "";
|
|
104
|
+
const queryIndex = path.indexOf("?");
|
|
105
|
+
if (queryIndex !== -1) {
|
|
106
|
+
search = path.slice(queryIndex);
|
|
107
|
+
path = path.slice(0, queryIndex);
|
|
108
|
+
}
|
|
109
|
+
const hashIndex = path.indexOf("#");
|
|
110
|
+
if (hashIndex !== -1) {
|
|
111
|
+
path = path.slice(0, hashIndex);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
pathname: normalizePathname(path),
|
|
115
|
+
search
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
var joinLocation = (pathname, search) => {
|
|
119
|
+
const path = normalizePathname(pathname);
|
|
120
|
+
if (!search) return path;
|
|
121
|
+
return search.startsWith("?") ? `${path}${search}` : `${path}?${search}`;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/core/route-event.ts
|
|
125
|
+
var createRouteEvent = () => {
|
|
126
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
127
|
+
return {
|
|
128
|
+
subscribe(listener) {
|
|
129
|
+
listeners.add(listener);
|
|
130
|
+
return () => listeners.delete(listener);
|
|
131
|
+
},
|
|
132
|
+
emit(payload) {
|
|
133
|
+
for (const listener of listeners) {
|
|
134
|
+
listener(payload);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/core/query.ts
|
|
141
|
+
var isQueryPrimitive = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
142
|
+
var parseQuery = (search) => {
|
|
143
|
+
const raw = search.startsWith("?") ? search.slice(1) : search;
|
|
144
|
+
if (!raw) return {};
|
|
145
|
+
const params = new URLSearchParams(raw);
|
|
146
|
+
const result = {};
|
|
147
|
+
for (const [key, value] of params.entries()) {
|
|
148
|
+
result[key] = value;
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
};
|
|
152
|
+
var parseQueryValues = (search) => {
|
|
153
|
+
const raw = search.startsWith("?") ? search.slice(1) : search;
|
|
154
|
+
if (!raw) return {};
|
|
155
|
+
const params = new URLSearchParams(raw);
|
|
156
|
+
const result = {};
|
|
157
|
+
for (const key of new Set(params.keys())) {
|
|
158
|
+
const values = params.getAll(key);
|
|
159
|
+
if (values.length === 1) {
|
|
160
|
+
result[key] = coerceQueryValue(values[0]);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
result[key] = values.map(coerceQueryValue);
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
};
|
|
167
|
+
var coerceQueryValue = (value) => {
|
|
168
|
+
if (value === "true") return "true";
|
|
169
|
+
if (value === "false") return "false";
|
|
170
|
+
return value;
|
|
171
|
+
};
|
|
172
|
+
var stringifyQuery = (query) => {
|
|
173
|
+
const params = new URLSearchParams();
|
|
174
|
+
for (const [key, value] of Object.entries(query)) {
|
|
175
|
+
if (value === null || value === void 0) continue;
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
|
+
for (const item of value) {
|
|
178
|
+
if (item === null || item === void 0) continue;
|
|
179
|
+
params.append(key, String(item));
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (isQueryPrimitive(value)) {
|
|
184
|
+
params.append(key, String(value));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const serialized = params.toString();
|
|
188
|
+
return serialized ? `?${serialized}` : "";
|
|
189
|
+
};
|
|
190
|
+
var mapQueryToRecord = (raw) => raw;
|
|
191
|
+
|
|
192
|
+
// src/core/route.ts
|
|
193
|
+
var routeStates = /* @__PURE__ */ new WeakMap();
|
|
194
|
+
var getRouteState = (route) => {
|
|
195
|
+
const state = routeStates.get(route);
|
|
196
|
+
if (!state) {
|
|
197
|
+
throw new Error("Unknown route instance");
|
|
198
|
+
}
|
|
199
|
+
return state;
|
|
200
|
+
};
|
|
201
|
+
var bindRouteToRouter = (route, router, pathTemplate) => {
|
|
202
|
+
const state = getRouteState(route);
|
|
203
|
+
state.router = router;
|
|
204
|
+
state.pathTemplate = pathTemplate;
|
|
205
|
+
};
|
|
206
|
+
var createRouteModel = (name) => {
|
|
207
|
+
const $isOpened = signal(false);
|
|
208
|
+
const $params = signal(null);
|
|
209
|
+
const $query = signal({});
|
|
210
|
+
const $path = signal(null);
|
|
211
|
+
const $fullPath = signal(null);
|
|
212
|
+
const opened = createRouteEvent();
|
|
213
|
+
const closed = createRouteEvent();
|
|
214
|
+
const state = {
|
|
215
|
+
name,
|
|
216
|
+
pathTemplate: null,
|
|
217
|
+
router: null,
|
|
218
|
+
$isOpened,
|
|
219
|
+
$params,
|
|
220
|
+
$query,
|
|
221
|
+
$path,
|
|
222
|
+
$fullPath,
|
|
223
|
+
opened,
|
|
224
|
+
closed,
|
|
225
|
+
wasOpened: false
|
|
226
|
+
};
|
|
227
|
+
const go = (...args) => {
|
|
228
|
+
const [params, options] = args;
|
|
229
|
+
if (!state.router || !state.pathTemplate) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`Route${name ? ` "${name}"` : ""} is not registered in a router. Pass it to createRouter({ routes }).`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const pathname = buildPath(state.pathTemplate, params ?? {});
|
|
235
|
+
const search = options?.query ? stringifyQuery(options.query) : "";
|
|
236
|
+
const fullPath = joinLocation(pathname, search);
|
|
237
|
+
if (options?.replace) state.router.replace(fullPath);
|
|
238
|
+
else state.router.go(fullPath);
|
|
239
|
+
};
|
|
240
|
+
const route = {
|
|
241
|
+
name,
|
|
242
|
+
$isOpened,
|
|
243
|
+
$params,
|
|
244
|
+
$query,
|
|
245
|
+
$path,
|
|
246
|
+
$fullPath,
|
|
247
|
+
opened,
|
|
248
|
+
closed,
|
|
249
|
+
go,
|
|
250
|
+
open: go,
|
|
251
|
+
close() {
|
|
252
|
+
if (!state.router) {
|
|
253
|
+
throw new Error(`Route${name ? ` "${name}"` : ""} is not registered in a router.`);
|
|
254
|
+
}
|
|
255
|
+
state.router.closeRoute(route);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
routeStates.set(route, state);
|
|
259
|
+
return route;
|
|
260
|
+
};
|
|
261
|
+
var applyRouteClosed = (state) => {
|
|
262
|
+
const wasOpened = state.wasOpened;
|
|
263
|
+
state.$isOpened.set(false);
|
|
264
|
+
state.$params.set(null);
|
|
265
|
+
state.$path.set(null);
|
|
266
|
+
state.$fullPath.set(null);
|
|
267
|
+
state.$query.set({});
|
|
268
|
+
state.wasOpened = false;
|
|
269
|
+
if (wasOpened) {
|
|
270
|
+
state.closed.emit();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
var applyRouteOpened = (state, payload) => {
|
|
274
|
+
const wasOpened = state.wasOpened;
|
|
275
|
+
state.$isOpened.set(true);
|
|
276
|
+
state.$params.set(payload.params);
|
|
277
|
+
state.$query.set(payload.query);
|
|
278
|
+
state.$path.set(payload.path);
|
|
279
|
+
state.$fullPath.set(payload.fullPath);
|
|
280
|
+
state.wasOpened = true;
|
|
281
|
+
if (!wasOpened) {
|
|
282
|
+
state.opened.emit(payload);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
var rawQueryToTyped = (raw) => mapQueryToRecord(raw);
|
|
286
|
+
|
|
287
|
+
// src/core/create-route.ts
|
|
288
|
+
var createRoute = (name) => createRouteModel(name);
|
|
289
|
+
|
|
290
|
+
// src/core/navigation.ts
|
|
291
|
+
var buildRouteLocation = (pathTemplate, params = {}, query) => {
|
|
292
|
+
const pathname = buildPath(pathTemplate, params);
|
|
293
|
+
const search = query ? stringifyQuery(query) : "";
|
|
294
|
+
return joinLocation(pathname, search);
|
|
295
|
+
};
|
|
296
|
+
var resolveRoutePath = (route, params, options) => {
|
|
297
|
+
const state = getRouteState(route);
|
|
298
|
+
if (!state.pathTemplate) {
|
|
299
|
+
throw new Error("Route is not registered in a router");
|
|
300
|
+
}
|
|
301
|
+
return buildRouteLocation(
|
|
302
|
+
state.pathTemplate,
|
|
303
|
+
params ?? {},
|
|
304
|
+
options?.query
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
var parseLocation = (location) => {
|
|
308
|
+
const { pathname, search } = splitLocation(location);
|
|
309
|
+
return {
|
|
310
|
+
pathname,
|
|
311
|
+
search,
|
|
312
|
+
query: parseQuery(search)
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// src/core/route-tree.ts
|
|
317
|
+
var flattenRouteTree = (configs, parentPath = "/", parent = null) => {
|
|
318
|
+
const result = [];
|
|
319
|
+
for (const config of configs) {
|
|
320
|
+
const path = joinRoutePaths(parentPath, config.path);
|
|
321
|
+
const entry = { path, route: config.route, parent };
|
|
322
|
+
result.push(entry);
|
|
323
|
+
if (config.children?.length) {
|
|
324
|
+
result.push(...flattenRouteTree(config.children, path, entry));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
};
|
|
329
|
+
var routeDepth = (entry) => {
|
|
330
|
+
let depth = 0;
|
|
331
|
+
let cursor = entry.parent;
|
|
332
|
+
while (cursor) {
|
|
333
|
+
depth += 1;
|
|
334
|
+
cursor = cursor.parent;
|
|
335
|
+
}
|
|
336
|
+
return depth;
|
|
337
|
+
};
|
|
338
|
+
var isBetterMatch = (entry, best) => {
|
|
339
|
+
if (entry.path.length > best.path.length) return true;
|
|
340
|
+
if (entry.path.length < best.path.length) return false;
|
|
341
|
+
return routeDepth(entry) > routeDepth(best);
|
|
342
|
+
};
|
|
343
|
+
var matchRouteChain = (pathname, flatRoutes) => {
|
|
344
|
+
let best = null;
|
|
345
|
+
for (const entry of flatRoutes) {
|
|
346
|
+
const result = matchPath(entry.path, pathname);
|
|
347
|
+
if (!result.matched) continue;
|
|
348
|
+
if (!best || isBetterMatch(entry, best.entry)) {
|
|
349
|
+
best = { entry, params: result.params };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!best) return null;
|
|
353
|
+
const chain = [];
|
|
354
|
+
let cursor = best.entry;
|
|
355
|
+
while (cursor) {
|
|
356
|
+
chain.unshift(cursor);
|
|
357
|
+
cursor = cursor.parent;
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
leaf: best.entry,
|
|
361
|
+
chain,
|
|
362
|
+
leafParams: best.params
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
var matchParamsForEntry = (entry, pathname) => matchPath(entry.path, pathname).params;
|
|
366
|
+
|
|
367
|
+
// src/core/lazy-view.ts
|
|
368
|
+
var isLazyRouteView = (page) => {
|
|
369
|
+
const state = getPageState(page);
|
|
370
|
+
return Boolean(state.viewLoader);
|
|
371
|
+
};
|
|
372
|
+
var ensureLazyViewLoaded = async (page, navigationId) => {
|
|
373
|
+
const state = getPageState(page);
|
|
374
|
+
const router = getRouteState(page).router;
|
|
375
|
+
if (!state.viewLoader) return true;
|
|
376
|
+
if (state.view) return true;
|
|
377
|
+
const runId = ++state.viewLoadRunId;
|
|
378
|
+
state.$viewPending.set(true);
|
|
379
|
+
state.$viewError.set(null);
|
|
380
|
+
try {
|
|
381
|
+
const module = await state.viewLoader();
|
|
382
|
+
if (runId !== state.viewLoadRunId) return false;
|
|
383
|
+
if (router && navigationId !== router.navigationId) return false;
|
|
384
|
+
const view = module?.default;
|
|
385
|
+
if (typeof view !== "function") {
|
|
386
|
+
throw new TypeError(
|
|
387
|
+
`Lazy route view "${page.name ?? "anonymous"}": dynamic import must export a default view function.`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
state.view = view;
|
|
391
|
+
state.$viewPending.set(false);
|
|
392
|
+
return true;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (runId !== state.viewLoadRunId) return false;
|
|
395
|
+
if (router && navigationId !== router.navigationId) return false;
|
|
396
|
+
state.$viewError.set(error);
|
|
397
|
+
state.$viewPending.set(false);
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
var cancelLazyViewLoads = (pages) => {
|
|
402
|
+
for (const page of pages) {
|
|
403
|
+
const state = getPageState(page);
|
|
404
|
+
if (!state.viewLoader) continue;
|
|
405
|
+
state.viewLoadRunId += 1;
|
|
406
|
+
state.$viewPending.set(false);
|
|
407
|
+
state.$viewError.set(null);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// src/core/page.ts
|
|
412
|
+
var pageStates = /* @__PURE__ */ new WeakMap();
|
|
413
|
+
var isPage = (value) => pageStates.has(value);
|
|
414
|
+
var isLayoutPage = (value) => {
|
|
415
|
+
const state = pageStates.get(value);
|
|
416
|
+
return Boolean(state && state.kind === "layout");
|
|
417
|
+
};
|
|
418
|
+
var assertLayoutPage = (value, label = "layout") => {
|
|
419
|
+
assertPage(value, label);
|
|
420
|
+
if (!isLayoutPage(value)) {
|
|
421
|
+
throw new TypeError(`${label}: expected createLayoutView() instance.`);
|
|
422
|
+
}
|
|
423
|
+
return value;
|
|
424
|
+
};
|
|
425
|
+
var assertPage = (value, label = "createRouter") => {
|
|
426
|
+
if (!isPage(value)) {
|
|
427
|
+
throw new TypeError(
|
|
428
|
+
`${label}: only createRouteView() / createLazyRouteView() instances are allowed. Use createRoute() for redirects/guards.`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return value;
|
|
432
|
+
};
|
|
433
|
+
var getPageState = (page) => {
|
|
434
|
+
const state = pageStates.get(page);
|
|
435
|
+
if (!state) {
|
|
436
|
+
throw new Error("Unknown page instance");
|
|
437
|
+
}
|
|
438
|
+
return state;
|
|
439
|
+
};
|
|
440
|
+
var createRouteViewModel = (options) => {
|
|
441
|
+
const resolved = typeof options === "string" ? (() => {
|
|
442
|
+
throw new TypeError(
|
|
443
|
+
`createRouteView("${options}") requires { name, view }. Pass an options object.`
|
|
444
|
+
);
|
|
445
|
+
})() : options;
|
|
446
|
+
if (!resolved.view && !resolved.viewLoader) {
|
|
447
|
+
throw new TypeError(
|
|
448
|
+
"createRouteView() requires { view } or createLazyRouteView() requires { view: () => import(...) }."
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const route = createRouteModel(resolved.name);
|
|
452
|
+
const baseState = getRouteState(route);
|
|
453
|
+
const $pending = signal(false);
|
|
454
|
+
const $error = signal(null);
|
|
455
|
+
const $viewPending = signal(false);
|
|
456
|
+
const $viewError = signal(null);
|
|
457
|
+
const $data = signal(null);
|
|
458
|
+
const state = {
|
|
459
|
+
...baseState,
|
|
460
|
+
kind: "page",
|
|
461
|
+
view: resolved.view,
|
|
462
|
+
viewLoader: resolved.viewLoader,
|
|
463
|
+
viewLoadRunId: 0,
|
|
464
|
+
beforeLoad: resolved.beforeLoad,
|
|
465
|
+
loader: resolved.loader,
|
|
466
|
+
loadingView: resolved.loadingView,
|
|
467
|
+
errorView: resolved.errorView,
|
|
468
|
+
$pending,
|
|
469
|
+
$error,
|
|
470
|
+
$viewPending,
|
|
471
|
+
$viewError,
|
|
472
|
+
$data,
|
|
473
|
+
loaderRunId: 0
|
|
474
|
+
};
|
|
475
|
+
const page = route;
|
|
476
|
+
pageStates.set(page, state);
|
|
477
|
+
page.closed.subscribe(() => {
|
|
478
|
+
state.loaderRunId += 1;
|
|
479
|
+
state.viewLoadRunId += 1;
|
|
480
|
+
$pending.set(false);
|
|
481
|
+
$error.set(null);
|
|
482
|
+
$viewPending.set(false);
|
|
483
|
+
$viewError.set(null);
|
|
484
|
+
$data.set(null);
|
|
485
|
+
});
|
|
486
|
+
return Object.assign(page, {
|
|
487
|
+
$pending,
|
|
488
|
+
$error,
|
|
489
|
+
$data,
|
|
490
|
+
beforeLoad: resolved.beforeLoad,
|
|
491
|
+
loader: resolved.loader,
|
|
492
|
+
loadingView: resolved.loadingView,
|
|
493
|
+
errorView: resolved.errorView,
|
|
494
|
+
replace(...args) {
|
|
495
|
+
const [params, options2] = args;
|
|
496
|
+
page.go(
|
|
497
|
+
params,
|
|
498
|
+
{ ...options2, replace: true }
|
|
499
|
+
);
|
|
500
|
+
},
|
|
501
|
+
resolve(params, options2) {
|
|
502
|
+
return resolveRoutePath(page, params ?? {}, options2);
|
|
503
|
+
},
|
|
504
|
+
isActive() {
|
|
505
|
+
return page.$isOpened.value();
|
|
506
|
+
},
|
|
507
|
+
preload() {
|
|
508
|
+
const params = page.$params.value();
|
|
509
|
+
const query = page.$query.value();
|
|
510
|
+
if (params === null) return;
|
|
511
|
+
const navigationId = ++state.loaderRunId;
|
|
512
|
+
void (async () => {
|
|
513
|
+
const viewOk = await ensureLazyViewLoaded(page, navigationId);
|
|
514
|
+
if (!viewOk) return;
|
|
515
|
+
await runPageBeforeLoad(
|
|
516
|
+
page,
|
|
517
|
+
{ params, query, navigationId },
|
|
518
|
+
navigationId
|
|
519
|
+
);
|
|
520
|
+
})();
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
var resolveBeforeLoad = (state) => state.beforeLoad ?? state.loader;
|
|
525
|
+
var runPageBeforeLoad = async (page, context, navigationId) => {
|
|
526
|
+
const state = getPageState(page);
|
|
527
|
+
const routeState = getRouteState(page);
|
|
528
|
+
const router = routeState.router;
|
|
529
|
+
const beforeLoad = resolveBeforeLoad(state);
|
|
530
|
+
if (!beforeLoad) {
|
|
531
|
+
state.$pending.set(false);
|
|
532
|
+
state.$error.set(null);
|
|
533
|
+
state.$data.set(null);
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
const runId = ++state.loaderRunId;
|
|
537
|
+
state.$pending.set(true);
|
|
538
|
+
state.$error.set(null);
|
|
539
|
+
try {
|
|
540
|
+
const data = await Promise.resolve(beforeLoad(context));
|
|
541
|
+
if (runId !== state.loaderRunId) return false;
|
|
542
|
+
if (router && navigationId !== router.navigationId) return false;
|
|
543
|
+
state.$data.set(data);
|
|
544
|
+
state.$pending.set(false);
|
|
545
|
+
return true;
|
|
546
|
+
} catch (error) {
|
|
547
|
+
if (runId !== state.loaderRunId) return false;
|
|
548
|
+
if (router && navigationId !== router.navigationId) return false;
|
|
549
|
+
state.$error.set(error);
|
|
550
|
+
state.$pending.set(false);
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
var runBeforeLoadChain = async (chain, pathname, query, navigationId) => {
|
|
555
|
+
for (const entry of chain) {
|
|
556
|
+
if (!isPage(entry.route)) continue;
|
|
557
|
+
const viewOk = await ensureLazyViewLoaded(entry.route, navigationId);
|
|
558
|
+
if (!viewOk) break;
|
|
559
|
+
const params = matchParamsForEntry(entry, pathname);
|
|
560
|
+
const ok = await runPageBeforeLoad(
|
|
561
|
+
entry.route,
|
|
562
|
+
{
|
|
563
|
+
params,
|
|
564
|
+
query: rawQueryToTyped(query),
|
|
565
|
+
navigationId
|
|
566
|
+
},
|
|
567
|
+
navigationId
|
|
568
|
+
);
|
|
569
|
+
if (!ok) break;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var cancelPageLoads = (pages) => {
|
|
573
|
+
cancelLazyViewLoads(pages);
|
|
574
|
+
for (const page of pages) {
|
|
575
|
+
const state = getPageState(page);
|
|
576
|
+
state.loaderRunId += 1;
|
|
577
|
+
state.$pending.set(false);
|
|
578
|
+
state.$error.set(null);
|
|
579
|
+
state.$data.set(null);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// src/core/create-route-view.ts
|
|
584
|
+
function createRouteView(options) {
|
|
585
|
+
return createRouteViewModel(options);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// src/core/create-lazy-route-view.ts
|
|
589
|
+
function createLazyRouteView(options) {
|
|
590
|
+
return createRouteViewModel({
|
|
591
|
+
name: options.name,
|
|
592
|
+
viewLoader: options.view,
|
|
593
|
+
beforeLoad: options.beforeLoad,
|
|
594
|
+
loadingView: options.loadingView,
|
|
595
|
+
errorView: options.errorView
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/core/create-layout-view.ts
|
|
600
|
+
var createLayoutView = (options) => {
|
|
601
|
+
const layout = createRouteViewModel(options);
|
|
602
|
+
getPageState(layout).kind = "layout";
|
|
603
|
+
return layout;
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// src/histories/browser-history.ts
|
|
607
|
+
var readLocation = () => {
|
|
608
|
+
const { pathname, search } = window.location;
|
|
609
|
+
return joinLocation(pathname, search);
|
|
610
|
+
};
|
|
611
|
+
var createBrowserHistory = () => {
|
|
612
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
613
|
+
const notify = () => {
|
|
614
|
+
const path = readLocation();
|
|
615
|
+
for (const listener of listeners) {
|
|
616
|
+
listener(path);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
const onPopState = () => notify();
|
|
620
|
+
return {
|
|
621
|
+
getLocation: readLocation,
|
|
622
|
+
push(path) {
|
|
623
|
+
const { pathname, search } = splitLocation(path);
|
|
624
|
+
window.history.pushState(null, "", joinLocation(pathname, search));
|
|
625
|
+
notify();
|
|
626
|
+
},
|
|
627
|
+
replace(path) {
|
|
628
|
+
const { pathname, search } = splitLocation(path);
|
|
629
|
+
window.history.replaceState(null, "", joinLocation(pathname, search));
|
|
630
|
+
notify();
|
|
631
|
+
},
|
|
632
|
+
listen(callback) {
|
|
633
|
+
listeners.add(callback);
|
|
634
|
+
window.addEventListener("popstate", onPopState);
|
|
635
|
+
return () => {
|
|
636
|
+
listeners.delete(callback);
|
|
637
|
+
window.removeEventListener("popstate", onPopState);
|
|
638
|
+
};
|
|
639
|
+
},
|
|
640
|
+
back() {
|
|
641
|
+
window.history.back();
|
|
642
|
+
},
|
|
643
|
+
forward() {
|
|
644
|
+
window.history.forward();
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// src/histories/hash-history.ts
|
|
650
|
+
var readHashPath = () => {
|
|
651
|
+
const hash = window.location.hash;
|
|
652
|
+
if (!hash || hash === "#") return "/";
|
|
653
|
+
const raw = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
654
|
+
const path = raw.startsWith("/") ? raw : `/${raw}`;
|
|
655
|
+
return normalizePathname(path);
|
|
656
|
+
};
|
|
657
|
+
var createHashHistory = () => {
|
|
658
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
659
|
+
const notify = () => {
|
|
660
|
+
const path = readHashPath();
|
|
661
|
+
for (const listener of listeners) {
|
|
662
|
+
listener(path);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
const onHashChange = () => notify();
|
|
666
|
+
const writeHash = (path, replace) => {
|
|
667
|
+
const normalized = normalizePathname(path);
|
|
668
|
+
const next = `#${normalized}`;
|
|
669
|
+
if (replace) window.location.replace(next);
|
|
670
|
+
else window.location.hash = next;
|
|
671
|
+
notify();
|
|
672
|
+
};
|
|
673
|
+
return {
|
|
674
|
+
getLocation: readHashPath,
|
|
675
|
+
push(path) {
|
|
676
|
+
writeHash(path, false);
|
|
677
|
+
},
|
|
678
|
+
replace(path) {
|
|
679
|
+
writeHash(path, true);
|
|
680
|
+
},
|
|
681
|
+
listen(callback) {
|
|
682
|
+
listeners.add(callback);
|
|
683
|
+
window.addEventListener("hashchange", onHashChange);
|
|
684
|
+
return () => {
|
|
685
|
+
listeners.delete(callback);
|
|
686
|
+
window.removeEventListener("hashchange", onHashChange);
|
|
687
|
+
};
|
|
688
|
+
},
|
|
689
|
+
back() {
|
|
690
|
+
window.history.back();
|
|
691
|
+
},
|
|
692
|
+
forward() {
|
|
693
|
+
window.history.forward();
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// src/histories/memory-history.ts
|
|
699
|
+
var createMemoryHistory = (initialPath = "/") => {
|
|
700
|
+
let index = 0;
|
|
701
|
+
const stack = [initialPath];
|
|
702
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
703
|
+
const notify = () => {
|
|
704
|
+
const path = stack[index];
|
|
705
|
+
for (const listener of listeners) {
|
|
706
|
+
listener(path);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const getLocation = () => stack[index];
|
|
710
|
+
return {
|
|
711
|
+
getLocation,
|
|
712
|
+
push(path) {
|
|
713
|
+
stack.splice(index + 1);
|
|
714
|
+
stack.push(path);
|
|
715
|
+
index = stack.length - 1;
|
|
716
|
+
notify();
|
|
717
|
+
},
|
|
718
|
+
replace(path) {
|
|
719
|
+
stack[index] = path;
|
|
720
|
+
notify();
|
|
721
|
+
},
|
|
722
|
+
listen(callback) {
|
|
723
|
+
listeners.add(callback);
|
|
724
|
+
return () => listeners.delete(callback);
|
|
725
|
+
},
|
|
726
|
+
back() {
|
|
727
|
+
if (index <= 0) return;
|
|
728
|
+
index -= 1;
|
|
729
|
+
notify();
|
|
730
|
+
},
|
|
731
|
+
forward() {
|
|
732
|
+
if (index >= stack.length - 1) return;
|
|
733
|
+
index += 1;
|
|
734
|
+
notify();
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// src/core/history-config.ts
|
|
740
|
+
var isRouterHistory = (value) => typeof value === "object" && value !== null && "getLocation" in value && "push" in value;
|
|
741
|
+
var resolveHistory = (config) => {
|
|
742
|
+
if (isRouterHistory(config)) {
|
|
743
|
+
return config;
|
|
744
|
+
}
|
|
745
|
+
if (typeof config === "string") {
|
|
746
|
+
switch (config) {
|
|
747
|
+
case "browser":
|
|
748
|
+
return createBrowserHistory();
|
|
749
|
+
case "hash":
|
|
750
|
+
return createHashHistory();
|
|
751
|
+
case "memory":
|
|
752
|
+
return createMemoryHistory("/");
|
|
753
|
+
default: {
|
|
754
|
+
const _exhaustive = config;
|
|
755
|
+
throw new TypeError(`Unknown history kind: ${_exhaustive}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return createMemoryHistory(config.initial ?? "/");
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/core/auth-guard.ts
|
|
763
|
+
var isDevMode = () => {
|
|
764
|
+
try {
|
|
765
|
+
return import.meta.env?.MODE === "development";
|
|
766
|
+
} catch {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
var resolveRedirectTarget = (target, context, fallback) => {
|
|
771
|
+
if (target === void 0) return fallback;
|
|
772
|
+
return typeof target === "function" ? target(context) : target;
|
|
773
|
+
};
|
|
774
|
+
var isPathAllowed = (pathname, allowedPaths) => {
|
|
775
|
+
const normalized = normalizePathname(pathname);
|
|
776
|
+
for (const pattern of allowedPaths) {
|
|
777
|
+
const normalizedPattern = normalizePathname(pattern);
|
|
778
|
+
if (normalized === normalizedPattern) return true;
|
|
779
|
+
if (matchPath(normalizedPattern, normalized).matched) return true;
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
782
|
+
};
|
|
783
|
+
var redirectIfNeeded = (pathname, target, history, allowedPaths, label) => {
|
|
784
|
+
const normalizedTarget = normalizePathname(target);
|
|
785
|
+
const normalizedCurrent = normalizePathname(pathname);
|
|
786
|
+
if (normalizedTarget === normalizedCurrent) {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
if (!isPathAllowed(normalizedTarget, allowedPaths)) {
|
|
790
|
+
const allowed = allowedPaths.join(", ");
|
|
791
|
+
const message = `[router] authorizationGuard ${label}: redirect target "${target}" is not allowed (${allowed}).`;
|
|
792
|
+
if (isDevMode()) {
|
|
793
|
+
throw new Error(message);
|
|
794
|
+
}
|
|
795
|
+
console.error(message);
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
history.replace(target);
|
|
799
|
+
return false;
|
|
800
|
+
};
|
|
801
|
+
var runAuthorizationGuard = (pathname, options, history) => {
|
|
802
|
+
const authorized = options.isAuthorized();
|
|
803
|
+
const redirectContext = (authorized2) => ({
|
|
804
|
+
pathname,
|
|
805
|
+
authorized: authorized2
|
|
806
|
+
});
|
|
807
|
+
if (!authorized) {
|
|
808
|
+
if (isPathAllowed(pathname, options.allowedUnauthorizedPaths)) {
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
const target = resolveRedirectTarget(
|
|
812
|
+
options.redirectTo,
|
|
813
|
+
redirectContext(false),
|
|
814
|
+
"/login"
|
|
815
|
+
);
|
|
816
|
+
return redirectIfNeeded(
|
|
817
|
+
pathname,
|
|
818
|
+
target,
|
|
819
|
+
history,
|
|
820
|
+
options.allowedUnauthorizedPaths,
|
|
821
|
+
"redirectTo"
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
if (options.allowedAuthorizedPaths?.length) {
|
|
825
|
+
if (!isPathAllowed(pathname, options.allowedAuthorizedPaths)) {
|
|
826
|
+
const target = resolveRedirectTarget(
|
|
827
|
+
options.redirectWhenAuthorized,
|
|
828
|
+
redirectContext(true),
|
|
829
|
+
"/"
|
|
830
|
+
);
|
|
831
|
+
return redirectIfNeeded(
|
|
832
|
+
pathname,
|
|
833
|
+
target,
|
|
834
|
+
history,
|
|
835
|
+
options.allowedAuthorizedPaths,
|
|
836
|
+
"redirectWhenAuthorized"
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (options.redirectWhenAuthorized !== void 0 && isPathAllowed(pathname, options.allowedUnauthorizedPaths)) {
|
|
841
|
+
const target = resolveRedirectTarget(
|
|
842
|
+
options.redirectWhenAuthorized,
|
|
843
|
+
redirectContext(true),
|
|
844
|
+
"/"
|
|
845
|
+
);
|
|
846
|
+
const normalizedTarget = normalizePathname(target);
|
|
847
|
+
const normalizedCurrent = normalizePathname(pathname);
|
|
848
|
+
if (normalizedTarget === normalizedCurrent) return true;
|
|
849
|
+
if (options.allowedAuthorizedPaths?.length && !isPathAllowed(normalizedTarget, options.allowedAuthorizedPaths)) {
|
|
850
|
+
const message = `[router] authorizationGuard redirectWhenAuthorized: "${target}" is not in allowedAuthorizedPaths.`;
|
|
851
|
+
if (isDevMode()) {
|
|
852
|
+
throw new Error(message);
|
|
853
|
+
}
|
|
854
|
+
console.error(message);
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
history.replace(target);
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
return true;
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// src/core/guard-registry.ts
|
|
864
|
+
var guards = [];
|
|
865
|
+
var registerGuard = (options) => {
|
|
866
|
+
guards.push(options);
|
|
867
|
+
return () => {
|
|
868
|
+
const index = guards.indexOf(options);
|
|
869
|
+
if (index !== -1) guards.splice(index, 1);
|
|
870
|
+
};
|
|
871
|
+
};
|
|
872
|
+
var runRouteGuards = (history, matchedRoute, query) => {
|
|
873
|
+
for (const guard of guards) {
|
|
874
|
+
if (guard.route !== matchedRoute) continue;
|
|
875
|
+
if (guard.canOpen()) return true;
|
|
876
|
+
const otherwiseState = getRouteState(guard.otherwise);
|
|
877
|
+
const params = otherwiseState.$params.peek() ?? {};
|
|
878
|
+
const target = resolveRoutePath(guard.otherwise, params, { query });
|
|
879
|
+
history.replace(target);
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
return true;
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// src/core/route-config.ts
|
|
886
|
+
var isRouteViewEntry = (item) => "routeView" in item && !("layoutView" in item);
|
|
887
|
+
var toRouteConfigs = (definitions) => {
|
|
888
|
+
const walk2 = (items, parentPath = "") => {
|
|
889
|
+
const result = [];
|
|
890
|
+
for (const item of items) {
|
|
891
|
+
const path = parentPath ? `${parentPath}/${item.path}`.replace(/\/+/g, "/") : item.path;
|
|
892
|
+
if ("route" in item && !("routeView" in item) && !("layoutView" in item)) {
|
|
893
|
+
const childConfigs = "children" in item && item.children?.length ? walk2(item.children, path) : void 0;
|
|
894
|
+
result.push({
|
|
895
|
+
path,
|
|
896
|
+
route: item.route,
|
|
897
|
+
...childConfigs?.length ? { children: childConfigs } : {}
|
|
898
|
+
});
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
if ("layoutView" in item) {
|
|
902
|
+
const { layoutView, children, path: segment } = item;
|
|
903
|
+
assertLayoutPage(
|
|
904
|
+
layoutView,
|
|
905
|
+
`routes[{ path: "${segment}", layoutView }]`
|
|
906
|
+
);
|
|
907
|
+
if (!children?.length) {
|
|
908
|
+
throw new TypeError(
|
|
909
|
+
`Layout route "${path}" requires "children" with nested route views.`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
result.push({
|
|
913
|
+
path,
|
|
914
|
+
route: layoutView,
|
|
915
|
+
children: walk2(children, path)
|
|
916
|
+
});
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (!isRouteViewEntry(item)) {
|
|
920
|
+
throw new TypeError(
|
|
921
|
+
`Route at "${path}" must define "routeView", "layoutView", or "route".`
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
const { routeView } = item;
|
|
925
|
+
assertPage(routeView, "createRouter");
|
|
926
|
+
const routeState = getPageState(routeView);
|
|
927
|
+
if (!routeState.view && !routeState.viewLoader) {
|
|
928
|
+
throw new TypeError(
|
|
929
|
+
`Route at "${path}" must define a view via createRouteView(), createLazyRouteView(), or createLayoutView().`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (isLayoutPage(routeView)) {
|
|
933
|
+
throw new TypeError(
|
|
934
|
+
`Route "${path}": use "layoutView" instead of "routeView" for createLayoutView() instances.`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
result.push({ path, route: routeView });
|
|
938
|
+
}
|
|
939
|
+
return result;
|
|
940
|
+
};
|
|
941
|
+
return walk2(definitions);
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// src/core/build-named-routes.ts
|
|
945
|
+
var requireEntryName = (entry, pathHint) => {
|
|
946
|
+
const name = entry.name;
|
|
947
|
+
if (!name) {
|
|
948
|
+
throw new TypeError(
|
|
949
|
+
`Route at "${pathHint}" must define "name" in createRouter({ routes }) for typed router.routes.`
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
return name;
|
|
953
|
+
};
|
|
954
|
+
var register = (bucket, key, route) => {
|
|
955
|
+
if (bucket[key]) {
|
|
956
|
+
throw new Error(`Duplicate route name "${key}" in createRouter({ routes }).`);
|
|
957
|
+
}
|
|
958
|
+
bucket[key] = route;
|
|
959
|
+
};
|
|
960
|
+
var walk = (entries, bucket, parentPath = "/") => {
|
|
961
|
+
for (const entry of entries) {
|
|
962
|
+
const pathHint = `${parentPath}${entry.path}`;
|
|
963
|
+
if ("routeView" in entry && !("layoutView" in entry)) {
|
|
964
|
+
register(bucket, requireEntryName(entry, pathHint), entry.routeView);
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if ("route" in entry && !("routeView" in entry) && !("layoutView" in entry)) {
|
|
968
|
+
register(bucket, requireEntryName(entry, pathHint), entry.route);
|
|
969
|
+
if ("children" in entry && entry.children?.length) {
|
|
970
|
+
walk(entry.children, bucket, pathHint);
|
|
971
|
+
}
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if ("layoutView" in entry && "children" in entry) {
|
|
975
|
+
register(bucket, requireEntryName(entry, pathHint), entry.layoutView);
|
|
976
|
+
const { children } = entry;
|
|
977
|
+
if (children?.length) {
|
|
978
|
+
walk(children, bucket, pathHint);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
var buildNamedRoutes = (entries) => {
|
|
984
|
+
const bucket = {};
|
|
985
|
+
walk(entries, bucket);
|
|
986
|
+
return bucket;
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
// src/core/resolve-route-view.ts
|
|
990
|
+
var emptyOutlet = () => null;
|
|
991
|
+
var resolvePageLoadingView = (page) => {
|
|
992
|
+
const state = getPageState(page);
|
|
993
|
+
if (state.loadingView) return state.loadingView;
|
|
994
|
+
if (state.view) {
|
|
995
|
+
return ({ params, query }) => state.view({ params, query, outlet: emptyOutlet, data: null });
|
|
996
|
+
}
|
|
997
|
+
throw new TypeError(
|
|
998
|
+
`Route view "${page.name ?? "anonymous"}": pass loadingView or view for router loadingView.`
|
|
999
|
+
);
|
|
1000
|
+
};
|
|
1001
|
+
var resolvePageErrorView = (page) => {
|
|
1002
|
+
const state = getPageState(page);
|
|
1003
|
+
if (state.errorView) return state.errorView;
|
|
1004
|
+
if (state.view) {
|
|
1005
|
+
return ({ error, params, query }) => state.view({ params, query, outlet: emptyOutlet, data: null, error });
|
|
1006
|
+
}
|
|
1007
|
+
throw new TypeError(
|
|
1008
|
+
`Route view "${page.name ?? "anonymous"}": pass errorView or view for router errorView.`
|
|
1009
|
+
);
|
|
1010
|
+
};
|
|
1011
|
+
var resolvePageNotFoundView = (page) => {
|
|
1012
|
+
const state = getPageState(page);
|
|
1013
|
+
if (!state.view) {
|
|
1014
|
+
throw new TypeError(
|
|
1015
|
+
`Route view "${page.name ?? "anonymous"}": pass view for router notFoundView.`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
return (context) => state.view(context);
|
|
1019
|
+
};
|
|
1020
|
+
var resolveLoadingViewOption = (option) => {
|
|
1021
|
+
if (!option) return void 0;
|
|
1022
|
+
if (typeof option === "function") return option;
|
|
1023
|
+
return resolvePageLoadingView(option);
|
|
1024
|
+
};
|
|
1025
|
+
var resolveErrorViewOption = (option) => {
|
|
1026
|
+
if (!option) return void 0;
|
|
1027
|
+
if (typeof option === "function") return option;
|
|
1028
|
+
return resolvePageErrorView(option);
|
|
1029
|
+
};
|
|
1030
|
+
var resolveNotFoundViewOption = (option) => {
|
|
1031
|
+
if (!option) return void 0;
|
|
1032
|
+
if (typeof option === "function") return option;
|
|
1033
|
+
return resolvePageNotFoundView(option);
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
// src/core/router-view.ts
|
|
1037
|
+
var entryPage = (def) => "layoutView" in def ? def.layoutView : "routeView" in def ? def.routeView : void 0;
|
|
1038
|
+
var pickLoadingView = (branch, leafIndex, globalLoading) => {
|
|
1039
|
+
const leaf = branch[leafIndex].page;
|
|
1040
|
+
if (leaf.loadingView) return leaf.loadingView;
|
|
1041
|
+
for (let i = leafIndex - 1; i >= 0; i -= 1) {
|
|
1042
|
+
const candidate = branch[i].page.loadingView;
|
|
1043
|
+
if (candidate) return candidate;
|
|
1044
|
+
}
|
|
1045
|
+
return globalLoading;
|
|
1046
|
+
};
|
|
1047
|
+
var pickErrorView = (branch, leafIndex, globalError) => {
|
|
1048
|
+
const leaf = branch[leafIndex].page;
|
|
1049
|
+
if (leaf.errorView) return leaf.errorView;
|
|
1050
|
+
for (let i = leafIndex - 1; i >= 0; i -= 1) {
|
|
1051
|
+
const candidate = branch[i].page.errorView;
|
|
1052
|
+
if (candidate) return candidate;
|
|
1053
|
+
}
|
|
1054
|
+
return globalError;
|
|
1055
|
+
};
|
|
1056
|
+
var renderLoading = (page, branch, leafIndex, globalLoading) => {
|
|
1057
|
+
const view = pickLoadingView(branch, leafIndex, globalLoading);
|
|
1058
|
+
if (!view) return null;
|
|
1059
|
+
const params = page.$params.value() ?? {};
|
|
1060
|
+
const query = page.$query.value() ?? {};
|
|
1061
|
+
return view({ params, query });
|
|
1062
|
+
};
|
|
1063
|
+
var renderError = (page, error, branch, leafIndex, globalError) => {
|
|
1064
|
+
const view = pickErrorView(branch, leafIndex, globalError);
|
|
1065
|
+
if (!view) return null;
|
|
1066
|
+
const params = page.$params.value() ?? {};
|
|
1067
|
+
const query = page.$query.value() ?? {};
|
|
1068
|
+
return view({ error, params, query });
|
|
1069
|
+
};
|
|
1070
|
+
var collectChildPageNodes = (definitions) => {
|
|
1071
|
+
const nodes = [];
|
|
1072
|
+
for (const def of definitions) {
|
|
1073
|
+
const page = entryPage(def);
|
|
1074
|
+
if (!page) continue;
|
|
1075
|
+
assertPage(page, "createRouter");
|
|
1076
|
+
nodes.push({
|
|
1077
|
+
page,
|
|
1078
|
+
children: "children" in def && def.children ? collectChildPageNodes(def.children) : void 0
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
return nodes;
|
|
1082
|
+
};
|
|
1083
|
+
var collectPageRoots = (definitions) => {
|
|
1084
|
+
const roots = [];
|
|
1085
|
+
for (const def of definitions) {
|
|
1086
|
+
const page = entryPage(def);
|
|
1087
|
+
if (page) {
|
|
1088
|
+
assertPage(page, "createRouter");
|
|
1089
|
+
roots.push({
|
|
1090
|
+
page,
|
|
1091
|
+
children: "children" in def && def.children ? collectChildPageNodes(def.children) : void 0
|
|
1092
|
+
});
|
|
1093
|
+
} else if ("children" in def && def.children?.length) {
|
|
1094
|
+
roots.push(...collectPageRoots(def.children));
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return roots;
|
|
1098
|
+
};
|
|
1099
|
+
var findBranch = (entries, activePage) => {
|
|
1100
|
+
for (const entry of entries) {
|
|
1101
|
+
if (entry.page === activePage) return [entry];
|
|
1102
|
+
if (entry.children?.length) {
|
|
1103
|
+
const childBranch = findBranch(entry.children, activePage);
|
|
1104
|
+
if (childBranch) return [entry, ...childBranch];
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return null;
|
|
1108
|
+
};
|
|
1109
|
+
var layoutBeforeLoadFailed = (branch, leafIndex) => {
|
|
1110
|
+
for (let i = 0; i < leafIndex; i += 1) {
|
|
1111
|
+
const page = branch[i].page;
|
|
1112
|
+
if (!isLayoutPage(page)) continue;
|
|
1113
|
+
const state = getPageState(page);
|
|
1114
|
+
if (state.viewLoader && !state.view) {
|
|
1115
|
+
if (state.$viewPending.value() || state.$viewError.value()) return true;
|
|
1116
|
+
}
|
|
1117
|
+
if (resolveBeforeLoad(state) && state.$error.value()) return true;
|
|
1118
|
+
if (resolveBeforeLoad(state) && state.$pending.value()) return true;
|
|
1119
|
+
}
|
|
1120
|
+
return false;
|
|
1121
|
+
};
|
|
1122
|
+
var renderPageView = (page, outlet, branch, leafIndex, globalLoading, globalError) => {
|
|
1123
|
+
const state = getPageState(page);
|
|
1124
|
+
const params = page.$params.value() ?? {};
|
|
1125
|
+
const query = page.$query.value() ?? {};
|
|
1126
|
+
const data = page.$data?.value() ?? null;
|
|
1127
|
+
if (state.viewLoader && !state.view) {
|
|
1128
|
+
if (state.$viewPending.value()) {
|
|
1129
|
+
return renderLoading(page, branch, leafIndex, globalLoading) ?? null;
|
|
1130
|
+
}
|
|
1131
|
+
if (state.$viewError.value()) {
|
|
1132
|
+
return renderError(page, state.$viewError.value(), branch, leafIndex, globalError) ?? null;
|
|
1133
|
+
}
|
|
1134
|
+
return renderLoading(page, branch, leafIndex, globalLoading) ?? null;
|
|
1135
|
+
}
|
|
1136
|
+
if (resolveBeforeLoad(state) && state.$pending.value()) {
|
|
1137
|
+
return renderLoading(page, branch, leafIndex, globalLoading) ?? null;
|
|
1138
|
+
}
|
|
1139
|
+
if (state.$error.value()) {
|
|
1140
|
+
return renderError(page, state.$error.value(), branch, leafIndex, globalError) ?? null;
|
|
1141
|
+
}
|
|
1142
|
+
if (!state.view) return null;
|
|
1143
|
+
return state.view({ params, query, outlet, data });
|
|
1144
|
+
};
|
|
1145
|
+
var renderBranch = (branch, index, globalLoading, globalError) => {
|
|
1146
|
+
const node = branch[index];
|
|
1147
|
+
if (!node) return null;
|
|
1148
|
+
const leafIndex = branch.length - 1;
|
|
1149
|
+
if (index > 0 && layoutBeforeLoadFailed(branch, index)) {
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
const outlet = () => index + 1 < branch.length ? renderBranch(branch, index + 1, globalLoading, globalError) : null;
|
|
1153
|
+
return renderPageView(
|
|
1154
|
+
node.page,
|
|
1155
|
+
outlet,
|
|
1156
|
+
branch,
|
|
1157
|
+
leafIndex,
|
|
1158
|
+
globalLoading,
|
|
1159
|
+
globalError
|
|
1160
|
+
);
|
|
1161
|
+
};
|
|
1162
|
+
var createRouterViewComponent = (router, options) => {
|
|
1163
|
+
const pageRoots = collectPageRoots(options.routes);
|
|
1164
|
+
const globalLoading = options.loadingView;
|
|
1165
|
+
const globalError = options.errorView;
|
|
1166
|
+
const notFoundView = options.notFoundView;
|
|
1167
|
+
return () => {
|
|
1168
|
+
const activeRoutes = router.$activeRoutes.value();
|
|
1169
|
+
const activePage = activeRoutes.at(-1);
|
|
1170
|
+
if (!activePage || !isPage(activePage)) {
|
|
1171
|
+
return notFoundView?.({ params: {}, query: {}, outlet: () => null, data: null }) ?? null;
|
|
1172
|
+
}
|
|
1173
|
+
const branch = findBranch(pageRoots, activePage);
|
|
1174
|
+
if (!branch?.length) {
|
|
1175
|
+
return notFoundView?.({ params: {}, query: {}, outlet: () => null, data: null }) ?? null;
|
|
1176
|
+
}
|
|
1177
|
+
return renderBranch(branch, 0, globalLoading, globalError);
|
|
1178
|
+
};
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
// src/core/router.ts
|
|
1182
|
+
var createRouterModel = (options) => {
|
|
1183
|
+
const globalLoadingView = resolveLoadingViewOption(options.loadingView);
|
|
1184
|
+
const globalErrorView = resolveErrorViewOption(options.errorView);
|
|
1185
|
+
const globalNotFoundView = resolveNotFoundViewOption(options.notFoundView);
|
|
1186
|
+
const routeConfigs = toRouteConfigs(options.routes);
|
|
1187
|
+
const flatRoutes = flattenRouteTree(routeConfigs);
|
|
1188
|
+
const allRoutes = flatRoutes.map((entry) => entry.route);
|
|
1189
|
+
const allPages = allRoutes.filter(isPage);
|
|
1190
|
+
const $path = signal("/");
|
|
1191
|
+
const $query = signal({});
|
|
1192
|
+
const $fullPath = signal("/");
|
|
1193
|
+
const $activeRoute = signal(null);
|
|
1194
|
+
const $activeRoutes = signal([]);
|
|
1195
|
+
const $params = signal({});
|
|
1196
|
+
const $activePage = computed(() => {
|
|
1197
|
+
const routes2 = $activeRoutes.value();
|
|
1198
|
+
for (let i = routes2.length - 1; i >= 0; i -= 1) {
|
|
1199
|
+
const route = routes2[i];
|
|
1200
|
+
if (isPage(route)) return route;
|
|
1201
|
+
}
|
|
1202
|
+
return null;
|
|
1203
|
+
});
|
|
1204
|
+
const $matched = computed(() => [...$activeRoutes.value()]);
|
|
1205
|
+
const $pending = computed(() => {
|
|
1206
|
+
const page = $activePage.value();
|
|
1207
|
+
return page ? getPageState(page).$pending.value() : false;
|
|
1208
|
+
});
|
|
1209
|
+
const $error = computed(() => {
|
|
1210
|
+
const page = $activePage.value();
|
|
1211
|
+
return page ? getPageState(page).$error.value() : null;
|
|
1212
|
+
});
|
|
1213
|
+
let started = false;
|
|
1214
|
+
let unlisten = null;
|
|
1215
|
+
let syncing = false;
|
|
1216
|
+
let syncQueued = false;
|
|
1217
|
+
let navigationId = 0;
|
|
1218
|
+
const closeAllRoutes = () => {
|
|
1219
|
+
cancelPageLoads(allPages);
|
|
1220
|
+
for (const route of allRoutes) {
|
|
1221
|
+
applyRouteClosed(getRouteState(route));
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
const routes = buildNamedRoutes(options.routes);
|
|
1225
|
+
let router;
|
|
1226
|
+
router = {
|
|
1227
|
+
history: options.history,
|
|
1228
|
+
flatRoutes,
|
|
1229
|
+
options,
|
|
1230
|
+
loadingView: globalLoadingView,
|
|
1231
|
+
errorView: globalErrorView,
|
|
1232
|
+
navigationId: 0,
|
|
1233
|
+
View: (() => null),
|
|
1234
|
+
$path,
|
|
1235
|
+
$query,
|
|
1236
|
+
$fullPath,
|
|
1237
|
+
$activeRoute,
|
|
1238
|
+
$activePage,
|
|
1239
|
+
$activeRoutes,
|
|
1240
|
+
$matched,
|
|
1241
|
+
$params,
|
|
1242
|
+
$pending,
|
|
1243
|
+
$error,
|
|
1244
|
+
routes,
|
|
1245
|
+
start() {
|
|
1246
|
+
if (started) return;
|
|
1247
|
+
started = true;
|
|
1248
|
+
for (const entry of flatRoutes) {
|
|
1249
|
+
bindRouteToRouter(entry.route, router, entry.path);
|
|
1250
|
+
}
|
|
1251
|
+
unlisten = options.history.listen(() => {
|
|
1252
|
+
router.sync();
|
|
1253
|
+
});
|
|
1254
|
+
router.sync();
|
|
1255
|
+
},
|
|
1256
|
+
stop() {
|
|
1257
|
+
if (!started) return;
|
|
1258
|
+
started = false;
|
|
1259
|
+
unlisten?.();
|
|
1260
|
+
unlisten = null;
|
|
1261
|
+
closeAllRoutes();
|
|
1262
|
+
$activeRoute.set(null);
|
|
1263
|
+
$activeRoutes.set([]);
|
|
1264
|
+
$params.set({});
|
|
1265
|
+
},
|
|
1266
|
+
go(path, opts) {
|
|
1267
|
+
if (opts?.replace) options.history.replace(path);
|
|
1268
|
+
else options.history.push(path);
|
|
1269
|
+
},
|
|
1270
|
+
navigate(path, opts) {
|
|
1271
|
+
router.go(path, opts);
|
|
1272
|
+
},
|
|
1273
|
+
replace(path) {
|
|
1274
|
+
options.history.replace(path);
|
|
1275
|
+
},
|
|
1276
|
+
back() {
|
|
1277
|
+
options.history.back();
|
|
1278
|
+
},
|
|
1279
|
+
forward() {
|
|
1280
|
+
options.history.forward();
|
|
1281
|
+
},
|
|
1282
|
+
reload() {
|
|
1283
|
+
router.sync();
|
|
1284
|
+
},
|
|
1285
|
+
resolve(route, params, opts) {
|
|
1286
|
+
const state = getRouteState(route);
|
|
1287
|
+
if (!state.pathTemplate) {
|
|
1288
|
+
throw new Error("Route is not registered in a router");
|
|
1289
|
+
}
|
|
1290
|
+
return buildRouteLocation(
|
|
1291
|
+
state.pathTemplate,
|
|
1292
|
+
params ?? {},
|
|
1293
|
+
opts?.query
|
|
1294
|
+
);
|
|
1295
|
+
},
|
|
1296
|
+
isActive(route) {
|
|
1297
|
+
return $activeRoutes.value().includes(route);
|
|
1298
|
+
},
|
|
1299
|
+
view() {
|
|
1300
|
+
return router.View;
|
|
1301
|
+
},
|
|
1302
|
+
closeRoute(route) {
|
|
1303
|
+
if (!$activeRoutes.peek().includes(route)) return;
|
|
1304
|
+
options.history.push("/");
|
|
1305
|
+
},
|
|
1306
|
+
sync() {
|
|
1307
|
+
if (syncing) {
|
|
1308
|
+
syncQueued = true;
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
syncing = true;
|
|
1312
|
+
try {
|
|
1313
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
1314
|
+
const currentNavigationId = ++navigationId;
|
|
1315
|
+
router.navigationId = currentNavigationId;
|
|
1316
|
+
const location = options.history.getLocation();
|
|
1317
|
+
const { pathname, search, query } = parseLocation(location);
|
|
1318
|
+
const fullPath = search ? `${pathname}${search}` : pathname;
|
|
1319
|
+
$path.set(pathname);
|
|
1320
|
+
$query.set(query);
|
|
1321
|
+
$fullPath.set(fullPath);
|
|
1322
|
+
if (options.authorizationGuard && !runAuthorizationGuard(pathname, options.authorizationGuard, options.history)) {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
const match = matchRouteChain(pathname, flatRoutes);
|
|
1326
|
+
if (!match) {
|
|
1327
|
+
cancelPageLoads(allPages);
|
|
1328
|
+
$activeRoute.set(null);
|
|
1329
|
+
$activeRoutes.set([]);
|
|
1330
|
+
$params.set({});
|
|
1331
|
+
closeAllRoutes();
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
if (!runRouteGuards(options.history, match.leaf.route, query)) {
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
const activeSet = new Set(match.chain.map((entry) => entry.route));
|
|
1338
|
+
$activeRoute.set(match.leaf.route);
|
|
1339
|
+
$activeRoutes.set(match.chain.map((entry) => entry.route));
|
|
1340
|
+
$params.set(match.leafParams);
|
|
1341
|
+
for (const route of allRoutes) {
|
|
1342
|
+
const state = getRouteState(route);
|
|
1343
|
+
if (!activeSet.has(route)) {
|
|
1344
|
+
applyRouteClosed(state);
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
const entry = match.chain.find((item) => item.route === route);
|
|
1348
|
+
const entryParams = matchParamsForEntry(entry, pathname);
|
|
1349
|
+
applyRouteOpened(state, {
|
|
1350
|
+
params: entryParams,
|
|
1351
|
+
query: rawQueryToTyped(query),
|
|
1352
|
+
path: pathname,
|
|
1353
|
+
fullPath
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
void runBeforeLoadChain(match.chain, pathname, query, currentNavigationId);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
} finally {
|
|
1360
|
+
syncing = false;
|
|
1361
|
+
if (syncQueued) {
|
|
1362
|
+
syncQueued = false;
|
|
1363
|
+
router.sync();
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
for (const entry of flatRoutes) {
|
|
1369
|
+
bindRouteToRouter(entry.route, router, entry.path);
|
|
1370
|
+
}
|
|
1371
|
+
return Object.assign(router, {
|
|
1372
|
+
View: createRouterViewComponent(router, {
|
|
1373
|
+
routes: options.routes,
|
|
1374
|
+
notFoundView: globalNotFoundView,
|
|
1375
|
+
loadingView: globalLoadingView,
|
|
1376
|
+
errorView: globalErrorView
|
|
1377
|
+
})
|
|
1378
|
+
});
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
// src/core/create-router.ts
|
|
1382
|
+
var createRouter = (options) => {
|
|
1383
|
+
const { history: historyConfig, routes, ...rest } = options;
|
|
1384
|
+
const history = resolveHistory(historyConfig);
|
|
1385
|
+
return createRouterModel({ ...rest, history, routes });
|
|
1386
|
+
};
|
|
1387
|
+
var createRouter2 = (options) => attachRouterQueryParams(
|
|
1388
|
+
createRouter(options)
|
|
1389
|
+
);
|
|
1390
|
+
var Link = (props) => {
|
|
1391
|
+
const { to, href, params, query, replace, children } = props;
|
|
1392
|
+
const resolvedHref = () => {
|
|
1393
|
+
if (href) return href;
|
|
1394
|
+
if (!to) return "#";
|
|
1395
|
+
const state = getRouteState(to);
|
|
1396
|
+
if (!state.router || !state.pathTemplate) return "#";
|
|
1397
|
+
return state.router.resolve(to, params ?? {}, { query });
|
|
1398
|
+
};
|
|
1399
|
+
const onClick = (event) => {
|
|
1400
|
+
if (!to) return;
|
|
1401
|
+
event.preventDefault();
|
|
1402
|
+
if (params !== void 0) {
|
|
1403
|
+
to.go(params, { query, replace });
|
|
1404
|
+
} else {
|
|
1405
|
+
to.go(void 0, { query, replace });
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
return h(
|
|
1409
|
+
"a",
|
|
1410
|
+
{
|
|
1411
|
+
href: resolvedHref,
|
|
1412
|
+
onClick: to ? onClick : void 0
|
|
1413
|
+
},
|
|
1414
|
+
children ?? null
|
|
1415
|
+
);
|
|
1416
|
+
};
|
|
1417
|
+
var NavLink = (props) => {
|
|
1418
|
+
const {
|
|
1419
|
+
to,
|
|
1420
|
+
href,
|
|
1421
|
+
params,
|
|
1422
|
+
query,
|
|
1423
|
+
replace,
|
|
1424
|
+
children,
|
|
1425
|
+
activeClass = "active",
|
|
1426
|
+
class: className
|
|
1427
|
+
} = props;
|
|
1428
|
+
const resolvedHref = () => {
|
|
1429
|
+
if (href) return href;
|
|
1430
|
+
if (!to) return "#";
|
|
1431
|
+
const state = getRouteState(to);
|
|
1432
|
+
if (!state.router || !state.pathTemplate) return "#";
|
|
1433
|
+
return state.router.resolve(to, params ?? {}, { query });
|
|
1434
|
+
};
|
|
1435
|
+
const classProp = () => {
|
|
1436
|
+
const active = to?.$isOpened.value() ?? false;
|
|
1437
|
+
return cx(className, active && activeClass);
|
|
1438
|
+
};
|
|
1439
|
+
const onClick = (event) => {
|
|
1440
|
+
if (!to) return;
|
|
1441
|
+
event.preventDefault();
|
|
1442
|
+
if (params !== void 0) {
|
|
1443
|
+
to.go(params, { query, replace });
|
|
1444
|
+
} else {
|
|
1445
|
+
to.go(void 0, { query, replace });
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
const ariaCurrent = () => to?.$isOpened.value() ? "page" : void 0;
|
|
1449
|
+
return h(
|
|
1450
|
+
"a",
|
|
1451
|
+
{
|
|
1452
|
+
href: resolvedHref,
|
|
1453
|
+
class: classProp,
|
|
1454
|
+
"aria-current": ariaCurrent,
|
|
1455
|
+
onClick: to ? onClick : void 0
|
|
1456
|
+
},
|
|
1457
|
+
children ?? null
|
|
1458
|
+
);
|
|
1459
|
+
};
|
|
1460
|
+
var ROUTER_KEY = /* @__PURE__ */ Symbol.for("echojs.router");
|
|
1461
|
+
var isRouterLike = (value) => typeof value === "object" && value !== null && typeof value.View === "function" && typeof value.start === "function";
|
|
1462
|
+
var createRouterProvider = (router, options = {}) => ({
|
|
1463
|
+
name: "router",
|
|
1464
|
+
setup(app) {
|
|
1465
|
+
app.provide?.(ROUTER_KEY, router);
|
|
1466
|
+
},
|
|
1467
|
+
resolveRoot() {
|
|
1468
|
+
if (options.autoStart !== false) {
|
|
1469
|
+
router.start();
|
|
1470
|
+
}
|
|
1471
|
+
const RootView = createView(() => router.View, "EchoRouterRoot");
|
|
1472
|
+
return RootView();
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
var routerPlugin = createRouterProvider;
|
|
1476
|
+
|
|
1477
|
+
// src/core/create-routes.ts
|
|
1478
|
+
function createRoutes(routes) {
|
|
1479
|
+
return routes;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// src/operators/guard.ts
|
|
1483
|
+
var guardRoute = (options) => registerGuard(options);
|
|
1484
|
+
|
|
1485
|
+
// src/operators/redirect.ts
|
|
1486
|
+
var redirect = (options) => {
|
|
1487
|
+
const { from, to, mapParams, mapQuery } = options;
|
|
1488
|
+
return from.opened.subscribe((payload) => {
|
|
1489
|
+
const params = mapParams ? mapParams(payload.params) : payload.params;
|
|
1490
|
+
const query = mapQuery ? mapQuery(payload.query) : payload.query;
|
|
1491
|
+
to.go(params, { query, replace: true });
|
|
1492
|
+
});
|
|
1493
|
+
};
|
|
1494
|
+
var chainRoute = (options) => {
|
|
1495
|
+
const { route, beforeOpen } = options;
|
|
1496
|
+
const $isOpened = signal(false);
|
|
1497
|
+
const $pending = signal(false);
|
|
1498
|
+
const $error = signal(null);
|
|
1499
|
+
const $params = signal(null);
|
|
1500
|
+
const $query = signal({});
|
|
1501
|
+
const $result = signal(null);
|
|
1502
|
+
let runId = 0;
|
|
1503
|
+
const reset = () => {
|
|
1504
|
+
$isOpened.set(false);
|
|
1505
|
+
$pending.set(false);
|
|
1506
|
+
$error.set(null);
|
|
1507
|
+
$params.set(null);
|
|
1508
|
+
$query.set({});
|
|
1509
|
+
$result.set(null);
|
|
1510
|
+
};
|
|
1511
|
+
route.opened.subscribe((payload) => {
|
|
1512
|
+
const id = ++runId;
|
|
1513
|
+
reset();
|
|
1514
|
+
$pending.set(true);
|
|
1515
|
+
$params.set(payload.params);
|
|
1516
|
+
$query.set(payload.query);
|
|
1517
|
+
Promise.resolve(beforeOpen({ params: payload.params, query: payload.query })).then((result) => {
|
|
1518
|
+
if (id !== runId) return;
|
|
1519
|
+
$pending.set(false);
|
|
1520
|
+
$error.set(null);
|
|
1521
|
+
$result.set(result);
|
|
1522
|
+
$isOpened.set(true);
|
|
1523
|
+
}).catch((error) => {
|
|
1524
|
+
if (id !== runId) return;
|
|
1525
|
+
$pending.set(false);
|
|
1526
|
+
$error.set(error);
|
|
1527
|
+
$isOpened.set(false);
|
|
1528
|
+
});
|
|
1529
|
+
});
|
|
1530
|
+
route.closed.subscribe(() => {
|
|
1531
|
+
runId += 1;
|
|
1532
|
+
reset();
|
|
1533
|
+
});
|
|
1534
|
+
return {
|
|
1535
|
+
$isOpened,
|
|
1536
|
+
$pending,
|
|
1537
|
+
$error,
|
|
1538
|
+
$params,
|
|
1539
|
+
$query,
|
|
1540
|
+
$result
|
|
1541
|
+
};
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1544
|
+
export { Link, NavLink, ROUTER_KEY, assertPage, buildNamedRoutes, buildPath, chainRoute, createBrowserHistory, createHashHistory, createLayoutView, createLazyRouteView, createMemoryHistory, createRoute, createRouteView, createRouter2 as createRouter, createRouter as createRouterCore, createRouterProvider, createRoutes, flattenRouteTree, guardRoute, isLayoutPage, isLazyRouteView, isPage, isRouterLike, joinLocation, joinRoutePaths, matchPath, matchRouteChain, normalizePathname, parseQuery, parseQueryValues, redirect, resolveHistory, routerPlugin, splitLocation, stringifyQuery };
|
|
1545
|
+
//# sourceMappingURL=index.js.map
|
|
1546
|
+
//# sourceMappingURL=index.js.map
|