@solidjs/router 1.0.0-next.9 → 2.0.0-next.12
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 +100 -7
- package/dist/data/action.js +34 -25
- package/dist/fs.d.ts +94 -0
- package/dist/fs.js +51 -0
- package/dist/index.d.ts +20 -1
- package/dist/index.js +233 -116
- package/dist/routers/factory.d.ts +81 -5
- package/dist/routers/factory.jsx +25 -14
- package/dist/routers/history.d.ts +2 -0
- package/dist/routers/history.js +2 -2
- package/dist/routers/index.d.ts +3 -2
- package/dist/routers/index.js +1 -1
- package/dist/routers/scrollRestoration.d.ts +41 -0
- package/dist/routers/scrollRestoration.js +134 -0
- package/dist/routing.d.ts +11 -3
- package/dist/routing.js +25 -8
- package/dist/server.d.ts +7 -35
- package/dist/server.js +35 -143
- package/dist/types.d.ts +66 -12
- package/package.json +8 -7
- 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/routers/factory.jsx
CHANGED
|
@@ -8,6 +8,7 @@ import { createBranches, createRouterContext, getRouteMatches, registerFlightRou
|
|
|
8
8
|
import { mockBase } from "../utils.js";
|
|
9
9
|
import { Root, Routes } from "./components.jsx";
|
|
10
10
|
import { browserHistory } from "./history.js";
|
|
11
|
+
import { createScrollRestoration, withScrollRestoration } from "./scrollRestoration.js";
|
|
11
12
|
/**
|
|
12
13
|
* Identity helper that preserves literal types when the route tree is
|
|
13
14
|
* declared as a separate variable. `createRouter` infers literally from
|
|
@@ -18,6 +19,9 @@ import { browserHistory } from "./history.js";
|
|
|
18
19
|
export function defineRoutes(routes) {
|
|
19
20
|
return routes;
|
|
20
21
|
}
|
|
22
|
+
export function defineRoute(route) {
|
|
23
|
+
return route;
|
|
24
|
+
}
|
|
21
25
|
/** Wraps a history adapter in the integration signal the router core consumes. Must run under a reactive owner. */
|
|
22
26
|
function createIntegration(history) {
|
|
23
27
|
let ignore = false;
|
|
@@ -45,22 +49,22 @@ function createIntegration(history) {
|
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* Server default: a static view of the request URL — no signal machinery, a
|
|
48
|
-
* server render never navigates.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
52
|
+
* server render never navigates. The request event (when the harness scopes
|
|
53
|
+
* one) wins; the provider's `url` prop is the fallback for renders outside a
|
|
54
|
+
* request scope (SSG scripts, server-side tests, runtimes without
|
|
55
|
+
* `node:async_hooks`). History adapters are a client navigation concern and
|
|
56
|
+
* play no part in locating a server render.
|
|
51
57
|
*/
|
|
52
|
-
function staticIntegration(
|
|
58
|
+
function staticIntegration(url, utils) {
|
|
53
59
|
const e = getRequestEvent();
|
|
60
|
+
const source = e ? e.request.url : url;
|
|
54
61
|
let value = "";
|
|
55
|
-
if (
|
|
56
|
-
const
|
|
57
|
-
value =
|
|
58
|
-
}
|
|
59
|
-
else if (history) {
|
|
60
|
-
value = history.get();
|
|
62
|
+
if (source) {
|
|
63
|
+
const u = new URL(source, mockBase);
|
|
64
|
+
value = u.pathname + u.search;
|
|
61
65
|
}
|
|
62
|
-
const obj =
|
|
63
|
-
return { signal: [() => obj, next => Object.assign(obj, next)], utils
|
|
66
|
+
const obj = { value };
|
|
67
|
+
return { signal: [() => obj, next => Object.assign(obj, next)], utils };
|
|
64
68
|
}
|
|
65
69
|
export function createRouter(config) {
|
|
66
70
|
const basePath = config.base || "";
|
|
@@ -90,9 +94,15 @@ export function createRouter(config) {
|
|
|
90
94
|
"Compose route trees in one createRouter config instead.");
|
|
91
95
|
}
|
|
92
96
|
const root = untrack(() => props.children);
|
|
97
|
+
let restoration;
|
|
98
|
+
let history = config.history;
|
|
99
|
+
if (!isServer && (config.scrollRestoration ?? !history)) {
|
|
100
|
+
restoration = createScrollRestoration();
|
|
101
|
+
history = withScrollRestoration(history || browserHistory(), restoration);
|
|
102
|
+
}
|
|
93
103
|
const integration = isServer
|
|
94
|
-
? staticIntegration(config.history)
|
|
95
|
-
: createIntegration(
|
|
104
|
+
? staticIntegration(props.url, config.history && config.history.utils)
|
|
105
|
+
: createIntegration(history || browserHistory());
|
|
96
106
|
let context;
|
|
97
107
|
const routerState = createRouterContext(integration, branches, () => context, {
|
|
98
108
|
base: basePath,
|
|
@@ -109,6 +119,7 @@ export function createRouter(config) {
|
|
|
109
119
|
setupLinkClaims(routerState, config.explicitLinks);
|
|
110
120
|
if (routerState.singleFlight)
|
|
111
121
|
onCleanup(registerFlightRouter(routerState));
|
|
122
|
+
restoration && restoration.create(routerState);
|
|
112
123
|
}
|
|
113
124
|
return (<RouterContextObj value={routerState}>
|
|
114
125
|
<Root routerState={routerState} root={root} preload={config.preload}>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { LocationChange, RouterUtils } from "../types.js";
|
|
2
|
+
export declare function bindEvent(target: EventTarget, type: string, handler: EventListener): () => void;
|
|
3
|
+
export declare function saveCurrentDepth(): void;
|
|
2
4
|
/**
|
|
3
5
|
* A history adapter: the source of truth for the current URL and how
|
|
4
6
|
* navigations write back to it. Adapters are plain imported values so
|
package/dist/routers/history.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isServer } from "@solidjs/web";
|
|
2
|
-
function bindEvent(target, type, handler) {
|
|
2
|
+
export function bindEvent(target, type, handler) {
|
|
3
3
|
target.addEventListener(type, handler);
|
|
4
4
|
return () => target.removeEventListener(type, handler);
|
|
5
5
|
}
|
|
@@ -8,7 +8,7 @@ function bindEvent(target, type, handler) {
|
|
|
8
8
|
// so blocking stays exact no matter when the first guard subscribes, while the
|
|
9
9
|
// guard machinery itself lives behind the lazy `beforeLeave` slot.
|
|
10
10
|
let depth;
|
|
11
|
-
function saveCurrentDepth() {
|
|
11
|
+
export function saveCurrentDepth() {
|
|
12
12
|
if (!window.history.state || window.history.state._depth == null) {
|
|
13
13
|
window.history.replaceState({ ...window.history.state, _depth: window.history.length - 1 }, "");
|
|
14
14
|
}
|
package/dist/routers/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { createRouter, defineRoutes } from "./factory.jsx";
|
|
2
|
-
export type {
|
|
1
|
+
export { createRouter, defineRoute, defineRoutes } from "./factory.jsx";
|
|
2
|
+
export type { DefinedRoute } from "./factory.jsx";
|
|
3
|
+
export type { RouterConfig, RouterInstance, RouterProps } from "./factory.jsx";
|
|
3
4
|
export { browserHistory, hashHistory, memoryHistory } from "./history.js";
|
|
4
5
|
export type { RouterHistory, MemoryHistoryAdapter } from "./history.js";
|
package/dist/routers/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createRouter, defineRoutes } from "./factory.jsx";
|
|
1
|
+
export { createRouter, defineRoute, defineRoutes } from "./factory.jsx";
|
|
2
2
|
export { browserHistory, hashHistory, memoryHistory } from "./history.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RouterContext } from "../types.js";
|
|
2
|
+
import type { RouterHistory } from "./history.js";
|
|
3
|
+
/**
|
|
4
|
+
* Explicit scroll restoration for back/forward navigation. The browser's
|
|
5
|
+
* native same-document heuristic is unreliable for suspense-driven rendering:
|
|
6
|
+
* if the destination route forces a layout while the document is still short,
|
|
7
|
+
* the saved offset for the previous entry is clamped and lost (#577).
|
|
8
|
+
*
|
|
9
|
+
* Positions are captured continuously from the scroll event, keyed by the
|
|
10
|
+
* `_depth` the router already stamps on every history entry — capturing at
|
|
11
|
+
* scroll time (rather than at exit) stays correct through `useBeforeLeave`
|
|
12
|
+
* blocked/reverted traversals. The map persists to sessionStorage on pagehide
|
|
13
|
+
* so restoration survives reloads, which `scrollRestoration = "manual"`
|
|
14
|
+
* otherwise disables.
|
|
15
|
+
*
|
|
16
|
+
* Restoration is a single scroll once routing settles — the same strategy
|
|
17
|
+
* SvelteKit, TanStack Router and React Router use. Settling after the
|
|
18
|
+
* transition commits is what makes the offset reachable; chasing a still-
|
|
19
|
+
* growing document afterwards (a ResizeObserver re-asserting the offset as
|
|
20
|
+
* content arrives) was tried and removed: no peer router does it, an
|
|
21
|
+
* unbounded observer re-clamps the viewport to the bottom when the target is
|
|
22
|
+
* never reachable (a list that is genuinely shorter now), and scroll-induced
|
|
23
|
+
* layout changes can feed it back into itself. Content that commits after the
|
|
24
|
+
* transition settles — an image without reserved space, a boundary below the
|
|
25
|
+
* fold — keeps whatever offset the document can hold.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createScrollRestoration(): {
|
|
28
|
+
/** When the adapter notifies a traversal: mark the target for restoration. */
|
|
29
|
+
onPop(): void;
|
|
30
|
+
/** After a push: forward entries died, and this depth may be reused. */
|
|
31
|
+
onPush(): void;
|
|
32
|
+
create(router: RouterContext): void;
|
|
33
|
+
};
|
|
34
|
+
export type ScrollRestoration = ReturnType<typeof createScrollRestoration>;
|
|
35
|
+
/**
|
|
36
|
+
* Threads restoration through a history adapter: pushes prune dead forward
|
|
37
|
+
* entries, and adapter notifications (unblocked pops) mark the traversal
|
|
38
|
+
* target. Notification runs after the adapter's depth bookkeeping, so the
|
|
39
|
+
* marked depth is the entry being restored to.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withScrollRestoration(history: RouterHistory, restoration: ScrollRestoration): RouterHistory;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createEffect, onCleanup } from "solid-js";
|
|
2
|
+
import { bindEvent, saveCurrentDepth } from "./history.js";
|
|
3
|
+
const STORAGE_KEY = "solid-router:scroll";
|
|
4
|
+
/**
|
|
5
|
+
* Explicit scroll restoration for back/forward navigation. The browser's
|
|
6
|
+
* native same-document heuristic is unreliable for suspense-driven rendering:
|
|
7
|
+
* if the destination route forces a layout while the document is still short,
|
|
8
|
+
* the saved offset for the previous entry is clamped and lost (#577).
|
|
9
|
+
*
|
|
10
|
+
* Positions are captured continuously from the scroll event, keyed by the
|
|
11
|
+
* `_depth` the router already stamps on every history entry — capturing at
|
|
12
|
+
* scroll time (rather than at exit) stays correct through `useBeforeLeave`
|
|
13
|
+
* blocked/reverted traversals. The map persists to sessionStorage on pagehide
|
|
14
|
+
* so restoration survives reloads, which `scrollRestoration = "manual"`
|
|
15
|
+
* otherwise disables.
|
|
16
|
+
*
|
|
17
|
+
* Restoration is a single scroll once routing settles — the same strategy
|
|
18
|
+
* SvelteKit, TanStack Router and React Router use. Settling after the
|
|
19
|
+
* transition commits is what makes the offset reachable; chasing a still-
|
|
20
|
+
* growing document afterwards (a ResizeObserver re-asserting the offset as
|
|
21
|
+
* content arrives) was tried and removed: no peer router does it, an
|
|
22
|
+
* unbounded observer re-clamps the viewport to the bottom when the target is
|
|
23
|
+
* never reachable (a list that is genuinely shorter now), and scroll-induced
|
|
24
|
+
* layout changes can feed it back into itself. Content that commits after the
|
|
25
|
+
* transition settles — an image without reserved space, a boundary below the
|
|
26
|
+
* fold — keeps whatever offset the document can hold.
|
|
27
|
+
*/
|
|
28
|
+
export function createScrollRestoration() {
|
|
29
|
+
window.history.scrollRestoration = "manual";
|
|
30
|
+
// the current entry needs its depth stamp for captures to have a key, even
|
|
31
|
+
// if something replaced history.state after the adapter stamped it
|
|
32
|
+
saveCurrentDepth();
|
|
33
|
+
let positions = {};
|
|
34
|
+
try {
|
|
35
|
+
positions = JSON.parse(sessionStorage.getItem(STORAGE_KEY)) || {};
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
38
|
+
const depth = () => window.history.state && window.history.state._depth;
|
|
39
|
+
let programmatic = false;
|
|
40
|
+
let pending;
|
|
41
|
+
const unbind = [
|
|
42
|
+
bindEvent(window, "scroll", () => {
|
|
43
|
+
const d = depth();
|
|
44
|
+
if (d != null)
|
|
45
|
+
positions[d] = window.scrollY;
|
|
46
|
+
// the user took over — a pending restore would yank them
|
|
47
|
+
if (!programmatic)
|
|
48
|
+
pending = undefined;
|
|
49
|
+
}),
|
|
50
|
+
bindEvent(window, "pagehide", () => {
|
|
51
|
+
try {
|
|
52
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(positions));
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
})
|
|
56
|
+
];
|
|
57
|
+
const restore = () => {
|
|
58
|
+
if (pending == null)
|
|
59
|
+
return;
|
|
60
|
+
const y = positions[pending];
|
|
61
|
+
pending = undefined;
|
|
62
|
+
if (y == null)
|
|
63
|
+
return;
|
|
64
|
+
// flagged so the resulting scroll event is not mistaken for the user
|
|
65
|
+
// taking over (which cancels a pending restore)
|
|
66
|
+
programmatic = true;
|
|
67
|
+
window.scrollTo(0, y);
|
|
68
|
+
programmatic = false;
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
/** When the adapter notifies a traversal: mark the target for restoration. */
|
|
72
|
+
onPop() {
|
|
73
|
+
pending = depth();
|
|
74
|
+
},
|
|
75
|
+
/** After a push: forward entries died, and this depth may be reused. */
|
|
76
|
+
onPush() {
|
|
77
|
+
const d = depth();
|
|
78
|
+
if (d != null)
|
|
79
|
+
for (const k in positions)
|
|
80
|
+
+k >= d && delete positions[k];
|
|
81
|
+
},
|
|
82
|
+
create(router) {
|
|
83
|
+
// Restore once the traversal has settled: key on the location (a fully
|
|
84
|
+
// synchronous pop commits without isRouting ever flipping) and on
|
|
85
|
+
// isRouting, which reports in-flight transitions — native pops
|
|
86
|
+
// included — and holds the restore until they commit. restore() no-ops
|
|
87
|
+
// unless a traversal marked a target, so push navigations are inert.
|
|
88
|
+
// `transparent` keeps the effect invisible to the hydration id scheme —
|
|
89
|
+
// same reasoning as the link-claims effect (claims.ts): this setup is
|
|
90
|
+
// client-only, so an id-consuming node here has no server counterpart
|
|
91
|
+
// and every hydration id allocated after it shifts by one child slot.
|
|
92
|
+
// The visible failure is any <Loading> content that settled before the
|
|
93
|
+
// shell flush (a cache hit, a preloaded query): its serialized value and
|
|
94
|
+
// inlined markup are keyed under the server's ids, the shifted client
|
|
95
|
+
// misses both, recomputes, and re-renders the route fresh — duplicating
|
|
96
|
+
// the server DOM and leaving it inert.
|
|
97
|
+
createEffect(() => ({
|
|
98
|
+
url: router.location.pathname + router.location.search + router.location.hash,
|
|
99
|
+
routing: router.isRouting()
|
|
100
|
+
}), current => {
|
|
101
|
+
if (!current.routing)
|
|
102
|
+
restore();
|
|
103
|
+
}, { transparent: true });
|
|
104
|
+
onCleanup(() => unbind.forEach(u => u()));
|
|
105
|
+
// reload/back_forward document loads land on an existing entry (a fresh
|
|
106
|
+
// navigation starts a new one and belongs at the top); the effect's
|
|
107
|
+
// initial run performs the restore after first render
|
|
108
|
+
const [nav] = (performance.getEntriesByType &&
|
|
109
|
+
performance.getEntriesByType("navigation"));
|
|
110
|
+
if (nav && nav.type !== "navigate")
|
|
111
|
+
pending = depth();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Threads restoration through a history adapter: pushes prune dead forward
|
|
117
|
+
* entries, and adapter notifications (unblocked pops) mark the traversal
|
|
118
|
+
* target. Notification runs after the adapter's depth bookkeeping, so the
|
|
119
|
+
* marked depth is the entry being restored to.
|
|
120
|
+
*/
|
|
121
|
+
export function withScrollRestoration(history, restoration) {
|
|
122
|
+
return {
|
|
123
|
+
...history,
|
|
124
|
+
set(next) {
|
|
125
|
+
history.set(next);
|
|
126
|
+
next.replace || restoration.onPush();
|
|
127
|
+
},
|
|
128
|
+
init: history.init &&
|
|
129
|
+
(notify => history.init(value => {
|
|
130
|
+
restoration.onPop();
|
|
131
|
+
notify(value);
|
|
132
|
+
}))
|
|
133
|
+
};
|
|
134
|
+
}
|
package/dist/routing.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JSX } from "@solidjs/web";
|
|
2
|
-
import type { Branch, Intent, LazyBoundary, Location, MatchFilters, NavigateOptions, Navigator, Params, RouteDescription, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SearchParams, SetSearchParams, TypedPath, TypedSearchPath } from "./types.js";
|
|
3
|
-
import type { FlashSubmission } from "
|
|
2
|
+
import type { Branch, Intent, LazyBoundary, Location, MatchFilters, NavigateOptions, Navigator, Params, PathMatch, RouteDescription, RouteContext, RouteDefinition, RouteMatch, RouteParams, RouterContext, RouterIntegration, SearchParams, SetSearchParams, TypedPath, TypedSearchPath } from "./types.js";
|
|
3
|
+
import type { FlashSubmission } from "@solidjs/web/server-functions/server";
|
|
4
4
|
/** Consider this API opaque and internal. It is likely to change in the future. */
|
|
5
5
|
export declare const RouterContextObj: import("solid-js").Context<RouterContext>;
|
|
6
6
|
export declare const RouteContextObj: import("solid-js").Context<RouteContext>;
|
|
@@ -62,14 +62,22 @@ export declare const useIsRouting: () => () => boolean;
|
|
|
62
62
|
* `useMatch` takes an accessor that returns the path and creates a `Memo` that returns match information if the current path matches the provided path.
|
|
63
63
|
* Useful for determining if a given path matches the current route.
|
|
64
64
|
*
|
|
65
|
+
* Accepts a pattern string — the match's `params` are typed from it — or a
|
|
66
|
+
* typed path node (a concrete URL, so no params to speak of):
|
|
67
|
+
*
|
|
65
68
|
* @example
|
|
66
69
|
* ```js
|
|
67
70
|
* const match = useMatch(() => props.href);
|
|
68
71
|
*
|
|
69
72
|
* return <div classList={{ active: Boolean(match()) }} />;
|
|
73
|
+
*
|
|
74
|
+
* const section = useMatch(() => "/docs/:page");
|
|
75
|
+
* section()?.params.page; // string
|
|
76
|
+
*
|
|
77
|
+
* const here = useMatch(() => paths.users(2));
|
|
70
78
|
* ```
|
|
71
79
|
*/
|
|
72
|
-
export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S>) =>
|
|
80
|
+
export declare const useMatch: <S extends string | TypedPath>(path: () => S, matchFilters?: MatchFilters<S extends string ? S : string>) => (() => PathMatch<S extends string ? RouteParams<S> : Params> | undefined);
|
|
73
81
|
/**
|
|
74
82
|
* `useRouteMatches` returns an accessor of the router's resolved matches for
|
|
75
83
|
* the current location — the chain of route definitions producing the current
|
package/dist/routing.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { flush, runWithOwner } from "solid-js";
|
|
2
|
-
import { createComponent, createContext, createMemo, createSignal, NotReadyError, untrack, useContext } from "solid-js";
|
|
2
|
+
import { createComponent, createContext, createMemo, createSignal, isPending, NotReadyError, untrack, useContext } from "solid-js";
|
|
3
3
|
import { isServer, getRequestEvent } from "@solidjs/web";
|
|
4
4
|
import { mockBase, comparablePath, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js";
|
|
5
|
-
import { clearFlashCookie, hasFlashCookie } from "
|
|
5
|
+
import { clearFlashCookie, hasFlashCookie } from "@solidjs/web/server-functions";
|
|
6
6
|
const MAX_REDIRECTS = 100;
|
|
7
7
|
/** Consider this API opaque and internal. It is likely to change in the future. */
|
|
8
8
|
export const RouterContextObj = createContext();
|
|
@@ -80,16 +80,26 @@ export const useIsRouting = () => useRouter().isRouting;
|
|
|
80
80
|
* `useMatch` takes an accessor that returns the path and creates a `Memo` that returns match information if the current path matches the provided path.
|
|
81
81
|
* Useful for determining if a given path matches the current route.
|
|
82
82
|
*
|
|
83
|
+
* Accepts a pattern string — the match's `params` are typed from it — or a
|
|
84
|
+
* typed path node (a concrete URL, so no params to speak of):
|
|
85
|
+
*
|
|
83
86
|
* @example
|
|
84
87
|
* ```js
|
|
85
88
|
* const match = useMatch(() => props.href);
|
|
86
89
|
*
|
|
87
90
|
* return <div classList={{ active: Boolean(match()) }} />;
|
|
91
|
+
*
|
|
92
|
+
* const section = useMatch(() => "/docs/:page");
|
|
93
|
+
* section()?.params.page; // string
|
|
94
|
+
*
|
|
95
|
+
* const here = useMatch(() => paths.users(2));
|
|
88
96
|
* ```
|
|
89
97
|
*/
|
|
90
98
|
export const useMatch = (path, matchFilters) => {
|
|
91
99
|
const location = useLocation();
|
|
92
|
-
const matchers = createMemo(() =>
|
|
100
|
+
const matchers = createMemo(() =>
|
|
101
|
+
// `String()` folds typed path nodes to their URL
|
|
102
|
+
expandOptionals(String(path())).map(path => createMatcher(path, undefined, matchFilters)));
|
|
93
103
|
return createMemo(() => {
|
|
94
104
|
for (const matcher of matchers()) {
|
|
95
105
|
const match = matcher(location.pathname);
|
|
@@ -496,7 +506,7 @@ export function provideFlightConsumer(factory) {
|
|
|
496
506
|
/**
|
|
497
507
|
* The flash-cookie codec, provided by the action side (data/action.ts) so
|
|
498
508
|
* the router core never carries it: the core consumes the cookie eagerly
|
|
499
|
-
* per request (detection + one-shot clear via the
|
|
509
|
+
* per request (detection + one-shot clear via the runtime's isomorphic half)
|
|
500
510
|
* but defers decoding to this slot, read when the submissions signal
|
|
501
511
|
* initializes. Actions are created at module scope, so on the server the
|
|
502
512
|
* decoder is always installed before useSubmission can read — and a
|
|
@@ -532,7 +542,7 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
532
542
|
else if (basePath && !initialSource.value) {
|
|
533
543
|
setSource({ value: basePath, replace: true, scroll: false });
|
|
534
544
|
}
|
|
535
|
-
const [
|
|
545
|
+
const [isNavigating, setIsRouting] = createSignal(false, { ownedWrite: true });
|
|
536
546
|
// Navigate override written from event handlers.
|
|
537
547
|
const [navigateTarget, setNavigateTarget] = createSignal(undefined, {
|
|
538
548
|
ownedWrite: true
|
|
@@ -547,7 +557,7 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
547
557
|
// The flash cookie is consumed eagerly: its one-shot clear (Set-Cookie)
|
|
548
558
|
// must be appended before streaming flushes the response headers, and an
|
|
549
559
|
// unread outcome must not haunt a later request's render. Only detection
|
|
550
|
-
// and clearing happen here (the
|
|
560
|
+
// and clearing happen here (the runtime's isomorphic half); the raw header
|
|
551
561
|
// is stashed and decoding waits for the action-provided codec, read when
|
|
552
562
|
// the lazily allocated submissions signal below first initializes.
|
|
553
563
|
let flashCookieHeader;
|
|
@@ -581,6 +591,13 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
581
591
|
throw new NotReadyError(Promise.all(pending.map(resolveLazySubtree)));
|
|
582
592
|
return m;
|
|
583
593
|
});
|
|
594
|
+
// Every write is a transition in Solid 2, so a native history pop forks the
|
|
595
|
+
// source signal exactly like programmatic navigation does. isRouting is
|
|
596
|
+
// therefore derived: the manual flag covers navigateFromRoute's explicit
|
|
597
|
+
// window, and isPending over the location/matches read reports any
|
|
598
|
+
// in-flight fork — including popstate traversals and the lazy-subtree
|
|
599
|
+
// resolution matches() parks on.
|
|
600
|
+
const isRouting = createMemo(() => isNavigating() || isPending(() => (matches(), location.search, location.hash)));
|
|
584
601
|
const buildParams = () => mergeParams(matches());
|
|
585
602
|
const wrapParams = utils.paramsWrapper
|
|
586
603
|
? (getParams) => utils.paramsWrapper(getParams, branches)
|
|
@@ -739,8 +756,8 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
739
756
|
intent = prevIntent;
|
|
740
757
|
}
|
|
741
758
|
// Seeds the initial submission from a no-JS form post: the server
|
|
742
|
-
// function
|
|
743
|
-
// cookie (
|
|
759
|
+
// function runtime redirected back with the outcome in a one-shot flash
|
|
760
|
+
// cookie (its default no-JS convention), consumed eagerly above and
|
|
744
761
|
// decoded here — so the post-redirect SSR renders useSubmission() state
|
|
745
762
|
// exactly as a scripted submission would. An explicitly pre-seeded
|
|
746
763
|
// `event.router.submission` (framework integrations) takes precedence.
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { JSX } from "@solidjs/web";
|
|
2
2
|
import type { CollectFlightDataHook, ServerFunctionOutcome } from "@solidjs/web/server-functions/server";
|
|
3
3
|
import type { RouteDefinition, RoutePreloadFunc } from "./types.js";
|
|
4
|
-
export type { FlashSubmission } from "./data/flash.js";
|
|
5
4
|
export type { CollectFlightDataHook, ServerFunctionOutcome };
|
|
6
5
|
export interface FlightDataCollectorOptions {
|
|
7
6
|
/**
|
|
@@ -19,7 +18,7 @@ export interface FlightDataCollectorOptions {
|
|
|
19
18
|
* render: the merged params of every match and `intent: "initial"`.
|
|
20
19
|
*/
|
|
21
20
|
rootPreload?: RoutePreloadFunc;
|
|
22
|
-
/** The app's base path, for
|
|
21
|
+
/** The app's base path, for compiling the route tree's matchers. */
|
|
23
22
|
base?: string;
|
|
24
23
|
}
|
|
25
24
|
/** A `createRouter` instance carries everything the collector needs. */
|
|
@@ -38,38 +37,11 @@ interface RouterInstanceLike {
|
|
|
38
37
|
* and `preload` are the single source of truth — or an options object for
|
|
39
38
|
* trees not created through the factory.
|
|
40
39
|
*
|
|
41
|
-
* Strategy: rerun the route data for the URL the client will show
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* flight-data consumer.
|
|
40
|
+
* Strategy: rerun the route data for the URL the client will show after
|
|
41
|
+
* the mutation (the outcome's pre-digested `targetUrl`), collecting each
|
|
42
|
+
* `query` result under its cache key, scoped to the outcome's
|
|
43
|
+
* `revalidateKeys` when present (routes newly entered via redirect always
|
|
44
|
+
* collect fully). The returned payload seeds the client router's cache
|
|
45
|
+
* through its registered flight-data consumer.
|
|
48
46
|
*/
|
|
49
47
|
export declare function createFlightDataCollector(options: FlightDataCollectorOptions | RouterInstanceLike): CollectFlightDataHook;
|
|
50
|
-
/**
|
|
51
|
-
* The request headers for the flight-data collection pass: the source
|
|
52
|
-
* request's headers with the mutation's `Set-Cookie` mutations folded into
|
|
53
|
-
* the `Cookie` header, so preloads observe the post-mutation cookie state
|
|
54
|
-
* (deletions honored via Max-Age/Expires).
|
|
55
|
-
*/
|
|
56
|
-
export declare function createSingleFlightHeaders(sourceEvent: {
|
|
57
|
-
request: Request;
|
|
58
|
-
response?: {
|
|
59
|
-
headers?: Headers;
|
|
60
|
-
};
|
|
61
|
-
}): Headers;
|
|
62
|
-
export interface NoJSHandlerOptions {
|
|
63
|
-
/** The app's base path, for resolving redirect `Location`s. */
|
|
64
|
-
base?: string;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Produces the `handleNoJS` implementation for
|
|
68
|
-
* `handleServerFunctionRequest` options: form posts made without the client
|
|
69
|
-
* runtime redirect back to the referring page (or to the result's
|
|
70
|
-
* `Location`) with the outcome riding a one-shot `flash` cookie. The
|
|
71
|
-
* router's SSR initialization reads the cookie into submission state, so
|
|
72
|
-
* `useSubmission()` renders the result exactly as a scripted submission
|
|
73
|
-
* would — progressive enhancement with no app code.
|
|
74
|
-
*/
|
|
75
|
-
export declare function createNoJSHandler(options?: NoJSHandlerOptions): (result: unknown, request: Request, args: unknown[], thrown?: boolean) => Response;
|