@solidjs/router 0.16.1 → 0.16.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/dist/components.jsx +3 -2
- package/dist/data/action.js +5 -2
- package/dist/data/query.js +5 -5
- package/dist/index.js +61 -18
- package/dist/lifecycle.js +8 -1
- package/dist/routers/Router.js +1 -1
- package/dist/routers/components.d.ts +2 -2
- package/dist/routers/components.jsx +4 -1
- package/dist/routing.js +29 -6
- package/dist/types.d.ts +5 -2
- package/dist/utils.js +9 -1
- package/package.json +1 -1
package/dist/components.jsx
CHANGED
|
@@ -18,8 +18,9 @@ export function A(props) {
|
|
|
18
18
|
const to_ = to();
|
|
19
19
|
if (to_ === undefined)
|
|
20
20
|
return [false, false];
|
|
21
|
-
|
|
22
|
-
const
|
|
21
|
+
// trailing slashes are ignored so `/route` and `/route/` share active state
|
|
22
|
+
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase().replace(/\/$/, "");
|
|
23
|
+
const loc = decodeURI(normalizePath(location.pathname).toLowerCase().replace(/\/$/, ""));
|
|
23
24
|
return [props.end ? path === loc : loc.startsWith(path + "/") || loc === path, path === loc];
|
|
24
25
|
});
|
|
25
26
|
return (<a {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{
|
package/dist/data/action.js
CHANGED
|
@@ -24,7 +24,7 @@ export function useSubmission(fn, filter) {
|
|
|
24
24
|
const submissions = useSubmissions(fn, filter);
|
|
25
25
|
return new Proxy({}, {
|
|
26
26
|
get(_, property) {
|
|
27
|
-
if (
|
|
27
|
+
if (submissions.length === 0 && (property === "clear" || property === "retry"))
|
|
28
28
|
return () => { };
|
|
29
29
|
return submissions[submissions.length - 1]?.[property];
|
|
30
30
|
}
|
|
@@ -118,7 +118,10 @@ function toAction(fn, url) {
|
|
|
118
118
|
fn.url = url;
|
|
119
119
|
if (!isServer) {
|
|
120
120
|
actions.set(url, fn);
|
|
121
|
-
|
|
121
|
+
// Only remove the registration if it still belongs to this instance —
|
|
122
|
+
// a re-created action (e.g. a new `.with()` binding after revalidation)
|
|
123
|
+
// may have registered itself under the same URL since.
|
|
124
|
+
getOwner() && onCleanup(() => actions.get(url) === fn && actions.delete(url));
|
|
122
125
|
}
|
|
123
126
|
return fn;
|
|
124
127
|
}
|
package/dist/data/query.js
CHANGED
|
@@ -28,12 +28,12 @@ function getCache() {
|
|
|
28
28
|
* Revalidates the given cache entry/entries.
|
|
29
29
|
*/
|
|
30
30
|
export function revalidate(key, force = true) {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
// force the cache miss synchronously so a `refetch` in the same tick sees it —
|
|
33
|
+
// startTransition defers its callback to a microtask (#497)
|
|
34
|
+
force && cacheKeyOp(key, entry => (entry[0] = 0));
|
|
31
35
|
return startTransition(() => {
|
|
32
|
-
|
|
33
|
-
cacheKeyOp(key, entry => {
|
|
34
|
-
force && (entry[0] = 0); //force cache miss
|
|
35
|
-
entry[4][1](now); // retrigger live signals
|
|
36
|
-
});
|
|
36
|
+
cacheKeyOp(key, entry => entry[4][1](now)); // retrigger live signals
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
export function cacheKeyOp(key, fn) {
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,14 @@ function createBeforeLeave() {
|
|
|
18
18
|
preventDefault: () => e.defaultPrevented = true
|
|
19
19
|
};
|
|
20
20
|
for (const l of listeners) l.listener({
|
|
21
|
-
|
|
21
|
+
to,
|
|
22
|
+
options,
|
|
23
|
+
// delegate to the shared event so later listeners' preventDefault
|
|
24
|
+
// calls are observable from earlier listeners
|
|
25
|
+
get defaultPrevented() {
|
|
26
|
+
return e.defaultPrevented;
|
|
27
|
+
},
|
|
28
|
+
preventDefault: e.preventDefault,
|
|
22
29
|
from: l.location,
|
|
23
30
|
retry: force => {
|
|
24
31
|
force && (ignore = true);
|
|
@@ -122,7 +129,12 @@ function createMatcher(path, partial, matchFilters) {
|
|
|
122
129
|
const segments = pattern.split("/").filter(Boolean);
|
|
123
130
|
const len = segments.length;
|
|
124
131
|
return location => {
|
|
125
|
-
const locSegments = location.split("/")
|
|
132
|
+
const locSegments = location.split("/");
|
|
133
|
+
// tolerate a single leading and trailing slash, but reject empty interior
|
|
134
|
+
// segments so `/foo//bar` doesn't silently match `/foo/bar` (#567)
|
|
135
|
+
if (locSegments[0] === "") locSegments.shift();
|
|
136
|
+
if (locSegments.length && locSegments[locSegments.length - 1] === "") locSegments.pop();
|
|
137
|
+
if (locSegments.includes("")) return null;
|
|
126
138
|
const lenDiff = locSegments.length - len;
|
|
127
139
|
if (lenDiff < 0 || lenDiff > 0 && splat === undefined && !partial) {
|
|
128
140
|
return null;
|
|
@@ -364,7 +376,11 @@ const useMatch = (path, matchFilters) => {
|
|
|
364
376
|
* const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb))
|
|
365
377
|
* ```
|
|
366
378
|
*/
|
|
367
|
-
const useCurrentMatches = () =>
|
|
379
|
+
const useCurrentMatches = () => {
|
|
380
|
+
const router = useRouter();
|
|
381
|
+
// return a copy so user mutations (eg. `.reverse()`) can't corrupt router state
|
|
382
|
+
return () => router.matches().slice();
|
|
383
|
+
};
|
|
368
384
|
|
|
369
385
|
/**
|
|
370
386
|
* Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
|
|
@@ -407,11 +423,20 @@ const useParams = () => useRouter().params;
|
|
|
407
423
|
* ```
|
|
408
424
|
*/
|
|
409
425
|
const useSearchParams = () => {
|
|
410
|
-
const
|
|
426
|
+
const router = useRouter();
|
|
427
|
+
const location = router.location;
|
|
411
428
|
const navigate = useNavigate();
|
|
412
429
|
const setSearchParams = (params, options) => {
|
|
413
|
-
const
|
|
414
|
-
|
|
430
|
+
const to = untrack(() => {
|
|
431
|
+
// merge onto the in-flight navigation target (if any) so consecutive
|
|
432
|
+
// synchronous calls compose instead of the later one winning
|
|
433
|
+
const pending = router.pendingTarget && new URL(router.pendingTarget.value, mockBase);
|
|
434
|
+
const pathname = pending ? pending.pathname : location.pathname;
|
|
435
|
+
const search = pending ? pending.search : location.search;
|
|
436
|
+
const hash = pending ? pending.hash : location.hash;
|
|
437
|
+
return pathname + mergeSearchString(search, params) + hash;
|
|
438
|
+
});
|
|
439
|
+
navigate(to, {
|
|
415
440
|
scroll: false,
|
|
416
441
|
resolve: false,
|
|
417
442
|
...options
|
|
@@ -455,6 +480,12 @@ const useBeforeLeave = listener => {
|
|
|
455
480
|
});
|
|
456
481
|
onCleanup(s);
|
|
457
482
|
};
|
|
483
|
+
|
|
484
|
+
// Encodes a static path segment like `encodeURIComponent`, but leaves RFC 3986
|
|
485
|
+
// pchar characters (sub-delims / ":" / "@") literal, matching how browsers
|
|
486
|
+
// report them in `location.pathname`. Non-ASCII characters (eg. CJK paths) are
|
|
487
|
+
// still percent-encoded exactly as before, since browsers encode those too.
|
|
488
|
+
const encodeSegment = s => encodeURIComponent(s).replace(/%(2B|40|3A|24|26|2C|3B|3D)/g, m => decodeURIComponent(m));
|
|
458
489
|
function createRoutes(routeDef, base = "") {
|
|
459
490
|
const {
|
|
460
491
|
component,
|
|
@@ -475,7 +506,7 @@ function createRoutes(routeDef, base = "") {
|
|
|
475
506
|
const path = joinPaths(base, expandedPath);
|
|
476
507
|
let pattern = isLeaf ? path : path.split("/*", 1)[0];
|
|
477
508
|
pattern = pattern.split("/").map(s => {
|
|
478
|
-
return s.startsWith(":") || s.startsWith("*") ? s :
|
|
509
|
+
return s.startsWith(":") || s.startsWith("*") ? s : encodeSegment(s);
|
|
479
510
|
}).join("/");
|
|
480
511
|
acc.push({
|
|
481
512
|
...shared,
|
|
@@ -549,7 +580,9 @@ function createLocation(path, state, queryWrapper) {
|
|
|
549
580
|
const url = createMemo(prev => {
|
|
550
581
|
const path_ = path();
|
|
551
582
|
try {
|
|
552
|
-
|
|
583
|
+
// anchor rooted paths against the origin explicitly - a path with
|
|
584
|
+
// doubled leading slashes would otherwise parse as protocol-relative
|
|
585
|
+
return new URL(path_[0] === "/" ? mockBase + path_ : path_, origin);
|
|
553
586
|
} catch (err) {
|
|
554
587
|
console.error(`Invalid path ${path_}`);
|
|
555
588
|
return prev;
|
|
@@ -677,6 +710,9 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
677
710
|
location,
|
|
678
711
|
params,
|
|
679
712
|
isRouting,
|
|
713
|
+
get pendingTarget() {
|
|
714
|
+
return lastTransitionTarget;
|
|
715
|
+
},
|
|
680
716
|
renderPath,
|
|
681
717
|
parsePath,
|
|
682
718
|
navigatorFactory,
|
|
@@ -925,6 +961,9 @@ function Routes(props) {
|
|
|
925
961
|
}
|
|
926
962
|
const disposers = [];
|
|
927
963
|
let root;
|
|
964
|
+
// dispose the detached per-route roots when this component unmounts, otherwise
|
|
965
|
+
// they stay subscribed to `matches` and crash on a later navigation (#451)
|
|
966
|
+
onCleanup(() => disposers.forEach(dispose => dispose()));
|
|
928
967
|
const routeStates = createMemo(on(props.routerState.matches, (nextMatches, prevMatches, prev) => {
|
|
929
968
|
let equal = prevMatches && nextMatches.length === prevMatches.length;
|
|
930
969
|
const next = [];
|
|
@@ -1078,12 +1117,12 @@ function getCache() {
|
|
|
1078
1117
|
* Revalidates the given cache entry/entries.
|
|
1079
1118
|
*/
|
|
1080
1119
|
function revalidate(key, force = true) {
|
|
1120
|
+
const now = Date.now();
|
|
1121
|
+
// force the cache miss synchronously so a `refetch` in the same tick sees it —
|
|
1122
|
+
// startTransition defers its callback to a microtask (#497)
|
|
1123
|
+
force && cacheKeyOp(key, entry => entry[0] = 0);
|
|
1081
1124
|
return startTransition(() => {
|
|
1082
|
-
|
|
1083
|
-
cacheKeyOp(key, entry => {
|
|
1084
|
-
force && (entry[0] = 0); //force cache miss
|
|
1085
|
-
entry[4][1](now); // retrigger live signals
|
|
1086
|
-
});
|
|
1125
|
+
cacheKeyOp(key, entry => entry[4][1](now)); // retrigger live signals
|
|
1087
1126
|
});
|
|
1088
1127
|
}
|
|
1089
1128
|
function cacheKeyOp(key, fn) {
|
|
@@ -1265,7 +1304,7 @@ function useSubmission(fn, filter) {
|
|
|
1265
1304
|
const submissions = useSubmissions(fn, filter);
|
|
1266
1305
|
return new Proxy({}, {
|
|
1267
1306
|
get(_, property) {
|
|
1268
|
-
if (submissions.length === 0 && property === "clear" || property === "retry") return () => {};
|
|
1307
|
+
if (submissions.length === 0 && (property === "clear" || property === "retry")) return () => {};
|
|
1269
1308
|
return submissions[submissions.length - 1]?.[property];
|
|
1270
1309
|
}
|
|
1271
1310
|
});
|
|
@@ -1356,7 +1395,10 @@ function toAction(fn, url) {
|
|
|
1356
1395
|
fn.url = url;
|
|
1357
1396
|
if (!isServer) {
|
|
1358
1397
|
actions.set(url, fn);
|
|
1359
|
-
|
|
1398
|
+
// Only remove the registration if it still belongs to this instance —
|
|
1399
|
+
// a re-created action (e.g. a new `.with()` binding after revalidation)
|
|
1400
|
+
// may have registered itself under the same URL since.
|
|
1401
|
+
getOwner() && onCleanup(() => actions.get(url) === fn && actions.delete(url));
|
|
1360
1402
|
}
|
|
1361
1403
|
return fn;
|
|
1362
1404
|
}
|
|
@@ -1511,7 +1553,7 @@ function setupNativeEvents({
|
|
|
1511
1553
|
function Router(props) {
|
|
1512
1554
|
if (isServer) return StaticRouter(props);
|
|
1513
1555
|
const getSource = () => {
|
|
1514
|
-
const url = window.location.pathname
|
|
1556
|
+
const url = window.location.pathname + window.location.search;
|
|
1515
1557
|
const state = window.history.state && window.history.state._depth && Object.keys(window.history.state).length === 1 ? undefined : window.history.state;
|
|
1516
1558
|
return {
|
|
1517
1559
|
value: url + window.location.hash,
|
|
@@ -1681,8 +1723,9 @@ function A(props) {
|
|
|
1681
1723
|
const isActive = createMemo(() => {
|
|
1682
1724
|
const to_ = to();
|
|
1683
1725
|
if (to_ === undefined) return [false, false];
|
|
1684
|
-
|
|
1685
|
-
const
|
|
1726
|
+
// trailing slashes are ignored so `/route` and `/route/` share active state
|
|
1727
|
+
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase().replace(/\/$/, "");
|
|
1728
|
+
const loc = decodeURI(normalizePath(location.pathname).toLowerCase().replace(/\/$/, ""));
|
|
1686
1729
|
return [props.end ? path === loc : loc.startsWith(path + "/") || loc === path, path === loc];
|
|
1687
1730
|
});
|
|
1688
1731
|
return (() => {
|
package/dist/lifecycle.js
CHANGED
|
@@ -17,7 +17,14 @@ export function createBeforeLeave() {
|
|
|
17
17
|
};
|
|
18
18
|
for (const l of listeners)
|
|
19
19
|
l.listener({
|
|
20
|
-
|
|
20
|
+
to,
|
|
21
|
+
options,
|
|
22
|
+
// delegate to the shared event so later listeners' preventDefault
|
|
23
|
+
// calls are observable from earlier listeners
|
|
24
|
+
get defaultPrevented() {
|
|
25
|
+
return e.defaultPrevented;
|
|
26
|
+
},
|
|
27
|
+
preventDefault: e.preventDefault,
|
|
21
28
|
from: l.location,
|
|
22
29
|
retry: (force) => {
|
|
23
30
|
force && (ignore = true);
|
package/dist/routers/Router.js
CHANGED
|
@@ -7,7 +7,7 @@ export function Router(props) {
|
|
|
7
7
|
if (isServer)
|
|
8
8
|
return StaticRouter(props);
|
|
9
9
|
const getSource = () => {
|
|
10
|
-
const url = window.location.pathname
|
|
10
|
+
const url = window.location.pathname + window.location.search;
|
|
11
11
|
const state = window.history.state && window.history.state._depth && Object.keys(window.history.state).length === 1 ? undefined : window.history.state;
|
|
12
12
|
return {
|
|
13
13
|
value: url + window.location.hash,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component, JSX } from "solid-js";
|
|
2
|
-
import type { MatchFilters, RouteDefinition, RoutePreloadFunc, RouterIntegration, RouteSectionProps } from "../types.js";
|
|
2
|
+
import type { MatchFilters, RouteDefinition, RoutePreloadFunc, RouterIntegration, RouteSectionComponent, RouteSectionProps } from "../types.js";
|
|
3
3
|
export type BaseRouterProps = {
|
|
4
4
|
base?: string;
|
|
5
5
|
/**
|
|
@@ -19,7 +19,7 @@ export type RouteProps<S extends string, T = unknown> = {
|
|
|
19
19
|
children?: JSX.Element;
|
|
20
20
|
preload?: RoutePreloadFunc<T>;
|
|
21
21
|
matchFilters?: MatchFilters<S>;
|
|
22
|
-
component?:
|
|
22
|
+
component?: RouteSectionComponent<T>;
|
|
23
23
|
info?: Record<string, any>;
|
|
24
24
|
/** @deprecated use preload */
|
|
25
25
|
load?: RoutePreloadFunc<T>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
|
-
import { children, createMemo, createRoot, getOwner, mergeProps, on, Show, untrack } from "solid-js";
|
|
2
|
+
import { children, createMemo, createRoot, getOwner, mergeProps, on, onCleanup, Show, untrack } from "solid-js";
|
|
3
3
|
import { getRequestEvent, isServer } from "solid-js/web";
|
|
4
4
|
import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
|
|
5
5
|
export const createRouterComponent = (router) => (props) => {
|
|
@@ -54,6 +54,9 @@ function Routes(props) {
|
|
|
54
54
|
}
|
|
55
55
|
const disposers = [];
|
|
56
56
|
let root;
|
|
57
|
+
// dispose the detached per-route roots when this component unmounts, otherwise
|
|
58
|
+
// they stay subscribed to `matches` and crash on a later navigation (#451)
|
|
59
|
+
onCleanup(() => disposers.forEach(dispose => dispose()));
|
|
57
60
|
const routeStates = createMemo(on(props.routerState.matches, (nextMatches, prevMatches, prev) => {
|
|
58
61
|
let equal = prevMatches && nextMatches.length === prevMatches.length;
|
|
59
62
|
const next = [];
|
package/dist/routing.js
CHANGED
|
@@ -116,7 +116,11 @@ export const useMatch = (path, matchFilters) => {
|
|
|
116
116
|
* const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb))
|
|
117
117
|
* ```
|
|
118
118
|
*/
|
|
119
|
-
export const useCurrentMatches = () =>
|
|
119
|
+
export const useCurrentMatches = () => {
|
|
120
|
+
const router = useRouter();
|
|
121
|
+
// return a copy so user mutations (eg. `.reverse()`) can't corrupt router state
|
|
122
|
+
return () => router.matches().slice();
|
|
123
|
+
};
|
|
120
124
|
/**
|
|
121
125
|
* Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
|
|
122
126
|
*
|
|
@@ -157,11 +161,20 @@ export const useParams = () => useRouter().params;
|
|
|
157
161
|
* ```
|
|
158
162
|
*/
|
|
159
163
|
export const useSearchParams = () => {
|
|
160
|
-
const
|
|
164
|
+
const router = useRouter();
|
|
165
|
+
const location = router.location;
|
|
161
166
|
const navigate = useNavigate();
|
|
162
167
|
const setSearchParams = (params, options) => {
|
|
163
|
-
const
|
|
164
|
-
|
|
168
|
+
const to = untrack(() => {
|
|
169
|
+
// merge onto the in-flight navigation target (if any) so consecutive
|
|
170
|
+
// synchronous calls compose instead of the later one winning
|
|
171
|
+
const pending = router.pendingTarget && new URL(router.pendingTarget.value, mockBase);
|
|
172
|
+
const pathname = pending ? pending.pathname : location.pathname;
|
|
173
|
+
const search = pending ? pending.search : location.search;
|
|
174
|
+
const hash = pending ? pending.hash : location.hash;
|
|
175
|
+
return pathname + mergeSearchString(search, params) + hash;
|
|
176
|
+
});
|
|
177
|
+
navigate(to, {
|
|
165
178
|
scroll: false,
|
|
166
179
|
resolve: false,
|
|
167
180
|
...options
|
|
@@ -204,6 +217,11 @@ export const useBeforeLeave = (listener) => {
|
|
|
204
217
|
});
|
|
205
218
|
onCleanup(s);
|
|
206
219
|
};
|
|
220
|
+
// Encodes a static path segment like `encodeURIComponent`, but leaves RFC 3986
|
|
221
|
+
// pchar characters (sub-delims / ":" / "@") literal, matching how browsers
|
|
222
|
+
// report them in `location.pathname`. Non-ASCII characters (eg. CJK paths) are
|
|
223
|
+
// still percent-encoded exactly as before, since browsers encode those too.
|
|
224
|
+
const encodeSegment = (s) => encodeURIComponent(s).replace(/%(2B|40|3A|24|26|2C|3B|3D)/g, m => decodeURIComponent(m));
|
|
207
225
|
export function createRoutes(routeDef, base = "") {
|
|
208
226
|
const { component, preload, load, children, info } = routeDef;
|
|
209
227
|
const isLeaf = !children || (Array.isArray(children) && !children.length);
|
|
@@ -220,7 +238,7 @@ export function createRoutes(routeDef, base = "") {
|
|
|
220
238
|
pattern = pattern
|
|
221
239
|
.split("/")
|
|
222
240
|
.map((s) => {
|
|
223
|
-
return s.startsWith(":") || s.startsWith("*") ? s :
|
|
241
|
+
return s.startsWith(":") || s.startsWith("*") ? s : encodeSegment(s);
|
|
224
242
|
})
|
|
225
243
|
.join("/");
|
|
226
244
|
acc.push({
|
|
@@ -296,7 +314,9 @@ function createLocation(path, state, queryWrapper) {
|
|
|
296
314
|
const url = createMemo(prev => {
|
|
297
315
|
const path_ = path();
|
|
298
316
|
try {
|
|
299
|
-
|
|
317
|
+
// anchor rooted paths against the origin explicitly - a path with
|
|
318
|
+
// doubled leading slashes would otherwise parse as protocol-relative
|
|
319
|
+
return new URL(path_[0] === "/" ? mockBase + path_ : path_, origin);
|
|
300
320
|
}
|
|
301
321
|
catch (err) {
|
|
302
322
|
console.error(`Invalid path ${path_}`);
|
|
@@ -421,6 +441,9 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
421
441
|
location,
|
|
422
442
|
params,
|
|
423
443
|
isRouting,
|
|
444
|
+
get pendingTarget() {
|
|
445
|
+
return lastTransitionTarget;
|
|
446
|
+
},
|
|
424
447
|
renderPath,
|
|
425
448
|
parsePath,
|
|
426
449
|
navigatorFactory,
|
package/dist/types.d.ts
CHANGED
|
@@ -71,12 +71,13 @@ export interface RouteSectionProps<T = unknown> {
|
|
|
71
71
|
data: T;
|
|
72
72
|
children?: JSX.Element;
|
|
73
73
|
}
|
|
74
|
-
export type
|
|
74
|
+
export type RouteSectionComponent<T = unknown> = Component<RouteSectionProps<T>> | Component<Omit<RouteSectionProps<T>, "children">> | Component<{}>;
|
|
75
|
+
export type RouteDefinition<S extends string | string[] = any, T = any> = {
|
|
75
76
|
path?: S;
|
|
76
77
|
matchFilters?: MatchFilters<S>;
|
|
77
78
|
preload?: RoutePreloadFunc<T>;
|
|
78
79
|
children?: RouteDefinition | RouteDefinition[];
|
|
79
|
-
component?:
|
|
80
|
+
component?: RouteSectionComponent<T>;
|
|
80
81
|
info?: Record<string, any>;
|
|
81
82
|
/** @deprecated use preload */
|
|
82
83
|
load?: RoutePreloadFunc;
|
|
@@ -137,6 +138,8 @@ export interface RouterContext {
|
|
|
137
138
|
params: Params;
|
|
138
139
|
navigatorFactory: NavigatorFactory;
|
|
139
140
|
isRouting: () => boolean;
|
|
141
|
+
/** The target of the in-flight navigation transition, if any. Not reactive. */
|
|
142
|
+
readonly pendingTarget?: LocationChange;
|
|
140
143
|
matches: () => RouteMatch[];
|
|
141
144
|
renderPath(path: string): string;
|
|
142
145
|
parsePath(str: string): string;
|
package/dist/utils.js
CHANGED
|
@@ -52,7 +52,15 @@ export function createMatcher(path, partial, matchFilters) {
|
|
|
52
52
|
const segments = pattern.split("/").filter(Boolean);
|
|
53
53
|
const len = segments.length;
|
|
54
54
|
return (location) => {
|
|
55
|
-
const locSegments = location.split("/")
|
|
55
|
+
const locSegments = location.split("/");
|
|
56
|
+
// tolerate a single leading and trailing slash, but reject empty interior
|
|
57
|
+
// segments so `/foo//bar` doesn't silently match `/foo/bar` (#567)
|
|
58
|
+
if (locSegments[0] === "")
|
|
59
|
+
locSegments.shift();
|
|
60
|
+
if (locSegments.length && locSegments[locSegments.length - 1] === "")
|
|
61
|
+
locSegments.pop();
|
|
62
|
+
if (locSegments.includes(""))
|
|
63
|
+
return null;
|
|
56
64
|
const lenDiff = locSegments.length - len;
|
|
57
65
|
if (lenDiff < 0 || (lenDiff > 0 && splat === undefined && !partial)) {
|
|
58
66
|
return null;
|