@stealthscale/provider-router 0.1.0 → 0.4.1
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 +77 -13
- package/dist/cyclonedx/bom.json +1 -1
- package/dist/index.d.ts +294 -203
- package/dist/index.js +2 -689
- package/dist/pages/link.js +162 -0
- package/dist/pages/link.props.js +3 -0
- package/dist/pages/pages.json +20 -0
- package/dist/shared-D7o15Y7B.js +813 -0
- package/locales/en/specimen/route-link.json +16 -0
- package/package.json +31 -24
- package/dist/rolldown-runtime-BBjsoOtd.js +0 -27
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { Link, Navigate, Outlet, Outlet as Outlet$1, RouterContextProvider, RouterProvider, createLink, createMemoryHistory, createRootRoute, createRootRouteWithContext, createRoute, createRoute as createRoute$1, createRouter, isNotFound, isRedirect, lazyRouteComponent, notFound, notFound as notFound$1, redirect, trimPath, useChildMatches, useLinkProps, useLocation, useMatches, useMatches as useMatches$1, useNavigate, useRouteContext, useRouteContext as useRouteContext$1, useRouter, useRouterState } from "@tanstack/react-router";
|
|
3
|
+
import { c } from "react/compiler-runtime";
|
|
4
|
+
import { getRouteSegments, hasMissingPathParams, interpolatePath } from "@tanstack/router-core";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/basepath.ts
|
|
7
|
+
/**
|
|
8
|
+
* Derives the path a router mounts its routes under from where the documents are served.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Path a router mounts under when the application is served at the root of its origin.
|
|
12
|
+
*/
|
|
13
|
+
const ROOT = "/";
|
|
14
|
+
/**
|
|
15
|
+
* Derives the router's base path from the base the bundler was given.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* The bundler's base locates the assets: a path under the application's origin for an ordinary
|
|
19
|
+
* deployment, an absolute URL for one whose assets are served from another host. The router's
|
|
20
|
+
* base path locates the documents, which is always a path on the application's origin. A
|
|
21
|
+
* path-only base locates both, so its path becomes the router's without the trailing slash the
|
|
22
|
+
* bundler writes. A base naming a host says nothing about the documents, so the router mounts at
|
|
23
|
+
* the root.
|
|
24
|
+
* @param base - Base the bundler was given, which is `import.meta.env.BASE_URL` in a page.
|
|
25
|
+
* @param documents - Path the documents are served under, for a deployment whose assets are served
|
|
26
|
+
* from another host. Omitted, the base decides.
|
|
27
|
+
* @returns The path the router mounts under, with a leading slash and no trailing slash.
|
|
28
|
+
*/
|
|
29
|
+
function basepathOf(base, documents) {
|
|
30
|
+
const trimmed = (documents ?? (base.startsWith("/") ? base : ROOT)).replace(/\/+$/u, "");
|
|
31
|
+
return trimmed === "" ? ROOT : trimmed;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/compile.ts
|
|
35
|
+
/**
|
|
36
|
+
* Compiles declarations into routes under a parent, without touching the parent.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Returns the component of a declaration's page, which loads on the first navigation where the page
|
|
40
|
+
* is lazy.
|
|
41
|
+
*
|
|
42
|
+
* @param component - The page, or the importer that resolves to one.
|
|
43
|
+
* @returns The component the route renders.
|
|
44
|
+
*/
|
|
45
|
+
function pageOf(component) {
|
|
46
|
+
return "load" in component ? lazyRouteComponent(component.load, component.export) : component;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Returns a component that renders a layout around the route below it.
|
|
50
|
+
*
|
|
51
|
+
* @param layout - The layout component the caller registered.
|
|
52
|
+
* @param options - The options the declaration stated for this layout.
|
|
53
|
+
* @returns A component the pathless route renders.
|
|
54
|
+
*/
|
|
55
|
+
function framed(layout, options) {
|
|
56
|
+
/**
|
|
57
|
+
* Renders the layout around the route below it.
|
|
58
|
+
*
|
|
59
|
+
* @returns The layout, with the outlet inside it.
|
|
60
|
+
*/
|
|
61
|
+
return function Layout() {
|
|
62
|
+
return createElement(layout, { options }, createElement(Outlet));
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Records a route as a child of another, or as one the caller places itself.
|
|
67
|
+
*
|
|
68
|
+
* @param building - The state this compilation has built up.
|
|
69
|
+
* @param under - The route it hangs under.
|
|
70
|
+
* @param route - The route being placed.
|
|
71
|
+
*/
|
|
72
|
+
function place(building, under, route) {
|
|
73
|
+
if (under === building.options.parent) {
|
|
74
|
+
building.top.push(route);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const siblings = building.children.get(under) ?? [];
|
|
78
|
+
siblings.push(route);
|
|
79
|
+
building.children.set(under, siblings);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Returns an id no sibling of a route has used yet.
|
|
83
|
+
*
|
|
84
|
+
* @param building - The state this compilation has built up.
|
|
85
|
+
* @param under - The route the id has to be unique among the children of.
|
|
86
|
+
* @param name - The layout's name, which the id is derived from.
|
|
87
|
+
* @returns The id, suffixed where the plain one is already used.
|
|
88
|
+
*/
|
|
89
|
+
function freeId(building, under, name) {
|
|
90
|
+
const used = building.taken.get(under) ?? /* @__PURE__ */ new Set();
|
|
91
|
+
building.taken.set(under, used);
|
|
92
|
+
let id = `_${name}`;
|
|
93
|
+
let next = 2;
|
|
94
|
+
while (used.has(id)) {
|
|
95
|
+
id = `_${name}${String(next)}`;
|
|
96
|
+
next += 1;
|
|
97
|
+
}
|
|
98
|
+
used.add(id);
|
|
99
|
+
return id;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Builds the pathless routes a declaration's layouts need, reusing one already built.
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* Two declarations share a frame where they name the same layouts with the same options. A frame
|
|
106
|
+
* is a route, so two declarations wanting different options need two of them. The key is the JSON
|
|
107
|
+
* of the names and the options together, which no pair of different chains can produce.
|
|
108
|
+
* @param building - The state this compilation has built up.
|
|
109
|
+
* @param declaration - The declaration whose layouts are being built.
|
|
110
|
+
* @param under - The route the outermost layout hangs under.
|
|
111
|
+
* @returns The route the declaration's own route hangs under.
|
|
112
|
+
* @throws {@link Error} Where a layout nothing provides is named, or where one a route above
|
|
113
|
+
* already renders is named again.
|
|
114
|
+
*/
|
|
115
|
+
function framing(building, declaration, under) {
|
|
116
|
+
const names = [...building.applied.get(under) ?? []];
|
|
117
|
+
let current = under;
|
|
118
|
+
for (const name of declaration.layout ?? []) {
|
|
119
|
+
const layout = building.options.layouts?.[name];
|
|
120
|
+
if (layout === void 0) throw new Error(`The route ${declaration.id} names the layout ${name}, which is absent.`);
|
|
121
|
+
if (names.includes(name)) throw new Error(`The route ${declaration.id} names the layout ${name}, which a route above it already renders.`);
|
|
122
|
+
names.push(name);
|
|
123
|
+
const key = JSON.stringify([names, declaration.layoutOptions ?? null]);
|
|
124
|
+
const built = building.frames.get(current) ?? /* @__PURE__ */ new Map();
|
|
125
|
+
building.frames.set(current, built);
|
|
126
|
+
const existing = built.get(key);
|
|
127
|
+
const above = current;
|
|
128
|
+
if (existing === void 0) {
|
|
129
|
+
const frame = createRoute({
|
|
130
|
+
component: framed(layout, declaration.layoutOptions),
|
|
131
|
+
getParentRoute: () => above,
|
|
132
|
+
id: freeId(building, above, name)
|
|
133
|
+
});
|
|
134
|
+
place(building, above, frame);
|
|
135
|
+
built.set(key, frame);
|
|
136
|
+
current = frame;
|
|
137
|
+
} else current = existing;
|
|
138
|
+
building.applied.set(current, [...names]);
|
|
139
|
+
}
|
|
140
|
+
return current;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Builds the loader options of a route whose declaration states a loader.
|
|
144
|
+
*
|
|
145
|
+
* @remarks
|
|
146
|
+
* The search is the loader's dependencies, so the router runs the loader again when the search
|
|
147
|
+
* changes, and keeps one result per search.
|
|
148
|
+
* @param loader - The loader the declaration stated.
|
|
149
|
+
* @returns The options to spread into the route.
|
|
150
|
+
*/
|
|
151
|
+
function loading(loader) {
|
|
152
|
+
return {
|
|
153
|
+
loader: ({ abortController, context, deps, params, preload }) => loader({
|
|
154
|
+
context,
|
|
155
|
+
params,
|
|
156
|
+
preload,
|
|
157
|
+
search: deps.search,
|
|
158
|
+
signal: abortController.signal
|
|
159
|
+
}),
|
|
160
|
+
loaderDeps: ({ search }) => ({ search })
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Builds the route one declaration compiles to.
|
|
165
|
+
*
|
|
166
|
+
* @param building - The state this compilation has built up.
|
|
167
|
+
* @param declaration - The route being compiled.
|
|
168
|
+
* @param under - The route it hangs under, frames included.
|
|
169
|
+
* @returns The route, which renders the declaration's page inside the frames it named.
|
|
170
|
+
*/
|
|
171
|
+
function routeOf(building, declaration, under) {
|
|
172
|
+
const { errorComponent, notFoundComponent } = building.options;
|
|
173
|
+
const { id, loader, navigation, when } = declaration;
|
|
174
|
+
return createRoute({
|
|
175
|
+
component: pageOf(declaration.component),
|
|
176
|
+
getParentRoute: () => under,
|
|
177
|
+
path: declaration.path,
|
|
178
|
+
staticData: { declared: {
|
|
179
|
+
id,
|
|
180
|
+
...navigation === void 0 ? {} : { navigation }
|
|
181
|
+
} },
|
|
182
|
+
...errorComponent === void 0 ? {} : { errorComponent: errorComponent(declaration) },
|
|
183
|
+
...notFoundComponent === void 0 ? {} : { notFoundComponent },
|
|
184
|
+
...declaration.search === void 0 ? {} : { validateSearch: declaration.search },
|
|
185
|
+
...when === void 0 ? {} : { beforeLoad: gate(building, declaration, when) },
|
|
186
|
+
...loader === void 0 ? {} : loading(loader)
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Builds the check a route runs before it is entered.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* The evaluator is read once, when the route is built, so the compiler refuses a declaration that
|
|
194
|
+
* states a condition without an evaluator before any navigation.
|
|
195
|
+
* @param building - The state this compilation has built up.
|
|
196
|
+
* @param declaration - The route being compiled.
|
|
197
|
+
* @param when - The condition the declaration stated.
|
|
198
|
+
* @returns The check, which the route runs with its context before it loads.
|
|
199
|
+
* @throws {@link Error} Where no evaluator was given.
|
|
200
|
+
*/
|
|
201
|
+
function gate(building, declaration, when) {
|
|
202
|
+
const { evaluate } = building.options;
|
|
203
|
+
if (evaluate === void 0) throw new Error(`The route ${declaration.id} states a condition and no evaluator was given.`);
|
|
204
|
+
return ({ context, location }) => {
|
|
205
|
+
if (!evaluate(when, context, location)) throw notFound();
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Compiles one declaration, and whatever it nests under, first.
|
|
210
|
+
*
|
|
211
|
+
* @param building - The state this compilation has built up.
|
|
212
|
+
* @param declaration - The route being compiled.
|
|
213
|
+
* @param seen - The ids being compiled further up this chain, which detects a cycle.
|
|
214
|
+
* @returns The route the declaration compiled to.
|
|
215
|
+
* @throws {@link Error} Where a parent is absent or the parents form a cycle.
|
|
216
|
+
*/
|
|
217
|
+
function compiledFor(building, declaration, seen) {
|
|
218
|
+
const already = building.compiled.get(declaration.id);
|
|
219
|
+
if (already !== void 0) return already;
|
|
220
|
+
if (seen.has(declaration.id)) throw new Error(`The route ${declaration.id} nests under itself.`);
|
|
221
|
+
const above = declaration.parent;
|
|
222
|
+
let under = building.options.parent;
|
|
223
|
+
if (above !== void 0) {
|
|
224
|
+
const owner = building.byId.get(above);
|
|
225
|
+
if (owner === void 0) throw new Error(`The route ${declaration.id} names the parent ${above}, which is absent.`);
|
|
226
|
+
under = compiledFor(building, owner, /* @__PURE__ */ new Set([...seen, declaration.id]));
|
|
227
|
+
}
|
|
228
|
+
const inside = framing(building, declaration, under);
|
|
229
|
+
const route = routeOf(building, declaration, inside);
|
|
230
|
+
place(building, inside, route);
|
|
231
|
+
building.applied.set(route, building.applied.get(inside) ?? []);
|
|
232
|
+
building.compiled.set(declaration.id, route);
|
|
233
|
+
return route;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Refuses a declaration this package cannot honour, before any of them is compiled.
|
|
237
|
+
*
|
|
238
|
+
* @remarks
|
|
239
|
+
* A matched route renders the whole screen, so a page rendered beside another as a pane has no
|
|
240
|
+
* route of its own. The library has no named outlet: `Outlet` takes no name, one route matches
|
|
241
|
+
* per level, and two outlets in one component render the same child twice.
|
|
242
|
+
* @param declarations - The routes to compile.
|
|
243
|
+
* @throws {@link Error} Where one states an outlet.
|
|
244
|
+
*/
|
|
245
|
+
function refuse(declarations) {
|
|
246
|
+
for (const declaration of declarations) if (declaration.outlet !== void 0) throw new Error(`The route ${declaration.id} states the outlet ${declaration.outlet}. A route renders the whole screen, so no pane exists to render a second page in.`);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Indexes declarations by id, refusing a second one that claims an id already taken.
|
|
250
|
+
*
|
|
251
|
+
* @param declarations - The routes to compile.
|
|
252
|
+
* @returns Each declaration against its id.
|
|
253
|
+
* @throws {@link Error} Where two declarations share an id.
|
|
254
|
+
*/
|
|
255
|
+
function indexed(declarations) {
|
|
256
|
+
const byId = /* @__PURE__ */ new Map();
|
|
257
|
+
for (const declaration of declarations) {
|
|
258
|
+
if (byId.has(declaration.id)) throw new Error(`Two routes declare the id ${declaration.id}.`);
|
|
259
|
+
byId.set(declaration.id, declaration);
|
|
260
|
+
}
|
|
261
|
+
return byId;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Compiles declarations into routes under the parent a caller states.
|
|
265
|
+
*
|
|
266
|
+
* @remarks
|
|
267
|
+
* The compiler creates routes and never mutates the parent, so a second call returns a second set
|
|
268
|
+
* of routes that shares no object with the first. Two routers in one process therefore never read
|
|
269
|
+
* each other's tree through the library's process-wide cache. Compile every contributor's
|
|
270
|
+
* declarations in one call: two calls under one parent cannot see each other's paths, and
|
|
271
|
+
* `routeMap` reports the collision once the tree is assembled.
|
|
272
|
+
* @param declarations - The routes to compile, in any order.
|
|
273
|
+
* @param options - The parent, and what the declarations may name.
|
|
274
|
+
* @returns The routes to place in the parent's own `addChildren` call.
|
|
275
|
+
* @throws {@link Error} Where two declarations share an id, where one names a parent or a layout
|
|
276
|
+
* nothing provides, where one names a layout a route above it already renders, where parents form
|
|
277
|
+
* a cycle, where one states a condition and no evaluator was given, or where one states an
|
|
278
|
+
* outlet.
|
|
279
|
+
*/
|
|
280
|
+
function compileRoutes(declarations, options) {
|
|
281
|
+
refuse(declarations);
|
|
282
|
+
const building = {
|
|
283
|
+
applied: /* @__PURE__ */ new Map(),
|
|
284
|
+
byId: indexed(declarations),
|
|
285
|
+
children: /* @__PURE__ */ new Map(),
|
|
286
|
+
compiled: /* @__PURE__ */ new Map(),
|
|
287
|
+
frames: /* @__PURE__ */ new Map(),
|
|
288
|
+
options,
|
|
289
|
+
taken: /* @__PURE__ */ new Map(),
|
|
290
|
+
top: []
|
|
291
|
+
};
|
|
292
|
+
for (const declaration of declarations) compiledFor(building, declaration, /* @__PURE__ */ new Set());
|
|
293
|
+
for (const [route, children] of building.children) route.addChildren(children);
|
|
294
|
+
return building.top;
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/reference.ts
|
|
298
|
+
/**
|
|
299
|
+
* Reads the id of a target.
|
|
300
|
+
*
|
|
301
|
+
* @param to - A reference, or the bare id.
|
|
302
|
+
* @returns The full id the route was declared under.
|
|
303
|
+
*/
|
|
304
|
+
function idOf(to) {
|
|
305
|
+
return typeof to === "string" ? to : to.id;
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/declared.ts
|
|
309
|
+
/**
|
|
310
|
+
* Reads the declaration a matched route was compiled from.
|
|
311
|
+
*/
|
|
312
|
+
/**
|
|
313
|
+
* Reads the id and the menu entry a matched route stores, or nothing where it stores neither.
|
|
314
|
+
*
|
|
315
|
+
* @remarks
|
|
316
|
+
* A route stores its id in `staticData`, the library's place for data a route is created with.
|
|
317
|
+
* The compiler writes it for a route built from a declaration, and `namedRoute` writes it for one
|
|
318
|
+
* written in code, so a menu, a breadcrumb or a telemetry hook reads the id from the match either
|
|
319
|
+
* way. The function checks the shape, because `staticData` is untyped and a route may store
|
|
320
|
+
* anything under the same name. It returns the checked object itself, so every read of one route
|
|
321
|
+
* returns one reference.
|
|
322
|
+
* @param match - A match, as `useMatches` returns one.
|
|
323
|
+
* @returns The id and the menu entry, or nothing.
|
|
324
|
+
*/
|
|
325
|
+
function declaredOf(match) {
|
|
326
|
+
const { staticData } = match;
|
|
327
|
+
const data = "declared" in staticData ? staticData.declared : void 0;
|
|
328
|
+
if (typeof data !== "object" || data === null) return void 0;
|
|
329
|
+
if (!("id" in data) || typeof data.id !== "string") return void 0;
|
|
330
|
+
return data;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Reads the id of the deepest named route the page is on.
|
|
334
|
+
*
|
|
335
|
+
* @remarks
|
|
336
|
+
* The hook reads the deepest match, because a named route nested under another is the page a
|
|
337
|
+
* person is looking at. A page rendered from unnamed routes alone returns nothing. The selector
|
|
338
|
+
* returns the object the route stores, not a copy. A route's static data is fixed once the tree
|
|
339
|
+
* is built, so the reference changes only when a different route matches, and a navigation that
|
|
340
|
+
* keeps the page does not render it again.
|
|
341
|
+
* @returns The id and the menu entry, or nothing where no matched route has an id.
|
|
342
|
+
*/
|
|
343
|
+
function useDeclaredRoute() {
|
|
344
|
+
const $ = c(1);
|
|
345
|
+
let t0;
|
|
346
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
347
|
+
t0 = { select: _temp };
|
|
348
|
+
$[0] = t0;
|
|
349
|
+
} else t0 = $[0];
|
|
350
|
+
return useMatches(t0);
|
|
351
|
+
}
|
|
352
|
+
function _temp(matches) {
|
|
353
|
+
return deepestIn(matches)?.declared;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Reads the parameters of the page being rendered, typed by the reference the caller passes.
|
|
357
|
+
*
|
|
358
|
+
* @remarks
|
|
359
|
+
* A compiled route is outside the tree an application registered, so `useParams` types the
|
|
360
|
+
* parameters against a tree the route is not in. The hook reads them from the match instead, and
|
|
361
|
+
* the reference types them. It checks at run time that the page is the route of the reference,
|
|
362
|
+
* and throws where it is not. The selector returns the id and the parameters alone, which are
|
|
363
|
+
* strings. Structural sharing compares them, so a navigation that changes neither does not render
|
|
364
|
+
* the page again.
|
|
365
|
+
* @param to - A reference to the route the calling component renders.
|
|
366
|
+
* @returns The parameters of the route's path.
|
|
367
|
+
* @throws {@link Error} Where the page being rendered is not the route of the reference.
|
|
368
|
+
*/
|
|
369
|
+
function useRouteParams(to) {
|
|
370
|
+
const $ = c(1);
|
|
371
|
+
let t0;
|
|
372
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
373
|
+
t0 = {
|
|
374
|
+
select: _temp2,
|
|
375
|
+
structuralSharing: true
|
|
376
|
+
};
|
|
377
|
+
$[0] = t0;
|
|
378
|
+
} else t0 = $[0];
|
|
379
|
+
const current = useMatches(t0);
|
|
380
|
+
if (current === void 0 || current.id !== idOf(to)) throw refused(to, "parameters");
|
|
381
|
+
return current.params;
|
|
382
|
+
}
|
|
383
|
+
function _temp2(matches) {
|
|
384
|
+
return paramsIn(matches);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Reads the search of the page being rendered, typed by the reference the caller passes.
|
|
388
|
+
*
|
|
389
|
+
* @remarks
|
|
390
|
+
* A compiled route is outside the tree an application registered, so `useSearch` types the search
|
|
391
|
+
* against a tree the route is not in. The hook reads it from the match instead, and the reference
|
|
392
|
+
* types it. It checks at run time that the page is the route of the reference, and throws where
|
|
393
|
+
* it is not. The value is the match's `_strictSearch`: what the validators of the route and the
|
|
394
|
+
* routes above it returned. The hook leaves out the keys of the address that the validators left
|
|
395
|
+
* out, so the page never receives a value the validator refused under the validator's type.
|
|
396
|
+
* Structural sharing compares the search, so the page does not render again after a navigation
|
|
397
|
+
* that left it equal.
|
|
398
|
+
* @param to - A reference to the route the calling component renders.
|
|
399
|
+
* @returns The search the route's validator returned.
|
|
400
|
+
* @throws {@link Error} Where the page being rendered is not the route of the reference.
|
|
401
|
+
*/
|
|
402
|
+
function useRouteSearch(to) {
|
|
403
|
+
const $ = c(1);
|
|
404
|
+
const declared = useDeclaredRoute();
|
|
405
|
+
let t0;
|
|
406
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
407
|
+
t0 = {
|
|
408
|
+
select: _temp3,
|
|
409
|
+
structuralSharing: true
|
|
410
|
+
};
|
|
411
|
+
$[0] = t0;
|
|
412
|
+
} else t0 = $[0];
|
|
413
|
+
const search = useMatches(t0);
|
|
414
|
+
if (declared?.id !== idOf(to)) throw refused(to, "search");
|
|
415
|
+
return search;
|
|
416
|
+
}
|
|
417
|
+
function _temp3(matches) {
|
|
418
|
+
return searchIn(matches);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Builds the error a hook throws when the page being rendered is not the route of a reference.
|
|
422
|
+
*
|
|
423
|
+
* @param to - The reference the caller passed.
|
|
424
|
+
* @param what - The value the hook was asked for, which the message mentions.
|
|
425
|
+
* @returns The error.
|
|
426
|
+
*/
|
|
427
|
+
function refused(to, what) {
|
|
428
|
+
return /* @__PURE__ */ new Error(`The page being rendered is not ${idOf(to)}, so it cannot return that route's ${what}.`);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Returns the deepest matched route that has an id, with its declaration.
|
|
432
|
+
*
|
|
433
|
+
* @param matches - Every matched route, outermost first.
|
|
434
|
+
* @returns The deepest declared match, or nothing where no match has an id.
|
|
435
|
+
*/
|
|
436
|
+
function deepestIn(matches) {
|
|
437
|
+
let deepest;
|
|
438
|
+
for (const match of matches) {
|
|
439
|
+
const declared = declaredOf(match);
|
|
440
|
+
if (declared !== void 0) deepest = {
|
|
441
|
+
declared,
|
|
442
|
+
match
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
return deepest;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Reads the id and the parameters of the deepest declared match.
|
|
449
|
+
*
|
|
450
|
+
* @param matches - Every matched route, outermost first.
|
|
451
|
+
* @returns The id and the parameters, or nothing where no match has an id.
|
|
452
|
+
*/
|
|
453
|
+
function paramsIn(matches) {
|
|
454
|
+
const deepest = deepestIn(matches);
|
|
455
|
+
return deepest === void 0 ? deepest : {
|
|
456
|
+
id: deepest.declared.id,
|
|
457
|
+
params: deepest.match.params
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Reads the validated search of the deepest declared match.
|
|
462
|
+
*
|
|
463
|
+
* @remarks
|
|
464
|
+
* The library's structural sharing compares plain objects and arrays deeply and any other value
|
|
465
|
+
* by reference, so it shares a search of any values. Its types accept only a selection whose type
|
|
466
|
+
* excludes functions. The type of a search of unknown values does not, so the function types the
|
|
467
|
+
* search as strings.
|
|
468
|
+
* @param matches - Every matched route, outermost first.
|
|
469
|
+
* @returns The search, or nothing where no match has an id.
|
|
470
|
+
*/
|
|
471
|
+
function searchIn(matches) {
|
|
472
|
+
return deepestIn(matches)?.match._strictSearch;
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/defaults.ts
|
|
476
|
+
/**
|
|
477
|
+
* How a router in this design system behaves before an application says otherwise.
|
|
478
|
+
*/
|
|
479
|
+
/**
|
|
480
|
+
* The options every application's router starts from, spread before its own.
|
|
481
|
+
*
|
|
482
|
+
* @remarks
|
|
483
|
+
* A route preloads on intent, which is when the pointer rests on a link or the link takes focus,
|
|
484
|
+
* so a page is usually loaded before it is asked for.
|
|
485
|
+
* A preloaded route never counts as fresh on its own. The query client decides freshness for
|
|
486
|
+
* everything it loads, and a router keeping its own staleness would serve a copy the client had
|
|
487
|
+
* already replaced.
|
|
488
|
+
* Scroll position is restored on the way back, because a person returning to a list expects to be
|
|
489
|
+
* where they left it rather than at the top.
|
|
490
|
+
* The window is moved in one step, on the way back and to the top of a new page alike. The
|
|
491
|
+
* library moves it with the page's own scroll behaviour by default, and the foundation sets that
|
|
492
|
+
* to smooth for a link into the page, so a page opened from a scrolled one glided in from above.
|
|
493
|
+
*/
|
|
494
|
+
const routerDefaults = {
|
|
495
|
+
defaultPreload: "intent",
|
|
496
|
+
defaultPreloadStaleTime: 0,
|
|
497
|
+
scrollRestoration: true,
|
|
498
|
+
scrollRestorationBehavior: "instant"
|
|
499
|
+
};
|
|
500
|
+
//#endregion
|
|
501
|
+
//#region src/href.ts
|
|
502
|
+
/**
|
|
503
|
+
* Resolves a declared route id to the path a link navigates to.
|
|
504
|
+
*/
|
|
505
|
+
/**
|
|
506
|
+
* Resolves a target to a path, with the parameters of the path filled in, for the functions of
|
|
507
|
+
* this package that type their parameters by the target.
|
|
508
|
+
*
|
|
509
|
+
* @remarks
|
|
510
|
+
* Throwing is deliberate in all three cases. Interpolation otherwise writes `undefined` where a
|
|
511
|
+
* parameter was not given, and a route that no router has processed has no path at all. A link
|
|
512
|
+
* that navigates somewhere wrong is worse than one that fails where somebody wrote it. The router
|
|
513
|
+
* parses a path with parameters into segments, and a path without parameters is its own href.
|
|
514
|
+
* @param map - The map every route is named in.
|
|
515
|
+
* @param to - A reference to the route, or the bare id of one.
|
|
516
|
+
* @param params - The parameters of the path.
|
|
517
|
+
* @returns The path, ready to navigate to.
|
|
518
|
+
* @throws {@link Error} Where the map contains no such id, where no router has processed the tree
|
|
519
|
+
* the route was placed in, or where a parameter of the path is missing.
|
|
520
|
+
*/
|
|
521
|
+
function resolveHref(map, to, params) {
|
|
522
|
+
const id = idOf(to);
|
|
523
|
+
const route = map.get(id);
|
|
524
|
+
if (route === void 0) throw new Error(`No route is declared with the id ${id}.`);
|
|
525
|
+
if (route.fullPath === void 0) throw new Error(`The route ${id} has no path yet, because no router has processed the tree it was placed in.`);
|
|
526
|
+
const path = route.fullPath;
|
|
527
|
+
const segments = getRouteSegments(route);
|
|
528
|
+
if (segments === void 0) return path;
|
|
529
|
+
const given = params ?? {};
|
|
530
|
+
if (hasMissingPathParams(segments, given)) throw new Error(`The route ${id} names a parameter that was not given.`);
|
|
531
|
+
return interpolatePath(path, segments, given);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Resolves a declared id to a path, with the parameters of the path filled in.
|
|
535
|
+
*
|
|
536
|
+
* @remarks
|
|
537
|
+
* The parameters are required where a typed reference's path has a required one, so the compiler
|
|
538
|
+
* refuses such a call without them. A bare id takes them at will, and the call throws where one
|
|
539
|
+
* is missing.
|
|
540
|
+
* @param map - The map every route is named in.
|
|
541
|
+
* @param to - A reference to the route, or the bare id of one.
|
|
542
|
+
* @param params - The parameters of the path, typed by the reference where one was given.
|
|
543
|
+
* @returns The path, ready to navigate to.
|
|
544
|
+
* @throws {@link Error} Where the map contains no such id, where no router has processed the tree
|
|
545
|
+
* the route was placed in, or where a parameter of the path is missing.
|
|
546
|
+
*/
|
|
547
|
+
function routeHref(map, to, ...params) {
|
|
548
|
+
return resolveHref(map, to, params[0]);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Reads the map every route is named in, from the router's own context.
|
|
552
|
+
*
|
|
553
|
+
* @remarks
|
|
554
|
+
* The router's own context passes the map to a component, so an application mounts no provider
|
|
555
|
+
* of this package's own to make a link by id work. Where the route is chosen after the render,
|
|
556
|
+
* pass the map to `routeHref` inside the event handler, because a hook cannot run there.
|
|
557
|
+
* @returns Every id the router knows, against the route that serves it.
|
|
558
|
+
* @throws {@link Error} Where the router was built without one.
|
|
559
|
+
*/
|
|
560
|
+
function useRouteMap() {
|
|
561
|
+
const $ = c(1);
|
|
562
|
+
let t0;
|
|
563
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
564
|
+
t0 = { strict: false };
|
|
565
|
+
$[0] = t0;
|
|
566
|
+
} else t0 = $[0];
|
|
567
|
+
const context = useRouteContext(t0);
|
|
568
|
+
if (context.routes === void 0) throw new Error("The router context contains no route map, so no id can be resolved. Pass one to routerOptions.");
|
|
569
|
+
return context.routes;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Resolves a reference to a path, through the map in the router context.
|
|
573
|
+
*
|
|
574
|
+
* @remarks
|
|
575
|
+
* A hook, so the route has to be known while the component renders. Use `useRouteMap` where the
|
|
576
|
+
* route is chosen later, such as in the handler of a row a person clicked. The parameters are
|
|
577
|
+
* required as `routeHref` requires them.
|
|
578
|
+
* @param to - A reference to the route, or the bare id of one.
|
|
579
|
+
* @param params - The parameters of the path, typed by the reference where one was given.
|
|
580
|
+
* @returns The path, ready to navigate to.
|
|
581
|
+
* @throws {@link Error} Where no map is in the router context, where the map contains no such id,
|
|
582
|
+
* or where a parameter of the path is missing.
|
|
583
|
+
*/
|
|
584
|
+
function useRouteHref(to, ...t0) {
|
|
585
|
+
const $ = c(4);
|
|
586
|
+
const params = t0;
|
|
587
|
+
const t1 = useRouteMap();
|
|
588
|
+
let t2;
|
|
589
|
+
if ($[0] !== params[0] || $[1] !== t1 || $[2] !== to) {
|
|
590
|
+
t2 = resolveHref(t1, to, params[0]);
|
|
591
|
+
$[0] = params[0];
|
|
592
|
+
$[1] = t1;
|
|
593
|
+
$[2] = to;
|
|
594
|
+
$[3] = t2;
|
|
595
|
+
} else t2 = $[3];
|
|
596
|
+
return t2;
|
|
597
|
+
}
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/link.tsx
|
|
600
|
+
/**
|
|
601
|
+
* Links to a route by the id a manifest declared it under.
|
|
602
|
+
*
|
|
603
|
+
* @remarks
|
|
604
|
+
* A declared route is outside the tree an application registered, so `Link` refuses its path at
|
|
605
|
+
* compile time. This resolves the id at run time and passes the library a plain string, which is
|
|
606
|
+
* the one place in this package where the compiler does not check a path. The search passes to
|
|
607
|
+
* the library's `Link` beside the path.
|
|
608
|
+
* @param props - The declared id, its parameters, its search, and whatever else the library's
|
|
609
|
+
* `Link` takes.
|
|
610
|
+
* @returns An anchor to the resolved path.
|
|
611
|
+
*/
|
|
612
|
+
function RouteLink(props) {
|
|
613
|
+
const $ = c(15);
|
|
614
|
+
let params;
|
|
615
|
+
let rest;
|
|
616
|
+
let search;
|
|
617
|
+
let to;
|
|
618
|
+
if ($[0] !== props) {
|
|
619
|
+
({params, search, to, ...rest} = props);
|
|
620
|
+
$[0] = props;
|
|
621
|
+
$[1] = params;
|
|
622
|
+
$[2] = rest;
|
|
623
|
+
$[3] = search;
|
|
624
|
+
$[4] = to;
|
|
625
|
+
} else {
|
|
626
|
+
params = $[1];
|
|
627
|
+
rest = $[2];
|
|
628
|
+
search = $[3];
|
|
629
|
+
to = $[4];
|
|
630
|
+
}
|
|
631
|
+
let t0;
|
|
632
|
+
if ($[5] !== search) {
|
|
633
|
+
t0 = search === void 0 ? {} : { search };
|
|
634
|
+
$[5] = search;
|
|
635
|
+
$[6] = t0;
|
|
636
|
+
} else t0 = $[6];
|
|
637
|
+
const t1 = useRouteMap();
|
|
638
|
+
let t2;
|
|
639
|
+
if ($[7] !== params || $[8] !== t1 || $[9] !== to) {
|
|
640
|
+
t2 = resolveHref(t1, to, params);
|
|
641
|
+
$[7] = params;
|
|
642
|
+
$[8] = t1;
|
|
643
|
+
$[9] = to;
|
|
644
|
+
$[10] = t2;
|
|
645
|
+
} else t2 = $[10];
|
|
646
|
+
let t3;
|
|
647
|
+
if ($[11] !== rest || $[12] !== t0 || $[13] !== t2) {
|
|
648
|
+
t3 = /* @__PURE__ */ jsx(Link, {
|
|
649
|
+
...rest,
|
|
650
|
+
...t0,
|
|
651
|
+
to: t2
|
|
652
|
+
});
|
|
653
|
+
$[11] = rest;
|
|
654
|
+
$[12] = t0;
|
|
655
|
+
$[13] = t2;
|
|
656
|
+
$[14] = t3;
|
|
657
|
+
} else t3 = $[14];
|
|
658
|
+
return t3;
|
|
659
|
+
}
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region src/map.ts
|
|
662
|
+
/**
|
|
663
|
+
* Reads the routes of a tree, whoever built them.
|
|
664
|
+
*/
|
|
665
|
+
/**
|
|
666
|
+
* Gives a route written in code the id that everything else links to it by.
|
|
667
|
+
*
|
|
668
|
+
* @remarks
|
|
669
|
+
* A route keeps its id in `staticData`, where the compiler writes the id of a route built from a
|
|
670
|
+
* declaration. The same field identifies both kinds, so a link does not need to know which kind
|
|
671
|
+
* it points at.
|
|
672
|
+
* @param id - The id to name the route under.
|
|
673
|
+
* @returns The static data to spread into `createRoute`.
|
|
674
|
+
*/
|
|
675
|
+
function namedRoute(id) {
|
|
676
|
+
return { staticData: { declared: { id } } };
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Reads every id of a tree, against the route that renders it.
|
|
680
|
+
*
|
|
681
|
+
* @remarks
|
|
682
|
+
* The map derives from the tree and is not collected beside it, so a route written in code and a
|
|
683
|
+
* route built from a declaration enter the map the same way. The walk also reports two routes
|
|
684
|
+
* that serve one URL. A development build reports them as a duplicate route, and a production
|
|
685
|
+
* build keeps the first and drops the rest without a report.
|
|
686
|
+
* @param tree - The assembled tree, as `createRouter` takes it.
|
|
687
|
+
* @returns Every id, against its route.
|
|
688
|
+
* @throws {@link Error} Where two routes have one id, or where two serve one path under a shared
|
|
689
|
+
* parent.
|
|
690
|
+
*/
|
|
691
|
+
function routeMap(tree) {
|
|
692
|
+
const named = /* @__PURE__ */ new Map();
|
|
693
|
+
collect(tree, named);
|
|
694
|
+
return named;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Walks a route's descendants, records the id of each one and refuses a repeated path.
|
|
698
|
+
*
|
|
699
|
+
* @param route - The route whose children are read.
|
|
700
|
+
* @param named - The map built so far.
|
|
701
|
+
* @throws {@link Error} Where two routes have one id, or where two serve one path at this level.
|
|
702
|
+
*/
|
|
703
|
+
function collect(route, named) {
|
|
704
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
705
|
+
for (const path of pathsUnder(route)) {
|
|
706
|
+
if (claimed.has(path)) throw new Error(`Two routes serve the path ${path} under the same parent.`);
|
|
707
|
+
claimed.add(path);
|
|
708
|
+
}
|
|
709
|
+
for (const child of childrenOf(route)) {
|
|
710
|
+
identify(child, named);
|
|
711
|
+
collect(child, named);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Records the id of a route that has one.
|
|
716
|
+
*
|
|
717
|
+
* @param route - The route to read.
|
|
718
|
+
* @param named - The map built so far.
|
|
719
|
+
* @throws {@link Error} Where another route has the same id already.
|
|
720
|
+
*/
|
|
721
|
+
function identify(route, named) {
|
|
722
|
+
const options = route.options;
|
|
723
|
+
const data = "staticData" in options ? options.staticData : void 0;
|
|
724
|
+
const declared = typeof data === "object" && data !== null ? declaredOf({ staticData: data }) : void 0;
|
|
725
|
+
if (declared === void 0) return;
|
|
726
|
+
if (named.has(declared.id)) throw new Error(`Two routes are named ${declared.id}.`);
|
|
727
|
+
named.set(declared.id, route);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Writes a path the one way the library reads it: the slashes around it trimmed, and one leading
|
|
731
|
+
* slash put back.
|
|
732
|
+
*
|
|
733
|
+
* @remarks
|
|
734
|
+
* `home`, `/home` and `home/` are one route to the library, so the check reads all three as
|
|
735
|
+
* `/home`.
|
|
736
|
+
*/
|
|
737
|
+
function normalised(path) {
|
|
738
|
+
const trimmed = trimPath(path);
|
|
739
|
+
return trimmed === "/" ? trimmed : `/${trimmed}`;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Lists the paths that a route's descendants serve at its own level, each written the one way the
|
|
743
|
+
* library reads it.
|
|
744
|
+
*
|
|
745
|
+
* @remarks
|
|
746
|
+
* A pathless route consumes no segment of a URL, so the routes below one are served at the level
|
|
747
|
+
* of the route above it. The walk goes through a pathless route and does not stop at it.
|
|
748
|
+
* @param route - The route whose level is being read.
|
|
749
|
+
* @returns One path per descendant served at this level.
|
|
750
|
+
*/
|
|
751
|
+
function pathsUnder(route) {
|
|
752
|
+
const paths = [];
|
|
753
|
+
for (const child of childrenOf(route)) {
|
|
754
|
+
const options = child.options;
|
|
755
|
+
const path = "path" in options ? options.path : void 0;
|
|
756
|
+
if (typeof path === "string") paths.push(normalised(path));
|
|
757
|
+
else paths.push(...pathsUnder(child));
|
|
758
|
+
}
|
|
759
|
+
return paths;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Reads the children of a route.
|
|
763
|
+
*
|
|
764
|
+
* @remarks
|
|
765
|
+
* The library types a route's children as `any`, because a typed tree types each one. The walk
|
|
766
|
+
* reads only the options that every route has.
|
|
767
|
+
* @param route - The route to read.
|
|
768
|
+
* @returns Its children, or an empty list where it has none.
|
|
769
|
+
*/
|
|
770
|
+
function childrenOf(route) {
|
|
771
|
+
return route.children ?? [];
|
|
772
|
+
}
|
|
773
|
+
//#endregion
|
|
774
|
+
//#region src/options.ts
|
|
775
|
+
/**
|
|
776
|
+
* Collects what every router in this design system starts from, as an object a caller spreads.
|
|
777
|
+
*/
|
|
778
|
+
/**
|
|
779
|
+
* Collects the options every router in this design system starts from.
|
|
780
|
+
*
|
|
781
|
+
* @remarks
|
|
782
|
+
* An object to spread rather than a factory that calls `createRouter`. The library states
|
|
783
|
+
* forty-eight router options, and a factory would expose the few it thought of and hide the rest.
|
|
784
|
+
* Spreading leaves every one of them the caller's to set, the four stated here included.
|
|
785
|
+
* @param context - The context the router hands a loader, holding the route map where a caller compiled
|
|
786
|
+
* declarations.
|
|
787
|
+
* @returns The options, to spread into `createRouter` before the caller's own.
|
|
788
|
+
*/
|
|
789
|
+
function routerOptions(context) {
|
|
790
|
+
return {
|
|
791
|
+
...routerDefaults,
|
|
792
|
+
context
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region src/root.ts
|
|
797
|
+
/**
|
|
798
|
+
* Builds the root route an application's tree starts from, typed for this design system's context.
|
|
799
|
+
*/
|
|
800
|
+
/**
|
|
801
|
+
* Builds a root route whose context contains whatever the application states and the route map.
|
|
802
|
+
*
|
|
803
|
+
* @remarks
|
|
804
|
+
* Calling the library's `createRootRoute` instead leaves the context typed as empty, and the
|
|
805
|
+
* first sign of it is `createRouter` refusing the options `routerOptions` returned. The error
|
|
806
|
+
* then names the router rather than the root, which is the file that has to change.
|
|
807
|
+
* @returns The root route factory, to call with the route's own options.
|
|
808
|
+
*/
|
|
809
|
+
function createAppRootRoute() {
|
|
810
|
+
return createRootRouteWithContext();
|
|
811
|
+
}
|
|
812
|
+
//#endregion
|
|
813
|
+
export { routerDefaults as A, routerOptions as C, routeHref as D, RouteLink as E, compileRoutes as F, basepathOf as I, useDeclaredRoute as M, useRouteParams as N, useRouteHref as O, useRouteSearch as P, createAppRootRoute as S, routeMap as T, useMatches$1 as _, createLink as a, useRouter as b, createRoute$1 as c, isRedirect as d, notFound$1 as f, useLocation as g, useLinkProps as h, RouterProvider as i, declaredOf as j, useRouteMap as k, createRouter as l, useChildMatches as m, Outlet$1 as n, createMemoryHistory as o, redirect as p, RouterContextProvider as r, createRootRoute as s, Navigate as t, isNotFound as u, useNavigate as v, namedRoute as w, useRouterState as x, useRouteContext$1 as y };
|