@solidjs/router 2.0.0-next.30 → 2.0.0-next.32
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 +1 -1
- package/dist/data/action.js +31 -22
- package/dist/data/events.js +1 -1
- package/dist/data/query.js +70 -33
- package/dist/fs.d.ts +1 -1
- package/dist/fs.js +1 -1
- package/dist/index.js +181 -111
- package/dist/paths.js +2 -6
- package/dist/routers/components.jsx +1 -3
- package/dist/routers/factory.jsx +64 -17
- package/dist/routers/history.js +7 -11
- package/dist/routers/scrollRestoration.js +1 -2
- package/dist/routing.js +11 -18
- package/dist/utils.js +15 -24
- package/package.json +5 -5
package/dist/routers/factory.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
|
-
import { createSignal, getOwner, onCleanup, onSettled, runWithOwner,
|
|
2
|
+
import { createSignal, getOwner, onCleanup, onSettled, runWithOwner, untrack } from "solid-js";
|
|
3
3
|
// standalone imports: `DEV` is undefined in solid's production build and
|
|
4
4
|
// `OBSERVE` outside its observe/dev builds, so app bundlers fold the
|
|
5
5
|
// `DEV &&` diagnostics and the `OBSERVE &&` attribution out of shipped bundles
|
|
@@ -53,7 +53,6 @@ function describeNavigation(match, next, written, from) {
|
|
|
53
53
|
const t = to();
|
|
54
54
|
return t === undefined ? undefined : new URL(t, mockBase).pathname;
|
|
55
55
|
};
|
|
56
|
-
const matches = (pathname) => untrack(() => match(pathname));
|
|
57
56
|
const ref = {
|
|
58
57
|
kind: "navigation",
|
|
59
58
|
get to() {
|
|
@@ -61,22 +60,61 @@ function describeNavigation(match, next, written, from) {
|
|
|
61
60
|
},
|
|
62
61
|
from,
|
|
63
62
|
get name() {
|
|
64
|
-
|
|
65
|
-
if (p === undefined)
|
|
66
|
-
return undefined;
|
|
67
|
-
const m = matches(p);
|
|
68
|
-
return m.length ? m[m.length - 1].route.pattern || "/" : p;
|
|
63
|
+
return routeName(match, pathname());
|
|
69
64
|
},
|
|
70
65
|
get params() {
|
|
71
|
-
|
|
72
|
-
const m = p === undefined ? [] : matches(p);
|
|
73
|
-
return m.length ? mergeParams(m) : undefined;
|
|
66
|
+
return routeParams(match, pathname());
|
|
74
67
|
}
|
|
75
68
|
};
|
|
76
69
|
if (next._navigation !== undefined && next._navigation > 1)
|
|
77
70
|
ref.redirect = next._navigation - 1;
|
|
78
71
|
return ref;
|
|
79
72
|
}
|
|
73
|
+
/** The parametrized route `pathname` matches — the last match's pattern, `/` for the root; the pathname itself when nothing matched. */
|
|
74
|
+
function routeName(match, pathname) {
|
|
75
|
+
if (pathname === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
const m = untrack(() => match(pathname));
|
|
78
|
+
return m.length ? m[m.length - 1].route.pattern || "/" : pathname;
|
|
79
|
+
}
|
|
80
|
+
/** The params the matched chain bound, merged; `undefined` when nothing matched. */
|
|
81
|
+
function routeParams(match, pathname) {
|
|
82
|
+
if (pathname === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
const m = untrack(() => match(pathname));
|
|
85
|
+
return m.length ? mergeParams(m) : undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The route the document arrived on, for solid's observe tier: the same
|
|
89
|
+
* declaration as a navigation's, around the work that establishes the
|
|
90
|
+
* router's initial match — building its context — since a fresh document has
|
|
91
|
+
* no location write to wrap. On the client the engine opens it at the time
|
|
92
|
+
* origin and settles it as the context is built (the first `"navigation"`
|
|
93
|
+
* record, `initial: true`); on the server the same call names the request's
|
|
94
|
+
* `"render"` record (`RenderEvent.route`). Every field is a getter read at
|
|
95
|
+
* settle, as for a navigation: `to` is the location as the router then holds
|
|
96
|
+
* it — the arrival normalized onto the base path when it came in empty, a
|
|
97
|
+
* write the frame itself took (see the setter) — and `name`/`params` match
|
|
98
|
+
* it then, so a lazy subtree that resolved while the server rendered names
|
|
99
|
+
* the exact route.
|
|
100
|
+
*/
|
|
101
|
+
function describeInitial(match, location) {
|
|
102
|
+
const to = () => untrack(location).value;
|
|
103
|
+
const pathname = () => new URL(to(), mockBase).pathname;
|
|
104
|
+
return {
|
|
105
|
+
kind: "navigation",
|
|
106
|
+
initial: true,
|
|
107
|
+
get to() {
|
|
108
|
+
return to();
|
|
109
|
+
},
|
|
110
|
+
get name() {
|
|
111
|
+
return routeName(match, pathname());
|
|
112
|
+
},
|
|
113
|
+
get params() {
|
|
114
|
+
return routeParams(match, pathname());
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
80
118
|
/** Wraps a history adapter in the integration signal the router core consumes. Must run under a reactive owner. */
|
|
81
119
|
function createIntegration(history, match) {
|
|
82
120
|
let committing = false;
|
|
@@ -91,8 +129,6 @@ function createIntegration(history, match) {
|
|
|
91
129
|
const signal = [
|
|
92
130
|
read,
|
|
93
131
|
(next) => {
|
|
94
|
-
if (sharedConfig.registry && !sharedConfig.done)
|
|
95
|
-
sharedConfig.done = true;
|
|
96
132
|
// What the write resolved to, or undefined when there was nothing to
|
|
97
133
|
// write (see `resolveLocationWrite`).
|
|
98
134
|
let written;
|
|
@@ -128,8 +164,15 @@ function createIntegration(history, match) {
|
|
|
128
164
|
// while a navigation is pending, which is the `from` a hop wants too.
|
|
129
165
|
// A write the no-op rule drops is still declared: the engine settles a
|
|
130
166
|
// navigation whose write "did not survive the equality gate" on the spot.
|
|
131
|
-
|
|
132
|
-
|
|
167
|
+
// A write from NO location is not a navigation: it is the arrival being
|
|
168
|
+
// normalized (`createRouterContext` moving an empty location — a hash
|
|
169
|
+
// history with no hash, a memory history seeded with "" — onto the base
|
|
170
|
+
// path), and the arrival is already declared by the initial frame open
|
|
171
|
+
// around the context build (`describeInitial`), which this write then
|
|
172
|
+
// stamps. The location is never empty again after that first write.
|
|
173
|
+
const from = untrack(read).value;
|
|
174
|
+
OBSERVE && from
|
|
175
|
+
? OBSERVE.attribution.withOrigin(describeNavigation(match, next, () => written, from), commit)
|
|
133
176
|
: commit();
|
|
134
177
|
}
|
|
135
178
|
];
|
|
@@ -210,11 +253,16 @@ export function createRouter(config) {
|
|
|
210
253
|
? staticIntegration(props.url, config.history && config.history.utils)
|
|
211
254
|
: createIntegration(history || browserHistory(), matchPath);
|
|
212
255
|
let context;
|
|
213
|
-
const
|
|
256
|
+
const buildContext = () => createRouterContext(integration, branches, () => context, {
|
|
214
257
|
base: basePath,
|
|
215
258
|
singleFlight: config.singleFlight,
|
|
216
259
|
transformUrl: config.transformUrl
|
|
217
260
|
});
|
|
261
|
+
// The route the document arrived on, declared around the initial match
|
|
262
|
+
// (see `describeInitial`); folded out of shipped bundles with `OBSERVE`.
|
|
263
|
+
const routerState = OBSERVE
|
|
264
|
+
? OBSERVE.attribution.withOrigin(describeInitial(matchPath, integration.signal[0]), buildContext)
|
|
265
|
+
: buildContext();
|
|
218
266
|
if (!isServer) {
|
|
219
267
|
setupNativeEvents({
|
|
220
268
|
preload: config.preloadLinks,
|
|
@@ -247,9 +295,8 @@ export function createRouter(config) {
|
|
|
247
295
|
}));
|
|
248
296
|
},
|
|
249
297
|
keysFor(url) {
|
|
250
|
-
const pathname = typeof url === "string" ? new URL(url, mockBase).pathname : url[HREF];
|
|
251
298
|
const keys = [];
|
|
252
|
-
for (const { route } of matchPath(
|
|
299
|
+
for (const { route } of matchPath(pathnameOf(url))) {
|
|
253
300
|
const key = serverRouteOf(route.component)?.call?.key;
|
|
254
301
|
typeof key === "string" && key && !keys.includes(key) && keys.push(key);
|
|
255
302
|
}
|
package/dist/routers/history.js
CHANGED
|
@@ -9,10 +9,11 @@ export function bindEvent(target, type, handler) {
|
|
|
9
9
|
// guard machinery itself lives behind the lazy `beforeLeave` slot.
|
|
10
10
|
let depth;
|
|
11
11
|
export function saveCurrentDepth() {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const h = window.history;
|
|
13
|
+
if (!h.state || h.state._depth == null) {
|
|
14
|
+
h.replaceState({ ...h.state, _depth: h.length - 1 }, "");
|
|
14
15
|
}
|
|
15
|
-
depth =
|
|
16
|
+
depth = h.state?._depth ?? h.length - 1;
|
|
16
17
|
}
|
|
17
18
|
function keepDepth(state) {
|
|
18
19
|
return {
|
|
@@ -50,15 +51,10 @@ function scrollToHash(hash, fallbackTop) {
|
|
|
50
51
|
}
|
|
51
52
|
export function browserHistory() {
|
|
52
53
|
const getSource = () => {
|
|
53
|
-
const
|
|
54
|
-
const state = window.history.state &&
|
|
55
|
-
window.history.state._depth &&
|
|
56
|
-
Object.keys(window.history.state).length === 1
|
|
57
|
-
? undefined
|
|
58
|
-
: window.history.state;
|
|
54
|
+
const s = window.history.state;
|
|
59
55
|
return {
|
|
60
|
-
value:
|
|
61
|
-
state
|
|
56
|
+
value: window.location.pathname + window.location.search + window.location.hash,
|
|
57
|
+
state: s && s._depth && Object.keys(s).length === 1 ? undefined : s
|
|
62
58
|
};
|
|
63
59
|
};
|
|
64
60
|
const beforeLeave = {};
|
|
@@ -105,8 +105,7 @@ export function createScrollRestoration() {
|
|
|
105
105
|
// reload/back_forward document loads land on an existing entry (a fresh
|
|
106
106
|
// navigation starts a new one and belongs at the top); the effect's
|
|
107
107
|
// initial run performs the restore after first render
|
|
108
|
-
const [nav] =
|
|
109
|
-
performance.getEntriesByType("navigation"));
|
|
108
|
+
const [nav] = performance.getEntriesByType?.("navigation");
|
|
110
109
|
if (nav && nav.type !== "navigate")
|
|
111
110
|
pending = depth();
|
|
112
111
|
}
|
package/dist/routing.js
CHANGED
|
@@ -391,9 +391,7 @@ export function createRoutes(routeDef, base = "") {
|
|
|
391
391
|
let pattern = isLeaf ? path : path.split("/*", 1)[0];
|
|
392
392
|
pattern = pattern
|
|
393
393
|
.split("/")
|
|
394
|
-
.map((s)
|
|
395
|
-
return s.startsWith(":") || s.startsWith("*") ? s : encodeSegment(s);
|
|
396
|
-
})
|
|
394
|
+
.map(s => (/^[:*]/.test(s) ? s : encodeSegment(s)))
|
|
397
395
|
.join("/");
|
|
398
396
|
acc.push({
|
|
399
397
|
...shared,
|
|
@@ -487,13 +485,12 @@ export function mergeParams(matches) {
|
|
|
487
485
|
return params;
|
|
488
486
|
}
|
|
489
487
|
function createLocation(path, state, queryWrapper) {
|
|
490
|
-
const
|
|
491
|
-
const url = createMemo((prev = origin) => {
|
|
488
|
+
const url = createMemo((prev = new URL(mockBase)) => {
|
|
492
489
|
const path_ = path();
|
|
493
490
|
try {
|
|
494
491
|
// anchor rooted paths against the origin explicitly - a path with
|
|
495
492
|
// doubled leading slashes would otherwise parse as protocol-relative
|
|
496
|
-
return new URL(path_[0] === "/" ? mockBase + path_ : path_,
|
|
493
|
+
return new URL(path_[0] === "/" ? mockBase + path_ : path_, mockBase);
|
|
497
494
|
}
|
|
498
495
|
catch (err) {
|
|
499
496
|
DEV && console.error(`Invalid path ${path_}`);
|
|
@@ -505,8 +502,8 @@ function createLocation(path, state, queryWrapper) {
|
|
|
505
502
|
const pathname = createMemo(() => url().pathname);
|
|
506
503
|
const search = createMemo(() => url().search);
|
|
507
504
|
const hash = createMemo(() => url().hash);
|
|
508
|
-
|
|
509
|
-
const queryFn = createMemo(() => extractSearchParams(
|
|
505
|
+
// Pathname and hash changes do not affect the query.
|
|
506
|
+
const queryFn = createMemo(() => extractSearchParams(new URL(search(), mockBase)));
|
|
510
507
|
return {
|
|
511
508
|
get pathname() {
|
|
512
509
|
return pathname();
|
|
@@ -521,7 +518,7 @@ function createLocation(path, state, queryWrapper) {
|
|
|
521
518
|
return state();
|
|
522
519
|
},
|
|
523
520
|
get key() {
|
|
524
|
-
return
|
|
521
|
+
return "";
|
|
525
522
|
},
|
|
526
523
|
query: queryWrapper ? queryWrapper(queryFn) : createMemoObject(queryFn)
|
|
527
524
|
};
|
|
@@ -660,7 +657,7 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
660
657
|
read = runWithOwner(routerOwner, () => createMemo(() => {
|
|
661
658
|
const result = resolveLazySubtree(record);
|
|
662
659
|
return result instanceof Promise ? result.then(() => undefined) : undefined;
|
|
663
|
-
}, { name: "lazyRoutes" }));
|
|
660
|
+
}, DEV && { name: "lazyRoutes" }));
|
|
664
661
|
lazyReaders.set(record, read);
|
|
665
662
|
}
|
|
666
663
|
return read();
|
|
@@ -695,7 +692,7 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
695
692
|
}
|
|
696
693
|
}
|
|
697
694
|
return m;
|
|
698
|
-
}, { name: "matches" });
|
|
695
|
+
}, DEV && { name: "matches" });
|
|
699
696
|
const routingPending = createMemo(() => isPending(() => {
|
|
700
697
|
try {
|
|
701
698
|
matches();
|
|
@@ -706,7 +703,7 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
706
703
|
}
|
|
707
704
|
location.search;
|
|
708
705
|
location.hash;
|
|
709
|
-
}), { name: "routingPending" });
|
|
706
|
+
}), DEV && { name: "routingPending" });
|
|
710
707
|
const isRouting = () => routingPending() || isPending(source);
|
|
711
708
|
const transitionIntent = () => {
|
|
712
709
|
if (!isPending(source))
|
|
@@ -912,10 +909,8 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
912
909
|
preloadIntent = "preload";
|
|
913
910
|
for (let match in next) {
|
|
914
911
|
const { route, params } = next[match];
|
|
915
|
-
route.component &&
|
|
916
|
-
route.component.preload &&
|
|
917
|
-
route.component.preload();
|
|
918
912
|
const { preload, component } = route;
|
|
913
|
+
component?.preload?.();
|
|
919
914
|
const now = current && current[match];
|
|
920
915
|
const unchanged = now &&
|
|
921
916
|
now.route.key === route.key &&
|
|
@@ -978,9 +973,7 @@ export function createRouteContext(router, parent, outlet, match, matches = () =
|
|
|
978
973
|
// its last valid value while this route is being torn down, so outgoing
|
|
979
974
|
// components and preloads never observe another route's params.
|
|
980
975
|
const params = wrapParams(() => mergeParams(matches()));
|
|
981
|
-
component
|
|
982
|
-
component.preload &&
|
|
983
|
-
component.preload();
|
|
976
|
+
component?.preload?.();
|
|
984
977
|
inPreloadFn = true;
|
|
985
978
|
const data = preload
|
|
986
979
|
? preload({ params, location, intent: router.intent?.() || "initial" })
|
package/dist/utils.js
CHANGED
|
@@ -43,7 +43,7 @@ export function validateSearch(schema, raw) {
|
|
|
43
43
|
return outcome;
|
|
44
44
|
}
|
|
45
45
|
export function extractSearchParams(url) {
|
|
46
|
-
const params =
|
|
46
|
+
const params = Object.create(null);
|
|
47
47
|
url.searchParams.forEach((value, key) => {
|
|
48
48
|
if (key in params) {
|
|
49
49
|
if (Array.isArray(params[key]))
|
|
@@ -54,7 +54,8 @@ export function extractSearchParams(url) {
|
|
|
54
54
|
else
|
|
55
55
|
params[key] = value;
|
|
56
56
|
});
|
|
57
|
-
|
|
57
|
+
// Keep __proto__ as an own property on the plain object returned to callers.
|
|
58
|
+
return { ...params };
|
|
58
59
|
}
|
|
59
60
|
export function createMatcher(path, partial, matchFilters) {
|
|
60
61
|
const [pattern, splat] = path.split("/*", 2);
|
|
@@ -78,13 +79,13 @@ export function createMatcher(path, partial, matchFilters) {
|
|
|
78
79
|
path: len ? "" : "/",
|
|
79
80
|
params: {}
|
|
80
81
|
};
|
|
81
|
-
const
|
|
82
|
+
const filters = matchFilters;
|
|
82
83
|
for (let i = 0; i < len; i++) {
|
|
83
84
|
const segment = segments[i];
|
|
84
85
|
const dynamic = segment[0] === ":";
|
|
85
86
|
const locSegment = dynamic ? locSegments[i] : locSegments[i].toLowerCase();
|
|
86
87
|
const key = dynamic ? segment.slice(1) : segment.toLowerCase();
|
|
87
|
-
if (dynamic && matchSegment(locSegment,
|
|
88
|
+
if (dynamic && matchSegment(locSegment, filters?.[key])) {
|
|
88
89
|
match.params[key] = locSegment;
|
|
89
90
|
}
|
|
90
91
|
else if (dynamic || !matchSegment(locSegment, key)) {
|
|
@@ -94,7 +95,7 @@ export function createMatcher(path, partial, matchFilters) {
|
|
|
94
95
|
}
|
|
95
96
|
if (splat) {
|
|
96
97
|
const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
97
|
-
if (matchSegment(remainder,
|
|
98
|
+
if (matchSegment(remainder, filters?.[splat])) {
|
|
98
99
|
match.params[splat] = remainder;
|
|
99
100
|
}
|
|
100
101
|
else {
|
|
@@ -104,25 +105,15 @@ export function createMatcher(path, partial, matchFilters) {
|
|
|
104
105
|
return match;
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return filter(input);
|
|
117
|
-
}
|
|
118
|
-
else if (Array.isArray(filter)) {
|
|
119
|
-
return filter.some(isEqual);
|
|
120
|
-
}
|
|
121
|
-
else if (filter instanceof RegExp) {
|
|
122
|
-
return filter.test(input);
|
|
123
|
-
}
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
108
|
+
// a string filter is the segment itself; anything else not listed never matches
|
|
109
|
+
const matchSegment = (input, filter) => filter === undefined ||
|
|
110
|
+
(typeof filter === "function"
|
|
111
|
+
? filter(input)
|
|
112
|
+
: Array.isArray(filter)
|
|
113
|
+
? filter.includes(input)
|
|
114
|
+
: filter instanceof RegExp
|
|
115
|
+
? filter.test(input)
|
|
116
|
+
: filter === input);
|
|
126
117
|
export function scoreRoute(route) {
|
|
127
118
|
const [pattern, splat] = route.pattern.split("/*", 2);
|
|
128
119
|
const segments = pattern.split("/").filter(Boolean);
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "2.0.0-next.
|
|
9
|
+
"version": "2.0.0-next.32",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -38,22 +38,22 @@
|
|
|
38
38
|
"@rollup/plugin-node-resolve": "15.3.0",
|
|
39
39
|
"@rollup/plugin-terser": "0.4.4",
|
|
40
40
|
"@solidjs/vite-plugin": "3.0.0-next.35",
|
|
41
|
-
"@solidjs/web": "^2.0.0-rc.
|
|
41
|
+
"@solidjs/web": "^2.0.0-rc.13",
|
|
42
42
|
"@types/jest": "^29.5.14",
|
|
43
43
|
"@types/node": "^22.10.0",
|
|
44
44
|
"babel-preset-solid": "^2.0.0-rc.2",
|
|
45
45
|
"jsdom": "^25.0.1",
|
|
46
46
|
"prettier": "^3.4.1",
|
|
47
47
|
"rollup": "^4.27.4",
|
|
48
|
-
"solid-js": "^2.0.0-rc.
|
|
48
|
+
"solid-js": "^2.0.0-rc.13",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vite": "^8.2.2",
|
|
51
51
|
"vitest": "^4.1.11"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@solidjs/web": "^2.0.0-rc.
|
|
54
|
+
"@solidjs/web": "^2.0.0-rc.13",
|
|
55
55
|
"filesystem-routing": ">=0.4.0",
|
|
56
|
-
"solid-js": "^2.0.0-rc.
|
|
56
|
+
"solid-js": "^2.0.0-rc.13"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"filesystem-routing": {
|