@solidjs/router 0.10.0-beta.0 → 0.10.0-beta.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/README.md +1 -3
- package/dist/components.jsx +7 -8
- package/dist/data/action.js +15 -20
- package/dist/data/cache.d.ts +1 -1
- package/dist/data/cache.js +26 -18
- package/dist/data/createAsync.d.ts +11 -2
- package/dist/data/createAsync.js +49 -3
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.js +1 -0
- package/dist/data/response.d.ts +1 -0
- package/dist/data/response.js +16 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +148 -73
- package/dist/routing.d.ts +3 -3
- package/dist/routing.js +40 -24
- package/dist/types.d.ts +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -372,14 +372,12 @@ This cache can be defined anywhere and then used inside your components with:
|
|
|
372
372
|
|
|
373
373
|
### `createAsync`
|
|
374
374
|
|
|
375
|
-
This is light wrapper over `createResource` that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0. It is a simpler async primitive where the function tracks like `createMemo` and it expects a promise back that it turns into a Signal. Reading it before it ready causes Suspense/Transitions to trigger.
|
|
375
|
+
This is light wrapper over `createResource` that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0. It is a simpler async primitive where the function tracks like `createMemo` and it expects a promise back that it turns into a Signal. Reading it before it is ready causes Suspense/Transitions to trigger.
|
|
376
376
|
|
|
377
377
|
```jsx
|
|
378
378
|
const user = createAsync(() => getUser(params.id))
|
|
379
379
|
```
|
|
380
380
|
|
|
381
|
-
`createAsync` is designed to only work with cached functions otherwise it will over fetch. If not using `cache`, continue using `createResource` instead.
|
|
382
|
-
|
|
383
381
|
### `action`
|
|
384
382
|
|
|
385
383
|
Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response builders can be found below(TODO).
|
package/dist/components.jsx
CHANGED
|
@@ -9,20 +9,19 @@ export const Router = (props) => {
|
|
|
9
9
|
const { source, url, base } = props;
|
|
10
10
|
const integration = source ||
|
|
11
11
|
(isServer
|
|
12
|
-
? staticIntegration({ value: url || ((e = getRequestEvent()) && e.request.url) || "" })
|
|
12
|
+
? staticIntegration({ value: url || ((e = getRequestEvent()) && getPath(e.request.url)) || "" })
|
|
13
13
|
: pathIntegration());
|
|
14
|
-
const routeDefs = children(() => props.
|
|
15
|
-
|
|
16
|
-
component: props.root,
|
|
17
|
-
children: props.children
|
|
18
|
-
}
|
|
19
|
-
: props.children);
|
|
20
|
-
const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
|
|
14
|
+
const routeDefs = children(() => props.children);
|
|
15
|
+
const branches = createMemo(() => createBranches(props.root ? { component: props.root, children: routeDefs() } : routeDefs(), props.base || ""));
|
|
21
16
|
const routerState = createRouterContext(integration, branches, base);
|
|
22
17
|
return (<RouterContextObj.Provider value={routerState}>
|
|
23
18
|
<Routes routerState={routerState} branches={branches()}/>
|
|
24
19
|
</RouterContextObj.Provider>);
|
|
25
20
|
};
|
|
21
|
+
function getPath(url) {
|
|
22
|
+
const u = new URL(url);
|
|
23
|
+
return u.pathname + u.search;
|
|
24
|
+
}
|
|
26
25
|
function Routes(props) {
|
|
27
26
|
const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
|
|
28
27
|
const params = createMemoObject(() => {
|
package/dist/data/action.js
CHANGED
|
@@ -49,6 +49,11 @@ export function action(fn, name) {
|
|
|
49
49
|
const [result, setResult] = createSignal();
|
|
50
50
|
let submission;
|
|
51
51
|
const router = this;
|
|
52
|
+
async function handler(res) {
|
|
53
|
+
const data = await handleResponse(res, router.navigatorFactory());
|
|
54
|
+
data ? setResult({ data }) : submission.clear();
|
|
55
|
+
return data;
|
|
56
|
+
}
|
|
52
57
|
router.submissions[1](s => [
|
|
53
58
|
...s,
|
|
54
59
|
(submission = {
|
|
@@ -66,29 +71,15 @@ export function action(fn, name) {
|
|
|
66
71
|
retry() {
|
|
67
72
|
setResult(undefined);
|
|
68
73
|
const p = fn(variables);
|
|
69
|
-
p.then(
|
|
70
|
-
const keys = handleResponse(data, router.navigatorFactory());
|
|
71
|
-
await revalidate(keys);
|
|
72
|
-
data ? setResult({ data }) : submission.clear();
|
|
73
|
-
return data;
|
|
74
|
-
}).catch(error => {
|
|
75
|
-
setResult({ data: error });
|
|
76
|
-
});
|
|
74
|
+
p.then(handler, handler);
|
|
77
75
|
return p;
|
|
78
76
|
}
|
|
79
77
|
})
|
|
80
78
|
]);
|
|
81
|
-
p.then(
|
|
82
|
-
const keys = handleResponse(data, router.navigatorFactory());
|
|
83
|
-
await revalidate(keys);
|
|
84
|
-
data ? setResult({ data }) : submission.clear();
|
|
85
|
-
return data;
|
|
86
|
-
}).catch(error => {
|
|
87
|
-
setResult({ data: error });
|
|
88
|
-
});
|
|
79
|
+
p.then(handler, handler);
|
|
89
80
|
return p;
|
|
90
81
|
}
|
|
91
|
-
const url = fn.url || `action:${name}` || !isServer ? `action:${fn.name}` : "";
|
|
82
|
+
const url = fn.url || (name && `action:${name}`) || (!isServer ? `action:${fn.name}` : "");
|
|
92
83
|
mutate.toString = () => {
|
|
93
84
|
if (!url)
|
|
94
85
|
throw new Error("Client Actions need explicit names if server rendered");
|
|
@@ -98,7 +89,8 @@ export function action(fn, name) {
|
|
|
98
89
|
registerAction(url, mutate);
|
|
99
90
|
return mutate;
|
|
100
91
|
}
|
|
101
|
-
function handleResponse(response, navigate) {
|
|
92
|
+
async function handleResponse(response, navigate) {
|
|
93
|
+
let data;
|
|
102
94
|
if (response instanceof Response && redirectStatusCodes.has(response.status)) {
|
|
103
95
|
const locationUrl = response.headers.get("Location") || "/";
|
|
104
96
|
if (locationUrl.startsWith("http")) {
|
|
@@ -108,6 +100,9 @@ function handleResponse(response, navigate) {
|
|
|
108
100
|
navigate(locationUrl);
|
|
109
101
|
}
|
|
110
102
|
}
|
|
111
|
-
|
|
112
|
-
|
|
103
|
+
else
|
|
104
|
+
data = response;
|
|
105
|
+
// TODO: handle keys
|
|
106
|
+
await revalidate();
|
|
107
|
+
return data;
|
|
113
108
|
}
|
package/dist/data/cache.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type ReconcileOptions } from "solid-js/store";
|
|
2
2
|
export declare function revalidate(key?: string | any[] | void): Promise<void>;
|
|
3
|
-
export declare function cache<T extends (...args: any) => U | Response, U>(fn: T, name: string, options
|
|
3
|
+
export declare function cache<T extends (...args: any) => U | Response, U>(fn: T, name: string, options?: ReconcileOptions): T;
|
package/dist/data/cache.js
CHANGED
|
@@ -52,7 +52,7 @@ export function cache(fn, name, options) {
|
|
|
52
52
|
cached[0] = now;
|
|
53
53
|
cached[1] =
|
|
54
54
|
"then" in cached[1]
|
|
55
|
-
? cached[1].then(handleResponse)
|
|
55
|
+
? cached[1].then(handleResponse, handleResponse)
|
|
56
56
|
: handleResponse(cached[1]);
|
|
57
57
|
cached[2] = intent;
|
|
58
58
|
}
|
|
@@ -61,11 +61,17 @@ export function cache(fn, name, options) {
|
|
|
61
61
|
}
|
|
62
62
|
return cached[1];
|
|
63
63
|
}
|
|
64
|
-
let res =
|
|
64
|
+
let res = !isServer && sharedConfig.context && sharedConfig.load
|
|
65
|
+
? sharedConfig.load(key) // hydrating
|
|
66
|
+
: fn(...args);
|
|
67
|
+
// serialize on server
|
|
68
|
+
if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
|
|
69
|
+
sharedConfig.context && sharedConfig.context.serialize(key, res);
|
|
70
|
+
}
|
|
65
71
|
if (intent !== "preload") {
|
|
66
72
|
res =
|
|
67
73
|
"then" in res
|
|
68
|
-
? res.then(handleResponse)
|
|
74
|
+
? res.then(handleResponse, handleResponse)
|
|
69
75
|
: handleResponse(res);
|
|
70
76
|
}
|
|
71
77
|
if (cached) {
|
|
@@ -80,25 +86,27 @@ export function cache(fn, name, options) {
|
|
|
80
86
|
else
|
|
81
87
|
cache.set(key, (cached = [now, res, intent, new Set(version ? [version] : [])]));
|
|
82
88
|
return res;
|
|
83
|
-
function handleRedirect(response) {
|
|
84
|
-
startTransition(() => {
|
|
85
|
-
let url = response.headers.get(LocationHeader);
|
|
86
|
-
if (url && url.startsWith("/")) {
|
|
87
|
-
navigate(url, {
|
|
88
|
-
replace: true
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
else if (!isServer && url) {
|
|
92
|
-
window.location.href = url;
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
89
|
function handleResponse(v) {
|
|
97
90
|
if (v instanceof Response && redirectStatusCodes.has(v.status)) {
|
|
98
|
-
if (navigate)
|
|
99
|
-
|
|
91
|
+
if (navigate) {
|
|
92
|
+
startTransition(() => {
|
|
93
|
+
let url = v.headers.get(LocationHeader);
|
|
94
|
+
if (url && url.startsWith("/")) {
|
|
95
|
+
navigate(url, {
|
|
96
|
+
replace: true
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else if (!isServer && url) {
|
|
100
|
+
window.location.href = url;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
100
104
|
return;
|
|
101
105
|
}
|
|
106
|
+
if (v instanceof Error)
|
|
107
|
+
throw v;
|
|
108
|
+
if (isServer)
|
|
109
|
+
return v;
|
|
102
110
|
setStore(key, reconcile(v, options));
|
|
103
111
|
return store[key];
|
|
104
112
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
|
|
3
3
|
*/
|
|
4
|
-
import { Accessor } from "solid-js";
|
|
5
|
-
export declare function createAsync<T>(fn: () => Promise<T
|
|
4
|
+
import { type Accessor } from "solid-js";
|
|
5
|
+
export declare function createAsync<T>(fn: () => Promise<T>, options: {
|
|
6
|
+
name?: string;
|
|
7
|
+
initialValue: T;
|
|
8
|
+
deferStream?: boolean;
|
|
9
|
+
}): Accessor<T>;
|
|
10
|
+
export declare function createAsync<T>(fn: () => Promise<T>, options?: {
|
|
11
|
+
name?: string;
|
|
12
|
+
initialValue?: T;
|
|
13
|
+
deferStream?: boolean;
|
|
14
|
+
}): Accessor<T | undefined>;
|
package/dist/data/createAsync.js
CHANGED
|
@@ -1,8 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
|
|
3
3
|
*/
|
|
4
|
-
import { createResource } from "solid-js";
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { createResource, sharedConfig } from "solid-js";
|
|
5
|
+
import { isServer } from "solid-js/web";
|
|
6
|
+
export function createAsync(fn, options) {
|
|
7
|
+
const [resource] = createResource(() => subFetch(fn), v => v, options);
|
|
7
8
|
return () => resource();
|
|
8
9
|
}
|
|
10
|
+
// mock promise while hydrating to prevent fetching
|
|
11
|
+
class MockPromise {
|
|
12
|
+
static all() {
|
|
13
|
+
return new MockPromise();
|
|
14
|
+
}
|
|
15
|
+
static allSettled() {
|
|
16
|
+
return new MockPromise();
|
|
17
|
+
}
|
|
18
|
+
static any() {
|
|
19
|
+
return new MockPromise();
|
|
20
|
+
}
|
|
21
|
+
static race() {
|
|
22
|
+
return new MockPromise();
|
|
23
|
+
}
|
|
24
|
+
static reject() {
|
|
25
|
+
return new MockPromise();
|
|
26
|
+
}
|
|
27
|
+
static resolve() {
|
|
28
|
+
return new MockPromise();
|
|
29
|
+
}
|
|
30
|
+
catch() {
|
|
31
|
+
return new MockPromise();
|
|
32
|
+
}
|
|
33
|
+
then() {
|
|
34
|
+
return new MockPromise();
|
|
35
|
+
}
|
|
36
|
+
finally() {
|
|
37
|
+
return new MockPromise();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function subFetch(fn) {
|
|
41
|
+
if (isServer || !sharedConfig.context)
|
|
42
|
+
return fn();
|
|
43
|
+
const ogFetch = fetch;
|
|
44
|
+
const ogPromise = Promise;
|
|
45
|
+
try {
|
|
46
|
+
window.fetch = () => new MockPromise();
|
|
47
|
+
Promise = MockPromise;
|
|
48
|
+
return fn();
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
window.fetch = ogFetch;
|
|
52
|
+
Promise = ogPromise;
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/data/index.d.ts
CHANGED
package/dist/data/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function redirect(url: string, init?: number | ResponseInit): Response;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function redirect(url, init = 302) {
|
|
2
|
+
let responseInit = init;
|
|
3
|
+
if (typeof responseInit === "number") {
|
|
4
|
+
responseInit = { status: responseInit };
|
|
5
|
+
}
|
|
6
|
+
else if (typeof responseInit.status === "undefined") {
|
|
7
|
+
responseInit.status = 302;
|
|
8
|
+
}
|
|
9
|
+
const headers = new Headers(responseInit.headers);
|
|
10
|
+
headers.set("Location", url);
|
|
11
|
+
const response = new Response(null, {
|
|
12
|
+
...responseInit,
|
|
13
|
+
headers: headers
|
|
14
|
+
});
|
|
15
|
+
return response;
|
|
16
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export * from "./lifecycle";
|
|
|
4
4
|
export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
|
|
5
5
|
export { mergeSearchString as _mergeSearchString } from "./utils";
|
|
6
6
|
export * from "./data";
|
|
7
|
-
export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
|
|
7
|
+
export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isServer, delegateEvents, getRequestEvent, createComponent as createComponent$1, spread, mergeProps as mergeProps$1, template } from 'solid-js/web';
|
|
2
|
-
import { createSignal, onCleanup, getOwner, runWithOwner, createMemo, createContext, useContext, untrack, createRenderEffect, on, startTransition,
|
|
2
|
+
import { createSignal, onCleanup, getOwner, runWithOwner, createMemo, createContext, useContext, untrack, createRenderEffect, on, startTransition, resetErrorBoundaries, createComponent, children, createRoot, Show, mergeProps, splitProps, createResource, sharedConfig, $TRACK } from 'solid-js';
|
|
3
3
|
import { createStore, reconcile } from 'solid-js/store';
|
|
4
4
|
|
|
5
5
|
function bindEvent(target, type, handler) {
|
|
@@ -501,7 +501,7 @@ function createLocation(path, state) {
|
|
|
501
501
|
const pathname = createMemo(() => url().pathname);
|
|
502
502
|
const search = createMemo(() => url().search, true);
|
|
503
503
|
const hash = createMemo(() => url().hash);
|
|
504
|
-
const key =
|
|
504
|
+
const key = () => "";
|
|
505
505
|
return {
|
|
506
506
|
get pathname() {
|
|
507
507
|
return pathname();
|
|
@@ -570,6 +570,16 @@ function createRouterContext(integration, getBranches, base = "") {
|
|
|
570
570
|
return resolvePath(basePath, to);
|
|
571
571
|
}
|
|
572
572
|
};
|
|
573
|
+
const router = {
|
|
574
|
+
base: baseRoute,
|
|
575
|
+
location,
|
|
576
|
+
isRouting,
|
|
577
|
+
renderPath,
|
|
578
|
+
parsePath,
|
|
579
|
+
navigatorFactory,
|
|
580
|
+
beforeLeave,
|
|
581
|
+
submissions: createSignal(submissions)
|
|
582
|
+
};
|
|
573
583
|
function navigateFromRoute(route, to, options) {
|
|
574
584
|
// Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
|
|
575
585
|
untrack(() => {
|
|
@@ -602,7 +612,12 @@ function createRouterContext(integration, getBranches, base = "") {
|
|
|
602
612
|
if (resolvedTo !== current || nextState !== state()) {
|
|
603
613
|
if (isServer) {
|
|
604
614
|
const e = getRequestEvent();
|
|
605
|
-
e && (e.response = Response
|
|
615
|
+
e && (e.response = new Response(null, {
|
|
616
|
+
status: 302,
|
|
617
|
+
headers: {
|
|
618
|
+
Location: resolvedTo
|
|
619
|
+
}
|
|
620
|
+
}));
|
|
606
621
|
setSource({
|
|
607
622
|
value: resolvedTo,
|
|
608
623
|
replace,
|
|
@@ -703,9 +718,9 @@ function createRouterContext(integration, getBranches, base = "") {
|
|
|
703
718
|
state: state && JSON.parse(state)
|
|
704
719
|
});
|
|
705
720
|
}
|
|
706
|
-
function doPreload(a,
|
|
721
|
+
function doPreload(a, url) {
|
|
707
722
|
const preload = a.getAttribute("preload") !== "false";
|
|
708
|
-
const matches = getRouteMatches(getBranches(),
|
|
723
|
+
const matches = getRouteMatches(getBranches(), url.pathname);
|
|
709
724
|
const prevIntent = intent;
|
|
710
725
|
intent = "preload";
|
|
711
726
|
for (let match in matches) {
|
|
@@ -716,7 +731,15 @@ function createRouterContext(integration, getBranches, base = "") {
|
|
|
716
731
|
route.component && route.component.preload && route.component.preload();
|
|
717
732
|
preload && route.load && route.load({
|
|
718
733
|
params,
|
|
719
|
-
location
|
|
734
|
+
location: {
|
|
735
|
+
pathname: url.pathname,
|
|
736
|
+
search: url.search,
|
|
737
|
+
hash: url.hash,
|
|
738
|
+
query: extractSearchParams(url),
|
|
739
|
+
state: null,
|
|
740
|
+
key: ""
|
|
741
|
+
},
|
|
742
|
+
intent
|
|
720
743
|
});
|
|
721
744
|
}
|
|
722
745
|
intent = prevIntent;
|
|
@@ -725,7 +748,7 @@ function createRouterContext(integration, getBranches, base = "") {
|
|
|
725
748
|
const res = handleAnchor(evt);
|
|
726
749
|
if (!res) return;
|
|
727
750
|
const [a, url] = res;
|
|
728
|
-
if (!preloadTimeout[url.pathname]) doPreload(a, url
|
|
751
|
+
if (!preloadTimeout[url.pathname]) doPreload(a, url);
|
|
729
752
|
}
|
|
730
753
|
function handleAnchorIn(evt) {
|
|
731
754
|
const res = handleAnchor(evt);
|
|
@@ -733,9 +756,9 @@ function createRouterContext(integration, getBranches, base = "") {
|
|
|
733
756
|
const [a, url] = res;
|
|
734
757
|
if (preloadTimeout[url.pathname]) return;
|
|
735
758
|
preloadTimeout[url.pathname] = setTimeout(() => {
|
|
736
|
-
doPreload(a, url
|
|
759
|
+
doPreload(a, url);
|
|
737
760
|
delete preloadTimeout[url.pathname];
|
|
738
|
-
},
|
|
761
|
+
}, 200);
|
|
739
762
|
}
|
|
740
763
|
function handleAnchorOut(evt) {
|
|
741
764
|
const res = handleAnchor(evt);
|
|
@@ -750,7 +773,7 @@ function createRouterContext(integration, getBranches, base = "") {
|
|
|
750
773
|
let actionRef = evt.submitter && evt.submitter.getAttribute("formaction") || evt.target.action;
|
|
751
774
|
if (actionRef && actionRef.startsWith("action:")) {
|
|
752
775
|
const data = new FormData(evt.target);
|
|
753
|
-
actions.get(actionRef.
|
|
776
|
+
actions.get(actionRef).call(router, data);
|
|
754
777
|
evt.preventDefault();
|
|
755
778
|
}
|
|
756
779
|
}
|
|
@@ -786,16 +809,7 @@ function createRouterContext(integration, getBranches, base = "") {
|
|
|
786
809
|
}
|
|
787
810
|
submissions = initFromFlash(location.query);
|
|
788
811
|
}
|
|
789
|
-
return
|
|
790
|
-
base: baseRoute,
|
|
791
|
-
location,
|
|
792
|
-
isRouting,
|
|
793
|
-
renderPath,
|
|
794
|
-
parsePath,
|
|
795
|
-
navigatorFactory,
|
|
796
|
-
beforeLeave,
|
|
797
|
-
submissions: createSignal(submissions)
|
|
798
|
-
};
|
|
812
|
+
return router;
|
|
799
813
|
}
|
|
800
814
|
function createRouteContext(router, parent, outlet, match, params) {
|
|
801
815
|
const {
|
|
@@ -827,7 +841,8 @@ function createRouteContext(router, parent, outlet, match, params) {
|
|
|
827
841
|
component && component.preload && component.preload();
|
|
828
842
|
load && load({
|
|
829
843
|
params,
|
|
830
|
-
location
|
|
844
|
+
location,
|
|
845
|
+
intent: intent || "navigate"
|
|
831
846
|
});
|
|
832
847
|
return route;
|
|
833
848
|
}
|
|
@@ -841,13 +856,13 @@ const Router = props => {
|
|
|
841
856
|
base
|
|
842
857
|
} = props;
|
|
843
858
|
const integration = source || (isServer ? staticIntegration({
|
|
844
|
-
value: url || (e = getRequestEvent()) && e.request.url || ""
|
|
859
|
+
value: url || (e = getRequestEvent()) && getPath(e.request.url) || ""
|
|
845
860
|
}) : pathIntegration());
|
|
846
|
-
const routeDefs = children(() => props.
|
|
861
|
+
const routeDefs = children(() => props.children);
|
|
862
|
+
const branches = createMemo(() => createBranches(props.root ? {
|
|
847
863
|
component: props.root,
|
|
848
|
-
children:
|
|
849
|
-
} : props.
|
|
850
|
-
const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
|
|
864
|
+
children: routeDefs()
|
|
865
|
+
} : routeDefs(), props.base || ""));
|
|
851
866
|
const routerState = createRouterContext(integration, branches, base);
|
|
852
867
|
return createComponent$1(RouterContextObj.Provider, {
|
|
853
868
|
value: routerState,
|
|
@@ -861,6 +876,10 @@ const Router = props => {
|
|
|
861
876
|
}
|
|
862
877
|
});
|
|
863
878
|
};
|
|
879
|
+
function getPath(url) {
|
|
880
|
+
const u = new URL(url);
|
|
881
|
+
return u.pathname + u.search;
|
|
882
|
+
}
|
|
864
883
|
function Routes(props) {
|
|
865
884
|
const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
|
|
866
885
|
const params = createMemoObject(() => {
|
|
@@ -997,11 +1016,55 @@ function Navigate(props) {
|
|
|
997
1016
|
/**
|
|
998
1017
|
* This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
|
|
999
1018
|
*/
|
|
1000
|
-
function createAsync(fn) {
|
|
1001
|
-
const [resource] = createResource(() => fn
|
|
1019
|
+
function createAsync(fn, options) {
|
|
1020
|
+
const [resource] = createResource(() => subFetch(fn), v => v, options);
|
|
1002
1021
|
return () => resource();
|
|
1003
1022
|
}
|
|
1004
1023
|
|
|
1024
|
+
// mock promise while hydrating to prevent fetching
|
|
1025
|
+
class MockPromise {
|
|
1026
|
+
static all() {
|
|
1027
|
+
return new MockPromise();
|
|
1028
|
+
}
|
|
1029
|
+
static allSettled() {
|
|
1030
|
+
return new MockPromise();
|
|
1031
|
+
}
|
|
1032
|
+
static any() {
|
|
1033
|
+
return new MockPromise();
|
|
1034
|
+
}
|
|
1035
|
+
static race() {
|
|
1036
|
+
return new MockPromise();
|
|
1037
|
+
}
|
|
1038
|
+
static reject() {
|
|
1039
|
+
return new MockPromise();
|
|
1040
|
+
}
|
|
1041
|
+
static resolve() {
|
|
1042
|
+
return new MockPromise();
|
|
1043
|
+
}
|
|
1044
|
+
catch() {
|
|
1045
|
+
return new MockPromise();
|
|
1046
|
+
}
|
|
1047
|
+
then() {
|
|
1048
|
+
return new MockPromise();
|
|
1049
|
+
}
|
|
1050
|
+
finally() {
|
|
1051
|
+
return new MockPromise();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
function subFetch(fn) {
|
|
1055
|
+
if (isServer || !sharedConfig.context) return fn();
|
|
1056
|
+
const ogFetch = fetch;
|
|
1057
|
+
const ogPromise = Promise;
|
|
1058
|
+
try {
|
|
1059
|
+
window.fetch = () => new MockPromise();
|
|
1060
|
+
Promise = MockPromise;
|
|
1061
|
+
return fn();
|
|
1062
|
+
} finally {
|
|
1063
|
+
window.fetch = ogFetch;
|
|
1064
|
+
Promise = ogPromise;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1005
1068
|
const LocationHeader = "Location";
|
|
1006
1069
|
const PRELOAD_TIMEOUT = 5000;
|
|
1007
1070
|
let cacheMap = new Map();
|
|
@@ -1049,7 +1112,7 @@ function cache(fn, name, options) {
|
|
|
1049
1112
|
version && cached[3].add(version);
|
|
1050
1113
|
if (cached[2] === "preload" && intent !== "preload") {
|
|
1051
1114
|
cached[0] = now;
|
|
1052
|
-
cached[1] = "then" in cached[1] ? cached[1].then(handleResponse) : handleResponse(cached[1]);
|
|
1115
|
+
cached[1] = "then" in cached[1] ? cached[1].then(handleResponse, handleResponse) : handleResponse(cached[1]);
|
|
1053
1116
|
cached[2] = intent;
|
|
1054
1117
|
}
|
|
1055
1118
|
if (!isServer && intent === "navigate") {
|
|
@@ -1058,9 +1121,15 @@ function cache(fn, name, options) {
|
|
|
1058
1121
|
|
|
1059
1122
|
return cached[1];
|
|
1060
1123
|
}
|
|
1061
|
-
let res =
|
|
1124
|
+
let res = !isServer && sharedConfig.context && sharedConfig.load ? sharedConfig.load(key) // hydrating
|
|
1125
|
+
: fn(...args);
|
|
1126
|
+
|
|
1127
|
+
// serialize on server
|
|
1128
|
+
if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
|
|
1129
|
+
sharedConfig.context && sharedConfig.context.serialize(key, res);
|
|
1130
|
+
}
|
|
1062
1131
|
if (intent !== "preload") {
|
|
1063
|
-
res = "then" in res ? res.then(handleResponse) : handleResponse(res);
|
|
1132
|
+
res = "then" in res ? res.then(handleResponse, handleResponse) : handleResponse(res);
|
|
1064
1133
|
}
|
|
1065
1134
|
if (cached) {
|
|
1066
1135
|
cached[0] = now;
|
|
@@ -1072,23 +1141,24 @@ function cache(fn, name, options) {
|
|
|
1072
1141
|
}
|
|
1073
1142
|
} else cache.set(key, cached = [now, res, intent, new Set(version ? [version] : [])]);
|
|
1074
1143
|
return res;
|
|
1075
|
-
function handleRedirect(response) {
|
|
1076
|
-
startTransition(() => {
|
|
1077
|
-
let url = response.headers.get(LocationHeader);
|
|
1078
|
-
if (url && url.startsWith("/")) {
|
|
1079
|
-
navigate(url, {
|
|
1080
|
-
replace: true
|
|
1081
|
-
});
|
|
1082
|
-
} else if (!isServer && url) {
|
|
1083
|
-
window.location.href = url;
|
|
1084
|
-
}
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
1144
|
function handleResponse(v) {
|
|
1088
1145
|
if (v instanceof Response && redirectStatusCodes.has(v.status)) {
|
|
1089
|
-
if (navigate)
|
|
1146
|
+
if (navigate) {
|
|
1147
|
+
startTransition(() => {
|
|
1148
|
+
let url = v.headers.get(LocationHeader);
|
|
1149
|
+
if (url && url.startsWith("/")) {
|
|
1150
|
+
navigate(url, {
|
|
1151
|
+
replace: true
|
|
1152
|
+
});
|
|
1153
|
+
} else if (!isServer && url) {
|
|
1154
|
+
window.location.href = url;
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1090
1158
|
return;
|
|
1091
1159
|
}
|
|
1160
|
+
if (v instanceof Error) throw v;
|
|
1161
|
+
if (isServer) return v;
|
|
1092
1162
|
setStore(key, reconcile(v, options));
|
|
1093
1163
|
return store[key];
|
|
1094
1164
|
}
|
|
@@ -1135,6 +1205,13 @@ function action(fn, name) {
|
|
|
1135
1205
|
const [result, setResult] = createSignal();
|
|
1136
1206
|
let submission;
|
|
1137
1207
|
const router = this;
|
|
1208
|
+
async function handler(res) {
|
|
1209
|
+
const data = await handleResponse(res, router.navigatorFactory());
|
|
1210
|
+
data ? setResult({
|
|
1211
|
+
data
|
|
1212
|
+
}) : submission.clear();
|
|
1213
|
+
return data;
|
|
1214
|
+
}
|
|
1138
1215
|
router.submissions[1](s => [...s, submission = {
|
|
1139
1216
|
input: variables,
|
|
1140
1217
|
url,
|
|
@@ -1150,36 +1227,14 @@ function action(fn, name) {
|
|
|
1150
1227
|
retry() {
|
|
1151
1228
|
setResult(undefined);
|
|
1152
1229
|
const p = fn(variables);
|
|
1153
|
-
p.then(
|
|
1154
|
-
const keys = handleResponse(data, router.navigatorFactory());
|
|
1155
|
-
await revalidate(keys);
|
|
1156
|
-
data ? setResult({
|
|
1157
|
-
data
|
|
1158
|
-
}) : submission.clear();
|
|
1159
|
-
return data;
|
|
1160
|
-
}).catch(error => {
|
|
1161
|
-
setResult({
|
|
1162
|
-
data: error
|
|
1163
|
-
});
|
|
1164
|
-
});
|
|
1230
|
+
p.then(handler, handler);
|
|
1165
1231
|
return p;
|
|
1166
1232
|
}
|
|
1167
1233
|
}]);
|
|
1168
|
-
p.then(
|
|
1169
|
-
const keys = handleResponse(data, router.navigatorFactory());
|
|
1170
|
-
await revalidate(keys);
|
|
1171
|
-
data ? setResult({
|
|
1172
|
-
data
|
|
1173
|
-
}) : submission.clear();
|
|
1174
|
-
return data;
|
|
1175
|
-
}).catch(error => {
|
|
1176
|
-
setResult({
|
|
1177
|
-
data: error
|
|
1178
|
-
});
|
|
1179
|
-
});
|
|
1234
|
+
p.then(handler, handler);
|
|
1180
1235
|
return p;
|
|
1181
1236
|
}
|
|
1182
|
-
const url = fn.url || `action:${name}` || !isServer ? `action:${fn.name}` : "";
|
|
1237
|
+
const url = fn.url || name && `action:${name}` || (!isServer ? `action:${fn.name}` : "");
|
|
1183
1238
|
mutate.toString = () => {
|
|
1184
1239
|
if (!url) throw new Error("Client Actions need explicit names if server rendered");
|
|
1185
1240
|
return url;
|
|
@@ -1187,7 +1242,8 @@ function action(fn, name) {
|
|
|
1187
1242
|
if (!isServer) registerAction(url, mutate);
|
|
1188
1243
|
return mutate;
|
|
1189
1244
|
}
|
|
1190
|
-
function handleResponse(response, navigate) {
|
|
1245
|
+
async function handleResponse(response, navigate) {
|
|
1246
|
+
let data;
|
|
1191
1247
|
if (response instanceof Response && redirectStatusCodes.has(response.status)) {
|
|
1192
1248
|
const locationUrl = response.headers.get("Location") || "/";
|
|
1193
1249
|
if (locationUrl.startsWith("http")) {
|
|
@@ -1195,9 +1251,28 @@ function handleResponse(response, navigate) {
|
|
|
1195
1251
|
} else {
|
|
1196
1252
|
navigate(locationUrl);
|
|
1197
1253
|
}
|
|
1254
|
+
} else data = response;
|
|
1255
|
+
// TODO: handle keys
|
|
1256
|
+
await revalidate();
|
|
1257
|
+
return data;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function redirect(url, init = 302) {
|
|
1261
|
+
let responseInit = init;
|
|
1262
|
+
if (typeof responseInit === "number") {
|
|
1263
|
+
responseInit = {
|
|
1264
|
+
status: responseInit
|
|
1265
|
+
};
|
|
1266
|
+
} else if (typeof responseInit.status === "undefined") {
|
|
1267
|
+
responseInit.status = 302;
|
|
1198
1268
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1269
|
+
const headers = new Headers(responseInit.headers);
|
|
1270
|
+
headers.set("Location", url);
|
|
1271
|
+
const response = new Response(null, {
|
|
1272
|
+
...responseInit,
|
|
1273
|
+
headers: headers
|
|
1274
|
+
});
|
|
1275
|
+
return response;
|
|
1201
1276
|
}
|
|
1202
1277
|
|
|
1203
|
-
export { A, A as Link, A as NavLink, Navigate, Route, Router, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createIntegration, createMemoryHistory, hashIntegration, memoryIntegration, normalizeIntegration, pathIntegration, revalidate, staticIntegration, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
|
|
1278
|
+
export { A, A as Link, A as NavLink, Navigate, Route, Router, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createIntegration, createMemoryHistory, hashIntegration, memoryIntegration, normalizeIntegration, pathIntegration, redirect, revalidate, staticIntegration, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
|
package/dist/routing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { BeforeLeaveEventArgs, Branch, Location, LocationChangeSignal, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
|
|
1
|
+
import { JSX, Accessor } from "solid-js";
|
|
2
|
+
import type { BeforeLeaveEventArgs, Branch, Intent, Location, LocationChangeSignal, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
|
|
3
3
|
export declare const RouterContextObj: import("solid-js").Context<RouterContext | undefined>;
|
|
4
4
|
export declare const RouteContextObj: import("solid-js").Context<RouteContext | undefined>;
|
|
5
5
|
export declare const useRouter: () => RouterContext;
|
|
@@ -19,6 +19,6 @@ export declare function createBranches(routeDef: RouteDefinition | RouteDefiniti
|
|
|
19
19
|
export declare function getRouteMatches(branches: Branch[], location: string): RouteMatch[];
|
|
20
20
|
export declare function createLocation(path: Accessor<string>, state: Accessor<any>): Location;
|
|
21
21
|
export declare function registerAction(url: string, fn: Function): void;
|
|
22
|
-
export declare function getIntent():
|
|
22
|
+
export declare function getIntent(): Intent | undefined;
|
|
23
23
|
export declare function createRouterContext(integration?: RouterIntegration | LocationChangeSignal, getBranches?: () => Branch[], base?: string): RouterContext;
|
|
24
24
|
export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, params: Params): RouteContext;
|
package/dist/routing.js
CHANGED
|
@@ -153,7 +153,7 @@ export function createLocation(path, state) {
|
|
|
153
153
|
const pathname = createMemo(() => url().pathname);
|
|
154
154
|
const search = createMemo(() => url().search, true);
|
|
155
155
|
const hash = createMemo(() => url().hash);
|
|
156
|
-
const key =
|
|
156
|
+
const key = () => "";
|
|
157
157
|
return {
|
|
158
158
|
get pathname() {
|
|
159
159
|
return pathname();
|
|
@@ -217,6 +217,16 @@ export function createRouterContext(integration, getBranches, base = "") {
|
|
|
217
217
|
return resolvePath(basePath, to);
|
|
218
218
|
}
|
|
219
219
|
};
|
|
220
|
+
const router = {
|
|
221
|
+
base: baseRoute,
|
|
222
|
+
location,
|
|
223
|
+
isRouting,
|
|
224
|
+
renderPath,
|
|
225
|
+
parsePath,
|
|
226
|
+
navigatorFactory,
|
|
227
|
+
beforeLeave,
|
|
228
|
+
submissions: createSignal(submissions)
|
|
229
|
+
};
|
|
220
230
|
function navigateFromRoute(route, to, options) {
|
|
221
231
|
// Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
|
|
222
232
|
untrack(() => {
|
|
@@ -249,7 +259,8 @@ export function createRouterContext(integration, getBranches, base = "") {
|
|
|
249
259
|
if (resolvedTo !== current || nextState !== state()) {
|
|
250
260
|
if (isServer) {
|
|
251
261
|
const e = getRequestEvent();
|
|
252
|
-
e &&
|
|
262
|
+
e &&
|
|
263
|
+
(e.response = new Response(null, { status: 302, headers: { Location: resolvedTo } }));
|
|
253
264
|
setSource({ value: resolvedTo, replace, scroll, state: nextState });
|
|
254
265
|
}
|
|
255
266
|
else if (beforeLeave.confirm(resolvedTo, options)) {
|
|
@@ -352,9 +363,9 @@ export function createRouterContext(integration, getBranches, base = "") {
|
|
|
352
363
|
state: state && JSON.parse(state)
|
|
353
364
|
});
|
|
354
365
|
}
|
|
355
|
-
function doPreload(a,
|
|
366
|
+
function doPreload(a, url) {
|
|
356
367
|
const preload = a.getAttribute("preload") !== "false";
|
|
357
|
-
const matches = getRouteMatches(getBranches(),
|
|
368
|
+
const matches = getRouteMatches(getBranches(), url.pathname);
|
|
358
369
|
const prevIntent = intent;
|
|
359
370
|
intent = "preload";
|
|
360
371
|
for (let match in matches) {
|
|
@@ -362,7 +373,20 @@ export function createRouterContext(integration, getBranches, base = "") {
|
|
|
362
373
|
route.component &&
|
|
363
374
|
route.component.preload &&
|
|
364
375
|
route.component.preload();
|
|
365
|
-
preload &&
|
|
376
|
+
preload &&
|
|
377
|
+
route.load &&
|
|
378
|
+
route.load({
|
|
379
|
+
params,
|
|
380
|
+
location: {
|
|
381
|
+
pathname: url.pathname,
|
|
382
|
+
search: url.search,
|
|
383
|
+
hash: url.hash,
|
|
384
|
+
query: extractSearchParams(url),
|
|
385
|
+
state: null,
|
|
386
|
+
key: ""
|
|
387
|
+
},
|
|
388
|
+
intent
|
|
389
|
+
});
|
|
366
390
|
}
|
|
367
391
|
intent = prevIntent;
|
|
368
392
|
}
|
|
@@ -372,7 +396,7 @@ export function createRouterContext(integration, getBranches, base = "") {
|
|
|
372
396
|
return;
|
|
373
397
|
const [a, url] = res;
|
|
374
398
|
if (!preloadTimeout[url.pathname])
|
|
375
|
-
doPreload(a, url
|
|
399
|
+
doPreload(a, url);
|
|
376
400
|
}
|
|
377
401
|
function handleAnchorIn(evt) {
|
|
378
402
|
const res = handleAnchor(evt);
|
|
@@ -382,9 +406,9 @@ export function createRouterContext(integration, getBranches, base = "") {
|
|
|
382
406
|
if (preloadTimeout[url.pathname])
|
|
383
407
|
return;
|
|
384
408
|
preloadTimeout[url.pathname] = setTimeout(() => {
|
|
385
|
-
doPreload(a, url
|
|
409
|
+
doPreload(a, url);
|
|
386
410
|
delete preloadTimeout[url.pathname];
|
|
387
|
-
},
|
|
411
|
+
}, 200);
|
|
388
412
|
}
|
|
389
413
|
function handleAnchorOut(evt) {
|
|
390
414
|
const res = handleAnchor(evt);
|
|
@@ -397,14 +421,13 @@ export function createRouterContext(integration, getBranches, base = "") {
|
|
|
397
421
|
}
|
|
398
422
|
}
|
|
399
423
|
function handleFormSubmit(evt) {
|
|
400
|
-
let actionRef = evt.submitter && evt.submitter.getAttribute("formaction") || evt.target.action;
|
|
424
|
+
let actionRef = (evt.submitter && evt.submitter.getAttribute("formaction")) || evt.target.action;
|
|
401
425
|
if (actionRef && actionRef.startsWith("action:")) {
|
|
402
426
|
const data = new FormData(evt.target);
|
|
403
|
-
actions.get(actionRef.
|
|
427
|
+
actions.get(actionRef).call(router, data);
|
|
404
428
|
evt.preventDefault();
|
|
405
429
|
}
|
|
406
430
|
}
|
|
407
|
-
;
|
|
408
431
|
// ensure delegated event run first
|
|
409
432
|
delegateEvents(["click", "submit"]);
|
|
410
433
|
document.addEventListener("click", handleAnchorClick);
|
|
@@ -429,24 +452,17 @@ export function createRouterContext(integration, getBranches, base = "") {
|
|
|
429
452
|
return [];
|
|
430
453
|
}
|
|
431
454
|
const input = new Map(param.entries);
|
|
432
|
-
return [
|
|
455
|
+
return [
|
|
456
|
+
{
|
|
433
457
|
url: param.url,
|
|
434
458
|
result: param.error ? new Error(param.result.message) : param.result,
|
|
435
459
|
input: input
|
|
436
|
-
}
|
|
460
|
+
}
|
|
461
|
+
];
|
|
437
462
|
}
|
|
438
463
|
submissions = initFromFlash(location.query);
|
|
439
464
|
}
|
|
440
|
-
return
|
|
441
|
-
base: baseRoute,
|
|
442
|
-
location,
|
|
443
|
-
isRouting,
|
|
444
|
-
renderPath,
|
|
445
|
-
parsePath,
|
|
446
|
-
navigatorFactory,
|
|
447
|
-
beforeLeave,
|
|
448
|
-
submissions: createSignal(submissions)
|
|
449
|
-
};
|
|
465
|
+
return router;
|
|
450
466
|
}
|
|
451
467
|
export function createRouteContext(router, parent, outlet, match, params) {
|
|
452
468
|
const { base, location } = router;
|
|
@@ -473,6 +489,6 @@ export function createRouteContext(router, parent, outlet, match, params) {
|
|
|
473
489
|
component &&
|
|
474
490
|
component.preload &&
|
|
475
491
|
component.preload();
|
|
476
|
-
load && load({ params, location });
|
|
492
|
+
load && load({ params, location, intent: intent || "navigate" });
|
|
477
493
|
return route;
|
|
478
494
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -39,16 +39,20 @@ export interface RouterIntegration {
|
|
|
39
39
|
signal: LocationChangeSignal;
|
|
40
40
|
utils?: Partial<RouterUtils>;
|
|
41
41
|
}
|
|
42
|
+
export type Intent = "native" | "navigate" | "preload";
|
|
42
43
|
export interface RouteLoadFuncArgs {
|
|
43
44
|
params: Params;
|
|
44
45
|
location: Location;
|
|
46
|
+
intent: Intent;
|
|
45
47
|
}
|
|
46
48
|
export type RouteLoadFunc = (args: RouteLoadFuncArgs) => void;
|
|
47
|
-
export interface RouteSectionProps
|
|
49
|
+
export interface RouteSectionProps {
|
|
50
|
+
params: Params;
|
|
51
|
+
location: Location;
|
|
48
52
|
children?: JSX.Element;
|
|
49
53
|
}
|
|
50
54
|
export type RouteDefinition<S extends string | string[] = any> = {
|
|
51
|
-
path
|
|
55
|
+
path?: S;
|
|
52
56
|
matchFilters?: MatchFilters<S>;
|
|
53
57
|
load?: RouteLoadFunc;
|
|
54
58
|
children?: RouteDefinition | RouteDefinition[];
|