@solidjs/router 0.9.0 → 0.10.0-beta.0
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 +325 -282
- package/dist/components.d.ts +7 -21
- package/dist/components.jsx +23 -30
- package/dist/data/action.d.ts +8 -0
- package/dist/data/action.js +113 -0
- package/dist/data/cache.d.ts +3 -0
- package/dist/data/cache.js +106 -0
- package/dist/data/createAsync.d.ts +5 -0
- package/dist/data/createAsync.js +8 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +380 -120
- package/dist/index.jsx +2 -1
- package/dist/routing.d.ts +8 -9
- package/dist/routing.js +144 -71
- package/dist/types.d.ts +21 -19
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +1 -0
- package/package.json +5 -5
package/dist/components.d.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import type { Component, JSX } from "solid-js";
|
|
2
|
-
import type { Location, LocationChangeSignal, MatchFilters, Navigator,
|
|
2
|
+
import type { Location, LocationChangeSignal, MatchFilters, Navigator, RouteLoadFunc, RouterIntegration, RouteSectionProps } from "./types";
|
|
3
3
|
declare module "solid-js" {
|
|
4
4
|
namespace JSX {
|
|
5
5
|
interface AnchorHTMLAttributes<T> {
|
|
6
6
|
state?: string;
|
|
7
7
|
noScroll?: boolean;
|
|
8
8
|
replace?: boolean;
|
|
9
|
-
|
|
9
|
+
preload?: boolean;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
export type RouterProps = {
|
|
14
14
|
base?: string;
|
|
15
|
-
|
|
15
|
+
root?: Component<RouteSectionProps>;
|
|
16
16
|
children: JSX.Element;
|
|
17
|
-
out?: object;
|
|
18
17
|
} & ({
|
|
19
18
|
url?: never;
|
|
20
19
|
source?: RouterIntegration | LocationChangeSignal;
|
|
@@ -23,27 +22,14 @@ export type RouterProps = {
|
|
|
23
22
|
url: string;
|
|
24
23
|
});
|
|
25
24
|
export declare const Router: (props: RouterProps) => JSX.Element;
|
|
26
|
-
export interface RoutesProps {
|
|
27
|
-
base?: string;
|
|
28
|
-
children: JSX.Element;
|
|
29
|
-
}
|
|
30
|
-
export declare const Routes: (props: RoutesProps) => JSX.Element;
|
|
31
|
-
export declare const useRoutes: (routes: RouteDefinition | RouteDefinition[] | Readonly<RouteDefinition[]>, base?: string) => () => JSX.Element;
|
|
32
25
|
export type RouteProps<S extends string> = {
|
|
33
|
-
path
|
|
26
|
+
path?: S | S[];
|
|
34
27
|
children?: JSX.Element;
|
|
35
|
-
|
|
28
|
+
load?: RouteLoadFunc;
|
|
36
29
|
matchFilters?: MatchFilters<S>;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
component: Component;
|
|
40
|
-
} | {
|
|
41
|
-
component?: never;
|
|
42
|
-
element?: JSX.Element;
|
|
43
|
-
preload?: () => void;
|
|
44
|
-
});
|
|
30
|
+
component?: Component;
|
|
31
|
+
};
|
|
45
32
|
export declare const Route: <S extends string>(props: RouteProps<S>) => JSX.Element;
|
|
46
|
-
export declare const Outlet: () => JSX.Element;
|
|
47
33
|
export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "state"> {
|
|
48
34
|
href: string;
|
|
49
35
|
replace?: boolean | undefined;
|
package/dist/components.jsx
CHANGED
|
@@ -2,24 +2,29 @@
|
|
|
2
2
|
import { children, createMemo, createRoot, mergeProps, on, Show, splitProps } from "solid-js";
|
|
3
3
|
import { isServer, getRequestEvent } from "solid-js/web";
|
|
4
4
|
import { pathIntegration, staticIntegration } from "./integration";
|
|
5
|
-
import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj, useHref, useLocation, useNavigate, useResolvedPath
|
|
6
|
-
import {
|
|
5
|
+
import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj, useHref, useLocation, useNavigate, useResolvedPath } from "./routing";
|
|
6
|
+
import { normalizePath, createMemoObject } from "./utils";
|
|
7
7
|
export const Router = (props) => {
|
|
8
8
|
let e;
|
|
9
|
-
const { source, url, base
|
|
9
|
+
const { source, url, base } = props;
|
|
10
10
|
const integration = source ||
|
|
11
11
|
(isServer
|
|
12
12
|
? staticIntegration({ value: url || ((e = getRequestEvent()) && e.request.url) || "" })
|
|
13
13
|
: pathIntegration());
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const routeDefs = children(() => props.root
|
|
15
|
+
? {
|
|
16
|
+
component: props.root,
|
|
17
|
+
children: props.children
|
|
18
|
+
}
|
|
19
|
+
: props.children);
|
|
20
|
+
const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
|
|
21
|
+
const routerState = createRouterContext(integration, branches, base);
|
|
22
|
+
return (<RouterContextObj.Provider value={routerState}>
|
|
23
|
+
<Routes routerState={routerState} branches={branches()}/>
|
|
24
|
+
</RouterContextObj.Provider>);
|
|
16
25
|
};
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const parentRoute = useRoute();
|
|
20
|
-
const routeDefs = children(() => props.children);
|
|
21
|
-
const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
|
|
22
|
-
const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
|
|
26
|
+
function Routes(props) {
|
|
27
|
+
const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
|
|
23
28
|
const params = createMemoObject(() => {
|
|
24
29
|
const m = matches();
|
|
25
30
|
const params = {};
|
|
@@ -28,14 +33,6 @@ export const Routes = (props) => {
|
|
|
28
33
|
}
|
|
29
34
|
return params;
|
|
30
35
|
});
|
|
31
|
-
if (router.out) {
|
|
32
|
-
router.out.matches.push(matches().map(({ route, path, params }) => ({
|
|
33
|
-
originalPath: route.originalPath,
|
|
34
|
-
pattern: route.pattern,
|
|
35
|
-
path,
|
|
36
|
-
params
|
|
37
|
-
})));
|
|
38
|
-
}
|
|
39
36
|
const disposers = [];
|
|
40
37
|
let root;
|
|
41
38
|
const routeStates = createMemo(on(matches, (nextMatches, prevMatches, prev) => {
|
|
@@ -54,7 +51,7 @@ export const Routes = (props) => {
|
|
|
54
51
|
}
|
|
55
52
|
createRoot(dispose => {
|
|
56
53
|
disposers[i] = dispose;
|
|
57
|
-
next[i] = createRouteContext(
|
|
54
|
+
next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => matches()[i], params);
|
|
58
55
|
});
|
|
59
56
|
}
|
|
60
57
|
}
|
|
@@ -68,9 +65,11 @@ export const Routes = (props) => {
|
|
|
68
65
|
return (<Show when={routeStates() && root} keyed>
|
|
69
66
|
{route => <RouteContextObj.Provider value={route}>{route.outlet()}</RouteContextObj.Provider>}
|
|
70
67
|
</Show>);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return () => <
|
|
68
|
+
}
|
|
69
|
+
const createOutlet = (child) => {
|
|
70
|
+
return () => (<Show when={child()} keyed>
|
|
71
|
+
{child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
|
|
72
|
+
</Show>);
|
|
74
73
|
};
|
|
75
74
|
export const Route = (props) => {
|
|
76
75
|
const childRoutes = children(() => props.children);
|
|
@@ -80,12 +79,6 @@ export const Route = (props) => {
|
|
|
80
79
|
}
|
|
81
80
|
});
|
|
82
81
|
};
|
|
83
|
-
export const Outlet = () => {
|
|
84
|
-
const route = useRoute();
|
|
85
|
-
return (<Show when={route.child} keyed>
|
|
86
|
-
{child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
|
|
87
|
-
</Show>);
|
|
88
|
-
};
|
|
89
82
|
export function A(props) {
|
|
90
83
|
props = mergeProps({ inactiveClass: "inactive", activeClass: "active" }, props);
|
|
91
84
|
const [, rest] = splitProps(props, [
|
|
@@ -107,7 +100,7 @@ export function A(props) {
|
|
|
107
100
|
const loc = normalizePath(location.pathname).toLowerCase();
|
|
108
101
|
return props.end ? path === loc : loc.startsWith(path);
|
|
109
102
|
});
|
|
110
|
-
return (<a
|
|
103
|
+
return (<a {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{
|
|
111
104
|
...(props.class && { [props.class]: true }),
|
|
112
105
|
[props.inactiveClass]: !isActive(),
|
|
113
106
|
[props.activeClass]: isActive(),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Submission } from "../types";
|
|
2
|
+
export type Action<T, U> = (vars: T) => Promise<U>;
|
|
3
|
+
export declare function useSubmissions<T, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U>[] & {
|
|
4
|
+
pending: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function useSubmission<T, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U>;
|
|
7
|
+
export declare function useAction<T, U>(action: Action<T, U>): Action<T, U>;
|
|
8
|
+
export declare function action<T, U = void>(fn: (args: T) => Promise<U>, name?: string): Action<T, U>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { $TRACK, createMemo, createSignal } from "solid-js";
|
|
2
|
+
import { isServer } from "solid-js/web";
|
|
3
|
+
import { registerAction, useRouter } from "../routing";
|
|
4
|
+
import { redirectStatusCodes } from "../utils";
|
|
5
|
+
import { revalidate } from "./cache";
|
|
6
|
+
export function useSubmissions(fn, filter) {
|
|
7
|
+
const router = useRouter();
|
|
8
|
+
const subs = createMemo(() => router.submissions[0]().filter(s => s.url === fn.toString() && (!filter || filter(s.input))));
|
|
9
|
+
return new Proxy([], {
|
|
10
|
+
get(_, property) {
|
|
11
|
+
if (property === $TRACK)
|
|
12
|
+
return subs();
|
|
13
|
+
if (property === "pending")
|
|
14
|
+
return subs().some(sub => !sub.result);
|
|
15
|
+
return subs()[property];
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function useSubmission(fn, filter) {
|
|
20
|
+
const submissions = useSubmissions(fn, filter);
|
|
21
|
+
return {
|
|
22
|
+
get clear() {
|
|
23
|
+
return submissions[0]?.clear;
|
|
24
|
+
},
|
|
25
|
+
get retry() {
|
|
26
|
+
return submissions[0]?.retry;
|
|
27
|
+
},
|
|
28
|
+
get url() {
|
|
29
|
+
return submissions[0]?.url;
|
|
30
|
+
},
|
|
31
|
+
get input() {
|
|
32
|
+
return submissions[0]?.input;
|
|
33
|
+
},
|
|
34
|
+
get result() {
|
|
35
|
+
return submissions[0]?.result;
|
|
36
|
+
},
|
|
37
|
+
get pending() {
|
|
38
|
+
return submissions[0]?.pending;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function useAction(action) {
|
|
43
|
+
const router = useRouter();
|
|
44
|
+
return action.bind(router);
|
|
45
|
+
}
|
|
46
|
+
export function action(fn, name) {
|
|
47
|
+
function mutate(variables) {
|
|
48
|
+
const p = fn(variables);
|
|
49
|
+
const [result, setResult] = createSignal();
|
|
50
|
+
let submission;
|
|
51
|
+
const router = this;
|
|
52
|
+
router.submissions[1](s => [
|
|
53
|
+
...s,
|
|
54
|
+
(submission = {
|
|
55
|
+
input: variables,
|
|
56
|
+
url,
|
|
57
|
+
get result() {
|
|
58
|
+
return result()?.data;
|
|
59
|
+
},
|
|
60
|
+
get pending() {
|
|
61
|
+
return !result();
|
|
62
|
+
},
|
|
63
|
+
clear() {
|
|
64
|
+
router.submissions[1](v => v.filter(i => i.input !== variables));
|
|
65
|
+
},
|
|
66
|
+
retry() {
|
|
67
|
+
setResult(undefined);
|
|
68
|
+
const p = fn(variables);
|
|
69
|
+
p.then(async (data) => {
|
|
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
|
+
});
|
|
77
|
+
return p;
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
]);
|
|
81
|
+
p.then(async (data) => {
|
|
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
|
+
});
|
|
89
|
+
return p;
|
|
90
|
+
}
|
|
91
|
+
const url = fn.url || `action:${name}` || !isServer ? `action:${fn.name}` : "";
|
|
92
|
+
mutate.toString = () => {
|
|
93
|
+
if (!url)
|
|
94
|
+
throw new Error("Client Actions need explicit names if server rendered");
|
|
95
|
+
return url;
|
|
96
|
+
};
|
|
97
|
+
if (!isServer)
|
|
98
|
+
registerAction(url, mutate);
|
|
99
|
+
return mutate;
|
|
100
|
+
}
|
|
101
|
+
function handleResponse(response, navigate) {
|
|
102
|
+
if (response instanceof Response && redirectStatusCodes.has(response.status)) {
|
|
103
|
+
const locationUrl = response.headers.get("Location") || "/";
|
|
104
|
+
if (locationUrl.startsWith("http")) {
|
|
105
|
+
window.location.href = locationUrl;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
navigate(locationUrl);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// return keys
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createSignal, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js";
|
|
2
|
+
import { createStore, reconcile } from "solid-js/store";
|
|
3
|
+
import { getRequestEvent, isServer } from "solid-js/web";
|
|
4
|
+
import { useNavigate, getIntent } from "../routing";
|
|
5
|
+
import { redirectStatusCodes } from "../utils";
|
|
6
|
+
const LocationHeader = "Location";
|
|
7
|
+
const PRELOAD_TIMEOUT = 5000;
|
|
8
|
+
let cacheMap = new Map();
|
|
9
|
+
function getCache() {
|
|
10
|
+
if (!isServer)
|
|
11
|
+
return cacheMap;
|
|
12
|
+
const req = getRequestEvent() || sharedConfig.context;
|
|
13
|
+
return req.routerCache || (req.routerCache = new Map());
|
|
14
|
+
}
|
|
15
|
+
export function revalidate(key) {
|
|
16
|
+
return startTransition(() => {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
for (let k of cacheMap.keys()) {
|
|
19
|
+
if (key === undefined || k === key) {
|
|
20
|
+
const set = cacheMap.get(k)[3];
|
|
21
|
+
revalidateSignals(set, now);
|
|
22
|
+
cacheMap.delete(k);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function revalidateSignals(set, time) {
|
|
28
|
+
for (let s of set)
|
|
29
|
+
s[1](time);
|
|
30
|
+
}
|
|
31
|
+
export function cache(fn, name, options) {
|
|
32
|
+
const [store, setStore] = createStore({});
|
|
33
|
+
return ((...args) => {
|
|
34
|
+
const cache = getCache();
|
|
35
|
+
const intent = getIntent();
|
|
36
|
+
const owner = getOwner();
|
|
37
|
+
const navigate = owner ? useNavigate() : undefined;
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const key = name + (args.length ? ":" + args.join(":") : "");
|
|
40
|
+
let cached = cache.get(key);
|
|
41
|
+
let version;
|
|
42
|
+
if (owner) {
|
|
43
|
+
version = createSignal(now, {
|
|
44
|
+
equals: (p, v) => v - p < 50 // margin of error
|
|
45
|
+
});
|
|
46
|
+
onCleanup(() => cached[3].delete(version));
|
|
47
|
+
version[0](); // track it;
|
|
48
|
+
}
|
|
49
|
+
if (cached && (isServer || intent === "native" || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
|
|
50
|
+
version && cached[3].add(version);
|
|
51
|
+
if (cached[2] === "preload" && intent !== "preload") {
|
|
52
|
+
cached[0] = now;
|
|
53
|
+
cached[1] =
|
|
54
|
+
"then" in cached[1]
|
|
55
|
+
? cached[1].then(handleResponse)
|
|
56
|
+
: handleResponse(cached[1]);
|
|
57
|
+
cached[2] = intent;
|
|
58
|
+
}
|
|
59
|
+
if (!isServer && intent === "navigate") {
|
|
60
|
+
startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
|
|
61
|
+
}
|
|
62
|
+
return cached[1];
|
|
63
|
+
}
|
|
64
|
+
let res = fn(...args);
|
|
65
|
+
if (intent !== "preload") {
|
|
66
|
+
res =
|
|
67
|
+
"then" in res
|
|
68
|
+
? res.then(handleResponse)
|
|
69
|
+
: handleResponse(res);
|
|
70
|
+
}
|
|
71
|
+
if (cached) {
|
|
72
|
+
cached[0] = now;
|
|
73
|
+
cached[1] = res;
|
|
74
|
+
cached[2] = intent;
|
|
75
|
+
version && cached[3].add(version);
|
|
76
|
+
if (!isServer && intent === "navigate") {
|
|
77
|
+
startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else
|
|
81
|
+
cache.set(key, (cached = [now, res, intent, new Set(version ? [version] : [])]));
|
|
82
|
+
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
|
+
function handleResponse(v) {
|
|
97
|
+
if (v instanceof Response && redirectStatusCodes.has(v.status)) {
|
|
98
|
+
if (navigate)
|
|
99
|
+
isServer ? handleRedirect(v) : setTimeout(() => handleRedirect(v), 0);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
setStore(key, reconcile(v, options));
|
|
103
|
+
return store[key];
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./components";
|
|
2
2
|
export * from "./integration";
|
|
3
3
|
export * from "./lifecycle";
|
|
4
|
-
export {
|
|
4
|
+
export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
|
|
5
5
|
export { mergeSearchString as _mergeSearchString } from "./utils";
|
|
6
|
-
export
|
|
6
|
+
export * from "./data";
|
|
7
|
+
export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
|