@solidjs/router 0.16.0 → 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 +11 -7
- package/dist/data/createAsync.js +3 -0
- package/dist/data/events.d.ts +8 -1
- package/dist/data/events.js +2 -2
- package/dist/data/query.js +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +61 -18
- package/dist/index.jsx +1 -1
- package/dist/lifecycle.js +8 -1
- package/dist/routers/HashRouter.js +1 -1
- package/dist/routers/MemoryRouter.js +1 -1
- package/dist/routers/Router.js +2 -2
- package/dist/routers/components.d.ts +2 -2
- package/dist/routers/components.jsx +4 -1
- package/dist/routing.d.ts +2 -1
- package/dist/routing.js +30 -6
- package/dist/types.d.ts +6 -3
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +17 -1
- package/package.json +2 -2
- package/dist/src/components.d.ts +0 -31
- package/dist/src/components.jsx +0 -39
- package/dist/src/data/action.d.ts +0 -17
- package/dist/src/data/action.js +0 -163
- package/dist/src/data/action.spec.d.ts +0 -1
- package/dist/src/data/action.spec.js +0 -297
- package/dist/src/data/createAsync.d.ts +0 -32
- package/dist/src/data/createAsync.js +0 -96
- package/dist/src/data/createAsync.spec.d.ts +0 -1
- package/dist/src/data/createAsync.spec.js +0 -196
- package/dist/src/data/events.d.ts +0 -9
- package/dist/src/data/events.js +0 -123
- package/dist/src/data/events.spec.d.ts +0 -1
- package/dist/src/data/events.spec.js +0 -567
- package/dist/src/data/index.d.ts +0 -4
- package/dist/src/data/index.js +0 -4
- package/dist/src/data/query.d.ts +0 -23
- package/dist/src/data/query.js +0 -232
- package/dist/src/data/query.spec.d.ts +0 -1
- package/dist/src/data/query.spec.js +0 -354
- package/dist/src/data/response.d.ts +0 -4
- package/dist/src/data/response.js +0 -42
- package/dist/src/data/response.spec.d.ts +0 -1
- package/dist/src/data/response.spec.js +0 -165
- package/dist/src/index.d.ts +0 -7
- package/dist/src/index.jsx +0 -6
- package/dist/src/lifecycle.d.ts +0 -5
- package/dist/src/lifecycle.js +0 -69
- package/dist/src/routers/HashRouter.d.ts +0 -9
- package/dist/src/routers/HashRouter.js +0 -41
- package/dist/src/routers/MemoryRouter.d.ts +0 -24
- package/dist/src/routers/MemoryRouter.js +0 -57
- package/dist/src/routers/Router.d.ts +0 -9
- package/dist/src/routers/Router.js +0 -45
- package/dist/src/routers/StaticRouter.d.ts +0 -6
- package/dist/src/routers/StaticRouter.js +0 -15
- package/dist/src/routers/components.d.ts +0 -27
- package/dist/src/routers/components.jsx +0 -118
- package/dist/src/routers/createRouter.d.ts +0 -10
- package/dist/src/routers/createRouter.js +0 -41
- package/dist/src/routers/index.d.ts +0 -11
- package/dist/src/routers/index.js +0 -6
- package/dist/src/routing.d.ts +0 -175
- package/dist/src/routing.js +0 -560
- package/dist/src/types.d.ts +0 -200
- package/dist/src/types.js +0 -1
- package/dist/src/utils.d.ts +0 -13
- package/dist/src/utils.js +0 -185
- package/dist/test/helpers.d.ts +0 -6
- package/dist/test/helpers.js +0 -50
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js";
|
|
2
2
|
import { isServer } from "solid-js/web";
|
|
3
3
|
import { useRouter } from "../routing.js";
|
|
4
|
-
import { mockBase } from "../utils.js";
|
|
4
|
+
import { mockBase, setFunctionName } from "../utils.js";
|
|
5
5
|
import { cacheKeyOp, hashKey, revalidate, query } from "./query.js";
|
|
6
6
|
export const actions = /* #__PURE__ */ new Map();
|
|
7
7
|
export function useSubmissions(fn, filter) {
|
|
@@ -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
|
}
|
|
@@ -53,7 +53,7 @@ export function action(fn, options = {}) {
|
|
|
53
53
|
error: result?.error,
|
|
54
54
|
pending: false,
|
|
55
55
|
retry() {
|
|
56
|
-
return retry = submission.retry();
|
|
56
|
+
return (retry = submission.retry());
|
|
57
57
|
}
|
|
58
58
|
});
|
|
59
59
|
if (retry)
|
|
@@ -93,10 +93,11 @@ export function action(fn, options = {}) {
|
|
|
93
93
|
return p.then(handler(), handler(true));
|
|
94
94
|
}
|
|
95
95
|
const o = typeof options === "string" ? { name: options } : options;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
(!isServer ? `https://action/${hashString(fn.toString())}` : "");
|
|
96
|
+
const name = o.name || (!isServer ? String(hashString(fn.toString())) : undefined);
|
|
97
|
+
const url = fn.url || (name && `https://action/${name}`) || "";
|
|
99
98
|
mutate.base = url;
|
|
99
|
+
if (name)
|
|
100
|
+
setFunctionName(mutate, name);
|
|
100
101
|
return toAction(mutate, url);
|
|
101
102
|
}
|
|
102
103
|
function toAction(fn, url) {
|
|
@@ -117,7 +118,10 @@ function toAction(fn, url) {
|
|
|
117
118
|
fn.url = url;
|
|
118
119
|
if (!isServer) {
|
|
119
120
|
actions.set(url, fn);
|
|
120
|
-
|
|
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));
|
|
121
125
|
}
|
|
122
126
|
return fn;
|
|
123
127
|
}
|
package/dist/data/createAsync.js
CHANGED
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
import { createResource, sharedConfig, untrack, catchError } from "solid-js";
|
|
5
5
|
import { createStore, reconcile, unwrap } from "solid-js/store";
|
|
6
6
|
import { isServer } from "solid-js/web";
|
|
7
|
+
import { setFunctionName } from "../utils.js";
|
|
7
8
|
export function createAsync(fn, options) {
|
|
8
9
|
let resource;
|
|
9
10
|
let prev = () => !resource || resource.state === "unresolved" ? undefined : resource.latest;
|
|
10
11
|
[resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, options);
|
|
11
12
|
const resultAccessor = (() => resource());
|
|
13
|
+
if (options?.name)
|
|
14
|
+
setFunctionName(resultAccessor, options.name);
|
|
12
15
|
Object.defineProperty(resultAccessor, "latest", {
|
|
13
16
|
get() {
|
|
14
17
|
return resource.latest;
|
package/dist/data/events.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { RouterContext } from "../types.js";
|
|
2
|
-
|
|
2
|
+
type NativeEventConfig = {
|
|
3
|
+
preload?: boolean;
|
|
4
|
+
explicitLinks?: boolean;
|
|
5
|
+
actionBase?: string;
|
|
6
|
+
transformUrl?: (url: string) => string;
|
|
7
|
+
};
|
|
8
|
+
export declare function setupNativeEvents({ preload, explicitLinks, actionBase, transformUrl }?: NativeEventConfig): (router: RouterContext) => void;
|
|
9
|
+
export {};
|
package/dist/data/events.js
CHANGED
|
@@ -2,7 +2,7 @@ import { delegateEvents } from "solid-js/web";
|
|
|
2
2
|
import { onCleanup } from "solid-js";
|
|
3
3
|
import { actions } from "./action.js";
|
|
4
4
|
import { mockBase } from "../utils.js";
|
|
5
|
-
export function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "/_server", transformUrl) {
|
|
5
|
+
export function setupNativeEvents({ preload = true, explicitLinks = false, actionBase = "/_server", transformUrl } = {}) {
|
|
6
6
|
return (router) => {
|
|
7
7
|
const basePath = router.base.path();
|
|
8
8
|
const navigateFromRoute = router.navigatorFactory(router.base);
|
|
@@ -65,7 +65,7 @@ export function setupNativeEvents(preload = true, explicitLinks = false, actionB
|
|
|
65
65
|
clearTimeout(preloadTimeout);
|
|
66
66
|
const res = handleAnchor(evt);
|
|
67
67
|
if (!res)
|
|
68
|
-
return lastElement = null;
|
|
68
|
+
return (lastElement = null);
|
|
69
69
|
const [a, url] = res;
|
|
70
70
|
if (lastElement === a)
|
|
71
71
|
return;
|
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.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "./routers/index.js";
|
|
2
2
|
export * from "./components.jsx";
|
|
3
3
|
export * from "./lifecycle.js";
|
|
4
|
-
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute } from "./routing.js";
|
|
4
|
+
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute, RouterContextObj as RouterContext } from "./routing.js";
|
|
5
5
|
export { mergeSearchString as _mergeSearchString } from "./utils.js";
|
|
6
6
|
export * from "./data/index.js";
|
|
7
7
|
export type { Location, LocationChange, SearchParams, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, Submission, BeforeLeaveEventArgs, RouteLoadFunc, RouteLoadFuncArgs, RouterResponseInit, CustomResponse } from "./types.js";
|
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/index.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "./routers/index.js";
|
|
2
2
|
export * from "./components.jsx";
|
|
3
3
|
export * from "./lifecycle.js";
|
|
4
|
-
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute } from "./routing.js";
|
|
4
|
+
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute, RouterContextObj as RouterContext } from "./routing.js";
|
|
5
5
|
export { mergeSearchString as _mergeSearchString } from "./utils.js";
|
|
6
6
|
export * from "./data/index.js";
|
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);
|
|
@@ -30,7 +30,7 @@ export function HashRouter(props) {
|
|
|
30
30
|
saveCurrentDepth();
|
|
31
31
|
},
|
|
32
32
|
init: notify => bindEvent(window, "hashchange", notifyIfNotBlocked(notify, delta => !beforeLeave.confirm(delta && delta < 0 ? delta : getSource()))),
|
|
33
|
-
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
|
|
33
|
+
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase }),
|
|
34
34
|
utils: {
|
|
35
35
|
go: delta => window.history.go(delta),
|
|
36
36
|
renderPath: path => `#${path}`,
|
|
@@ -49,7 +49,7 @@ export function MemoryRouter(props) {
|
|
|
49
49
|
get: memoryHistory.get,
|
|
50
50
|
set: memoryHistory.set,
|
|
51
51
|
init: memoryHistory.listen,
|
|
52
|
-
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
|
|
52
|
+
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase }),
|
|
53
53
|
utils: {
|
|
54
54
|
go: memoryHistory.go
|
|
55
55
|
}
|
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,
|
|
@@ -36,7 +36,7 @@ export function Router(props) {
|
|
|
36
36
|
return !beforeLeave.confirm(s.value, { state: s.state });
|
|
37
37
|
}
|
|
38
38
|
})),
|
|
39
|
-
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase, props.transformUrl),
|
|
39
|
+
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase, transformUrl: props.transformUrl }),
|
|
40
40
|
utils: {
|
|
41
41
|
go: delta => window.history.go(delta),
|
|
42
42
|
beforeLeave
|
|
@@ -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.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { JSX, Accessor } from "solid-js";
|
|
2
2
|
import type { BeforeLeaveEventArgs, Branch, Intent, Location, MatchFilters, NavigateOptions, Navigator, Params, RouteDescription, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SearchParams, SetSearchParams } from "./types.js";
|
|
3
|
+
/** Consider this API opaque and internal. It is likely to change in the future. */
|
|
3
4
|
export declare const RouterContextObj: import("solid-js").Context<RouterContext | undefined>;
|
|
4
5
|
export declare const RouteContextObj: import("solid-js").Context<RouteContext | undefined>;
|
|
5
6
|
export declare const useRouter: () => RouterContext;
|
|
6
7
|
export declare const useRoute: () => RouteContext;
|
|
7
8
|
export declare const useResolvedPath: (path: () => string) => Accessor<string | undefined>;
|
|
8
|
-
export declare const useHref: (to: () =>
|
|
9
|
+
export declare const useHref: <T extends string | undefined>(to: () => T) => () => string | T;
|
|
9
10
|
/**
|
|
10
11
|
* Retrieves method to do navigation. The method accepts a path to navigate to and an optional object with the following options:
|
|
11
12
|
*
|
package/dist/routing.js
CHANGED
|
@@ -4,6 +4,7 @@ import { isServer, getRequestEvent } from "solid-js/web";
|
|
|
4
4
|
import { createBeforeLeave } from "./lifecycle.js";
|
|
5
5
|
import { mockBase, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js";
|
|
6
6
|
const MAX_REDIRECTS = 100;
|
|
7
|
+
/** Consider this API opaque and internal. It is likely to change in the future. */
|
|
7
8
|
export const RouterContextObj = createContext();
|
|
8
9
|
export const RouteContextObj = createContext();
|
|
9
10
|
export const useRouter = () => invariant(useContext(RouterContextObj), "<A> and 'use' router primitives can be only used inside a Route.");
|
|
@@ -115,7 +116,11 @@ export const useMatch = (path, matchFilters) => {
|
|
|
115
116
|
* const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb))
|
|
116
117
|
* ```
|
|
117
118
|
*/
|
|
118
|
-
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
|
+
};
|
|
119
124
|
/**
|
|
120
125
|
* Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
|
|
121
126
|
*
|
|
@@ -156,11 +161,20 @@ export const useParams = () => useRouter().params;
|
|
|
156
161
|
* ```
|
|
157
162
|
*/
|
|
158
163
|
export const useSearchParams = () => {
|
|
159
|
-
const
|
|
164
|
+
const router = useRouter();
|
|
165
|
+
const location = router.location;
|
|
160
166
|
const navigate = useNavigate();
|
|
161
167
|
const setSearchParams = (params, options) => {
|
|
162
|
-
const
|
|
163
|
-
|
|
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, {
|
|
164
178
|
scroll: false,
|
|
165
179
|
resolve: false,
|
|
166
180
|
...options
|
|
@@ -203,6 +217,11 @@ export const useBeforeLeave = (listener) => {
|
|
|
203
217
|
});
|
|
204
218
|
onCleanup(s);
|
|
205
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));
|
|
206
225
|
export function createRoutes(routeDef, base = "") {
|
|
207
226
|
const { component, preload, load, children, info } = routeDef;
|
|
208
227
|
const isLeaf = !children || (Array.isArray(children) && !children.length);
|
|
@@ -219,7 +238,7 @@ export function createRoutes(routeDef, base = "") {
|
|
|
219
238
|
pattern = pattern
|
|
220
239
|
.split("/")
|
|
221
240
|
.map((s) => {
|
|
222
|
-
return s.startsWith(":") || s.startsWith("*") ? s :
|
|
241
|
+
return s.startsWith(":") || s.startsWith("*") ? s : encodeSegment(s);
|
|
223
242
|
})
|
|
224
243
|
.join("/");
|
|
225
244
|
acc.push({
|
|
@@ -295,7 +314,9 @@ function createLocation(path, state, queryWrapper) {
|
|
|
295
314
|
const url = createMemo(prev => {
|
|
296
315
|
const path_ = path();
|
|
297
316
|
try {
|
|
298
|
-
|
|
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);
|
|
299
320
|
}
|
|
300
321
|
catch (err) {
|
|
301
322
|
console.error(`Invalid path ${path_}`);
|
|
@@ -420,6 +441,9 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
420
441
|
location,
|
|
421
442
|
params,
|
|
422
443
|
isRouting,
|
|
444
|
+
get pendingTarget() {
|
|
445
|
+
return lastTransitionTarget;
|
|
446
|
+
},
|
|
423
447
|
renderPath,
|
|
424
448
|
parsePath,
|
|
425
449
|
navigatorFactory,
|
package/dist/types.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface NavigateOptions<S = unknown> {
|
|
|
42
42
|
state: S;
|
|
43
43
|
}
|
|
44
44
|
export interface Navigator {
|
|
45
|
-
(to: string, options?: Partial<NavigateOptions>): void;
|
|
45
|
+
(to: string | number, options?: Partial<NavigateOptions>): void;
|
|
46
46
|
(delta: number): void;
|
|
47
47
|
}
|
|
48
48
|
export type NavigatorFactory = (route?: RouteContext) => Navigator;
|
|
@@ -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.d.ts
CHANGED
|
@@ -10,3 +10,4 @@ export declare function scoreRoute(route: RouteDescription): number;
|
|
|
10
10
|
export declare function createMemoObject<T extends Record<string | symbol, unknown>>(fn: () => T): T;
|
|
11
11
|
export declare function mergeSearchString(search: string, params: SetSearchParams): string;
|
|
12
12
|
export declare function expandOptionals(pattern: string): string[];
|
|
13
|
+
export declare function setFunctionName<T>(obj: T, value: string): T;
|
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;
|
|
@@ -175,3 +183,11 @@ export function expandOptionals(pattern) {
|
|
|
175
183
|
}
|
|
176
184
|
return expandOptionals(suffix).reduce((results, expansion) => [...results, ...prefixes.map(p => p + expansion)], []);
|
|
177
185
|
}
|
|
186
|
+
export function setFunctionName(obj, value) {
|
|
187
|
+
Object.defineProperty(obj, "name", {
|
|
188
|
+
value,
|
|
189
|
+
writable: false,
|
|
190
|
+
configurable: false
|
|
191
|
+
});
|
|
192
|
+
return obj;
|
|
193
|
+
}
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "0.16.
|
|
9
|
+
"version": "0.16.2",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"solid-js": "^1.8.6"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
|
-
"build": "tsc && rollup -c",
|
|
54
|
+
"build": "rm -rf dist && tsc && rollup -c",
|
|
55
55
|
"test": "vitest run && npm run test:types",
|
|
56
56
|
"test:watch": "vitest",
|
|
57
57
|
"test:types": "tsc --project tsconfig.test.json",
|
package/dist/src/components.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { JSX } from "solid-js";
|
|
2
|
-
import type { Location, Navigator } from "./types.js";
|
|
3
|
-
declare module "solid-js" {
|
|
4
|
-
namespace JSX {
|
|
5
|
-
interface AnchorHTMLAttributes<T> {
|
|
6
|
-
state?: string;
|
|
7
|
-
noScroll?: boolean;
|
|
8
|
-
replace?: boolean;
|
|
9
|
-
preload?: boolean;
|
|
10
|
-
link?: boolean;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "state"> {
|
|
15
|
-
href: string;
|
|
16
|
-
replace?: boolean | undefined;
|
|
17
|
-
noScroll?: boolean | undefined;
|
|
18
|
-
state?: unknown | undefined;
|
|
19
|
-
inactiveClass?: string | undefined;
|
|
20
|
-
activeClass?: string | undefined;
|
|
21
|
-
end?: boolean | undefined;
|
|
22
|
-
}
|
|
23
|
-
export declare function A(props: AnchorProps): JSX.Element;
|
|
24
|
-
export interface NavigateProps {
|
|
25
|
-
href: ((args: {
|
|
26
|
-
navigate: Navigator;
|
|
27
|
-
location: Location;
|
|
28
|
-
}) => string) | string;
|
|
29
|
-
state?: unknown;
|
|
30
|
-
}
|
|
31
|
-
export declare function Navigate(props: NavigateProps): null;
|