@solidjs/router 1.0.0-next.8 → 1.0.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 +715 -531
- package/dist/components.d.ts +31 -0
- package/dist/components.jsx +40 -0
- package/dist/data/action.d.ts +11 -40
- package/dist/data/action.js +100 -274
- package/dist/data/createAsync.d.ts +32 -0
- package/dist/data/createAsync.js +96 -0
- package/dist/data/events.d.ts +0 -8
- package/dist/data/events.js +23 -24
- package/dist/data/index.d.ts +4 -2
- package/dist/data/index.js +4 -2
- package/dist/data/query.d.ts +3 -1
- package/dist/data/query.js +22 -39
- package/dist/data/response.d.ts +4 -0
- package/dist/data/response.js +42 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1024 -1578
- package/dist/index.jsx +2 -2
- package/dist/lifecycle.d.ts +4 -29
- package/dist/lifecycle.js +37 -40
- package/dist/routers/HashRouter.d.ts +9 -0
- package/dist/routers/HashRouter.js +41 -0
- package/dist/routers/MemoryRouter.d.ts +24 -0
- package/dist/routers/MemoryRouter.js +57 -0
- package/dist/routers/Router.d.ts +17 -0
- package/dist/routers/Router.js +59 -0
- package/dist/routers/StaticRouter.d.ts +6 -0
- package/dist/routers/StaticRouter.js +15 -0
- package/dist/routers/components.d.ts +26 -12
- package/dist/routers/components.jsx +54 -68
- package/dist/routers/createRouter.d.ts +10 -0
- package/dist/routers/createRouter.js +41 -0
- package/dist/routers/index.d.ts +11 -4
- package/dist/routers/index.js +6 -2
- package/dist/routers/scrollRestoration.d.ts +32 -0
- package/dist/routers/scrollRestoration.js +96 -0
- package/dist/routing.d.ts +52 -88
- package/dist/routing.js +166 -381
- package/dist/types.d.ts +33 -83
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -2
- package/package.json +7 -11
- package/dist/claims.d.ts +0 -21
- package/dist/claims.js +0 -115
- package/dist/data/flash.d.ts +0 -21
- package/dist/data/flash.js +0 -78
- package/dist/data/flashCookie.d.ts +0 -7
- package/dist/data/flashCookie.js +0 -20
- package/dist/data/serverForms.d.ts +0 -1
- package/dist/data/serverForms.js +0 -5
- package/dist/paths.d.ts +0 -117
- package/dist/paths.js +0 -41
- package/dist/routers/factory.d.ts +0 -45
- package/dist/routers/factory.jsx +0 -143
- package/dist/routers/history.d.ts +0 -24
- package/dist/routers/history.js +0 -180
- package/dist/server.d.ts +0 -75
- package/dist/server.js +0 -264
package/dist/index.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "./routers/index.js";
|
|
2
|
+
export * from "./components.jsx";
|
|
2
3
|
export * from "./lifecycle.js";
|
|
3
|
-
export { useHref, useIsRouting,
|
|
4
|
+
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute, RouterContextObj as RouterContext } from "./routing.js";
|
|
4
5
|
export { mergeSearchString as _mergeSearchString } from "./utils.js";
|
|
5
|
-
export { int } from "./paths.js";
|
|
6
6
|
export * from "./data/index.js";
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -1,30 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BeforeLeaveLifecycle, LocationChange } from "./types.js";
|
|
2
2
|
export declare function createBeforeLeave(): BeforeLeaveLifecycle;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* - from (*Location*): current location (before change).
|
|
8
|
-
* - to (*string | number*): path passed to `navigate`.
|
|
9
|
-
* - options (*NavigateOptions*): options passed to navigate.
|
|
10
|
-
* - preventDefault (*function*): call to block the route change.
|
|
11
|
-
* - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
|
|
12
|
-
* - retry (*function*, force?: boolean ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```js
|
|
16
|
-
* useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
|
17
|
-
* if (form.isDirty && !e.defaultPrevented) {
|
|
18
|
-
* // preventDefault to block immediately and prompt user async
|
|
19
|
-
* e.preventDefault();
|
|
20
|
-
* setTimeout(() => {
|
|
21
|
-
* if (window.confirm("Discard unsaved changes - are you sure?")) {
|
|
22
|
-
* // user wants to proceed anyway so retry with force=true
|
|
23
|
-
* e.retry(true);
|
|
24
|
-
* }
|
|
25
|
-
* }, 100);
|
|
26
|
-
* }
|
|
27
|
-
* });
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
|
|
3
|
+
export declare function saveCurrentDepth(): void;
|
|
4
|
+
export declare function keepDepth(state: any): any;
|
|
5
|
+
export declare function notifyIfNotBlocked(notify: (value?: string | LocationChange) => void, block: (delta: number | null) => boolean): () => void;
|
package/dist/lifecycle.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useLocation, useNavigate, useRouter } from "./routing.js";
|
|
1
|
+
import { isServer } from "solid-js/web";
|
|
3
2
|
export function createBeforeLeave() {
|
|
4
3
|
let listeners = new Set();
|
|
5
4
|
function subscribe(listener) {
|
|
@@ -39,41 +38,39 @@ export function createBeforeLeave() {
|
|
|
39
38
|
confirm
|
|
40
39
|
};
|
|
41
40
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
onCleanup(s);
|
|
79
|
-
};
|
|
41
|
+
// The following supports browser initiated blocking (eg back/forward)
|
|
42
|
+
let depth;
|
|
43
|
+
export function saveCurrentDepth() {
|
|
44
|
+
if (!window.history.state || window.history.state._depth == null) {
|
|
45
|
+
window.history.replaceState({ ...window.history.state, _depth: window.history.length - 1 }, "");
|
|
46
|
+
}
|
|
47
|
+
depth = window.history.state._depth;
|
|
48
|
+
}
|
|
49
|
+
if (!isServer) {
|
|
50
|
+
saveCurrentDepth();
|
|
51
|
+
}
|
|
52
|
+
export function keepDepth(state) {
|
|
53
|
+
return {
|
|
54
|
+
...state,
|
|
55
|
+
_depth: window.history.state && window.history.state._depth
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function notifyIfNotBlocked(notify, block) {
|
|
59
|
+
let ignore = false;
|
|
60
|
+
return () => {
|
|
61
|
+
const prevDepth = depth;
|
|
62
|
+
saveCurrentDepth();
|
|
63
|
+
const delta = prevDepth == null ? null : depth - prevDepth;
|
|
64
|
+
if (ignore) {
|
|
65
|
+
ignore = false;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (delta && block(delta)) {
|
|
69
|
+
ignore = true;
|
|
70
|
+
window.history.go(-delta);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
notify();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { JSX } from "solid-js";
|
|
2
|
+
import type { BaseRouterProps } from "./components.jsx";
|
|
3
|
+
export declare function hashParser(str: string): string;
|
|
4
|
+
export type HashRouterProps = BaseRouterProps & {
|
|
5
|
+
actionBase?: string;
|
|
6
|
+
explicitLinks?: boolean;
|
|
7
|
+
preload?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function HashRouter(props: HashRouterProps): JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { setupNativeEvents } from "../data/events.js";
|
|
2
|
+
import { createRouter, scrollToHash, bindEvent } from "./createRouter.js";
|
|
3
|
+
import { createBeforeLeave, keepDepth, notifyIfNotBlocked, saveCurrentDepth } from "../lifecycle.js";
|
|
4
|
+
export function hashParser(str) {
|
|
5
|
+
const to = str.replace(/^.*?#/, "");
|
|
6
|
+
// Hash-only hrefs like `#foo` from plain anchors will come in as `/#foo` whereas a link to
|
|
7
|
+
// `/foo` will be `/#/foo`. Check if the to starts with a `/` and if not append it as a hash
|
|
8
|
+
// to the current path so we can handle these in-page anchors correctly.
|
|
9
|
+
if (!to.startsWith("/")) {
|
|
10
|
+
const [, path = "/"] = window.location.hash.split("#", 2);
|
|
11
|
+
return `${path}#${to}`;
|
|
12
|
+
}
|
|
13
|
+
return to;
|
|
14
|
+
}
|
|
15
|
+
export function HashRouter(props) {
|
|
16
|
+
const getSource = () => window.location.hash.slice(1);
|
|
17
|
+
const beforeLeave = createBeforeLeave();
|
|
18
|
+
return createRouter({
|
|
19
|
+
get: getSource,
|
|
20
|
+
set({ value, replace, scroll, state }) {
|
|
21
|
+
if (replace) {
|
|
22
|
+
window.history.replaceState(keepDepth(state), "", "#" + value);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
window.history.pushState(state, "", "#" + value);
|
|
26
|
+
}
|
|
27
|
+
const hashIndex = value.indexOf("#");
|
|
28
|
+
const hash = hashIndex >= 0 ? value.slice(hashIndex + 1) : "";
|
|
29
|
+
scrollToHash(hash, scroll);
|
|
30
|
+
saveCurrentDepth();
|
|
31
|
+
},
|
|
32
|
+
init: notify => bindEvent(window, "hashchange", notifyIfNotBlocked(notify, delta => !beforeLeave.confirm(delta && delta < 0 ? delta : getSource()))),
|
|
33
|
+
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase }),
|
|
34
|
+
utils: {
|
|
35
|
+
go: delta => window.history.go(delta),
|
|
36
|
+
renderPath: path => `#${path}`,
|
|
37
|
+
parsePath: hashParser,
|
|
38
|
+
beforeLeave
|
|
39
|
+
}
|
|
40
|
+
})(props);
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LocationChange } from "../types.js";
|
|
2
|
+
import type { BaseRouterProps } from "./components.jsx";
|
|
3
|
+
import type { JSX } from "solid-js";
|
|
4
|
+
export type MemoryHistory = {
|
|
5
|
+
get: () => string;
|
|
6
|
+
set: (change: LocationChange) => void;
|
|
7
|
+
go: (delta: number) => void;
|
|
8
|
+
listen: (listener: (value: string) => void) => () => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function createMemoryHistory(): {
|
|
11
|
+
get: () => string;
|
|
12
|
+
set: ({ value, scroll, replace }: LocationChange) => void;
|
|
13
|
+
back: () => void;
|
|
14
|
+
forward: () => void;
|
|
15
|
+
go: (n: number) => void;
|
|
16
|
+
listen: (listener: (value: string) => void) => () => void;
|
|
17
|
+
};
|
|
18
|
+
export type MemoryRouterProps = BaseRouterProps & {
|
|
19
|
+
history?: MemoryHistory;
|
|
20
|
+
actionBase?: string;
|
|
21
|
+
explicitLinks?: boolean;
|
|
22
|
+
preload?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export declare function MemoryRouter(props: MemoryRouterProps): JSX.Element;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createRouter, scrollToHash } from "./createRouter.js";
|
|
2
|
+
import { setupNativeEvents } from "../data/events.js";
|
|
3
|
+
export function createMemoryHistory() {
|
|
4
|
+
const entries = ["/"];
|
|
5
|
+
let index = 0;
|
|
6
|
+
const listeners = [];
|
|
7
|
+
const go = (n) => {
|
|
8
|
+
// https://github.com/remix-run/react-router/blob/682810ca929d0e3c64a76f8d6e465196b7a2ac58/packages/router/history.ts#L245
|
|
9
|
+
index = Math.max(0, Math.min(index + n, entries.length - 1));
|
|
10
|
+
const value = entries[index];
|
|
11
|
+
listeners.forEach(listener => listener(value));
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
get: () => entries[index],
|
|
15
|
+
set: ({ value, scroll, replace }) => {
|
|
16
|
+
if (replace) {
|
|
17
|
+
entries[index] = value;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
entries.splice(index + 1, entries.length - index, value);
|
|
21
|
+
index++;
|
|
22
|
+
}
|
|
23
|
+
listeners.forEach(listener => listener(value));
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
if (scroll) {
|
|
26
|
+
scrollToHash(value.split("#")[1] || "", true);
|
|
27
|
+
}
|
|
28
|
+
}, 0);
|
|
29
|
+
},
|
|
30
|
+
back: () => {
|
|
31
|
+
go(-1);
|
|
32
|
+
},
|
|
33
|
+
forward: () => {
|
|
34
|
+
go(1);
|
|
35
|
+
},
|
|
36
|
+
go,
|
|
37
|
+
listen: (listener) => {
|
|
38
|
+
listeners.push(listener);
|
|
39
|
+
return () => {
|
|
40
|
+
const index = listeners.indexOf(listener);
|
|
41
|
+
listeners.splice(index, 1);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function MemoryRouter(props) {
|
|
47
|
+
const memoryHistory = props.history || createMemoryHistory();
|
|
48
|
+
return createRouter({
|
|
49
|
+
get: memoryHistory.get,
|
|
50
|
+
set: memoryHistory.set,
|
|
51
|
+
init: memoryHistory.listen,
|
|
52
|
+
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase }),
|
|
53
|
+
utils: {
|
|
54
|
+
go: memoryHistory.go
|
|
55
|
+
}
|
|
56
|
+
})(props);
|
|
57
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BaseRouterProps } from "./components.jsx";
|
|
2
|
+
import type { JSX } from "solid-js";
|
|
3
|
+
export type RouterProps = BaseRouterProps & {
|
|
4
|
+
url?: string;
|
|
5
|
+
actionBase?: string;
|
|
6
|
+
explicitLinks?: boolean;
|
|
7
|
+
preload?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Opt-in explicit scroll restoration for back/forward navigation (read
|
|
10
|
+
* once, not reactive). Sets `history.scrollRestoration = "manual"` and
|
|
11
|
+
* restores saved positions after the navigation settles, replacing the
|
|
12
|
+
* browser heuristic that loses offsets when the destination route forces
|
|
13
|
+
* a layout while rendering (#577).
|
|
14
|
+
*/
|
|
15
|
+
scrollRestoration?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare function Router(props: RouterProps): JSX.Element;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isServer } from "solid-js/web";
|
|
2
|
+
import { createRouter, scrollToHash, bindEvent } from "./createRouter.js";
|
|
3
|
+
import { StaticRouter } from "./StaticRouter.js";
|
|
4
|
+
import { setupNativeEvents } from "../data/events.js";
|
|
5
|
+
import { createBeforeLeave, keepDepth, notifyIfNotBlocked, saveCurrentDepth } from "../lifecycle.js";
|
|
6
|
+
import { createScrollRestoration } from "./scrollRestoration.js";
|
|
7
|
+
export function Router(props) {
|
|
8
|
+
if (isServer)
|
|
9
|
+
return StaticRouter(props);
|
|
10
|
+
const restoration = props.scrollRestoration ? createScrollRestoration() : undefined;
|
|
11
|
+
const getSource = () => {
|
|
12
|
+
const url = window.location.pathname + window.location.search;
|
|
13
|
+
const state = window.history.state && window.history.state._depth && Object.keys(window.history.state).length === 1 ? undefined : window.history.state;
|
|
14
|
+
return {
|
|
15
|
+
value: url + window.location.hash,
|
|
16
|
+
state
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const beforeLeave = createBeforeLeave();
|
|
20
|
+
return createRouter({
|
|
21
|
+
get: getSource,
|
|
22
|
+
set({ value, replace, scroll, state }) {
|
|
23
|
+
if (replace) {
|
|
24
|
+
window.history.replaceState(keepDepth(state), "", value);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
window.history.pushState(state, "", value);
|
|
28
|
+
}
|
|
29
|
+
scrollToHash(decodeURIComponent(window.location.hash.slice(1)), scroll);
|
|
30
|
+
saveCurrentDepth();
|
|
31
|
+
restoration && !replace && restoration.onPush();
|
|
32
|
+
},
|
|
33
|
+
init: notify => {
|
|
34
|
+
const handler = notifyIfNotBlocked(notify, delta => {
|
|
35
|
+
if (delta) {
|
|
36
|
+
return !beforeLeave.confirm(delta);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const s = getSource();
|
|
40
|
+
return !beforeLeave.confirm(s.value, { state: s.state });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return bindEvent(window, "popstate", restoration
|
|
44
|
+
? () => {
|
|
45
|
+
restoration.onPop();
|
|
46
|
+
handler();
|
|
47
|
+
}
|
|
48
|
+
: handler);
|
|
49
|
+
},
|
|
50
|
+
create: router => {
|
|
51
|
+
setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase, transformUrl: props.transformUrl })(router);
|
|
52
|
+
restoration && restoration.create(router);
|
|
53
|
+
},
|
|
54
|
+
utils: {
|
|
55
|
+
go: delta => window.history.go(delta),
|
|
56
|
+
beforeLeave
|
|
57
|
+
}
|
|
58
|
+
})(props);
|
|
59
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getRequestEvent } from "solid-js/web";
|
|
2
|
+
import { createRouterComponent } from "./components.jsx";
|
|
3
|
+
function getPath(url) {
|
|
4
|
+
const u = new URL(url);
|
|
5
|
+
return u.pathname + u.search;
|
|
6
|
+
}
|
|
7
|
+
export function StaticRouter(props) {
|
|
8
|
+
let e;
|
|
9
|
+
const obj = {
|
|
10
|
+
value: props.url || ((e = getRequestEvent()) && getPath(e.request.url)) || "",
|
|
11
|
+
};
|
|
12
|
+
return createRouterComponent({
|
|
13
|
+
signal: [() => obj, next => Object.assign(obj, next)]
|
|
14
|
+
})(props);
|
|
15
|
+
}
|
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
import type { Component } from "solid-js";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import type { Component, JSX } from "solid-js";
|
|
2
|
+
import type { MatchFilters, RouteDefinition, RoutePreloadFunc, RouterIntegration, RouteSectionComponent, RouteSectionProps } from "../types.js";
|
|
3
|
+
export type BaseRouterProps = {
|
|
4
|
+
base?: string;
|
|
5
|
+
/**
|
|
6
|
+
* A component that wraps the content of every route.
|
|
7
|
+
*/
|
|
6
8
|
root?: Component<RouteSectionProps>;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
9
|
+
rootPreload?: RoutePreloadFunc;
|
|
10
|
+
singleFlight?: boolean;
|
|
11
|
+
children?: JSX.Element | RouteDefinition | RouteDefinition[];
|
|
12
|
+
transformUrl?: (url: string) => string;
|
|
13
|
+
/** @deprecated use rootPreload */
|
|
14
|
+
rootLoad?: RoutePreloadFunc;
|
|
15
|
+
};
|
|
16
|
+
export declare const createRouterComponent: (router: RouterIntegration) => (props: BaseRouterProps) => JSX.Element;
|
|
17
|
+
export type RouteProps<S extends string, T = unknown> = {
|
|
18
|
+
path?: S | S[];
|
|
19
|
+
children?: JSX.Element;
|
|
20
|
+
preload?: RoutePreloadFunc<T>;
|
|
21
|
+
matchFilters?: MatchFilters<S>;
|
|
22
|
+
component?: RouteSectionComponent<T>;
|
|
23
|
+
info?: Record<string, any>;
|
|
24
|
+
/** @deprecated use preload */
|
|
25
|
+
load?: RoutePreloadFunc<T>;
|
|
26
|
+
};
|
|
27
|
+
export declare const Route: <S extends string, T = unknown>(props: RouteProps<S, T>) => JSX.Element;
|
|
@@ -1,72 +1,67 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
|
-
import { createMemo, createRoot, getOwner, onCleanup,
|
|
3
|
-
import { getRequestEvent, isServer } from "
|
|
4
|
-
import { createRouteContext, getIntent, getRouteMatches,
|
|
5
|
-
export
|
|
2
|
+
import { children, createMemo, createRoot, getOwner, mergeProps, on, onCleanup, Show, untrack } from "solid-js";
|
|
3
|
+
import { getRequestEvent, isServer } from "solid-js/web";
|
|
4
|
+
import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
|
|
5
|
+
export const createRouterComponent = (router) => (props) => {
|
|
6
|
+
const { base } = props;
|
|
7
|
+
const routeDefs = children(() => props.children);
|
|
8
|
+
const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
|
|
9
|
+
let context;
|
|
10
|
+
const routerState = createRouterContext(router, branches, () => context, {
|
|
11
|
+
base,
|
|
12
|
+
singleFlight: props.singleFlight,
|
|
13
|
+
transformUrl: props.transformUrl,
|
|
14
|
+
});
|
|
15
|
+
router.create && router.create(routerState);
|
|
16
|
+
return (<RouterContextObj.Provider value={routerState}>
|
|
17
|
+
<Root routerState={routerState} root={props.root} preload={props.rootPreload || props.rootLoad}>
|
|
18
|
+
{(context = getOwner()) && null}
|
|
19
|
+
<Routes routerState={routerState} branches={branches()}/>
|
|
20
|
+
</Root>
|
|
21
|
+
</RouterContextObj.Provider>);
|
|
22
|
+
};
|
|
23
|
+
function Root(props) {
|
|
6
24
|
const location = props.routerState.location;
|
|
7
25
|
const params = props.routerState.params;
|
|
8
26
|
const data = createMemo(() => props.preload &&
|
|
9
27
|
untrack(() => {
|
|
10
28
|
setInPreloadFn(true);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
finally {
|
|
15
|
-
setInPreloadFn(false);
|
|
16
|
-
}
|
|
29
|
+
props.preload({ params, location, intent: getIntent() || "initial" });
|
|
30
|
+
setInPreloadFn(false);
|
|
17
31
|
}));
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
return props.children;
|
|
32
|
+
return (<Show when={props.root} keyed fallback={props.children}>
|
|
33
|
+
{Root => (<Root params={params} location={location} data={data()}>
|
|
34
|
+
{props.children}
|
|
35
|
+
</Root>)}
|
|
36
|
+
</Show>);
|
|
25
37
|
}
|
|
26
|
-
|
|
38
|
+
function Routes(props) {
|
|
27
39
|
if (isServer) {
|
|
28
40
|
const e = getRequestEvent();
|
|
29
41
|
if (e && e.router && e.router.dataOnly) {
|
|
30
|
-
dataOnly(e, props.routerState, props.branches
|
|
42
|
+
dataOnly(e, props.routerState, props.branches);
|
|
31
43
|
return;
|
|
32
44
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// frameworks read this after the render settles.
|
|
37
|
-
Object.defineProperty((e.router || (e.router = {})), "matches", {
|
|
38
|
-
configurable: true,
|
|
39
|
-
enumerable: true,
|
|
40
|
-
get: () => props.routerState.matches().map(({ route, path, params }) => ({
|
|
45
|
+
e &&
|
|
46
|
+
((e.router || (e.router = {})).matches ||
|
|
47
|
+
(e.router.matches = props.routerState.matches().map(({ route, path, params }) => ({
|
|
41
48
|
path: route.originalPath,
|
|
42
49
|
pattern: route.pattern,
|
|
43
50
|
match: path,
|
|
44
51
|
params,
|
|
45
52
|
info: route.info
|
|
46
|
-
}))
|
|
47
|
-
});
|
|
48
|
-
}
|
|
53
|
+
}))));
|
|
49
54
|
}
|
|
50
55
|
const disposers = [];
|
|
51
56
|
let root;
|
|
52
|
-
let prevMatches;
|
|
53
57
|
// dispose the detached per-route roots when this component unmounts, otherwise
|
|
54
58
|
// they stay subscribed to `matches` and crash on a later navigation (#451)
|
|
55
59
|
onCleanup(() => disposers.forEach(dispose => dispose()));
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// computation (which disposes its children every time it re-runs).
|
|
59
|
-
const owner = getOwner();
|
|
60
|
-
const routeStates = createMemo((prev) => {
|
|
61
|
-
// While a lazy subtree resolves, `matches()` is not ready and this
|
|
62
|
-
// computation parks with it — no route contexts are created against
|
|
63
|
-
// placeholder matches.
|
|
64
|
-
const nextMatches = props.routerState.matches();
|
|
65
|
-
const previousMatches = prevMatches;
|
|
66
|
-
let equal = previousMatches && nextMatches.length === previousMatches.length;
|
|
60
|
+
const routeStates = createMemo(on(props.routerState.matches, (nextMatches, prevMatches, prev) => {
|
|
61
|
+
let equal = prevMatches && nextMatches.length === prevMatches.length;
|
|
67
62
|
const next = [];
|
|
68
63
|
for (let i = 0, len = nextMatches.length; i < len; i++) {
|
|
69
|
-
const prevMatch =
|
|
64
|
+
const prevMatch = prevMatches && prevMatches[i];
|
|
70
65
|
const nextMatch = nextMatches[i];
|
|
71
66
|
if (prev && prevMatch && nextMatch.route.key === prevMatch.route.key) {
|
|
72
67
|
next[i] = prev[i];
|
|
@@ -76,51 +71,42 @@ export function Routes(props) {
|
|
|
76
71
|
if (disposers[i]) {
|
|
77
72
|
disposers[i]();
|
|
78
73
|
}
|
|
79
|
-
|
|
74
|
+
createRoot(dispose => {
|
|
80
75
|
disposers[i] = dispose;
|
|
81
|
-
|
|
82
|
-
// Retain the last matches in which this route participated so
|
|
83
|
-
// that its components and preloads never observe another
|
|
84
|
-
// route's params/path while this route is being torn down.
|
|
85
|
-
const matchesAtLevel = createMemo((prev) => {
|
|
76
|
+
next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => {
|
|
86
77
|
const routeMatches = props.routerState.matches();
|
|
87
|
-
|
|
88
|
-
return m && m.route.key === routeKey ? routeMatches : prev || nextMatches;
|
|
78
|
+
return routeMatches[i] ?? routeMatches[0];
|
|
89
79
|
});
|
|
90
|
-
|
|
91
|
-
}));
|
|
80
|
+
});
|
|
92
81
|
}
|
|
93
82
|
}
|
|
94
83
|
disposers.splice(nextMatches.length).forEach(dispose => dispose());
|
|
95
84
|
if (prev && equal) {
|
|
96
|
-
prevMatches = nextMatches;
|
|
97
85
|
return prev;
|
|
98
86
|
}
|
|
99
87
|
root = next[0];
|
|
100
|
-
prevMatches = nextMatches;
|
|
101
88
|
return next;
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return <>{outlet()}</>;
|
|
89
|
+
}));
|
|
90
|
+
return createOutlet(() => routeStates() && root)();
|
|
105
91
|
}
|
|
106
92
|
const createOutlet = (child) => {
|
|
107
|
-
return () => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
93
|
+
return () => (<Show when={child()} keyed>
|
|
94
|
+
{child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
|
|
95
|
+
</Show>);
|
|
96
|
+
};
|
|
97
|
+
export const Route = (props) => {
|
|
98
|
+
const childRoutes = children(() => props.children);
|
|
99
|
+
return mergeProps(props, {
|
|
100
|
+
get children() {
|
|
101
|
+
return childRoutes();
|
|
111
102
|
}
|
|
112
|
-
|
|
113
|
-
};
|
|
103
|
+
});
|
|
114
104
|
};
|
|
115
105
|
// for data only mode with single flight mutations
|
|
116
106
|
function dataOnly(event, routerState, branches) {
|
|
117
107
|
const url = new URL(event.request.url);
|
|
118
108
|
const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
|
|
119
109
|
const matches = getRouteMatches(branches, url.pathname);
|
|
120
|
-
// This pass is synchronous — an unresolved lazy subtree can only be
|
|
121
|
-
// kicked, not awaited (the flight collector's own runner awaits; see
|
|
122
|
-
// src/server.ts). Best effort so a subsequent pass sees the table.
|
|
123
|
-
unresolvedLazyMatches([...prevMatches, ...matches]).forEach(resolveLazySubtree);
|
|
124
110
|
for (let match = 0; match < matches.length; match++) {
|
|
125
111
|
if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
|
|
126
112
|
event.router.dataOnly = true;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LocationChange, RouterContext, RouterUtils } from "../types.js";
|
|
2
|
+
export declare function createRouter(config: {
|
|
3
|
+
get: () => string | LocationChange;
|
|
4
|
+
set: (next: LocationChange) => void;
|
|
5
|
+
init?: (notify: (value?: string | LocationChange) => void) => () => void;
|
|
6
|
+
create?: (router: RouterContext) => void;
|
|
7
|
+
utils?: Partial<RouterUtils>;
|
|
8
|
+
}): (props: import("./components.jsx").BaseRouterProps) => import("solid-js").JSX.Element;
|
|
9
|
+
export declare function bindEvent(target: EventTarget, type: string, handler: EventListener): () => void;
|
|
10
|
+
export declare function scrollToHash(hash: string, fallbackTop?: boolean): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createSignal, onCleanup, sharedConfig } from "solid-js";
|
|
2
|
+
import { createRouterComponent } from "./components.jsx";
|
|
3
|
+
function intercept([value, setValue], get, set) {
|
|
4
|
+
return [get ? () => get(value()) : value, set ? (v) => setValue(set(v)) : setValue];
|
|
5
|
+
}
|
|
6
|
+
export function createRouter(config) {
|
|
7
|
+
let ignore = false;
|
|
8
|
+
const wrap = (value) => (typeof value === "string" ? { value } : value);
|
|
9
|
+
const signal = intercept(createSignal(wrap(config.get()), {
|
|
10
|
+
equals: (a, b) => a.value === b.value && a.state === b.state
|
|
11
|
+
}), undefined, next => {
|
|
12
|
+
!ignore && config.set(next);
|
|
13
|
+
if (sharedConfig.registry && !sharedConfig.done)
|
|
14
|
+
sharedConfig.done = true;
|
|
15
|
+
return next;
|
|
16
|
+
});
|
|
17
|
+
config.init &&
|
|
18
|
+
onCleanup(config.init((value = config.get()) => {
|
|
19
|
+
ignore = true;
|
|
20
|
+
signal[1](wrap(value));
|
|
21
|
+
ignore = false;
|
|
22
|
+
}));
|
|
23
|
+
return createRouterComponent({
|
|
24
|
+
signal,
|
|
25
|
+
create: config.create,
|
|
26
|
+
utils: config.utils
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function bindEvent(target, type, handler) {
|
|
30
|
+
target.addEventListener(type, handler);
|
|
31
|
+
return () => target.removeEventListener(type, handler);
|
|
32
|
+
}
|
|
33
|
+
export function scrollToHash(hash, fallbackTop) {
|
|
34
|
+
const el = hash && document.getElementById(hash);
|
|
35
|
+
if (el) {
|
|
36
|
+
el.scrollIntoView();
|
|
37
|
+
}
|
|
38
|
+
else if (fallbackTop) {
|
|
39
|
+
window.scrollTo(0, 0);
|
|
40
|
+
}
|
|
41
|
+
}
|