@solidjs/router 0.17.0-next.6 → 1.0.0-next.11
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 +526 -716
- package/dist/claims.d.ts +21 -0
- package/dist/claims.js +115 -0
- package/dist/data/action.d.ts +15 -0
- package/dist/data/action.js +165 -25
- package/dist/data/events.d.ts +8 -0
- package/dist/data/events.js +23 -22
- package/dist/data/flash.d.ts +1 -5
- package/dist/data/flash.js +10 -18
- package/dist/data/flashCookie.d.ts +7 -0
- package/dist/data/flashCookie.js +20 -0
- package/dist/data/serverForms.d.ts +1 -0
- package/dist/data/serverForms.js +5 -0
- package/dist/fs.d.ts +53 -0
- package/dist/fs.js +30 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1896 -1156
- package/dist/index.jsx +2 -2
- package/dist/lifecycle.d.ts +29 -4
- package/dist/lifecycle.js +40 -37
- package/dist/paths.d.ts +117 -0
- package/dist/paths.js +41 -0
- package/dist/routers/components.d.ts +10 -21
- package/dist/routers/components.jsx +29 -51
- package/dist/routers/factory.d.ts +66 -0
- package/dist/routers/factory.jsx +151 -0
- package/dist/routers/history.d.ts +26 -0
- package/dist/routers/history.js +180 -0
- package/dist/routers/index.d.ts +4 -11
- package/dist/routers/index.js +2 -6
- package/dist/routers/scrollRestoration.d.ts +41 -0
- package/dist/routers/scrollRestoration.js +134 -0
- package/dist/routing.d.ts +79 -52
- package/dist/routing.js +304 -128
- package/dist/server.d.ts +31 -17
- package/dist/server.js +68 -12
- package/dist/types.d.ts +74 -5
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +2 -0
- package/package.json +8 -7
- package/dist/components.d.ts +0 -31
- package/dist/components.jsx +0 -46
- package/dist/routers/HashRouter.d.ts +0 -9
- package/dist/routers/HashRouter.js +0 -41
- package/dist/routers/MemoryRouter.d.ts +0 -24
- package/dist/routers/MemoryRouter.js +0 -57
- package/dist/routers/Router.d.ts +0 -9
- package/dist/routers/Router.js +0 -45
- package/dist/routers/StaticRouter.d.ts +0 -6
- package/dist/routers/StaticRouter.js +0 -15
- package/dist/routers/createRouter.d.ts +0 -10
- package/dist/routers/createRouter.js +0 -40
package/dist/server.js
CHANGED
|
@@ -18,13 +18,21 @@
|
|
|
18
18
|
// back (303) carrying the outcome in a one-shot flash cookie that the
|
|
19
19
|
// router's SSR initialization reads into submission state.
|
|
20
20
|
import { provideRequestEvent } from "@solidjs/web/storage";
|
|
21
|
-
import { createBranches, getRouteMatches, mergeParams,
|
|
21
|
+
import { createBranches, getRouteMatches, mergeParams, peekLazySubtrees, resolveLazySubtree } from "./routing.js";
|
|
22
22
|
import { extractSearchParams } from "./utils.js";
|
|
23
23
|
import { encodeFlashCookie } from "./data/flash.js";
|
|
24
|
+
// the instance is the provider component, so it (unlike an options object) is a function
|
|
25
|
+
function isRouterInstance(options) {
|
|
26
|
+
return typeof options === "function";
|
|
27
|
+
}
|
|
24
28
|
/**
|
|
25
29
|
* Produces the `collectFlightData` implementation for
|
|
26
30
|
* `configureServerFunctionsServer` (or `handleServerFunctionRequest`
|
|
27
|
-
* options).
|
|
31
|
+
* options). Accepts a `createRouter` instance directly — its routes, base,
|
|
32
|
+
* and `preload` are the single source of truth — or an options object for
|
|
33
|
+
* trees not created through the factory.
|
|
34
|
+
*
|
|
35
|
+
* Strategy: rerun the route data for the URL the client will show
|
|
28
36
|
* after the mutation — the redirect `Location` when the outcome carries
|
|
29
37
|
* one, the referring page otherwise — collecting each `query` result under
|
|
30
38
|
* its cache key, scoped to the outcome's `X-Revalidate` keys when present
|
|
@@ -33,14 +41,23 @@ import { encodeFlashCookie } from "./data/flash.js";
|
|
|
33
41
|
* flight-data consumer.
|
|
34
42
|
*/
|
|
35
43
|
export function createFlightDataCollector(options) {
|
|
36
|
-
const { routes, rootPreload, base = "" } = options
|
|
44
|
+
const { routes, rootPreload, base = "" } = isRouterInstance(options)
|
|
45
|
+
? { routes: options.routes, rootPreload: options.config.preload, base: options.config.base }
|
|
46
|
+
: options;
|
|
37
47
|
if (!routes)
|
|
38
48
|
throw new Error("createFlightDataCollector requires `routes`");
|
|
39
49
|
let branches;
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
let compiledVersion = -1;
|
|
51
|
+
// thunks are called at first collection so per-request trees build lazily;
|
|
52
|
+
// recompiled when a lazy subtree resolves (shared, append-only state)
|
|
53
|
+
const resolveBranches = () => {
|
|
54
|
+
const version = peekLazySubtrees();
|
|
55
|
+
if (!branches || compiledVersion !== version) {
|
|
56
|
+
branches = createBranches(typeof routes === "function" ? routes() : routes, base);
|
|
57
|
+
compiledVersion = version;
|
|
58
|
+
}
|
|
59
|
+
return branches;
|
|
60
|
+
};
|
|
44
61
|
return async (sourceEvent, outcome) => {
|
|
45
62
|
// no referrer, nothing to produce data for (e.g. non-browser callers)
|
|
46
63
|
const referrer = outcome.request.headers.get("referer");
|
|
@@ -67,7 +84,9 @@ export function createFlightDataCollector(options) {
|
|
|
67
84
|
// pointed at the target URL, with the mutation's own cookie mutations
|
|
68
85
|
// folded into the request, in data-only router mode.
|
|
69
86
|
const event = { ...sourceEvent };
|
|
70
|
-
event.request = new Request(url, {
|
|
87
|
+
event.request = new Request(url, {
|
|
88
|
+
headers: createSingleFlightHeaders(sourceEvent, outcome.response)
|
|
89
|
+
});
|
|
71
90
|
event.router = {
|
|
72
91
|
dataOnly: revalidate || true,
|
|
73
92
|
previousUrl: referrer,
|
|
@@ -75,6 +94,7 @@ export function createFlightDataCollector(options) {
|
|
|
75
94
|
};
|
|
76
95
|
return provideRequestEvent(event, async () => {
|
|
77
96
|
try {
|
|
97
|
+
await resolveLazyMatches(resolveBranches, url, referrer);
|
|
78
98
|
runPreloads(event, resolveBranches(), url, referrer, rootPreload);
|
|
79
99
|
}
|
|
80
100
|
catch (error) {
|
|
@@ -94,6 +114,24 @@ export function createFlightDataCollector(options) {
|
|
|
94
114
|
});
|
|
95
115
|
};
|
|
96
116
|
}
|
|
117
|
+
// Lazy subtrees matched by the target (or previous) URL must resolve before
|
|
118
|
+
// the preload pass — the collector would otherwise be blind to inner routes'
|
|
119
|
+
// data. Resolution is shared with the client-side machinery (append-only,
|
|
120
|
+
// cached per thunk); the loop handles boundaries nested inside boundaries.
|
|
121
|
+
async function resolveLazyMatches(resolveBranches, url, previousUrl) {
|
|
122
|
+
for (;;) {
|
|
123
|
+
const branches = resolveBranches();
|
|
124
|
+
const target = new URL(url);
|
|
125
|
+
const matches = [
|
|
126
|
+
...getRouteMatches(branches, target.pathname),
|
|
127
|
+
...getRouteMatches(branches, new URL(previousUrl, target).pathname)
|
|
128
|
+
];
|
|
129
|
+
const pending = matches.filter(m => m.route.lazy && !m.route.lazy.resolved);
|
|
130
|
+
if (!pending.length)
|
|
131
|
+
return;
|
|
132
|
+
await Promise.all(pending.map(m => resolveLazySubtree(m.route.lazy)));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
97
135
|
// The pure preload runner: the same collection loop the router's <Routes>
|
|
98
136
|
// component runs when it detects a data-only render (routers/components.tsx),
|
|
99
137
|
// driven directly off the route tree — matched routes' preloads run and
|
|
@@ -132,11 +170,18 @@ function runPreloads(event, branches, url, previousUrl, rootPreload) {
|
|
|
132
170
|
* The request headers for the flight-data collection pass: the source
|
|
133
171
|
* request's headers with the mutation's `Set-Cookie` mutations folded into
|
|
134
172
|
* the `Cookie` header, so preloads observe the post-mutation cookie state
|
|
135
|
-
* (deletions honored via Max-Age/Expires).
|
|
173
|
+
* (deletions honored via Max-Age/Expires). Cookies attached to the
|
|
174
|
+
* returned/thrown response itself (e.g. `redirect(to, { headers })`) never
|
|
175
|
+
* reach the event response, but a browser round trip would have sent them
|
|
176
|
+
* back with the next request — fold them in too, after the event's, so
|
|
177
|
+
* they win on conflict.
|
|
136
178
|
*/
|
|
137
|
-
export function createSingleFlightHeaders(sourceEvent) {
|
|
179
|
+
export function createSingleFlightHeaders(sourceEvent, outcomeResponse) {
|
|
138
180
|
const headers = new Headers(sourceEvent.request.headers);
|
|
139
|
-
const setCookies =
|
|
181
|
+
const setCookies = [
|
|
182
|
+
...(sourceEvent.response?.headers?.getSetCookie() ?? []),
|
|
183
|
+
...(outcomeResponse?.headers?.getSetCookie() ?? [])
|
|
184
|
+
];
|
|
140
185
|
if (!setCookies.length)
|
|
141
186
|
return headers;
|
|
142
187
|
const cookies = {};
|
|
@@ -200,7 +245,15 @@ export function createNoJSHandler(options = {}) {
|
|
|
200
245
|
const { base = "" } = options;
|
|
201
246
|
return function handleNoJS(result, request, args, thrown) {
|
|
202
247
|
const url = new URL(request.url);
|
|
203
|
-
|
|
248
|
+
// an unusable referer (no-referrer policy, garbage) still beats leaving
|
|
249
|
+
// the browser sitting on the server function endpoint
|
|
250
|
+
let back = new URL(base || "/", url.origin).toString();
|
|
251
|
+
try {
|
|
252
|
+
const referer = request.headers.get("referer");
|
|
253
|
+
if (referer)
|
|
254
|
+
back = new URL(referer).toString();
|
|
255
|
+
}
|
|
256
|
+
catch { }
|
|
204
257
|
// form post -> GET: 303 See Other unless the result names a redirect status
|
|
205
258
|
let status = 303;
|
|
206
259
|
let headers;
|
|
@@ -214,6 +267,9 @@ export function createNoJSHandler(options = {}) {
|
|
|
214
267
|
else {
|
|
215
268
|
headers.set("Location", back);
|
|
216
269
|
}
|
|
270
|
+
// any body is dropped from the redirect we build — don't advertise it
|
|
271
|
+
headers.delete("Content-Type");
|
|
272
|
+
headers.delete("Content-Length");
|
|
217
273
|
}
|
|
218
274
|
else {
|
|
219
275
|
headers = new Headers({ Location: back });
|
package/dist/types.d.ts
CHANGED
|
@@ -25,6 +25,42 @@ declare module "@solidjs/web" {
|
|
|
25
25
|
}
|
|
26
26
|
export type Params = Record<string, string | undefined>;
|
|
27
27
|
export type SearchParams = Record<string, string | string[] | undefined>;
|
|
28
|
+
declare const PATH_PARAMS: unique symbol;
|
|
29
|
+
export interface TypedPath<P extends Params = Params> extends JSX.SerializableAttributeValue {
|
|
30
|
+
readonly [PATH_PARAMS]: P;
|
|
31
|
+
toString(): string;
|
|
32
|
+
}
|
|
33
|
+
declare const PATH_SEARCH: unique symbol;
|
|
34
|
+
export interface TypedSearchPath<In = SetSearchParams, Out = SearchParams> {
|
|
35
|
+
readonly [PATH_SEARCH]: {
|
|
36
|
+
input: In;
|
|
37
|
+
output: Out;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The Standard Schema interface (https://standardschema.dev) — vendored as a
|
|
42
|
+
* type so any conforming validator (Valibot, Zod, ArkType, ...) can type a
|
|
43
|
+
* route's `search` without the router depending on a validation library.
|
|
44
|
+
*/
|
|
45
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
46
|
+
readonly "~standard": {
|
|
47
|
+
readonly version: 1;
|
|
48
|
+
readonly vendor: string;
|
|
49
|
+
readonly validate: (value: unknown) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
|
|
50
|
+
readonly types?: {
|
|
51
|
+
readonly input: Input;
|
|
52
|
+
readonly output: Output;
|
|
53
|
+
} | undefined;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export type StandardSchemaResult<Output> = {
|
|
57
|
+
readonly value: Output;
|
|
58
|
+
readonly issues?: undefined;
|
|
59
|
+
} | {
|
|
60
|
+
readonly issues: ReadonlyArray<{
|
|
61
|
+
readonly message: string;
|
|
62
|
+
}>;
|
|
63
|
+
};
|
|
28
64
|
export type SetParams = Record<string, string | number | boolean | null | undefined>;
|
|
29
65
|
export type SetSearchParams = Record<string, string | string[] | number | number[] | boolean | boolean[] | null | undefined>;
|
|
30
66
|
export interface Path {
|
|
@@ -44,7 +80,7 @@ export interface NavigateOptions<S = unknown> {
|
|
|
44
80
|
state: S;
|
|
45
81
|
}
|
|
46
82
|
export interface Navigator {
|
|
47
|
-
(to: string | number, options?: Partial<NavigateOptions>): void;
|
|
83
|
+
(to: string | TypedPath | number, options?: Partial<NavigateOptions>): void;
|
|
48
84
|
(delta: number): void;
|
|
49
85
|
}
|
|
50
86
|
export type NavigatorFactory = (route?: RouteContext) => Navigator;
|
|
@@ -57,7 +93,6 @@ export interface LocationChange<S = unknown> {
|
|
|
57
93
|
}
|
|
58
94
|
export interface RouterIntegration {
|
|
59
95
|
signal: [get: () => LocationChange, set: (next: LocationChange) => void];
|
|
60
|
-
create?: (router: RouterContext) => void;
|
|
61
96
|
utils?: Partial<RouterUtils>;
|
|
62
97
|
}
|
|
63
98
|
export type Intent = "initial" | "native" | "navigate" | "preload";
|
|
@@ -74,12 +109,28 @@ export interface RouteSectionProps<T = unknown> {
|
|
|
74
109
|
children?: JSX.Element;
|
|
75
110
|
}
|
|
76
111
|
export type RouteSectionComponent<T = unknown> = Component<RouteSectionProps<T>> | Component<Omit<RouteSectionProps<T>, "children">> | Component<{}>;
|
|
112
|
+
/**
|
|
113
|
+
* A lazy route subtree: a thunk producing the child definitions — typically
|
|
114
|
+
* `() => import("./feature/routes")` (the module's `default` or `routes`
|
|
115
|
+
* export is used). The subtree's code loads on demand (hover intent or
|
|
116
|
+
* navigation kicks it, and the load folds into the navigation transition)
|
|
117
|
+
* but its *types* still flow into `paths` through the import's promise type.
|
|
118
|
+
* Thunks must be deterministic — resolution is cached append-only and shared
|
|
119
|
+
* by every mount, request, and the server's flight collector.
|
|
120
|
+
*/
|
|
121
|
+
export type LazyRouteChildren = () => readonly RouteDefinition[] | Promise<readonly RouteDefinition[] | {
|
|
122
|
+
default: readonly RouteDefinition[];
|
|
123
|
+
} | {
|
|
124
|
+
routes: readonly RouteDefinition[];
|
|
125
|
+
}>;
|
|
77
126
|
export type RouteDefinition<S extends string | string[] = any, T = any> = {
|
|
78
127
|
path?: S;
|
|
79
128
|
matchFilters?: MatchFilters<S>;
|
|
80
129
|
preload?: RoutePreloadFunc<T>;
|
|
81
|
-
children?: RouteDefinition | RouteDefinition[];
|
|
130
|
+
children?: RouteDefinition | readonly RouteDefinition[] | LazyRouteChildren;
|
|
82
131
|
component?: RouteSectionComponent<T>;
|
|
132
|
+
/** Standard Schema validator for this route's search params; its input type flows into the typed path proxy. */
|
|
133
|
+
search?: StandardSchemaV1<any, any>;
|
|
83
134
|
info?: Record<string, any>;
|
|
84
135
|
};
|
|
85
136
|
export type MatchFilter = readonly string[] | RegExp | ((s: string) => boolean);
|
|
@@ -110,6 +161,14 @@ export interface RouteDescription {
|
|
|
110
161
|
matcher: (location: string) => PathMatch | null;
|
|
111
162
|
matchFilters?: MatchFilters;
|
|
112
163
|
info?: Record<string, any>;
|
|
164
|
+
/** Present on the placeholder that stands in for an unresolved lazy subtree. */
|
|
165
|
+
lazy?: LazyBoundary;
|
|
166
|
+
}
|
|
167
|
+
/** Resolution state for a lazy subtree boundary, shared per thunk. */
|
|
168
|
+
export interface LazyBoundary {
|
|
169
|
+
thunk: LazyRouteChildren;
|
|
170
|
+
promise?: Promise<readonly RouteDefinition[]>;
|
|
171
|
+
resolved?: readonly RouteDefinition[];
|
|
113
172
|
}
|
|
114
173
|
export interface Branch {
|
|
115
174
|
routes: RouteDescription[];
|
|
@@ -129,7 +188,7 @@ export interface RouterUtils {
|
|
|
129
188
|
renderPath(path: string): string;
|
|
130
189
|
parsePath(str: string): string;
|
|
131
190
|
go(delta: number): void;
|
|
132
|
-
beforeLeave:
|
|
191
|
+
beforeLeave: BeforeLeaveSlot;
|
|
133
192
|
paramsWrapper: (getParams: () => Params, branches: () => Branch[]) => Params;
|
|
134
193
|
queryWrapper: (getQuery: () => SearchParams) => SearchParams;
|
|
135
194
|
}
|
|
@@ -145,7 +204,7 @@ export interface RouterContext {
|
|
|
145
204
|
matches: () => RouteMatch[];
|
|
146
205
|
renderPath(path: string): string;
|
|
147
206
|
parsePath(str: string): string;
|
|
148
|
-
beforeLeave:
|
|
207
|
+
beforeLeave: BeforeLeaveSlot;
|
|
149
208
|
preloadRoute: (url: URL, preloadData?: boolean) => void;
|
|
150
209
|
singleFlight: boolean;
|
|
151
210
|
submissions: Signal<Submission<any, any>[]>;
|
|
@@ -167,6 +226,15 @@ export interface BeforeLeaveLifecycle {
|
|
|
167
226
|
subscribe(listener: BeforeLeaveListener): () => void;
|
|
168
227
|
confirm(to: string | number, options?: Partial<NavigateOptions>): boolean;
|
|
169
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Lazily-filled holder for the beforeLeave guard. The router and history
|
|
231
|
+
* adapters only carry this empty object; `useBeforeLeave` installs the actual
|
|
232
|
+
* lifecycle on first use so its machinery tree-shakes out of apps that never
|
|
233
|
+
* block navigation.
|
|
234
|
+
*/
|
|
235
|
+
export interface BeforeLeaveSlot {
|
|
236
|
+
current?: BeforeLeaveLifecycle;
|
|
237
|
+
}
|
|
170
238
|
export type Submission<T, U> = {
|
|
171
239
|
readonly input: T;
|
|
172
240
|
readonly result?: U;
|
|
@@ -182,3 +250,4 @@ export type CacheEntry = [number, Promise<any>, any, Intent | undefined, Signal<
|
|
|
182
250
|
count: number;
|
|
183
251
|
}];
|
|
184
252
|
export type NarrowResponse<T> = T extends ResponseEnvelope<infer U> ? U : Exclude<T, Response>;
|
|
253
|
+
export {};
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { MatchFilters, PathMatch, RouteDescription, SearchParams, SetSearchParams } from "./types.js";
|
|
2
2
|
export declare const mockBase = "http://sr";
|
|
3
3
|
export declare function normalizePath(path: string, omitSlash?: boolean): string;
|
|
4
|
+
/** Pathname stripped of search/hash and trailing slash, lowercased — the form link matching compares. */
|
|
5
|
+
export declare const comparablePath: (path: string) => string;
|
|
4
6
|
export declare function resolvePath(base: string, path: string, from?: string): string | undefined;
|
|
5
7
|
export declare function invariant<T>(value: T | null | undefined, message: string): T;
|
|
6
8
|
export declare function joinPaths(from: string, to: string): string;
|
package/dist/utils.js
CHANGED
|
@@ -6,6 +6,8 @@ export function normalizePath(path, omitSlash = false) {
|
|
|
6
6
|
const s = path.replace(trimPathRegex, "$1");
|
|
7
7
|
return s ? (omitSlash || /^[?#]/.test(s) ? s : "/" + s) : "";
|
|
8
8
|
}
|
|
9
|
+
/** Pathname stripped of search/hash and trailing slash, lowercased — the form link matching compares. */
|
|
10
|
+
export const comparablePath = (path) => normalizePath(path.split(/[?#]/, 1)[0]).toLowerCase().replace(/\/$/, "");
|
|
9
11
|
export function resolvePath(base, path, from) {
|
|
10
12
|
if (hasSchemeRegex.test(path)) {
|
|
11
13
|
return undefined;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "1.0.0-next.11",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -23,14 +23,15 @@
|
|
|
23
23
|
"solid": "./dist/index.jsx",
|
|
24
24
|
"default": "./dist/index.js"
|
|
25
25
|
},
|
|
26
|
-
"./server": "./dist/server.js"
|
|
26
|
+
"./server": "./dist/server.js",
|
|
27
|
+
"./fs": "./dist/fs.js"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
29
30
|
"dist"
|
|
30
31
|
],
|
|
31
32
|
"sideEffects": false,
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@solidjs/web": "2.0.0-beta.
|
|
34
|
+
"@solidjs/web": "2.0.0-beta.22",
|
|
34
35
|
"@babel/core": "^7.26.0",
|
|
35
36
|
"@babel/preset-typescript": "^7.26.0",
|
|
36
37
|
"@changesets/cli": "^2.27.10",
|
|
@@ -39,19 +40,19 @@
|
|
|
39
40
|
"@rollup/plugin-terser": "0.4.4",
|
|
40
41
|
"@types/jest": "^29.5.14",
|
|
41
42
|
"@types/node": "^22.10.0",
|
|
42
|
-
"babel-preset-solid": "2.0.0-beta.
|
|
43
|
+
"babel-preset-solid": "2.0.0-beta.22",
|
|
43
44
|
"jsdom": "^25.0.1",
|
|
44
45
|
"prettier": "^3.4.1",
|
|
45
46
|
"rollup": "^4.27.4",
|
|
46
|
-
"solid-js": "2.0.0-beta.
|
|
47
|
+
"solid-js": "2.0.0-beta.22",
|
|
47
48
|
"typescript": "^5.7.2",
|
|
48
49
|
"vite": "^6.0.0",
|
|
49
50
|
"vite-plugin-solid": "3.0.0-next.10",
|
|
50
51
|
"vitest": "^2.1.6"
|
|
51
52
|
},
|
|
52
53
|
"peerDependencies": {
|
|
53
|
-
"@solidjs/web": ">=2.0.0-beta.
|
|
54
|
-
"solid-js": ">=2.0.0-beta.
|
|
54
|
+
"@solidjs/web": ">=2.0.0-beta.22 <2.0.0-experimental.0",
|
|
55
|
+
"solid-js": ">=2.0.0-beta.22 <2.0.0-experimental.0"
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
58
|
"build": "rm -rf dist && tsc && rollup -c",
|
package/dist/components.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { JSX } from "@solidjs/web";
|
|
2
|
-
import type { Location, Navigator } from "./types.js";
|
|
3
|
-
declare module "@solidjs/web/jsx-runtime" {
|
|
4
|
-
namespace JSX {
|
|
5
|
-
interface AnchorHTMLAttributes<T> {
|
|
6
|
-
state?: string;
|
|
7
|
-
noScroll?: boolean;
|
|
8
|
-
replace?: boolean;
|
|
9
|
-
preload?: boolean;
|
|
10
|
-
link?: boolean;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "state"> {
|
|
15
|
-
href: string;
|
|
16
|
-
replace?: boolean | undefined;
|
|
17
|
-
noScroll?: boolean | undefined;
|
|
18
|
-
state?: unknown | undefined;
|
|
19
|
-
inactiveClass?: string | undefined;
|
|
20
|
-
activeClass?: string | undefined;
|
|
21
|
-
end?: boolean | undefined;
|
|
22
|
-
}
|
|
23
|
-
export declare function A(props: AnchorProps): JSX.Element;
|
|
24
|
-
export interface NavigateProps {
|
|
25
|
-
href: ((args: {
|
|
26
|
-
navigate: Navigator;
|
|
27
|
-
location: Location;
|
|
28
|
-
}) => string) | string;
|
|
29
|
-
state?: unknown;
|
|
30
|
-
}
|
|
31
|
-
export declare function Navigate(props: NavigateProps): null;
|
package/dist/components.jsx
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { createMemo, merge, omit } from "solid-js";
|
|
2
|
-
import { useHref, useLocation, useNavigate, useResolvedPath } from "./routing.js";
|
|
3
|
-
import { normalizePath } from "./utils.js";
|
|
4
|
-
function toClassName(value) {
|
|
5
|
-
if (!value)
|
|
6
|
-
return "";
|
|
7
|
-
if (typeof value === "string" || typeof value === "number")
|
|
8
|
-
return String(value);
|
|
9
|
-
if (Array.isArray(value))
|
|
10
|
-
return value.map(toClassName).filter(Boolean).join(" ");
|
|
11
|
-
if (typeof value === "object") {
|
|
12
|
-
return Object.entries(value)
|
|
13
|
-
.filter(([, enabled]) => enabled)
|
|
14
|
-
.map(([name]) => name)
|
|
15
|
-
.join(" ");
|
|
16
|
-
}
|
|
17
|
-
return "";
|
|
18
|
-
}
|
|
19
|
-
export function A(props) {
|
|
20
|
-
props = merge({ inactiveClass: "inactive", activeClass: "active" }, props);
|
|
21
|
-
const rest = omit(props, "href", "state", "class", "activeClass", "inactiveClass", "end");
|
|
22
|
-
const to = useResolvedPath(() => props.href);
|
|
23
|
-
const href = useHref(to);
|
|
24
|
-
const location = useLocation();
|
|
25
|
-
const isActive = createMemo(() => {
|
|
26
|
-
const to_ = to();
|
|
27
|
-
if (to_ === undefined)
|
|
28
|
-
return [false, false];
|
|
29
|
-
// trailing slashes are ignored so `/route` and `/route/` share active state
|
|
30
|
-
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase().replace(/\/$/, "");
|
|
31
|
-
const loc = decodeURI(normalizePath(location.pathname).toLowerCase().replace(/\/$/, ""));
|
|
32
|
-
return [props.end ? path === loc : loc.startsWith(path + "/") || loc === path, path === loc];
|
|
33
|
-
});
|
|
34
|
-
const className = createMemo(() => [toClassName(props.class), isActive()[0] ? props.activeClass : props.inactiveClass]
|
|
35
|
-
.filter(Boolean)
|
|
36
|
-
.join(" "));
|
|
37
|
-
return (<a {...rest} href={href() || props.href} state={JSON.stringify(props.state)} class={className()} link aria-current={isActive()[1] ? "page" : undefined}/>);
|
|
38
|
-
}
|
|
39
|
-
export function Navigate(props) {
|
|
40
|
-
const navigate = useNavigate();
|
|
41
|
-
const location = useLocation();
|
|
42
|
-
const { href, state } = props;
|
|
43
|
-
const path = typeof href === "function" ? href({ navigate, location }) : href;
|
|
44
|
-
navigate(path, { replace: true, state });
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { JSX } from "@solidjs/web";
|
|
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;
|
|
@@ -1,41 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { LocationChange } from "../types.js";
|
|
2
|
-
import type { BaseRouterProps } from "./components.jsx";
|
|
3
|
-
import type { JSX } from "@solidjs/web";
|
|
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;
|
|
@@ -1,57 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/routers/Router.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { BaseRouterProps } from "./components.jsx";
|
|
2
|
-
import type { JSX } from "@solidjs/web";
|
|
3
|
-
export type RouterProps = BaseRouterProps & {
|
|
4
|
-
url?: string;
|
|
5
|
-
actionBase?: string;
|
|
6
|
-
explicitLinks?: boolean;
|
|
7
|
-
preload?: boolean;
|
|
8
|
-
};
|
|
9
|
-
export declare function Router(props: RouterProps): JSX.Element;
|
package/dist/routers/Router.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { isServer } from "@solidjs/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
|
-
export function Router(props) {
|
|
7
|
-
if (isServer)
|
|
8
|
-
return StaticRouter(props);
|
|
9
|
-
const getSource = () => {
|
|
10
|
-
const url = window.location.pathname + window.location.search;
|
|
11
|
-
const state = window.history.state && window.history.state._depth && Object.keys(window.history.state).length === 1 ? undefined : window.history.state;
|
|
12
|
-
return {
|
|
13
|
-
value: url + window.location.hash,
|
|
14
|
-
state
|
|
15
|
-
};
|
|
16
|
-
};
|
|
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
|
-
scrollToHash(decodeURIComponent(window.location.hash.slice(1)), scroll);
|
|
28
|
-
saveCurrentDepth();
|
|
29
|
-
},
|
|
30
|
-
init: notify => bindEvent(window, "popstate", notifyIfNotBlocked(notify, delta => {
|
|
31
|
-
if (delta) {
|
|
32
|
-
return !beforeLeave.confirm(delta);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
const s = getSource();
|
|
36
|
-
return !beforeLeave.confirm(s.value, { state: s.state });
|
|
37
|
-
}
|
|
38
|
-
})),
|
|
39
|
-
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase, transformUrl: props.transformUrl }),
|
|
40
|
-
utils: {
|
|
41
|
-
go: delta => window.history.go(delta),
|
|
42
|
-
beforeLeave
|
|
43
|
-
}
|
|
44
|
-
})(props);
|
|
45
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { getRequestEvent } from "@solidjs/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,10 +0,0 @@
|
|
|
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("@solidjs/web").JSX.Element;
|
|
9
|
-
export declare function bindEvent(target: EventTarget, type: string, handler: EventListener): () => void;
|
|
10
|
-
export declare function scrollToHash(hash: string, fallbackTop?: boolean): void;
|