@solidjs/router 0.17.0-next.3 → 0.17.0-next.5
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.d.ts +2 -2
- package/dist/components.jsx +3 -2
- package/dist/data/action.d.ts +1 -1
- package/dist/data/action.js +31 -14
- package/dist/data/index.d.ts +0 -1
- package/dist/data/index.js +0 -1
- package/dist/data/query.js +19 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +147 -113
- package/dist/lifecycle.js +8 -1
- package/dist/routers/HashRouter.d.ts +1 -1
- package/dist/routers/MemoryRouter.d.ts +1 -1
- package/dist/routers/Router.d.ts +1 -1
- package/dist/routers/Router.js +1 -1
- package/dist/routers/StaticRouter.d.ts +1 -1
- package/dist/routers/components.d.ts +4 -3
- package/dist/routers/components.jsx +18 -5
- package/dist/routers/createRouter.d.ts +1 -1
- package/dist/routing.d.ts +6 -6
- package/dist/routing.js +51 -20
- package/dist/types.d.ts +11 -12
- package/dist/utils.js +9 -1
- package/package.json +7 -7
- package/dist/data/response.d.ts +0 -4
- package/dist/data/response.js +0 -42
package/dist/components.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { JSX } from "
|
|
1
|
+
import type { JSX } from "@solidjs/web";
|
|
2
2
|
import type { Location, Navigator } from "./types.js";
|
|
3
|
-
declare module "
|
|
3
|
+
declare module "@solidjs/web/jsx-runtime" {
|
|
4
4
|
namespace JSX {
|
|
5
5
|
interface AnchorHTMLAttributes<T> {
|
|
6
6
|
state?: string;
|
package/dist/components.jsx
CHANGED
|
@@ -26,8 +26,9 @@ export function A(props) {
|
|
|
26
26
|
const to_ = to();
|
|
27
27
|
if (to_ === undefined)
|
|
28
28
|
return [false, false];
|
|
29
|
-
|
|
30
|
-
const
|
|
29
|
+
// trailing slashes are ignored so `/route` and `/route/` share active state
|
|
30
|
+
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase().replace(/\/$/, "");
|
|
31
|
+
const loc = decodeURI(normalizePath(location.pathname).toLowerCase().replace(/\/$/, ""));
|
|
31
32
|
return [props.end ? path === loc : loc.startsWith(path + "/") || loc === path, path === loc];
|
|
32
33
|
});
|
|
33
34
|
const className = createMemo(() => [toClassName(props.class), isActive()[0] ? props.activeClass : props.inactiveClass]
|
package/dist/data/action.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JSX } from "
|
|
1
|
+
import { type JSX } from "@solidjs/web";
|
|
2
2
|
import type { Submission, NarrowResponse } from "../types.js";
|
|
3
3
|
export type Action<T extends Array<any>, U, V = T> = (T extends [FormData | URLSearchParams] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<NarrowResponse<U>>) & {
|
|
4
4
|
url: string;
|
package/dist/data/action.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { $TRACK, action as createSolidAction, createMemo, onCleanup, getOwner } from "solid-js";
|
|
2
|
-
import { isServer } from "@solidjs/web";
|
|
2
|
+
import { isResponseEnvelope, isServer } from "@solidjs/web";
|
|
3
|
+
import { decodeResponse } from "@solidjs/web/server-functions";
|
|
3
4
|
import { useRouter } from "../routing.js";
|
|
4
5
|
import { mockBase, setFunctionName } from "../utils.js";
|
|
5
6
|
import { cacheKeyOp, hashKey, revalidate, query } from "./query.js";
|
|
@@ -122,7 +123,11 @@ function toAction(invoke, url, boundArgs = [], base = url, submitHooks = new Map
|
|
|
122
123
|
fn[invokeSymbol] = invoke;
|
|
123
124
|
if (!isServer) {
|
|
124
125
|
actions.set(url, fn);
|
|
125
|
-
|
|
126
|
+
// Only remove the registration if it still belongs to this instance —
|
|
127
|
+
// a re-created action (e.g. a new `.with()` binding after revalidation)
|
|
128
|
+
// may have registered itself under the same URL since.
|
|
129
|
+
getOwner() &&
|
|
130
|
+
onCleanup(() => actions.get(url) === fn && actions.delete(url));
|
|
126
131
|
}
|
|
127
132
|
return fn;
|
|
128
133
|
}
|
|
@@ -147,19 +152,35 @@ async function handleResponse(response, error, navigate) {
|
|
|
147
152
|
let custom;
|
|
148
153
|
let keys;
|
|
149
154
|
let flightKeys;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
let metadata;
|
|
156
|
+
if (isResponseEnvelope(response)) {
|
|
157
|
+
// client-only respond(): the value rides in memory beside the metadata
|
|
158
|
+
data = response.value;
|
|
159
|
+
metadata = response.response;
|
|
160
|
+
}
|
|
161
|
+
else if (response instanceof Response) {
|
|
162
|
+
metadata = response;
|
|
163
|
+
// responses the transport hands over whole (redirects, revalidation,
|
|
164
|
+
// single-flight) carry a codec-encoded body the router decodes itself
|
|
165
|
+
if (response.body) {
|
|
166
|
+
data = await decodeResponse(response);
|
|
155
167
|
if (response.headers.has("X-Single-Flight")) {
|
|
156
|
-
|
|
168
|
+
custom = data;
|
|
169
|
+
data = custom._$value;
|
|
157
170
|
delete custom._$value;
|
|
158
171
|
flightKeys = Object.keys(custom);
|
|
159
172
|
}
|
|
160
173
|
}
|
|
161
|
-
|
|
162
|
-
|
|
174
|
+
}
|
|
175
|
+
else if (error)
|
|
176
|
+
return { error: response };
|
|
177
|
+
else
|
|
178
|
+
data = response;
|
|
179
|
+
if (metadata) {
|
|
180
|
+
if (metadata.headers.has("X-Revalidate"))
|
|
181
|
+
keys = metadata.headers.get("X-Revalidate").split(",");
|
|
182
|
+
if (metadata.headers.has("Location")) {
|
|
183
|
+
const locationUrl = metadata.headers.get("Location") || "/";
|
|
163
184
|
if (locationUrl.startsWith("http")) {
|
|
164
185
|
window.location.href = locationUrl;
|
|
165
186
|
}
|
|
@@ -168,10 +189,6 @@ async function handleResponse(response, error, navigate) {
|
|
|
168
189
|
}
|
|
169
190
|
}
|
|
170
191
|
}
|
|
171
|
-
else if (error)
|
|
172
|
-
return { error: response };
|
|
173
|
-
else
|
|
174
|
-
data = response;
|
|
175
192
|
// invalidate
|
|
176
193
|
cacheKeyOp(keys, entry => (entry[0] = 0));
|
|
177
194
|
// set cache
|
package/dist/data/index.d.ts
CHANGED
package/dist/data/index.js
CHANGED
package/dist/data/query.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSignal, getObserver, getOwner, onCleanup, sharedConfig } from "solid-js";
|
|
2
|
-
import { getRequestEvent, isServer } from "@solidjs/web";
|
|
2
|
+
import { getRequestEvent, isResponseEnvelope, isServer } from "@solidjs/web";
|
|
3
|
+
import { decodeResponse } from "@solidjs/web/server-functions";
|
|
3
4
|
import { useNavigate, getIntent, getInPreloadFn } from "../routing.js";
|
|
4
5
|
const LocationHeader = "Location";
|
|
5
6
|
const PRELOAD_TIMEOUT = 5000;
|
|
@@ -143,6 +144,14 @@ export function query(fn, name) {
|
|
|
143
144
|
return res;
|
|
144
145
|
function handleResponse(error) {
|
|
145
146
|
return async (v) => {
|
|
147
|
+
let enveloped;
|
|
148
|
+
let hasEnveloped = false;
|
|
149
|
+
if (isResponseEnvelope(v)) {
|
|
150
|
+
// respond(): the value rides in memory beside the metadata
|
|
151
|
+
enveloped = v.value;
|
|
152
|
+
hasEnveloped = true;
|
|
153
|
+
v = v.response;
|
|
154
|
+
}
|
|
146
155
|
if (v instanceof Response) {
|
|
147
156
|
const e = getRequestEvent();
|
|
148
157
|
if (e) {
|
|
@@ -164,8 +173,15 @@ export function query(fn, name) {
|
|
|
164
173
|
e.response.status = 302;
|
|
165
174
|
return;
|
|
166
175
|
}
|
|
167
|
-
if (
|
|
168
|
-
v =
|
|
176
|
+
if (hasEnveloped)
|
|
177
|
+
v = enveloped;
|
|
178
|
+
else if (v.body) {
|
|
179
|
+
// responses the transport hands over whole (revalidation) carry a
|
|
180
|
+
// codec-encoded body; anything else (a raw user Response) stays whole
|
|
181
|
+
const decoded = await decodeResponse(v);
|
|
182
|
+
if (decoded !== undefined)
|
|
183
|
+
v = decoded;
|
|
184
|
+
}
|
|
169
185
|
}
|
|
170
186
|
if (error)
|
|
171
187
|
throw v;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export * from "./lifecycle.js";
|
|
|
4
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
|
-
export type { Location, LocationChange, SearchParams, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, Submission, BeforeLeaveEventArgs
|
|
7
|
+
export type { Location, LocationChange, SearchParams, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, Submission, BeforeLeaveEventArgs } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { isServer, getRequestEvent, createComponent as createComponent$1, memo, delegateEvents, spread, mergeProps, template } from '@solidjs/web';
|
|
1
|
+
import { isServer, getRequestEvent, createComponent as createComponent$1, memo, isResponseEnvelope, delegateEvents, spread, mergeProps, template } from '@solidjs/web';
|
|
2
2
|
import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, flush, createComponent, children, merge, createRoot, sharedConfig, getObserver, $TRACK, action as action$1, omit } from 'solid-js';
|
|
3
|
+
import { decodeResponse } from '@solidjs/web/server-functions';
|
|
3
4
|
|
|
4
5
|
function createBeforeLeave() {
|
|
5
6
|
let listeners = new Set();
|
|
@@ -17,7 +18,14 @@ function createBeforeLeave() {
|
|
|
17
18
|
preventDefault: () => e.defaultPrevented = true
|
|
18
19
|
};
|
|
19
20
|
for (const l of listeners) l.listener({
|
|
20
|
-
|
|
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,
|
|
21
29
|
from: l.location,
|
|
22
30
|
retry: force => {
|
|
23
31
|
force && (ignore = true);
|
|
@@ -121,7 +129,12 @@ function createMatcher(path, partial, matchFilters) {
|
|
|
121
129
|
const segments = pattern.split("/").filter(Boolean);
|
|
122
130
|
const len = segments.length;
|
|
123
131
|
return location => {
|
|
124
|
-
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;
|
|
125
138
|
const lenDiff = locSegments.length - len;
|
|
126
139
|
if (lenDiff < 0 || lenDiff > 0 && splat === undefined && !partial) {
|
|
127
140
|
return null;
|
|
@@ -370,7 +383,11 @@ const useMatch = (path, matchFilters) => {
|
|
|
370
383
|
* const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb))
|
|
371
384
|
* ```
|
|
372
385
|
*/
|
|
373
|
-
const useCurrentMatches = () =>
|
|
386
|
+
const useCurrentMatches = () => {
|
|
387
|
+
const router = useRouter();
|
|
388
|
+
// return a copy so user mutations (eg. `.reverse()`) can't corrupt router state
|
|
389
|
+
return () => router.matches().slice();
|
|
390
|
+
};
|
|
374
391
|
|
|
375
392
|
/**
|
|
376
393
|
* Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
|
|
@@ -383,7 +400,7 @@ const useCurrentMatches = () => useRouter().matches;
|
|
|
383
400
|
* const getUser = query(() => fetchUser(params.id), "user");
|
|
384
401
|
* ```
|
|
385
402
|
*/
|
|
386
|
-
const useParams = () =>
|
|
403
|
+
const useParams = () => useRoute().params;
|
|
387
404
|
|
|
388
405
|
/**
|
|
389
406
|
* Retrieves a tuple containing a reactive object to read the current location's query parameters and a method to update them.
|
|
@@ -413,11 +430,20 @@ const useParams = () => useRouter().params;
|
|
|
413
430
|
* ```
|
|
414
431
|
*/
|
|
415
432
|
const useSearchParams = () => {
|
|
416
|
-
const
|
|
433
|
+
const router = useRouter();
|
|
434
|
+
const location = router.location;
|
|
417
435
|
const navigate = useNavigate();
|
|
418
436
|
const setSearchParams = (params, options) => {
|
|
419
|
-
const
|
|
420
|
-
|
|
437
|
+
const to = untrack(() => {
|
|
438
|
+
// merge onto the in-flight navigation target (if any) so consecutive
|
|
439
|
+
// synchronous calls compose instead of the later one winning
|
|
440
|
+
const pending = router.pendingTarget && new URL(router.pendingTarget.value, mockBase);
|
|
441
|
+
const pathname = pending ? pending.pathname : location.pathname;
|
|
442
|
+
const search = pending ? pending.search : location.search;
|
|
443
|
+
const hash = pending ? pending.hash : location.hash;
|
|
444
|
+
return pathname + mergeSearchString(search, params) + hash;
|
|
445
|
+
});
|
|
446
|
+
navigate(to, {
|
|
421
447
|
scroll: false,
|
|
422
448
|
resolve: false,
|
|
423
449
|
...options
|
|
@@ -461,6 +487,12 @@ const useBeforeLeave = listener => {
|
|
|
461
487
|
});
|
|
462
488
|
onCleanup(s);
|
|
463
489
|
};
|
|
490
|
+
|
|
491
|
+
// Encodes a static path segment like `encodeURIComponent`, but leaves RFC 3986
|
|
492
|
+
// pchar characters (sub-delims / ":" / "@") literal, matching how browsers
|
|
493
|
+
// report them in `location.pathname`. Non-ASCII characters (eg. CJK paths) are
|
|
494
|
+
// still percent-encoded exactly as before, since browsers encode those too.
|
|
495
|
+
const encodeSegment = s => encodeURIComponent(s).replace(/%(2B|40|3A|24|26|2C|3B|3D)/g, m => decodeURIComponent(m));
|
|
464
496
|
function createRoutes(routeDef, base = "") {
|
|
465
497
|
const {
|
|
466
498
|
component,
|
|
@@ -480,7 +512,7 @@ function createRoutes(routeDef, base = "") {
|
|
|
480
512
|
const path = joinPaths(base, expandedPath);
|
|
481
513
|
let pattern = isLeaf ? path : path.split("/*", 1)[0];
|
|
482
514
|
pattern = pattern.split("/").map(s => {
|
|
483
|
-
return s.startsWith(":") || s.startsWith("*") ? s :
|
|
515
|
+
return s.startsWith(":") || s.startsWith("*") ? s : encodeSegment(s);
|
|
484
516
|
}).join("/");
|
|
485
517
|
acc.push({
|
|
486
518
|
...shared,
|
|
@@ -549,12 +581,21 @@ function getRouteMatches(branches, location) {
|
|
|
549
581
|
}
|
|
550
582
|
return [];
|
|
551
583
|
}
|
|
584
|
+
function mergeParams(matches) {
|
|
585
|
+
const params = {};
|
|
586
|
+
for (let i = 0; i < matches.length; i++) {
|
|
587
|
+
Object.assign(params, matches[i].params);
|
|
588
|
+
}
|
|
589
|
+
return params;
|
|
590
|
+
}
|
|
552
591
|
function createLocation(path, state, queryWrapper) {
|
|
553
592
|
const origin = new URL(mockBase);
|
|
554
593
|
const url = createMemo((prev = origin) => {
|
|
555
594
|
const path_ = path();
|
|
556
595
|
try {
|
|
557
|
-
|
|
596
|
+
// anchor rooted paths against the origin explicitly - a path with
|
|
597
|
+
// doubled leading slashes would otherwise parse as protocol-relative
|
|
598
|
+
return new URL(path_[0] === "/" ? mockBase + path_ : path_, origin);
|
|
558
599
|
} catch (err) {
|
|
559
600
|
console.error(`Invalid path ${path_}`);
|
|
560
601
|
return prev;
|
|
@@ -642,17 +683,12 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
642
683
|
}
|
|
643
684
|
return getRouteMatches(branches(), location.pathname);
|
|
644
685
|
});
|
|
645
|
-
const buildParams = () =>
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
for (let i = 0; i < m.length; i++) {
|
|
649
|
-
Object.assign(params, m[i].params);
|
|
650
|
-
}
|
|
651
|
-
return params;
|
|
652
|
-
};
|
|
653
|
-
const params = utils.paramsWrapper ? utils.paramsWrapper(buildParams, branches) : createMemoObject(buildParams);
|
|
686
|
+
const buildParams = () => mergeParams(matches());
|
|
687
|
+
const wrapParams = utils.paramsWrapper ? getParams => utils.paramsWrapper(getParams, branches) : getParams => createMemoObject(getParams);
|
|
688
|
+
const params = wrapParams(buildParams);
|
|
654
689
|
const baseRoute = {
|
|
655
690
|
pattern: basePath,
|
|
691
|
+
params,
|
|
656
692
|
path: () => basePath,
|
|
657
693
|
outlet: () => null,
|
|
658
694
|
resolvePath(to) {
|
|
@@ -663,7 +699,11 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
663
699
|
base: baseRoute,
|
|
664
700
|
location,
|
|
665
701
|
params,
|
|
702
|
+
wrapParams,
|
|
666
703
|
isRouting,
|
|
704
|
+
get pendingTarget() {
|
|
705
|
+
return lastTransitionTarget;
|
|
706
|
+
},
|
|
667
707
|
renderPath,
|
|
668
708
|
parsePath,
|
|
669
709
|
navigatorFactory,
|
|
@@ -808,11 +848,11 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
808
848
|
}];
|
|
809
849
|
}
|
|
810
850
|
}
|
|
811
|
-
function createRouteContext(router, parent, outlet, match) {
|
|
851
|
+
function createRouteContext(router, parent, outlet, match, matches = () => [match()]) {
|
|
812
852
|
const {
|
|
813
853
|
base,
|
|
814
854
|
location,
|
|
815
|
-
|
|
855
|
+
wrapParams
|
|
816
856
|
} = router;
|
|
817
857
|
const {
|
|
818
858
|
pattern,
|
|
@@ -820,6 +860,10 @@ function createRouteContext(router, parent, outlet, match) {
|
|
|
820
860
|
preload
|
|
821
861
|
} = match().route;
|
|
822
862
|
const path = createMemo(() => match().path);
|
|
863
|
+
// Params scoped to this route's lifetime. `matches` is expected to retain
|
|
864
|
+
// its last valid value while this route is being torn down, so outgoing
|
|
865
|
+
// components and preloads never observe another route's params.
|
|
866
|
+
const params = wrapParams(() => mergeParams(matches()));
|
|
823
867
|
component && component.preload && component.preload();
|
|
824
868
|
inPreloadFn = true;
|
|
825
869
|
const data = preload ? preload({
|
|
@@ -831,6 +875,7 @@ function createRouteContext(router, parent, outlet, match) {
|
|
|
831
875
|
const route = {
|
|
832
876
|
parent,
|
|
833
877
|
pattern,
|
|
878
|
+
params,
|
|
834
879
|
path,
|
|
835
880
|
outlet: () => component ? createComponent(component, {
|
|
836
881
|
params,
|
|
@@ -940,6 +985,13 @@ function Routes(props) {
|
|
|
940
985
|
const disposers = [];
|
|
941
986
|
let root;
|
|
942
987
|
let prevMatches;
|
|
988
|
+
// dispose the detached per-route roots when this component unmounts, otherwise
|
|
989
|
+
// they stay subscribed to `matches` and crash on a later navigation (#451)
|
|
990
|
+
onCleanup(() => disposers.forEach(dispose => dispose()));
|
|
991
|
+
// Route roots must outlive re-runs of the `routeStates` memo below, so they
|
|
992
|
+
// are created under the owner of this component rather than the memo's
|
|
993
|
+
// computation (which disposes its children every time it re-runs).
|
|
994
|
+
const owner = getOwner();
|
|
943
995
|
const routeStates = createMemo(prev => {
|
|
944
996
|
const nextMatches = props.routerState.matches();
|
|
945
997
|
const previousMatches = prevMatches;
|
|
@@ -955,13 +1007,19 @@ function Routes(props) {
|
|
|
955
1007
|
if (disposers[i]) {
|
|
956
1008
|
disposers[i]();
|
|
957
1009
|
}
|
|
958
|
-
createRoot(dispose => {
|
|
1010
|
+
runWithOwner(owner, () => createRoot(dispose => {
|
|
959
1011
|
disposers[i] = dispose;
|
|
960
|
-
|
|
1012
|
+
const routeKey = nextMatch.route.key;
|
|
1013
|
+
// Retain the last matches in which this route participated so
|
|
1014
|
+
// that its components and preloads never observe another
|
|
1015
|
+
// route's params/path while this route is being torn down.
|
|
1016
|
+
const matchesAtLevel = createMemo(prev => {
|
|
961
1017
|
const routeMatches = props.routerState.matches();
|
|
962
|
-
|
|
1018
|
+
const m = routeMatches[i];
|
|
1019
|
+
return m && m.route.key === routeKey ? routeMatches : prev || nextMatches;
|
|
963
1020
|
});
|
|
964
|
-
|
|
1021
|
+
next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()?.[i + 1]), () => matchesAtLevel()[i], matchesAtLevel);
|
|
1022
|
+
}));
|
|
965
1023
|
}
|
|
966
1024
|
}
|
|
967
1025
|
disposers.splice(nextMatches.length).forEach(dispose => dispose());
|
|
@@ -1193,6 +1251,14 @@ function query(fn, name) {
|
|
|
1193
1251
|
return res;
|
|
1194
1252
|
function handleResponse(error) {
|
|
1195
1253
|
return async v => {
|
|
1254
|
+
let enveloped;
|
|
1255
|
+
let hasEnveloped = false;
|
|
1256
|
+
if (isResponseEnvelope(v)) {
|
|
1257
|
+
// respond(): the value rides in memory beside the metadata
|
|
1258
|
+
enveloped = v.value;
|
|
1259
|
+
hasEnveloped = true;
|
|
1260
|
+
v = v.response;
|
|
1261
|
+
}
|
|
1196
1262
|
if (v instanceof Response) {
|
|
1197
1263
|
const e = getRequestEvent();
|
|
1198
1264
|
if (e) {
|
|
@@ -1208,7 +1274,12 @@ function query(fn, name) {
|
|
|
1208
1274
|
});else if (!isServer) window.location.href = url;else if (e) e.response.status = 302;
|
|
1209
1275
|
return;
|
|
1210
1276
|
}
|
|
1211
|
-
if (
|
|
1277
|
+
if (hasEnveloped) v = enveloped;else if (v.body) {
|
|
1278
|
+
// responses the transport hands over whole (revalidation) carry a
|
|
1279
|
+
// codec-encoded body; anything else (a raw user Response) stays whole
|
|
1280
|
+
const decoded = await decodeResponse(v);
|
|
1281
|
+
if (decoded !== undefined) v = decoded;
|
|
1282
|
+
}
|
|
1212
1283
|
}
|
|
1213
1284
|
if (error) throw v;
|
|
1214
1285
|
cached[2] = v;
|
|
@@ -1386,7 +1457,10 @@ function toAction(invoke, url, boundArgs = [], base = url, submitHooks = new Map
|
|
|
1386
1457
|
fn[invokeSymbol] = invoke;
|
|
1387
1458
|
if (!isServer) {
|
|
1388
1459
|
actions.set(url, fn);
|
|
1389
|
-
|
|
1460
|
+
// Only remove the registration if it still belongs to this instance —
|
|
1461
|
+
// a re-created action (e.g. a new `.with()` binding after revalidation)
|
|
1462
|
+
// may have registered itself under the same URL since.
|
|
1463
|
+
getOwner() && onCleanup(() => actions.get(url) === fn && actions.delete(url));
|
|
1390
1464
|
}
|
|
1391
1465
|
return fn;
|
|
1392
1466
|
}
|
|
@@ -1411,27 +1485,38 @@ async function handleResponse(response, error, navigate) {
|
|
|
1411
1485
|
let custom;
|
|
1412
1486
|
let keys;
|
|
1413
1487
|
let flightKeys;
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1488
|
+
let metadata;
|
|
1489
|
+
if (isResponseEnvelope(response)) {
|
|
1490
|
+
// client-only respond(): the value rides in memory beside the metadata
|
|
1491
|
+
data = response.value;
|
|
1492
|
+
metadata = response.response;
|
|
1493
|
+
} else if (response instanceof Response) {
|
|
1494
|
+
metadata = response;
|
|
1495
|
+
// responses the transport hands over whole (redirects, revalidation,
|
|
1496
|
+
// single-flight) carry a codec-encoded body the router decodes itself
|
|
1497
|
+
if (response.body) {
|
|
1498
|
+
data = await decodeResponse(response);
|
|
1418
1499
|
if (response.headers.has("X-Single-Flight")) {
|
|
1419
|
-
|
|
1500
|
+
custom = data;
|
|
1501
|
+
data = custom._$value;
|
|
1420
1502
|
delete custom._$value;
|
|
1421
1503
|
flightKeys = Object.keys(custom);
|
|
1422
1504
|
}
|
|
1423
1505
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1506
|
+
} else if (error) return {
|
|
1507
|
+
error: response
|
|
1508
|
+
};else data = response;
|
|
1509
|
+
if (metadata) {
|
|
1510
|
+
if (metadata.headers.has("X-Revalidate")) keys = metadata.headers.get("X-Revalidate").split(",");
|
|
1511
|
+
if (metadata.headers.has("Location")) {
|
|
1512
|
+
const locationUrl = metadata.headers.get("Location") || "/";
|
|
1426
1513
|
if (locationUrl.startsWith("http")) {
|
|
1427
1514
|
window.location.href = locationUrl;
|
|
1428
1515
|
} else {
|
|
1429
1516
|
navigate(locationUrl);
|
|
1430
1517
|
}
|
|
1431
1518
|
}
|
|
1432
|
-
}
|
|
1433
|
-
error: response
|
|
1434
|
-
};else data = response;
|
|
1519
|
+
}
|
|
1435
1520
|
// invalidate
|
|
1436
1521
|
cacheKeyOp(keys, entry => entry[0] = 0);
|
|
1437
1522
|
// set cache
|
|
@@ -1556,7 +1641,7 @@ function setupNativeEvents({
|
|
|
1556
1641
|
function Router(props) {
|
|
1557
1642
|
if (isServer) return StaticRouter(props);
|
|
1558
1643
|
const getSource = () => {
|
|
1559
|
-
const url = window.location.pathname
|
|
1644
|
+
const url = window.location.pathname + window.location.search;
|
|
1560
1645
|
const state = window.history.state && window.history.state._depth && Object.keys(window.history.state).length === 1 ? undefined : window.history.state;
|
|
1561
1646
|
return {
|
|
1562
1647
|
value: url + window.location.hash,
|
|
@@ -1714,6 +1799,9 @@ function MemoryRouter(props) {
|
|
|
1714
1799
|
}
|
|
1715
1800
|
|
|
1716
1801
|
var _tmpl$ = /*#__PURE__*/template(`<a>`);
|
|
1802
|
+
|
|
1803
|
+
// the JSX namespace lives in @solidjs/web's jsx-runtime module as of 2.0
|
|
1804
|
+
|
|
1717
1805
|
function toClassName(value) {
|
|
1718
1806
|
if (!value) return "";
|
|
1719
1807
|
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
@@ -1735,30 +1823,29 @@ function A(props) {
|
|
|
1735
1823
|
const isActive = createMemo(() => {
|
|
1736
1824
|
const to_ = to();
|
|
1737
1825
|
if (to_ === undefined) return [false, false];
|
|
1738
|
-
|
|
1739
|
-
const
|
|
1826
|
+
// trailing slashes are ignored so `/route` and `/route/` share active state
|
|
1827
|
+
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase().replace(/\/$/, "");
|
|
1828
|
+
const loc = decodeURI(normalizePath(location.pathname).toLowerCase().replace(/\/$/, ""));
|
|
1740
1829
|
return [props.end ? path === loc : loc.startsWith(path + "/") || loc === path, path === loc];
|
|
1741
1830
|
});
|
|
1742
1831
|
const className = createMemo(() => [toClassName(props.class), isActive()[0] ? props.activeClass : props.inactiveClass].filter(Boolean).join(" "));
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
return _el$;
|
|
1761
|
-
})();
|
|
1832
|
+
var _el$ = _tmpl$();
|
|
1833
|
+
spread(_el$, mergeProps(rest, {
|
|
1834
|
+
get href() {
|
|
1835
|
+
return href() || props.href;
|
|
1836
|
+
},
|
|
1837
|
+
get state() {
|
|
1838
|
+
return JSON.stringify(props.state);
|
|
1839
|
+
},
|
|
1840
|
+
get ["class"]() {
|
|
1841
|
+
return className();
|
|
1842
|
+
},
|
|
1843
|
+
"link": true,
|
|
1844
|
+
get ["aria-current"]() {
|
|
1845
|
+
return isActive()[1] ? "page" : undefined;
|
|
1846
|
+
}
|
|
1847
|
+
}), false);
|
|
1848
|
+
return _el$;
|
|
1762
1849
|
}
|
|
1763
1850
|
function Navigate(props) {
|
|
1764
1851
|
const navigate = useNavigate();
|
|
@@ -1778,57 +1865,4 @@ function Navigate(props) {
|
|
|
1778
1865
|
return null;
|
|
1779
1866
|
}
|
|
1780
1867
|
|
|
1781
|
-
|
|
1782
|
-
let responseInit;
|
|
1783
|
-
let revalidate;
|
|
1784
|
-
if (typeof init === "number") {
|
|
1785
|
-
responseInit = {
|
|
1786
|
-
status: init
|
|
1787
|
-
};
|
|
1788
|
-
} else {
|
|
1789
|
-
({
|
|
1790
|
-
revalidate,
|
|
1791
|
-
...responseInit
|
|
1792
|
-
} = init);
|
|
1793
|
-
if (typeof responseInit.status === "undefined") {
|
|
1794
|
-
responseInit.status = 302;
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
const headers = new Headers(responseInit.headers);
|
|
1798
|
-
headers.set("Location", url);
|
|
1799
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
1800
|
-
const response = new Response(null, {
|
|
1801
|
-
...responseInit,
|
|
1802
|
-
headers: headers
|
|
1803
|
-
});
|
|
1804
|
-
return response;
|
|
1805
|
-
}
|
|
1806
|
-
function reload(init = {}) {
|
|
1807
|
-
const {
|
|
1808
|
-
revalidate,
|
|
1809
|
-
...responseInit
|
|
1810
|
-
} = init;
|
|
1811
|
-
const headers = new Headers(responseInit.headers);
|
|
1812
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
1813
|
-
return new Response(null, {
|
|
1814
|
-
...responseInit,
|
|
1815
|
-
headers
|
|
1816
|
-
});
|
|
1817
|
-
}
|
|
1818
|
-
function json(data, init = {}) {
|
|
1819
|
-
const {
|
|
1820
|
-
revalidate,
|
|
1821
|
-
...responseInit
|
|
1822
|
-
} = init;
|
|
1823
|
-
const headers = new Headers(responseInit.headers);
|
|
1824
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
1825
|
-
headers.set("Content-Type", "application/json");
|
|
1826
|
-
const response = new Response(JSON.stringify(data), {
|
|
1827
|
-
...responseInit,
|
|
1828
|
-
headers
|
|
1829
|
-
});
|
|
1830
|
-
response.customBody = () => data;
|
|
1831
|
-
return response;
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
export { A, HashRouter, MemoryRouter, Navigate, Route, Router, RouterContextObj as RouterContext, StaticRouter, mergeSearchString as _mergeSearchString, action, createBeforeLeave, createMemoryHistory, createRouter, json, keepDepth, notifyIfNotBlocked, query, redirect, reload, revalidate, saveCurrentDepth, useAction, useBeforeLeave, useCurrentMatches, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, usePreloadRoute, useResolvedPath, useSearchParams, useSubmissions };
|
|
1868
|
+
export { A, HashRouter, MemoryRouter, Navigate, Route, Router, RouterContextObj as RouterContext, StaticRouter, mergeSearchString as _mergeSearchString, action, createBeforeLeave, createMemoryHistory, createRouter, keepDepth, notifyIfNotBlocked, query, revalidate, saveCurrentDepth, useAction, useBeforeLeave, useCurrentMatches, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, usePreloadRoute, useResolvedPath, useSearchParams, useSubmissions };
|
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);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LocationChange } from "../types.js";
|
|
2
2
|
import type { BaseRouterProps } from "./components.jsx";
|
|
3
|
-
import type { JSX } from "
|
|
3
|
+
import type { JSX } from "@solidjs/web";
|
|
4
4
|
export type MemoryHistory = {
|
|
5
5
|
get: () => string;
|
|
6
6
|
set: (change: LocationChange) => void;
|
package/dist/routers/Router.d.ts
CHANGED
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,6 @@
|
|
|
1
|
-
import type { Component
|
|
2
|
-
import type {
|
|
1
|
+
import type { Component } from "solid-js";
|
|
2
|
+
import type { JSX } from "@solidjs/web";
|
|
3
|
+
import type { MatchFilters, RouteDefinition, RoutePreloadFunc, RouterIntegration, RouteSectionComponent, RouteSectionProps } from "../types.js";
|
|
3
4
|
export type BaseRouterProps = {
|
|
4
5
|
base?: string;
|
|
5
6
|
/**
|
|
@@ -17,7 +18,7 @@ export type RouteProps<S extends string, T = unknown> = {
|
|
|
17
18
|
children?: JSX.Element;
|
|
18
19
|
preload?: RoutePreloadFunc<T>;
|
|
19
20
|
matchFilters?: MatchFilters<S>;
|
|
20
|
-
component?:
|
|
21
|
+
component?: RouteSectionComponent<T>;
|
|
21
22
|
info?: Record<string, any>;
|
|
22
23
|
};
|
|
23
24
|
export declare const Route: <S extends string, T = unknown>(props: RouteProps<S, T>) => JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
|
-
import { children, createMemo, createRoot, getOwner, merge, untrack } from "solid-js";
|
|
2
|
+
import { children, createMemo, createRoot, getOwner, merge, onCleanup, runWithOwner, untrack } from "solid-js";
|
|
3
3
|
import { getRequestEvent, isServer } from "@solidjs/web";
|
|
4
4
|
import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
|
|
5
5
|
export const createRouterComponent = (router) => function IntegratedRouter(props) {
|
|
@@ -64,6 +64,13 @@ function Routes(props) {
|
|
|
64
64
|
const disposers = [];
|
|
65
65
|
let root;
|
|
66
66
|
let prevMatches;
|
|
67
|
+
// dispose the detached per-route roots when this component unmounts, otherwise
|
|
68
|
+
// they stay subscribed to `matches` and crash on a later navigation (#451)
|
|
69
|
+
onCleanup(() => disposers.forEach(dispose => dispose()));
|
|
70
|
+
// Route roots must outlive re-runs of the `routeStates` memo below, so they
|
|
71
|
+
// are created under the owner of this component rather than the memo's
|
|
72
|
+
// computation (which disposes its children every time it re-runs).
|
|
73
|
+
const owner = getOwner();
|
|
67
74
|
const routeStates = createMemo((prev) => {
|
|
68
75
|
const nextMatches = props.routerState.matches();
|
|
69
76
|
const previousMatches = prevMatches;
|
|
@@ -80,13 +87,19 @@ function Routes(props) {
|
|
|
80
87
|
if (disposers[i]) {
|
|
81
88
|
disposers[i]();
|
|
82
89
|
}
|
|
83
|
-
createRoot(dispose => {
|
|
90
|
+
runWithOwner(owner, () => createRoot(dispose => {
|
|
84
91
|
disposers[i] = dispose;
|
|
85
|
-
|
|
92
|
+
const routeKey = nextMatch.route.key;
|
|
93
|
+
// Retain the last matches in which this route participated so
|
|
94
|
+
// that its components and preloads never observe another
|
|
95
|
+
// route's params/path while this route is being torn down.
|
|
96
|
+
const matchesAtLevel = createMemo((prev) => {
|
|
86
97
|
const routeMatches = props.routerState.matches();
|
|
87
|
-
|
|
98
|
+
const m = routeMatches[i];
|
|
99
|
+
return m && m.route.key === routeKey ? routeMatches : prev || nextMatches;
|
|
88
100
|
});
|
|
89
|
-
|
|
101
|
+
next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()?.[i + 1]), () => matchesAtLevel()[i], matchesAtLevel);
|
|
102
|
+
}));
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
disposers.splice(nextMatches.length).forEach(dispose => dispose());
|
|
@@ -5,6 +5,6 @@ export declare function createRouter(config: {
|
|
|
5
5
|
init?: (notify: (value?: string | LocationChange) => void) => () => void;
|
|
6
6
|
create?: (router: RouterContext) => void;
|
|
7
7
|
utils?: Partial<RouterUtils>;
|
|
8
|
-
}): (props: import("./components.jsx").BaseRouterProps) => import("
|
|
8
|
+
}): (props: import("./components.jsx").BaseRouterProps) => import("@solidjs/web").JSX.Element;
|
|
9
9
|
export declare function bindEvent(target: EventTarget, type: string, handler: EventListener): () => void;
|
|
10
10
|
export declare function scrollToHash(hash: string, fallbackTop?: boolean): void;
|
package/dist/routing.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { JSX
|
|
1
|
+
import type { JSX } from "@solidjs/web";
|
|
2
2
|
import type { BeforeLeaveEventArgs, Branch, Intent, Location, MatchFilters, NavigateOptions, Navigator, Params, RouteDescription, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SearchParams, SetSearchParams } from "./types.js";
|
|
3
3
|
/** Consider this API opaque and internal. It is likely to change in the future. */
|
|
4
|
-
export declare const RouterContextObj: import("solid-js").Context<RouterContext
|
|
5
|
-
export declare const RouteContextObj: import("solid-js").Context<RouteContext
|
|
4
|
+
export declare const RouterContextObj: import("solid-js").Context<RouterContext>;
|
|
5
|
+
export declare const RouteContextObj: import("solid-js").Context<RouteContext>;
|
|
6
6
|
export declare const useRouter: () => RouterContext;
|
|
7
7
|
export declare const useRoute: () => RouteContext;
|
|
8
|
-
export declare const useResolvedPath: (path: () => string) =>
|
|
8
|
+
export declare const useResolvedPath: (path: () => string) => import("solid-js").SourceAccessor<string | undefined>;
|
|
9
9
|
export declare const useHref: <T extends string | undefined>(to: () => T) => () => string | T;
|
|
10
10
|
/**
|
|
11
11
|
* Retrieves method to do navigation. The method accepts a path to navigate to and an optional object with the following options:
|
|
@@ -79,7 +79,7 @@ export declare const usePreloadRoute: () => (url: string | URL, options?: {
|
|
|
79
79
|
* return <div classList={{ active: Boolean(match()) }} />;
|
|
80
80
|
* ```
|
|
81
81
|
*/
|
|
82
|
-
export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S>) =>
|
|
82
|
+
export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S>) => import("solid-js").SourceAccessor<import("./types.js").PathMatch | undefined>;
|
|
83
83
|
/**
|
|
84
84
|
* `useCurrentMatches` returns all the matches for the current matched route.
|
|
85
85
|
* Useful for getting all the route information.
|
|
@@ -172,4 +172,4 @@ export declare function createRouterContext(integration: RouterIntegration, bran
|
|
|
172
172
|
singleFlight?: boolean;
|
|
173
173
|
transformUrl?: (url: string) => string;
|
|
174
174
|
}): RouterContext;
|
|
175
|
-
export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch): RouteContext;
|
|
175
|
+
export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, matches?: () => RouteMatch[]): RouteContext;
|
package/dist/routing.js
CHANGED
|
@@ -124,7 +124,11 @@ export const useMatch = (path, matchFilters) => {
|
|
|
124
124
|
* const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb))
|
|
125
125
|
* ```
|
|
126
126
|
*/
|
|
127
|
-
export const useCurrentMatches = () =>
|
|
127
|
+
export const useCurrentMatches = () => {
|
|
128
|
+
const router = useRouter();
|
|
129
|
+
// return a copy so user mutations (eg. `.reverse()`) can't corrupt router state
|
|
130
|
+
return () => router.matches().slice();
|
|
131
|
+
};
|
|
128
132
|
/**
|
|
129
133
|
* Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
|
|
130
134
|
*
|
|
@@ -136,7 +140,7 @@ export const useCurrentMatches = () => useRouter().matches;
|
|
|
136
140
|
* const getUser = query(() => fetchUser(params.id), "user");
|
|
137
141
|
* ```
|
|
138
142
|
*/
|
|
139
|
-
export const useParams = () =>
|
|
143
|
+
export const useParams = () => useRoute().params;
|
|
140
144
|
/**
|
|
141
145
|
* Retrieves a tuple containing a reactive object to read the current location's query parameters and a method to update them.
|
|
142
146
|
* The object is a proxy so you must access properties to subscribe to reactive updates.
|
|
@@ -165,11 +169,20 @@ export const useParams = () => useRouter().params;
|
|
|
165
169
|
* ```
|
|
166
170
|
*/
|
|
167
171
|
export const useSearchParams = () => {
|
|
168
|
-
const
|
|
172
|
+
const router = useRouter();
|
|
173
|
+
const location = router.location;
|
|
169
174
|
const navigate = useNavigate();
|
|
170
175
|
const setSearchParams = (params, options) => {
|
|
171
|
-
const
|
|
172
|
-
|
|
176
|
+
const to = untrack(() => {
|
|
177
|
+
// merge onto the in-flight navigation target (if any) so consecutive
|
|
178
|
+
// synchronous calls compose instead of the later one winning
|
|
179
|
+
const pending = router.pendingTarget && new URL(router.pendingTarget.value, mockBase);
|
|
180
|
+
const pathname = pending ? pending.pathname : location.pathname;
|
|
181
|
+
const search = pending ? pending.search : location.search;
|
|
182
|
+
const hash = pending ? pending.hash : location.hash;
|
|
183
|
+
return pathname + mergeSearchString(search, params) + hash;
|
|
184
|
+
});
|
|
185
|
+
navigate(to, {
|
|
173
186
|
scroll: false,
|
|
174
187
|
resolve: false,
|
|
175
188
|
...options
|
|
@@ -212,6 +225,11 @@ export const useBeforeLeave = (listener) => {
|
|
|
212
225
|
});
|
|
213
226
|
onCleanup(s);
|
|
214
227
|
};
|
|
228
|
+
// Encodes a static path segment like `encodeURIComponent`, but leaves RFC 3986
|
|
229
|
+
// pchar characters (sub-delims / ":" / "@") literal, matching how browsers
|
|
230
|
+
// report them in `location.pathname`. Non-ASCII characters (eg. CJK paths) are
|
|
231
|
+
// still percent-encoded exactly as before, since browsers encode those too.
|
|
232
|
+
const encodeSegment = (s) => encodeURIComponent(s).replace(/%(2B|40|3A|24|26|2C|3B|3D)/g, m => decodeURIComponent(m));
|
|
215
233
|
export function createRoutes(routeDef, base = "") {
|
|
216
234
|
const { component, preload, children, info } = routeDef;
|
|
217
235
|
const isLeaf = !children || (Array.isArray(children) && !children.length);
|
|
@@ -228,7 +246,7 @@ export function createRoutes(routeDef, base = "") {
|
|
|
228
246
|
pattern = pattern
|
|
229
247
|
.split("/")
|
|
230
248
|
.map((s) => {
|
|
231
|
-
return s.startsWith(":") || s.startsWith("*") ? s :
|
|
249
|
+
return s.startsWith(":") || s.startsWith("*") ? s : encodeSegment(s);
|
|
232
250
|
})
|
|
233
251
|
.join("/");
|
|
234
252
|
acc.push({
|
|
@@ -299,12 +317,21 @@ export function getRouteMatches(branches, location) {
|
|
|
299
317
|
}
|
|
300
318
|
return [];
|
|
301
319
|
}
|
|
320
|
+
function mergeParams(matches) {
|
|
321
|
+
const params = {};
|
|
322
|
+
for (let i = 0; i < matches.length; i++) {
|
|
323
|
+
Object.assign(params, matches[i].params);
|
|
324
|
+
}
|
|
325
|
+
return params;
|
|
326
|
+
}
|
|
302
327
|
function createLocation(path, state, queryWrapper) {
|
|
303
328
|
const origin = new URL(mockBase);
|
|
304
329
|
const url = createMemo((prev = origin) => {
|
|
305
330
|
const path_ = path();
|
|
306
331
|
try {
|
|
307
|
-
|
|
332
|
+
// anchor rooted paths against the origin explicitly - a path with
|
|
333
|
+
// doubled leading slashes would otherwise parse as protocol-relative
|
|
334
|
+
return new URL(path_[0] === "/" ? mockBase + path_ : path_, origin);
|
|
308
335
|
}
|
|
309
336
|
catch (err) {
|
|
310
337
|
console.error(`Invalid path ${path_}`);
|
|
@@ -382,19 +409,14 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
382
409
|
}
|
|
383
410
|
return getRouteMatches(branches(), location.pathname);
|
|
384
411
|
});
|
|
385
|
-
const buildParams = () =>
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
return params;
|
|
392
|
-
};
|
|
393
|
-
const params = utils.paramsWrapper
|
|
394
|
-
? utils.paramsWrapper(buildParams, branches)
|
|
395
|
-
: createMemoObject(buildParams);
|
|
412
|
+
const buildParams = () => mergeParams(matches());
|
|
413
|
+
const wrapParams = utils.paramsWrapper
|
|
414
|
+
? (getParams) => utils.paramsWrapper(getParams, branches)
|
|
415
|
+
: (getParams) => createMemoObject(getParams);
|
|
416
|
+
const params = wrapParams(buildParams);
|
|
396
417
|
const baseRoute = {
|
|
397
418
|
pattern: basePath,
|
|
419
|
+
params,
|
|
398
420
|
path: () => basePath,
|
|
399
421
|
outlet: () => null,
|
|
400
422
|
resolvePath(to) {
|
|
@@ -405,7 +427,11 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
405
427
|
base: baseRoute,
|
|
406
428
|
location,
|
|
407
429
|
params,
|
|
430
|
+
wrapParams,
|
|
408
431
|
isRouting,
|
|
432
|
+
get pendingTarget() {
|
|
433
|
+
return lastTransitionTarget;
|
|
434
|
+
},
|
|
409
435
|
renderPath,
|
|
410
436
|
parsePath,
|
|
411
437
|
navigatorFactory,
|
|
@@ -539,10 +565,14 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
539
565
|
];
|
|
540
566
|
}
|
|
541
567
|
}
|
|
542
|
-
export function createRouteContext(router, parent, outlet, match) {
|
|
543
|
-
const { base, location,
|
|
568
|
+
export function createRouteContext(router, parent, outlet, match, matches = () => [match()]) {
|
|
569
|
+
const { base, location, wrapParams } = router;
|
|
544
570
|
const { pattern, component, preload } = match().route;
|
|
545
571
|
const path = createMemo(() => match().path);
|
|
572
|
+
// Params scoped to this route's lifetime. `matches` is expected to retain
|
|
573
|
+
// its last valid value while this route is being torn down, so outgoing
|
|
574
|
+
// components and preloads never observe another route's params.
|
|
575
|
+
const params = wrapParams(() => mergeParams(matches()));
|
|
546
576
|
component &&
|
|
547
577
|
component.preload &&
|
|
548
578
|
component.preload();
|
|
@@ -552,6 +582,7 @@ export function createRouteContext(router, parent, outlet, match) {
|
|
|
552
582
|
const route = {
|
|
553
583
|
parent,
|
|
554
584
|
pattern,
|
|
585
|
+
params,
|
|
555
586
|
path,
|
|
556
587
|
outlet: () => component
|
|
557
588
|
? createComponent(component, {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Component,
|
|
1
|
+
import type { Component, Signal } from "solid-js";
|
|
2
|
+
import type { JSX, ResponseEnvelope } from "@solidjs/web";
|
|
2
3
|
declare module "@solidjs/web" {
|
|
3
4
|
interface RequestEvent {
|
|
4
5
|
response: {
|
|
@@ -55,7 +56,7 @@ export interface LocationChange<S = unknown> {
|
|
|
55
56
|
rawPath?: string;
|
|
56
57
|
}
|
|
57
58
|
export interface RouterIntegration {
|
|
58
|
-
signal:
|
|
59
|
+
signal: [get: () => LocationChange, set: (next: LocationChange) => void];
|
|
59
60
|
create?: (router: RouterContext) => void;
|
|
60
61
|
utils?: Partial<RouterUtils>;
|
|
61
62
|
}
|
|
@@ -72,12 +73,13 @@ export interface RouteSectionProps<T = unknown> {
|
|
|
72
73
|
data: T;
|
|
73
74
|
children?: JSX.Element;
|
|
74
75
|
}
|
|
75
|
-
export type
|
|
76
|
+
export type RouteSectionComponent<T = unknown> = Component<RouteSectionProps<T>> | Component<Omit<RouteSectionProps<T>, "children">> | Component<{}>;
|
|
77
|
+
export type RouteDefinition<S extends string | string[] = any, T = any> = {
|
|
76
78
|
path?: S;
|
|
77
79
|
matchFilters?: MatchFilters<S>;
|
|
78
80
|
preload?: RoutePreloadFunc<T>;
|
|
79
81
|
children?: RouteDefinition | RouteDefinition[];
|
|
80
|
-
component?:
|
|
82
|
+
component?: RouteSectionComponent<T>;
|
|
81
83
|
info?: Record<string, any>;
|
|
82
84
|
};
|
|
83
85
|
export type MatchFilter = readonly string[] | RegExp | ((s: string) => boolean);
|
|
@@ -118,6 +120,7 @@ export interface RouteContext {
|
|
|
118
120
|
parent?: RouteContext;
|
|
119
121
|
child?: RouteContext;
|
|
120
122
|
pattern: string;
|
|
123
|
+
params: Params;
|
|
121
124
|
path: () => string;
|
|
122
125
|
outlet: () => JSX.Element;
|
|
123
126
|
resolvePath(to: string): string | undefined;
|
|
@@ -134,8 +137,11 @@ export interface RouterContext {
|
|
|
134
137
|
base: RouteContext;
|
|
135
138
|
location: Location;
|
|
136
139
|
params: Params;
|
|
140
|
+
wrapParams: (getParams: () => Params) => Params;
|
|
137
141
|
navigatorFactory: NavigatorFactory;
|
|
138
142
|
isRouting: () => boolean;
|
|
143
|
+
/** The target of the in-flight navigation transition, if any. Not reactive. */
|
|
144
|
+
readonly pendingTarget?: LocationChange;
|
|
139
145
|
matches: () => RouteMatch[];
|
|
140
146
|
renderPath(path: string): string;
|
|
141
147
|
parsePath(str: string): string;
|
|
@@ -175,11 +181,4 @@ export interface MaybePreloadableComponent extends Component {
|
|
|
175
181
|
export type CacheEntry = [number, Promise<any>, any, Intent | undefined, Signal<number> & {
|
|
176
182
|
count: number;
|
|
177
183
|
}];
|
|
178
|
-
export type NarrowResponse<T> = T extends
|
|
179
|
-
export type RouterResponseInit = Omit<ResponseInit, "body"> & {
|
|
180
|
-
revalidate?: string | string[];
|
|
181
|
-
};
|
|
182
|
-
export type CustomResponse<T> = Omit<Response, "clone"> & {
|
|
183
|
-
customBody: () => T;
|
|
184
|
-
clone(...args: readonly unknown[]): CustomResponse<T>;
|
|
185
|
-
};
|
|
184
|
+
export type NarrowResponse<T> = T extends ResponseEnvelope<infer U> ? U : Exclude<T, Response>;
|
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;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "0.17.0-next.
|
|
9
|
+
"version": "0.17.0-next.5",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@solidjs/web": "2.0.0-beta.
|
|
32
|
+
"@solidjs/web": "2.0.0-beta.19",
|
|
33
33
|
"@babel/core": "^7.26.0",
|
|
34
34
|
"@babel/preset-typescript": "^7.26.0",
|
|
35
35
|
"@changesets/cli": "^2.27.10",
|
|
@@ -38,19 +38,19 @@
|
|
|
38
38
|
"@rollup/plugin-terser": "0.4.4",
|
|
39
39
|
"@types/jest": "^29.5.14",
|
|
40
40
|
"@types/node": "^22.10.0",
|
|
41
|
-
"babel-preset-solid": "2.0.0-beta.
|
|
41
|
+
"babel-preset-solid": "2.0.0-beta.19",
|
|
42
42
|
"jsdom": "^25.0.1",
|
|
43
43
|
"prettier": "^3.4.1",
|
|
44
44
|
"rollup": "^4.27.4",
|
|
45
|
-
"solid-js": "2.0.0-beta.
|
|
45
|
+
"solid-js": "2.0.0-beta.19",
|
|
46
46
|
"typescript": "^5.7.2",
|
|
47
47
|
"vite": "^6.0.0",
|
|
48
|
-
"vite-plugin-solid": "3.0.0-next.
|
|
48
|
+
"vite-plugin-solid": "3.0.0-next.10",
|
|
49
49
|
"vitest": "^2.1.6"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@solidjs/web": ">=2.0.0-beta.
|
|
53
|
-
"solid-js": ">=2.0.0-beta.
|
|
52
|
+
"@solidjs/web": ">=2.0.0-beta.19 <2.0.0-experimental.0",
|
|
53
|
+
"solid-js": ">=2.0.0-beta.19 <2.0.0-experimental.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "rm -rf dist && tsc && rollup -c",
|
package/dist/data/response.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { RouterResponseInit, CustomResponse } from "../types.js";
|
|
2
|
-
export declare function redirect(url: string, init?: number | RouterResponseInit): CustomResponse<never>;
|
|
3
|
-
export declare function reload(init?: RouterResponseInit): CustomResponse<never>;
|
|
4
|
-
export declare function json<T>(data: T, init?: RouterResponseInit): CustomResponse<T>;
|
package/dist/data/response.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export function redirect(url, init = 302) {
|
|
2
|
-
let responseInit;
|
|
3
|
-
let revalidate;
|
|
4
|
-
if (typeof init === "number") {
|
|
5
|
-
responseInit = { status: init };
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
({ revalidate, ...responseInit } = init);
|
|
9
|
-
if (typeof responseInit.status === "undefined") {
|
|
10
|
-
responseInit.status = 302;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
const headers = new Headers(responseInit.headers);
|
|
14
|
-
headers.set("Location", url);
|
|
15
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
16
|
-
const response = new Response(null, {
|
|
17
|
-
...responseInit,
|
|
18
|
-
headers: headers
|
|
19
|
-
});
|
|
20
|
-
return response;
|
|
21
|
-
}
|
|
22
|
-
export function reload(init = {}) {
|
|
23
|
-
const { revalidate, ...responseInit } = init;
|
|
24
|
-
const headers = new Headers(responseInit.headers);
|
|
25
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
26
|
-
return new Response(null, {
|
|
27
|
-
...responseInit,
|
|
28
|
-
headers
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
export function json(data, init = {}) {
|
|
32
|
-
const { revalidate, ...responseInit } = init;
|
|
33
|
-
const headers = new Headers(responseInit.headers);
|
|
34
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
35
|
-
headers.set("Content-Type", "application/json");
|
|
36
|
-
const response = new Response(JSON.stringify(data), {
|
|
37
|
-
...responseInit,
|
|
38
|
-
headers
|
|
39
|
-
});
|
|
40
|
-
response.customBody = () => data;
|
|
41
|
-
return response;
|
|
42
|
-
}
|