@solidjs/router 0.4.2
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/LICENSE +21 -0
- package/README.md +557 -0
- package/dist/components.d.ts +65 -0
- package/dist/components.jsx +111 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +936 -0
- package/dist/index.jsx +4 -0
- package/dist/integration.d.ts +6 -0
- package/dist/integration.js +105 -0
- package/dist/routing.d.ts +24 -0
- package/dist/routing.js +377 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +132 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
import { isServer, createComponent as createComponent$1, mergeProps as mergeProps$1, spread, insert, effect, setAttribute, template } from 'solid-js/web';
|
|
2
|
+
import { createSignal, onCleanup, runWithOwner, createMemo, getOwner, createContext, useContext, untrack, useTransition, on, resetErrorBoundaries, createRenderEffect, createComponent, children, createRoot, Show, mergeProps, splitProps } from 'solid-js';
|
|
3
|
+
|
|
4
|
+
function bindEvent(target, type, handler) {
|
|
5
|
+
target.addEventListener(type, handler);
|
|
6
|
+
return () => target.removeEventListener(type, handler);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function intercept([value, setValue], get, set) {
|
|
10
|
+
return [get ? () => get(value()) : value, set ? v => setValue(set(v)) : setValue];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function querySelector(selector) {
|
|
14
|
+
// Guard against selector being an invalid CSS selector
|
|
15
|
+
try {
|
|
16
|
+
return document.querySelector(selector);
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function scrollToHash(hash, fallbackTop) {
|
|
23
|
+
const el = querySelector(`#${hash}`);
|
|
24
|
+
|
|
25
|
+
if (el) {
|
|
26
|
+
el.scrollIntoView();
|
|
27
|
+
} else if (fallbackTop) {
|
|
28
|
+
window.scrollTo(0, 0);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createIntegration(get, set, init, utils) {
|
|
33
|
+
let ignore = false;
|
|
34
|
+
|
|
35
|
+
const wrap = value => typeof value === "string" ? {
|
|
36
|
+
value
|
|
37
|
+
} : value;
|
|
38
|
+
|
|
39
|
+
const signal = intercept(createSignal(wrap(get()), {
|
|
40
|
+
equals: (a, b) => a.value === b.value
|
|
41
|
+
}), undefined, next => {
|
|
42
|
+
!ignore && set(next);
|
|
43
|
+
return next;
|
|
44
|
+
});
|
|
45
|
+
init && onCleanup(init((value = get()) => {
|
|
46
|
+
ignore = true;
|
|
47
|
+
signal[1](wrap(value));
|
|
48
|
+
ignore = false;
|
|
49
|
+
}));
|
|
50
|
+
return {
|
|
51
|
+
signal,
|
|
52
|
+
utils
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function normalizeIntegration(integration) {
|
|
56
|
+
if (!integration) {
|
|
57
|
+
return {
|
|
58
|
+
signal: createSignal({
|
|
59
|
+
value: ""
|
|
60
|
+
})
|
|
61
|
+
};
|
|
62
|
+
} else if (Array.isArray(integration)) {
|
|
63
|
+
return {
|
|
64
|
+
signal: integration
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return integration;
|
|
69
|
+
}
|
|
70
|
+
function staticIntegration(obj) {
|
|
71
|
+
return {
|
|
72
|
+
signal: [() => obj, next => Object.assign(obj, next)]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function pathIntegration() {
|
|
76
|
+
return createIntegration(() => ({
|
|
77
|
+
value: window.location.pathname + window.location.search + window.location.hash,
|
|
78
|
+
state: history.state
|
|
79
|
+
}), ({
|
|
80
|
+
value,
|
|
81
|
+
replace,
|
|
82
|
+
scroll,
|
|
83
|
+
state
|
|
84
|
+
}) => {
|
|
85
|
+
if (replace) {
|
|
86
|
+
window.history.replaceState(state, "", value);
|
|
87
|
+
} else {
|
|
88
|
+
window.history.pushState(state, "", value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
scrollToHash(window.location.hash.slice(1), scroll);
|
|
92
|
+
}, notify => bindEvent(window, "popstate", () => notify()), {
|
|
93
|
+
go: delta => window.history.go(delta)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function hashIntegration() {
|
|
97
|
+
return createIntegration(() => window.location.hash.slice(1), ({
|
|
98
|
+
value,
|
|
99
|
+
replace,
|
|
100
|
+
scroll,
|
|
101
|
+
state
|
|
102
|
+
}) => {
|
|
103
|
+
if (replace) {
|
|
104
|
+
window.history.replaceState(state, "", "#" + value);
|
|
105
|
+
} else {
|
|
106
|
+
window.location.hash = value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const hashIndex = value.indexOf("#");
|
|
110
|
+
const hash = hashIndex >= 0 ? value.slice(hashIndex + 1) : "";
|
|
111
|
+
scrollToHash(hash, scroll);
|
|
112
|
+
}, notify => bindEvent(window, "hashchange", () => notify()), {
|
|
113
|
+
go: delta => window.history.go(delta),
|
|
114
|
+
renderPath: path => `#${path}`,
|
|
115
|
+
parsePath: str => {
|
|
116
|
+
const to = str.replace(/^.*?#/, ""); // Hash-only hrefs like `#foo` from plain anchors will come in as `/#foo` whereas a link to
|
|
117
|
+
// `/foo` will be `/#/foo`. Check if the to starts with a `/` and if not append it as a hash
|
|
118
|
+
// to the current path so we can handle these in-page anchors correctly.
|
|
119
|
+
|
|
120
|
+
if (!to.startsWith("/")) {
|
|
121
|
+
const [, path = "/"] = window.location.hash.split("#", 2);
|
|
122
|
+
return `${path}#${to}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return to;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
|
|
131
|
+
const trimPathRegex = /^\/+|\/+$/g;
|
|
132
|
+
|
|
133
|
+
function normalize(path, omitSlash = false) {
|
|
134
|
+
const s = path.replace(trimPathRegex, "");
|
|
135
|
+
return s ? omitSlash || /^[?#]/.test(s) ? s : "/" + s : "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resolvePath(base, path, from) {
|
|
139
|
+
if (hasSchemeRegex.test(path)) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const basePath = normalize(base);
|
|
144
|
+
const fromPath = from && normalize(from);
|
|
145
|
+
let result = "";
|
|
146
|
+
|
|
147
|
+
if (!fromPath || path.startsWith("/")) {
|
|
148
|
+
result = basePath;
|
|
149
|
+
} else if (fromPath.toLowerCase().indexOf(basePath.toLowerCase()) !== 0) {
|
|
150
|
+
result = basePath + fromPath;
|
|
151
|
+
} else {
|
|
152
|
+
result = fromPath;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return (result || "/") + normalize(path, !result);
|
|
156
|
+
}
|
|
157
|
+
function invariant(value, message) {
|
|
158
|
+
if (value == null) {
|
|
159
|
+
throw new Error(message);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
function joinPaths(from, to) {
|
|
165
|
+
return normalize(from).replace(/\/*(\*.*)?$/g, "") + normalize(to);
|
|
166
|
+
}
|
|
167
|
+
function extractSearchParams(url) {
|
|
168
|
+
const params = {};
|
|
169
|
+
url.searchParams.forEach((value, key) => {
|
|
170
|
+
params[key] = value;
|
|
171
|
+
});
|
|
172
|
+
return params;
|
|
173
|
+
}
|
|
174
|
+
function urlDecode(str, isQuery) {
|
|
175
|
+
return decodeURIComponent(isQuery ? str.replace(/\+/g, " ") : str);
|
|
176
|
+
}
|
|
177
|
+
function createMatcher(path, partial) {
|
|
178
|
+
const [pattern, splat] = path.split("/*", 2);
|
|
179
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
180
|
+
const len = segments.length;
|
|
181
|
+
return location => {
|
|
182
|
+
const locSegments = location.split("/").filter(Boolean);
|
|
183
|
+
const lenDiff = locSegments.length - len;
|
|
184
|
+
|
|
185
|
+
if (lenDiff < 0 || lenDiff > 0 && splat === undefined && !partial) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const match = {
|
|
190
|
+
path: len ? "" : "/",
|
|
191
|
+
params: {}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
for (let i = 0; i < len; i++) {
|
|
195
|
+
const segment = segments[i];
|
|
196
|
+
const locSegment = locSegments[i];
|
|
197
|
+
|
|
198
|
+
if (segment[0] === ":") {
|
|
199
|
+
match.params[segment.slice(1)] = locSegment;
|
|
200
|
+
} else if (segment.localeCompare(locSegment, undefined, {
|
|
201
|
+
sensitivity: "base"
|
|
202
|
+
}) !== 0) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
match.path += `/${locSegment}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (splat) {
|
|
210
|
+
match.params[splat] = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return match;
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function scoreRoute(route) {
|
|
217
|
+
const [pattern, splat] = route.pattern.split("/*", 2);
|
|
218
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
219
|
+
return segments.reduce((score, segment) => score + (segment.startsWith(":") ? 2 : 3), segments.length - (splat === undefined ? 0 : 1));
|
|
220
|
+
}
|
|
221
|
+
function createMemoObject(fn) {
|
|
222
|
+
const map = new Map();
|
|
223
|
+
const owner = getOwner();
|
|
224
|
+
return new Proxy({}, {
|
|
225
|
+
get(_, property) {
|
|
226
|
+
if (!map.has(property)) {
|
|
227
|
+
runWithOwner(owner, () => map.set(property, createMemo(() => fn()[property])));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return map.get(property)();
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
getOwnPropertyDescriptor() {
|
|
234
|
+
return {
|
|
235
|
+
enumerable: true,
|
|
236
|
+
configurable: true
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
ownKeys() {
|
|
241
|
+
return Reflect.ownKeys(fn());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function mergeSearchString(search, params) {
|
|
247
|
+
const merged = new URLSearchParams(search);
|
|
248
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
249
|
+
if (value == null || value === "") {
|
|
250
|
+
merged.delete(key);
|
|
251
|
+
} else {
|
|
252
|
+
merged.set(key, String(value));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const s = merged.toString();
|
|
256
|
+
return s ? `?${s}` : "";
|
|
257
|
+
}
|
|
258
|
+
function expandOptionals(pattern) {
|
|
259
|
+
let match = /(\/?\:[^\/]+)\?/.exec(pattern);
|
|
260
|
+
if (!match) return [pattern];
|
|
261
|
+
let prefix = pattern.slice(0, match.index);
|
|
262
|
+
let suffix = pattern.slice(match.index + match[0].length);
|
|
263
|
+
const prefixes = [prefix, prefix += match[1]]; // This section handles adjacent optional params. We don't actually want all permuations since
|
|
264
|
+
// that will lead to equivalent routes which have the same number of params. For example
|
|
265
|
+
// `/:a?/:b?/:c`? only has the unique expansion: `/`, `/:a`, `/:a/:b`, `/:a/:b/:c` and we can
|
|
266
|
+
// discard `/:b`, `/:c`, `/:b/:c` by building them up in order and not recursing. This also helps
|
|
267
|
+
// ensure predictability where earlier params have precidence.
|
|
268
|
+
|
|
269
|
+
while (match = /^(\/\:[^\/]+)\?/.exec(suffix)) {
|
|
270
|
+
prefixes.push(prefix += match[1]);
|
|
271
|
+
suffix = suffix.slice(match[0].length);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return expandOptionals(suffix).reduce((results, expansion) => [...results, ...prefixes.map(p => p + expansion)], []);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const MAX_REDIRECTS = 100;
|
|
278
|
+
const RouterContextObj = createContext();
|
|
279
|
+
const RouteContextObj = createContext();
|
|
280
|
+
const useRouter = () => invariant(useContext(RouterContextObj), "Make sure your app is wrapped in a <Router />");
|
|
281
|
+
let TempRoute;
|
|
282
|
+
const useRoute = () => TempRoute || useContext(RouteContextObj) || useRouter().base;
|
|
283
|
+
const useResolvedPath = path => {
|
|
284
|
+
const route = useRoute();
|
|
285
|
+
return createMemo(() => route.resolvePath(path()));
|
|
286
|
+
};
|
|
287
|
+
const useHref = to => {
|
|
288
|
+
const router = useRouter();
|
|
289
|
+
return createMemo(() => {
|
|
290
|
+
const to_ = to();
|
|
291
|
+
return to_ !== undefined ? router.renderPath(to_) : to_;
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
const useNavigate = () => useRouter().navigatorFactory();
|
|
295
|
+
const useLocation = () => useRouter().location;
|
|
296
|
+
const useIsRouting = () => useRouter().isRouting;
|
|
297
|
+
const useMatch = path => {
|
|
298
|
+
const location = useLocation();
|
|
299
|
+
const matcher = createMemo(() => createMatcher(path()));
|
|
300
|
+
return createMemo(() => matcher()(location.pathname));
|
|
301
|
+
};
|
|
302
|
+
const useParams = () => useRoute().params;
|
|
303
|
+
const useRouteData = () => useRoute().data;
|
|
304
|
+
const useSearchParams = () => {
|
|
305
|
+
const location = useLocation();
|
|
306
|
+
const navigate = useNavigate();
|
|
307
|
+
|
|
308
|
+
const setSearchParams = (params, options) => {
|
|
309
|
+
const searchString = untrack(() => mergeSearchString(location.search, params));
|
|
310
|
+
navigate(searchString, {
|
|
311
|
+
scroll: false,
|
|
312
|
+
...options,
|
|
313
|
+
resolve: true
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
return [location.query, setSearchParams];
|
|
318
|
+
};
|
|
319
|
+
function createRoutes(routeDef, base = "", fallback) {
|
|
320
|
+
const {
|
|
321
|
+
component,
|
|
322
|
+
data,
|
|
323
|
+
children
|
|
324
|
+
} = routeDef;
|
|
325
|
+
const isLeaf = !children || Array.isArray(children) && !children.length;
|
|
326
|
+
const shared = {
|
|
327
|
+
key: routeDef,
|
|
328
|
+
element: component ? () => createComponent(component, {}) : () => {
|
|
329
|
+
const {
|
|
330
|
+
element
|
|
331
|
+
} = routeDef;
|
|
332
|
+
return element === undefined && fallback ? createComponent(fallback, {}) : element;
|
|
333
|
+
},
|
|
334
|
+
preload: routeDef.component ? component.preload : routeDef.preload,
|
|
335
|
+
data
|
|
336
|
+
};
|
|
337
|
+
return asArray(routeDef.path).reduce((acc, path) => {
|
|
338
|
+
for (const originalPath of expandOptionals(path)) {
|
|
339
|
+
const path = joinPaths(base, originalPath);
|
|
340
|
+
const pattern = isLeaf ? path : path.split("/*", 1)[0];
|
|
341
|
+
acc.push({ ...shared,
|
|
342
|
+
originalPath,
|
|
343
|
+
pattern,
|
|
344
|
+
matcher: createMatcher(pattern, !isLeaf)
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return acc;
|
|
349
|
+
}, []);
|
|
350
|
+
}
|
|
351
|
+
function createBranch(routes, index = 0) {
|
|
352
|
+
return {
|
|
353
|
+
routes,
|
|
354
|
+
score: scoreRoute(routes[routes.length - 1]) * 10000 - index,
|
|
355
|
+
|
|
356
|
+
matcher(location) {
|
|
357
|
+
const matches = [];
|
|
358
|
+
|
|
359
|
+
for (let i = routes.length - 1; i >= 0; i--) {
|
|
360
|
+
const route = routes[i];
|
|
361
|
+
const match = route.matcher(location);
|
|
362
|
+
|
|
363
|
+
if (!match) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
matches.unshift({ ...match,
|
|
368
|
+
route
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return matches;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function asArray(value) {
|
|
379
|
+
return Array.isArray(value) ? value : [value];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function createBranches(routeDef, base = "", fallback, stack = [], branches = []) {
|
|
383
|
+
const routeDefs = asArray(routeDef);
|
|
384
|
+
|
|
385
|
+
for (let i = 0, len = routeDefs.length; i < len; i++) {
|
|
386
|
+
const def = routeDefs[i];
|
|
387
|
+
|
|
388
|
+
if (def && typeof def === "object" && def.hasOwnProperty("path")) {
|
|
389
|
+
const routes = createRoutes(def, base, fallback);
|
|
390
|
+
|
|
391
|
+
for (const route of routes) {
|
|
392
|
+
stack.push(route);
|
|
393
|
+
|
|
394
|
+
if (def.children) {
|
|
395
|
+
createBranches(def.children, route.pattern, fallback, stack, branches);
|
|
396
|
+
} else {
|
|
397
|
+
const branch = createBranch([...stack], branches.length);
|
|
398
|
+
branches.push(branch);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
stack.pop();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} // Stack will be empty on final return
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
return stack.length ? branches : branches.sort((a, b) => b.score - a.score);
|
|
408
|
+
}
|
|
409
|
+
function getRouteMatches(branches, location) {
|
|
410
|
+
for (let i = 0, len = branches.length; i < len; i++) {
|
|
411
|
+
const match = branches[i].matcher(location);
|
|
412
|
+
|
|
413
|
+
if (match) {
|
|
414
|
+
return match;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
420
|
+
function createLocation(path, state) {
|
|
421
|
+
const origin = new URL("http://sar");
|
|
422
|
+
const url = createMemo(prev => {
|
|
423
|
+
const path_ = path();
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
return new URL(path_, origin);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
console.error(`Invalid path ${path_}`);
|
|
429
|
+
return prev;
|
|
430
|
+
}
|
|
431
|
+
}, origin, {
|
|
432
|
+
equals: (a, b) => a.href === b.href
|
|
433
|
+
});
|
|
434
|
+
const pathname = createMemo(() => urlDecode(url().pathname));
|
|
435
|
+
const search = createMemo(() => urlDecode(url().search, true));
|
|
436
|
+
const hash = createMemo(() => urlDecode(url().hash));
|
|
437
|
+
const key = createMemo(() => "");
|
|
438
|
+
return {
|
|
439
|
+
get pathname() {
|
|
440
|
+
return pathname();
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
get search() {
|
|
444
|
+
return search();
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
get hash() {
|
|
448
|
+
return hash();
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
get state() {
|
|
452
|
+
return state();
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
get key() {
|
|
456
|
+
return key();
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
query: createMemoObject(on(search, () => extractSearchParams(url())))
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function createRouterContext(integration, base = "", data, out) {
|
|
463
|
+
const {
|
|
464
|
+
signal: [source, setSource],
|
|
465
|
+
utils = {}
|
|
466
|
+
} = normalizeIntegration(integration);
|
|
467
|
+
|
|
468
|
+
const parsePath = utils.parsePath || (p => p);
|
|
469
|
+
|
|
470
|
+
const renderPath = utils.renderPath || (p => p);
|
|
471
|
+
|
|
472
|
+
const basePath = resolvePath("", base);
|
|
473
|
+
const output = isServer && out ? Object.assign(out, {
|
|
474
|
+
matches: [],
|
|
475
|
+
url: undefined
|
|
476
|
+
}) : undefined;
|
|
477
|
+
|
|
478
|
+
if (basePath === undefined) {
|
|
479
|
+
throw new Error(`${basePath} is not a valid base path`);
|
|
480
|
+
} else if (basePath && !source().value) {
|
|
481
|
+
setSource({
|
|
482
|
+
value: basePath,
|
|
483
|
+
replace: true,
|
|
484
|
+
scroll: false
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const [isRouting, start] = useTransition();
|
|
489
|
+
const [reference, setReference] = createSignal(source().value);
|
|
490
|
+
const [state, setState] = createSignal(source().state);
|
|
491
|
+
const location = createLocation(reference, state);
|
|
492
|
+
const referrers = [];
|
|
493
|
+
const baseRoute = {
|
|
494
|
+
pattern: basePath,
|
|
495
|
+
params: {},
|
|
496
|
+
path: () => basePath,
|
|
497
|
+
outlet: () => null,
|
|
498
|
+
|
|
499
|
+
resolvePath(to) {
|
|
500
|
+
return resolvePath(basePath, to);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
if (data) {
|
|
506
|
+
try {
|
|
507
|
+
TempRoute = baseRoute;
|
|
508
|
+
baseRoute.data = data({
|
|
509
|
+
data: undefined,
|
|
510
|
+
params: {},
|
|
511
|
+
location,
|
|
512
|
+
navigate: navigatorFactory(baseRoute)
|
|
513
|
+
});
|
|
514
|
+
} finally {
|
|
515
|
+
TempRoute = undefined;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function navigateFromRoute(route, to, options) {
|
|
520
|
+
// Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
|
|
521
|
+
untrack(() => {
|
|
522
|
+
if (typeof to === "number") {
|
|
523
|
+
if (!to) ; else if (utils.go) {
|
|
524
|
+
utils.go(to);
|
|
525
|
+
} else {
|
|
526
|
+
console.warn("Router integration does not support relative routing");
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const {
|
|
533
|
+
replace,
|
|
534
|
+
resolve,
|
|
535
|
+
scroll,
|
|
536
|
+
state: nextState
|
|
537
|
+
} = {
|
|
538
|
+
replace: false,
|
|
539
|
+
resolve: true,
|
|
540
|
+
scroll: true,
|
|
541
|
+
...options
|
|
542
|
+
};
|
|
543
|
+
const resolvedTo = resolve ? route.resolvePath(to) : resolvePath("", to);
|
|
544
|
+
|
|
545
|
+
if (resolvedTo === undefined) {
|
|
546
|
+
throw new Error(`Path '${to}' is not a routable path`);
|
|
547
|
+
} else if (referrers.length >= MAX_REDIRECTS) {
|
|
548
|
+
throw new Error("Too many redirects");
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const current = reference();
|
|
552
|
+
|
|
553
|
+
if (resolvedTo !== current || nextState !== state()) {
|
|
554
|
+
if (isServer) {
|
|
555
|
+
if (output) {
|
|
556
|
+
output.url = resolvedTo;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
setSource({
|
|
560
|
+
value: resolvedTo,
|
|
561
|
+
replace,
|
|
562
|
+
scroll,
|
|
563
|
+
state: nextState
|
|
564
|
+
});
|
|
565
|
+
} else {
|
|
566
|
+
const len = referrers.push({
|
|
567
|
+
value: current,
|
|
568
|
+
replace,
|
|
569
|
+
scroll,
|
|
570
|
+
state: state()
|
|
571
|
+
});
|
|
572
|
+
start(() => {
|
|
573
|
+
setReference(resolvedTo);
|
|
574
|
+
setState(nextState);
|
|
575
|
+
resetErrorBoundaries();
|
|
576
|
+
}).then(() => {
|
|
577
|
+
if (referrers.length === len) {
|
|
578
|
+
navigateEnd({
|
|
579
|
+
value: resolvedTo,
|
|
580
|
+
state: nextState
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function navigatorFactory(route) {
|
|
590
|
+
// Workaround for vite issue (https://github.com/vitejs/vite/issues/3803)
|
|
591
|
+
route = route || useContext(RouteContextObj) || baseRoute;
|
|
592
|
+
return (to, options) => navigateFromRoute(route, to, options);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function navigateEnd(next) {
|
|
596
|
+
const first = referrers[0];
|
|
597
|
+
|
|
598
|
+
if (first) {
|
|
599
|
+
if (next.value !== first.value || next.state !== first.state) {
|
|
600
|
+
setSource({ ...next,
|
|
601
|
+
replace: first.replace,
|
|
602
|
+
scroll: first.scroll
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
referrers.length = 0;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
createRenderEffect(() => {
|
|
611
|
+
const {
|
|
612
|
+
value,
|
|
613
|
+
state
|
|
614
|
+
} = source(); // Untrack this whole block so `start` doesn't cause Solid's Listener to be preserved
|
|
615
|
+
|
|
616
|
+
untrack(() => {
|
|
617
|
+
if (value !== reference()) {
|
|
618
|
+
start(() => {
|
|
619
|
+
setReference(value);
|
|
620
|
+
setState(state);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
if (!isServer) {
|
|
627
|
+
function isSvg(el) {
|
|
628
|
+
return el.namespaceURI === "http://www.w3.org/2000/svg";
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function handleAnchorClick(evt) {
|
|
632
|
+
if (evt.defaultPrevented || evt.button !== 0 || evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey) return;
|
|
633
|
+
const a = evt.composedPath().find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
|
|
634
|
+
if (!a) return;
|
|
635
|
+
const svg = isSvg(a);
|
|
636
|
+
const href = svg ? a.href.baseVal : a.href;
|
|
637
|
+
const target = svg ? a.target.baseVal : a.target;
|
|
638
|
+
if (target || !href && !a.hasAttribute("state")) return;
|
|
639
|
+
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
|
640
|
+
if (a.hasAttribute("download") || rel && rel.includes("external")) return;
|
|
641
|
+
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
|
642
|
+
const pathname = urlDecode(url.pathname);
|
|
643
|
+
if (url.origin !== window.location.origin || basePath && pathname && !pathname.toLowerCase().startsWith(basePath.toLowerCase())) return;
|
|
644
|
+
const to = parsePath(pathname + urlDecode(url.search, true) + urlDecode(url.hash));
|
|
645
|
+
const state = a.getAttribute("state");
|
|
646
|
+
evt.preventDefault();
|
|
647
|
+
navigateFromRoute(baseRoute, to, {
|
|
648
|
+
resolve: false,
|
|
649
|
+
replace: a.hasAttribute("replace"),
|
|
650
|
+
scroll: !a.hasAttribute("noscroll"),
|
|
651
|
+
state: state && JSON.parse(state)
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
document.addEventListener("click", handleAnchorClick);
|
|
656
|
+
onCleanup(() => document.removeEventListener("click", handleAnchorClick));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return {
|
|
660
|
+
base: baseRoute,
|
|
661
|
+
out: output,
|
|
662
|
+
location,
|
|
663
|
+
isRouting,
|
|
664
|
+
renderPath,
|
|
665
|
+
parsePath,
|
|
666
|
+
navigatorFactory
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function createRouteContext(router, parent, child, match) {
|
|
670
|
+
const {
|
|
671
|
+
base,
|
|
672
|
+
location,
|
|
673
|
+
navigatorFactory
|
|
674
|
+
} = router;
|
|
675
|
+
const {
|
|
676
|
+
pattern,
|
|
677
|
+
element: outlet,
|
|
678
|
+
preload,
|
|
679
|
+
data
|
|
680
|
+
} = match().route;
|
|
681
|
+
const path = createMemo(() => match().path);
|
|
682
|
+
const params = createMemoObject(() => match().params);
|
|
683
|
+
preload && preload();
|
|
684
|
+
const route = {
|
|
685
|
+
parent,
|
|
686
|
+
pattern,
|
|
687
|
+
|
|
688
|
+
get child() {
|
|
689
|
+
return child();
|
|
690
|
+
},
|
|
691
|
+
|
|
692
|
+
path,
|
|
693
|
+
params,
|
|
694
|
+
data: parent.data,
|
|
695
|
+
outlet,
|
|
696
|
+
|
|
697
|
+
resolvePath(to) {
|
|
698
|
+
return resolvePath(base.path(), to, path());
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
if (data) {
|
|
704
|
+
try {
|
|
705
|
+
TempRoute = route;
|
|
706
|
+
route.data = data({
|
|
707
|
+
data: parent.data,
|
|
708
|
+
params,
|
|
709
|
+
location,
|
|
710
|
+
navigate: navigatorFactory(route)
|
|
711
|
+
});
|
|
712
|
+
} finally {
|
|
713
|
+
TempRoute = undefined;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return route;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const _tmpl$ = /*#__PURE__*/template(`<a></a>`, 2);
|
|
721
|
+
const Router = props => {
|
|
722
|
+
const {
|
|
723
|
+
source,
|
|
724
|
+
url,
|
|
725
|
+
base,
|
|
726
|
+
data,
|
|
727
|
+
out
|
|
728
|
+
} = props;
|
|
729
|
+
const integration = source || (isServer ? staticIntegration({
|
|
730
|
+
value: url || ""
|
|
731
|
+
}) : pathIntegration());
|
|
732
|
+
const routerState = createRouterContext(integration, base, data, out);
|
|
733
|
+
return createComponent$1(RouterContextObj.Provider, {
|
|
734
|
+
value: routerState,
|
|
735
|
+
|
|
736
|
+
get children() {
|
|
737
|
+
return props.children;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
});
|
|
741
|
+
};
|
|
742
|
+
const Routes = props => {
|
|
743
|
+
const router = useRouter();
|
|
744
|
+
const parentRoute = useRoute();
|
|
745
|
+
const routeDefs = children(() => props.children);
|
|
746
|
+
const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
|
|
747
|
+
const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
|
|
748
|
+
|
|
749
|
+
if (router.out) {
|
|
750
|
+
router.out.matches.push(matches().map(({
|
|
751
|
+
route,
|
|
752
|
+
path,
|
|
753
|
+
params
|
|
754
|
+
}) => ({
|
|
755
|
+
originalPath: route.originalPath,
|
|
756
|
+
pattern: route.pattern,
|
|
757
|
+
path,
|
|
758
|
+
params
|
|
759
|
+
})));
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const disposers = [];
|
|
763
|
+
let root;
|
|
764
|
+
const routeStates = createMemo(on(matches, (nextMatches, prevMatches, prev) => {
|
|
765
|
+
let equal = prevMatches && nextMatches.length === prevMatches.length;
|
|
766
|
+
const next = [];
|
|
767
|
+
|
|
768
|
+
for (let i = 0, len = nextMatches.length; i < len; i++) {
|
|
769
|
+
const prevMatch = prevMatches && prevMatches[i];
|
|
770
|
+
const nextMatch = nextMatches[i];
|
|
771
|
+
|
|
772
|
+
if (prev && prevMatch && nextMatch.route.key === prevMatch.route.key) {
|
|
773
|
+
next[i] = prev[i];
|
|
774
|
+
} else {
|
|
775
|
+
equal = false;
|
|
776
|
+
|
|
777
|
+
if (disposers[i]) {
|
|
778
|
+
disposers[i]();
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
createRoot(dispose => {
|
|
782
|
+
disposers[i] = dispose;
|
|
783
|
+
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i]);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
disposers.splice(nextMatches.length).forEach(dispose => dispose());
|
|
789
|
+
|
|
790
|
+
if (prev && equal) {
|
|
791
|
+
return prev;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
root = next[0];
|
|
795
|
+
return next;
|
|
796
|
+
}));
|
|
797
|
+
return createComponent$1(Show, {
|
|
798
|
+
get when() {
|
|
799
|
+
return routeStates() && root;
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
children: route => createComponent$1(RouteContextObj.Provider, {
|
|
803
|
+
value: route,
|
|
804
|
+
|
|
805
|
+
get children() {
|
|
806
|
+
return route.outlet();
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
})
|
|
810
|
+
});
|
|
811
|
+
};
|
|
812
|
+
const useRoutes = (routes, base) => {
|
|
813
|
+
return () => createComponent$1(Routes, {
|
|
814
|
+
base: base,
|
|
815
|
+
children: routes
|
|
816
|
+
});
|
|
817
|
+
};
|
|
818
|
+
const Route = props => {
|
|
819
|
+
const childRoutes = children(() => props.children);
|
|
820
|
+
return mergeProps(props, {
|
|
821
|
+
get children() {
|
|
822
|
+
return childRoutes();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
});
|
|
826
|
+
};
|
|
827
|
+
const Outlet = () => {
|
|
828
|
+
const route = useRoute();
|
|
829
|
+
return createComponent$1(Show, {
|
|
830
|
+
get when() {
|
|
831
|
+
return route.child;
|
|
832
|
+
},
|
|
833
|
+
|
|
834
|
+
children: child => createComponent$1(RouteContextObj.Provider, {
|
|
835
|
+
value: child,
|
|
836
|
+
|
|
837
|
+
get children() {
|
|
838
|
+
return child.outlet();
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
})
|
|
842
|
+
});
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
function LinkBase(props) {
|
|
846
|
+
const [, rest] = splitProps(props, ["children", "to", "href", "state"]);
|
|
847
|
+
const href = useHref(() => props.to);
|
|
848
|
+
return (() => {
|
|
849
|
+
const _el$ = _tmpl$.cloneNode(true);
|
|
850
|
+
|
|
851
|
+
spread(_el$, rest, false, true);
|
|
852
|
+
|
|
853
|
+
insert(_el$, () => props.children);
|
|
854
|
+
|
|
855
|
+
effect(_p$ => {
|
|
856
|
+
const _v$ = href() || props.href,
|
|
857
|
+
_v$2 = JSON.stringify(props.state);
|
|
858
|
+
|
|
859
|
+
_v$ !== _p$._v$ && setAttribute(_el$, "href", _p$._v$ = _v$);
|
|
860
|
+
_v$2 !== _p$._v$2 && setAttribute(_el$, "state", _p$._v$2 = _v$2);
|
|
861
|
+
return _p$;
|
|
862
|
+
}, {
|
|
863
|
+
_v$: undefined,
|
|
864
|
+
_v$2: undefined
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
return _el$;
|
|
868
|
+
})();
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function Link(props) {
|
|
872
|
+
const to = useResolvedPath(() => props.href);
|
|
873
|
+
return createComponent$1(LinkBase, mergeProps$1(props, {
|
|
874
|
+
get to() {
|
|
875
|
+
return to();
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
}));
|
|
879
|
+
}
|
|
880
|
+
function NavLink(props) {
|
|
881
|
+
props = mergeProps({
|
|
882
|
+
inactiveClass: "inactive",
|
|
883
|
+
activeClass: "active"
|
|
884
|
+
}, props);
|
|
885
|
+
const [, rest] = splitProps(props, ["activeClass", "inactiveClass", "end"]);
|
|
886
|
+
const location = useLocation();
|
|
887
|
+
const to = useResolvedPath(() => props.href);
|
|
888
|
+
const isActive = createMemo(() => {
|
|
889
|
+
const to_ = to();
|
|
890
|
+
|
|
891
|
+
if (to_ === undefined) {
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const path = to_.split(/[?#]/, 1)[0].toLowerCase();
|
|
896
|
+
const loc = location.pathname.toLowerCase();
|
|
897
|
+
return props.end ? path === loc : loc.startsWith(path);
|
|
898
|
+
});
|
|
899
|
+
return createComponent$1(LinkBase, mergeProps$1(rest, {
|
|
900
|
+
get to() {
|
|
901
|
+
return to();
|
|
902
|
+
},
|
|
903
|
+
|
|
904
|
+
get classList() {
|
|
905
|
+
return {
|
|
906
|
+
[props.inactiveClass]: !isActive(),
|
|
907
|
+
[props.activeClass]: isActive(),
|
|
908
|
+
...rest.classList
|
|
909
|
+
};
|
|
910
|
+
},
|
|
911
|
+
|
|
912
|
+
get ["aria-current"]() {
|
|
913
|
+
return isActive() ? "page" : undefined;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
}));
|
|
917
|
+
}
|
|
918
|
+
function Navigate(props) {
|
|
919
|
+
const navigate = useNavigate();
|
|
920
|
+
const location = useLocation();
|
|
921
|
+
const {
|
|
922
|
+
href,
|
|
923
|
+
state
|
|
924
|
+
} = props;
|
|
925
|
+
const path = typeof href === "function" ? href({
|
|
926
|
+
navigate,
|
|
927
|
+
location
|
|
928
|
+
}) : href;
|
|
929
|
+
navigate(path, {
|
|
930
|
+
replace: true,
|
|
931
|
+
state
|
|
932
|
+
});
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export { Link, NavLink, Navigate, Outlet, Route, Router, Routes, mergeSearchString as _mergeSearchString, createIntegration, hashIntegration, normalizeIntegration, pathIntegration, staticIntegration, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useRouteData, useRoutes, useSearchParams };
|