@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/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { Component, Signal } from "solid-js";
|
|
2
|
-
|
|
3
|
-
declare module "@solidjs/web" {
|
|
1
|
+
import type { Component, JSX, Signal } from "solid-js";
|
|
2
|
+
declare module "solid-js/web" {
|
|
4
3
|
interface RequestEvent {
|
|
5
4
|
response: {
|
|
6
5
|
status?: number;
|
|
@@ -12,8 +11,7 @@ declare module "@solidjs/web" {
|
|
|
12
11
|
cache?: Map<string, CacheEntry>;
|
|
13
12
|
submission?: {
|
|
14
13
|
input: any;
|
|
15
|
-
result
|
|
16
|
-
error?: any;
|
|
14
|
+
result: any;
|
|
17
15
|
url: string;
|
|
18
16
|
};
|
|
19
17
|
dataOnly?: boolean | string[];
|
|
@@ -25,42 +23,6 @@ declare module "@solidjs/web" {
|
|
|
25
23
|
}
|
|
26
24
|
export type Params = Record<string, string | undefined>;
|
|
27
25
|
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
|
-
};
|
|
64
26
|
export type SetParams = Record<string, string | number | boolean | null | undefined>;
|
|
65
27
|
export type SetSearchParams = Record<string, string | string[] | number | number[] | boolean | boolean[] | null | undefined>;
|
|
66
28
|
export interface Path {
|
|
@@ -80,7 +42,7 @@ export interface NavigateOptions<S = unknown> {
|
|
|
80
42
|
state: S;
|
|
81
43
|
}
|
|
82
44
|
export interface Navigator {
|
|
83
|
-
(to: string |
|
|
45
|
+
(to: string | number, options?: Partial<NavigateOptions>): void;
|
|
84
46
|
(delta: number): void;
|
|
85
47
|
}
|
|
86
48
|
export type NavigatorFactory = (route?: RouteContext) => Navigator;
|
|
@@ -92,7 +54,8 @@ export interface LocationChange<S = unknown> {
|
|
|
92
54
|
rawPath?: string;
|
|
93
55
|
}
|
|
94
56
|
export interface RouterIntegration {
|
|
95
|
-
signal:
|
|
57
|
+
signal: Signal<LocationChange>;
|
|
58
|
+
create?: (router: RouterContext) => void;
|
|
96
59
|
utils?: Partial<RouterUtils>;
|
|
97
60
|
}
|
|
98
61
|
export type Intent = "initial" | "native" | "navigate" | "preload";
|
|
@@ -109,29 +72,15 @@ export interface RouteSectionProps<T = unknown> {
|
|
|
109
72
|
children?: JSX.Element;
|
|
110
73
|
}
|
|
111
74
|
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
|
-
}>;
|
|
126
75
|
export type RouteDefinition<S extends string | string[] = any, T = any> = {
|
|
127
76
|
path?: S;
|
|
128
77
|
matchFilters?: MatchFilters<S>;
|
|
129
78
|
preload?: RoutePreloadFunc<T>;
|
|
130
|
-
children?: RouteDefinition |
|
|
79
|
+
children?: RouteDefinition | RouteDefinition[];
|
|
131
80
|
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>;
|
|
134
81
|
info?: Record<string, any>;
|
|
82
|
+
/** @deprecated use preload */
|
|
83
|
+
load?: RoutePreloadFunc;
|
|
135
84
|
};
|
|
136
85
|
export type MatchFilter = readonly string[] | RegExp | ((s: string) => boolean);
|
|
137
86
|
export type PathParams<P extends string | readonly string[]> = P extends `${infer Head}/${infer Tail}` ? [...PathParams<Head>, ...PathParams<Tail>] : P extends `:${infer S}?` ? [S] : P extends `:${infer S}` ? [S] : P extends `*${infer S}` ? [S] : [];
|
|
@@ -161,14 +110,6 @@ export interface RouteDescription {
|
|
|
161
110
|
matcher: (location: string) => PathMatch | null;
|
|
162
111
|
matchFilters?: MatchFilters;
|
|
163
112
|
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[];
|
|
172
113
|
}
|
|
173
114
|
export interface Branch {
|
|
174
115
|
routes: RouteDescription[];
|
|
@@ -179,7 +120,6 @@ export interface RouteContext {
|
|
|
179
120
|
parent?: RouteContext;
|
|
180
121
|
child?: RouteContext;
|
|
181
122
|
pattern: string;
|
|
182
|
-
params: Params;
|
|
183
123
|
path: () => string;
|
|
184
124
|
outlet: () => JSX.Element;
|
|
185
125
|
resolvePath(to: string): string | undefined;
|
|
@@ -188,7 +128,7 @@ export interface RouterUtils {
|
|
|
188
128
|
renderPath(path: string): string;
|
|
189
129
|
parsePath(str: string): string;
|
|
190
130
|
go(delta: number): void;
|
|
191
|
-
beforeLeave:
|
|
131
|
+
beforeLeave: BeforeLeaveLifecycle;
|
|
192
132
|
paramsWrapper: (getParams: () => Params, branches: () => Branch[]) => Params;
|
|
193
133
|
queryWrapper: (getQuery: () => SearchParams) => SearchParams;
|
|
194
134
|
}
|
|
@@ -196,7 +136,6 @@ export interface RouterContext {
|
|
|
196
136
|
base: RouteContext;
|
|
197
137
|
location: Location;
|
|
198
138
|
params: Params;
|
|
199
|
-
wrapParams: (getParams: () => Params) => Params;
|
|
200
139
|
navigatorFactory: NavigatorFactory;
|
|
201
140
|
isRouting: () => boolean;
|
|
202
141
|
/** The target of the in-flight navigation transition, if any. Not reactive. */
|
|
@@ -204,7 +143,7 @@ export interface RouterContext {
|
|
|
204
143
|
matches: () => RouteMatch[];
|
|
205
144
|
renderPath(path: string): string;
|
|
206
145
|
parsePath(str: string): string;
|
|
207
|
-
beforeLeave:
|
|
146
|
+
beforeLeave: BeforeLeaveLifecycle;
|
|
208
147
|
preloadRoute: (url: URL, preloadData?: boolean) => void;
|
|
209
148
|
singleFlight: boolean;
|
|
210
149
|
submissions: Signal<Submission<any, any>[]>;
|
|
@@ -226,28 +165,39 @@ export interface BeforeLeaveLifecycle {
|
|
|
226
165
|
subscribe(listener: BeforeLeaveListener): () => void;
|
|
227
166
|
confirm(to: string | number, options?: Partial<NavigateOptions>): boolean;
|
|
228
167
|
}
|
|
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
|
-
}
|
|
238
168
|
export type Submission<T, U> = {
|
|
239
169
|
readonly input: T;
|
|
240
170
|
readonly result?: U;
|
|
241
171
|
readonly error: any;
|
|
172
|
+
readonly pending: boolean;
|
|
242
173
|
readonly url: string;
|
|
243
174
|
clear: () => void;
|
|
244
175
|
retry: () => void;
|
|
245
176
|
};
|
|
177
|
+
export type SubmissionStub = {
|
|
178
|
+
readonly input: undefined;
|
|
179
|
+
readonly result: undefined;
|
|
180
|
+
readonly error: undefined;
|
|
181
|
+
readonly pending: undefined;
|
|
182
|
+
readonly url: undefined;
|
|
183
|
+
clear: () => void;
|
|
184
|
+
retry: () => void;
|
|
185
|
+
};
|
|
246
186
|
export interface MaybePreloadableComponent extends Component {
|
|
247
187
|
preload?: () => void;
|
|
248
188
|
}
|
|
249
189
|
export type CacheEntry = [number, Promise<any>, any, Intent | undefined, Signal<number> & {
|
|
250
190
|
count: number;
|
|
251
191
|
}];
|
|
252
|
-
export type NarrowResponse<T> = T extends
|
|
253
|
-
export {
|
|
192
|
+
export type NarrowResponse<T> = T extends CustomResponse<infer U> ? U : Exclude<T, Response>;
|
|
193
|
+
export type RouterResponseInit = Omit<ResponseInit, "body"> & {
|
|
194
|
+
revalidate?: string | string[];
|
|
195
|
+
};
|
|
196
|
+
export type CustomResponse<T> = Omit<Response, "clone"> & {
|
|
197
|
+
customBody: () => T;
|
|
198
|
+
clone(...args: readonly unknown[]): CustomResponse<T>;
|
|
199
|
+
};
|
|
200
|
+
/** @deprecated */
|
|
201
|
+
export type RouteLoadFunc = RoutePreloadFunc;
|
|
202
|
+
/** @deprecated */
|
|
203
|
+
export type RouteLoadFuncArgs = RoutePreloadFuncArgs;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
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;
|
|
6
4
|
export declare function resolvePath(base: string, path: string, from?: string): string | undefined;
|
|
7
5
|
export declare function invariant<T>(value: T | null | undefined, message: string): T;
|
|
8
6
|
export declare function joinPaths(from: string, to: string): string;
|
package/dist/utils.js
CHANGED
|
@@ -6,8 +6,6 @@ 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(/\/$/, "");
|
|
11
9
|
export function resolvePath(base, path, from) {
|
|
12
10
|
if (hasSchemeRegex.test(path)) {
|
|
13
11
|
return undefined;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "1.0.0
|
|
9
|
+
"version": "1.0.0",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -22,15 +22,13 @@
|
|
|
22
22
|
".": {
|
|
23
23
|
"solid": "./dist/index.jsx",
|
|
24
24
|
"default": "./dist/index.js"
|
|
25
|
-
}
|
|
26
|
-
"./server": "./dist/server.js"
|
|
25
|
+
}
|
|
27
26
|
},
|
|
28
27
|
"files": [
|
|
29
28
|
"dist"
|
|
30
29
|
],
|
|
31
30
|
"sideEffects": false,
|
|
32
31
|
"devDependencies": {
|
|
33
|
-
"@solidjs/web": "2.0.0-beta.22",
|
|
34
32
|
"@babel/core": "^7.26.0",
|
|
35
33
|
"@babel/preset-typescript": "^7.26.0",
|
|
36
34
|
"@changesets/cli": "^2.27.10",
|
|
@@ -39,25 +37,23 @@
|
|
|
39
37
|
"@rollup/plugin-terser": "0.4.4",
|
|
40
38
|
"@types/jest": "^29.5.14",
|
|
41
39
|
"@types/node": "^22.10.0",
|
|
42
|
-
"babel-preset-solid": "
|
|
40
|
+
"babel-preset-solid": "^1.9.3",
|
|
43
41
|
"jsdom": "^25.0.1",
|
|
44
42
|
"prettier": "^3.4.1",
|
|
45
43
|
"rollup": "^4.27.4",
|
|
46
|
-
"solid-js": "
|
|
44
|
+
"solid-js": "^1.9.3",
|
|
47
45
|
"typescript": "^5.7.2",
|
|
48
46
|
"vite": "^6.0.0",
|
|
49
|
-
"vite-plugin-solid": "
|
|
47
|
+
"vite-plugin-solid": "^2.11.0",
|
|
50
48
|
"vitest": "^2.1.6"
|
|
51
49
|
},
|
|
52
50
|
"peerDependencies": {
|
|
53
|
-
"
|
|
54
|
-
"solid-js": ">=2.0.0-beta.22 <2.0.0-experimental.0"
|
|
51
|
+
"solid-js": "^1.8.6"
|
|
55
52
|
},
|
|
56
53
|
"scripts": {
|
|
57
54
|
"build": "rm -rf dist && tsc && rollup -c",
|
|
58
|
-
"test": "vitest run &&
|
|
55
|
+
"test": "vitest run && npm run test:types",
|
|
59
56
|
"test:watch": "vitest",
|
|
60
|
-
"test:server": "vitest run --config vitest.config.server.ts",
|
|
61
57
|
"test:types": "tsc --project tsconfig.test.json",
|
|
62
58
|
"pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\"",
|
|
63
59
|
"release": "pnpm build && changeset publish"
|
package/dist/claims.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { RouterContext } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* The compiler claims every `a[href]` (and `form[action]`, which this handler
|
|
4
|
-
* ignores) at creation, and the runtime re-claims on `href` writes. This
|
|
5
|
-
* consumer gives each router-managed anchor the link-state vocabulary without
|
|
6
|
-
* a wrapper component:
|
|
7
|
-
*
|
|
8
|
-
* - `aria-current="page"` — the location matches the link exactly
|
|
9
|
-
* - `data-active` — exact or prefix match
|
|
10
|
-
* - `data-pending` — the link is the target of an in-flight navigation
|
|
11
|
-
*
|
|
12
|
-
* Elements are claimed at creation, so late mounts (`<Show>`, `<For>`,
|
|
13
|
-
* portals) are correct immediately. One render effect (owned by the router)
|
|
14
|
-
* subscribes to the location and sweeps a registry of claimed anchors —
|
|
15
|
-
* anchors themselves carry no reactive machinery, just a registry entry
|
|
16
|
-
* removed by their creating owner's cleanup. State is applied once at claim
|
|
17
|
-
* so it is correct before the next navigation; re-claims (an `href` write)
|
|
18
|
-
* are the same one-shot untracked refresh, reading the element's current
|
|
19
|
-
* `href` from the DOM.
|
|
20
|
-
*/
|
|
21
|
-
export declare function setupLinkClaims(router: RouterContext, explicitLinks?: boolean): void;
|
package/dist/claims.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { registerElementClaim } from "@solidjs/web";
|
|
2
|
-
import { createRenderEffect, getOwner, onCleanup, untrack } from "solid-js";
|
|
3
|
-
import { comparablePath } from "./utils.js";
|
|
4
|
-
/**
|
|
5
|
-
* The compiler claims every `a[href]` (and `form[action]`, which this handler
|
|
6
|
-
* ignores) at creation, and the runtime re-claims on `href` writes. This
|
|
7
|
-
* consumer gives each router-managed anchor the link-state vocabulary without
|
|
8
|
-
* a wrapper component:
|
|
9
|
-
*
|
|
10
|
-
* - `aria-current="page"` — the location matches the link exactly
|
|
11
|
-
* - `data-active` — exact or prefix match
|
|
12
|
-
* - `data-pending` — the link is the target of an in-flight navigation
|
|
13
|
-
*
|
|
14
|
-
* Elements are claimed at creation, so late mounts (`<Show>`, `<For>`,
|
|
15
|
-
* portals) are correct immediately. One render effect (owned by the router)
|
|
16
|
-
* subscribes to the location and sweeps a registry of claimed anchors —
|
|
17
|
-
* anchors themselves carry no reactive machinery, just a registry entry
|
|
18
|
-
* removed by their creating owner's cleanup. State is applied once at claim
|
|
19
|
-
* so it is correct before the next navigation; re-claims (an `href` write)
|
|
20
|
-
* are the same one-shot untracked refresh, reading the element's current
|
|
21
|
-
* `href` from the DOM.
|
|
22
|
-
*/
|
|
23
|
-
export function setupLinkClaims(router, explicitLinks) {
|
|
24
|
-
const basePath = router.base.path();
|
|
25
|
-
// per-element record; `current` remembers whether we set `aria-current`,
|
|
26
|
-
// so user-authored values (steppers, breadcrumbs) are never stripped
|
|
27
|
-
const claimed = new WeakMap();
|
|
28
|
-
const registry = new Set();
|
|
29
|
-
function isSvg(el) {
|
|
30
|
-
return el.namespaceURI === "http://www.w3.org/2000/svg";
|
|
31
|
-
}
|
|
32
|
-
/** The comparable pathname when the router manages this anchor, else `undefined`. */
|
|
33
|
-
function managedPath(a) {
|
|
34
|
-
if (explicitLinks && !a.hasAttribute("link"))
|
|
35
|
-
return;
|
|
36
|
-
const svg = isSvg(a);
|
|
37
|
-
// claims fire at creation while the element is still in the template's
|
|
38
|
-
// inert fragment, where the `href` property is not resolved — resolve the
|
|
39
|
-
// raw attribute against the live document instead
|
|
40
|
-
const href = svg ? a.href.baseVal : a.getAttribute("href");
|
|
41
|
-
const target = svg ? a.target.baseVal : a.target;
|
|
42
|
-
if (target || !href)
|
|
43
|
-
return;
|
|
44
|
-
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
|
45
|
-
if (a.hasAttribute("download") || rel.includes("external"))
|
|
46
|
-
return;
|
|
47
|
-
let url;
|
|
48
|
-
try {
|
|
49
|
-
url = new URL(href, document.baseURI);
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (url.origin !== window.location.origin ||
|
|
55
|
-
(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
|
|
56
|
-
return;
|
|
57
|
-
return comparablePath(url.pathname);
|
|
58
|
-
}
|
|
59
|
-
function linkState(a) {
|
|
60
|
-
// read reactive sources unconditionally so the owning effect stays
|
|
61
|
-
// subscribed even while the anchor is not router-managed
|
|
62
|
-
const loc = decodeURI(comparablePath(router.location.pathname));
|
|
63
|
-
const routing = router.isRouting();
|
|
64
|
-
const path = managedPath(a);
|
|
65
|
-
// the root path is a prefix of everything, so it only matches exactly —
|
|
66
|
-
// there is no per-anchor `end` opt-out like useLinkState has
|
|
67
|
-
const matches = (target) => path !== undefined && (target === path || (path !== "" && target.startsWith(path + "/")));
|
|
68
|
-
// effects observe the committed location during a transition, so the
|
|
69
|
-
// in-flight target comes from pendingTarget — readable here because the
|
|
70
|
-
// isRouting write flushes after the target is assigned
|
|
71
|
-
const pending = routing && !!router.pendingTarget && matches(decodeURI(comparablePath(router.pendingTarget.value)));
|
|
72
|
-
return { active: matches(loc), pending, exact: path !== undefined && loc === path };
|
|
73
|
-
}
|
|
74
|
-
function apply(a, rec, { active, pending, exact }) {
|
|
75
|
-
active ? a.setAttribute("data-active", "") : a.removeAttribute("data-active");
|
|
76
|
-
pending ? a.setAttribute("data-pending", "") : a.removeAttribute("data-pending");
|
|
77
|
-
if (exact !== rec.current) {
|
|
78
|
-
exact ? a.setAttribute("aria-current", "page") : a.removeAttribute("aria-current");
|
|
79
|
-
rec.current = exact;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const refresh = (a, rec) => untrack(() => apply(a, rec, linkState(a)));
|
|
83
|
-
// The one subscription for every anchor: compute tracks the sources
|
|
84
|
-
// linkState derives from (the in-flight pendingTarget is readable in the
|
|
85
|
-
// effect phase because the isRouting write flushes after the target is
|
|
86
|
-
// assigned), the effect phase sweeps the registry untracked.
|
|
87
|
-
//
|
|
88
|
-
// `transparent` keeps the effect invisible to the hydration id scheme.
|
|
89
|
-
// This setup is client-only, so an id-consuming node here has no server
|
|
90
|
-
// counterpart and every subsequent hydration id would shift by one child
|
|
91
|
-
// slot — lazy-route lookups miss and hydration leaves server nodes
|
|
92
|
-
// unclaimed. (The option is honored by the runtime but missing from the
|
|
93
|
-
// published EffectOptions type, hence the cast.)
|
|
94
|
-
createRenderEffect(() => (router.location.pathname, router.isRouting()), () => registry.forEach(a => refresh(a, claimed.get(a))), { transparent: true });
|
|
95
|
-
onCleanup(registerElementClaim(node => {
|
|
96
|
-
if (node.nodeName.toUpperCase() !== "A")
|
|
97
|
-
return;
|
|
98
|
-
const a = node;
|
|
99
|
-
// re-claim (href changed): the claiming write runs inside another
|
|
100
|
-
// effect, so refresh without leaking subscriptions into it
|
|
101
|
-
const existing = claimed.get(a);
|
|
102
|
-
if (existing)
|
|
103
|
-
return refresh(a, existing);
|
|
104
|
-
const rec = { current: false };
|
|
105
|
-
claimed.set(a, rec);
|
|
106
|
-
// claims fire during component setup, so an owner is present in
|
|
107
|
-
// practice to bound the registry entry's lifetime; without one, state
|
|
108
|
-
// is still applied once at creation
|
|
109
|
-
if (getOwner()) {
|
|
110
|
-
registry.add(a);
|
|
111
|
-
onCleanup(() => registry.delete(a));
|
|
112
|
-
}
|
|
113
|
-
refresh(a, rec);
|
|
114
|
-
}));
|
|
115
|
-
}
|
package/dist/data/flash.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export { FLASH_COOKIE, hasFlashCookie, clearFlashCookie } from "./flashCookie.js";
|
|
2
|
-
/** What rides the cookie: a Submission minus its lifecycle methods. */
|
|
3
|
-
export interface FlashSubmission {
|
|
4
|
-
input: any[];
|
|
5
|
-
url: string;
|
|
6
|
-
result?: any;
|
|
7
|
-
error?: any;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Encodes the outcome of a no-JS submission as a Set-Cookie value. `url` is
|
|
11
|
-
* the action url (pathname + search of the server function call) so the
|
|
12
|
-
* seeded submission matches `useSubmission` filters; `thrown` errors land on
|
|
13
|
-
* `error`, returned values on `result` (mirroring the scripted split).
|
|
14
|
-
*/
|
|
15
|
-
export declare function encodeFlashCookie(url: string, result: any, input: any[], thrown?: boolean): string;
|
|
16
|
-
/**
|
|
17
|
-
* Decodes the flash cookie out of a request's Cookie header. Returns
|
|
18
|
-
* undefined when absent or unreadable (a malformed cookie must never take
|
|
19
|
-
* down SSR — it is cleared either way).
|
|
20
|
-
*/
|
|
21
|
-
export declare function decodeFlashCookie(cookieHeader: string | null): FlashSubmission | undefined;
|
package/dist/data/flash.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// The no-JS form convention's cookie codec. When a form posts to a server
|
|
2
|
-
// function without the client runtime (no instance header), the server
|
|
3
|
-
// handler redirects back carrying the outcome in a one-shot "flash" cookie;
|
|
4
|
-
// the next SSR pass reads it and seeds the router's submission state so
|
|
5
|
-
// useSubmission() renders the result exactly as a scripted submission would.
|
|
6
|
-
// Both codec halves live in this module so the write (src/server.ts, the
|
|
7
|
-
// handler's handleNoJS) and the read (provided to the router core by
|
|
8
|
-
// data/action.ts) can never drift apart. The cookie's name/clearing live in
|
|
9
|
-
// flashCookie.ts — the only piece the router core itself consumes — so this
|
|
10
|
-
// codec stays out of bundles that never load the action layer.
|
|
11
|
-
import { FLASH_COOKIE, matchFlashCookie } from "./flashCookie.js";
|
|
12
|
-
export { FLASH_COOKIE, hasFlashCookie, clearFlashCookie } from "./flashCookie.js";
|
|
13
|
-
// Form payloads have no JSON encoding, so entries are captured as pair
|
|
14
|
-
// arrays under a marker key ($f / $u) and revived to real FormData /
|
|
15
|
-
// URLSearchParams on the way out. File entries cannot ride a cookie and are
|
|
16
|
-
// dropped.
|
|
17
|
-
function encodeInputValue(value) {
|
|
18
|
-
if (value instanceof FormData)
|
|
19
|
-
return { $f: [...value.entries()].filter(([, v]) => typeof v === "string") };
|
|
20
|
-
if (value instanceof URLSearchParams)
|
|
21
|
-
return { $u: [...value.entries()] };
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
function decodeInputValue(value) {
|
|
25
|
-
if (value && typeof value === "object") {
|
|
26
|
-
if (Array.isArray(value.$f)) {
|
|
27
|
-
const form = new FormData();
|
|
28
|
-
for (const [k, v] of value.$f)
|
|
29
|
-
form.append(k, v);
|
|
30
|
-
return form;
|
|
31
|
-
}
|
|
32
|
-
if (Array.isArray(value.$u))
|
|
33
|
-
return new URLSearchParams(value.$u);
|
|
34
|
-
}
|
|
35
|
-
return value;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Encodes the outcome of a no-JS submission as a Set-Cookie value. `url` is
|
|
39
|
-
* the action url (pathname + search of the server function call) so the
|
|
40
|
-
* seeded submission matches `useSubmission` filters; `thrown` errors land on
|
|
41
|
-
* `error`, returned values on `result` (mirroring the scripted split).
|
|
42
|
-
*/
|
|
43
|
-
export function encodeFlashCookie(url, result, input, thrown) {
|
|
44
|
-
const isError = result instanceof Error;
|
|
45
|
-
const payload = {
|
|
46
|
-
url,
|
|
47
|
-
result: isError ? result.message : result,
|
|
48
|
-
error: isError,
|
|
49
|
-
thrown: !!thrown,
|
|
50
|
-
input: input.map(encodeInputValue)
|
|
51
|
-
};
|
|
52
|
-
return `${FLASH_COOKIE}=${encodeURIComponent(JSON.stringify(payload))}; Secure; HttpOnly; Path=/`;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Decodes the flash cookie out of a request's Cookie header. Returns
|
|
56
|
-
* undefined when absent or unreadable (a malformed cookie must never take
|
|
57
|
-
* down SSR — it is cleared either way).
|
|
58
|
-
*/
|
|
59
|
-
export function decodeFlashCookie(cookieHeader) {
|
|
60
|
-
const match = matchFlashCookie(cookieHeader);
|
|
61
|
-
if (!match)
|
|
62
|
-
return;
|
|
63
|
-
try {
|
|
64
|
-
const payload = JSON.parse(decodeURIComponent(match));
|
|
65
|
-
if (!payload || !payload.result)
|
|
66
|
-
return;
|
|
67
|
-
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
68
|
-
return {
|
|
69
|
-
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
70
|
-
url: payload.url,
|
|
71
|
-
result: payload.thrown ? undefined : result,
|
|
72
|
-
error: payload.thrown ? result : undefined
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
console.error(error);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare const FLASH_COOKIE = "flash";
|
|
2
|
-
/** Whether a Cookie header carries a flash cookie (readable or not). */
|
|
3
|
-
export declare function hasFlashCookie(cookieHeader: string | null): boolean;
|
|
4
|
-
/** The raw encoded flash payload out of a Cookie header, if present. */
|
|
5
|
-
export declare function matchFlashCookie(cookieHeader: string | null): string | undefined;
|
|
6
|
-
/** The Set-Cookie value clearing the flash cookie after it has been read. */
|
|
7
|
-
export declare function clearFlashCookie(): string;
|
package/dist/data/flashCookie.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// The flash cookie's name and one-shot clearing — split from the codec in
|
|
2
|
-
// flash.ts so the router core can consume the cookie eagerly (the clear must
|
|
3
|
-
// be appended before streaming flushes the response headers, and an unread
|
|
4
|
-
// outcome must not haunt a later request) without carrying the encode/decode
|
|
5
|
-
// machinery into client bundles that never load the action layer.
|
|
6
|
-
export const FLASH_COOKIE = "flash";
|
|
7
|
-
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
8
|
-
/** Whether a Cookie header carries a flash cookie (readable or not). */
|
|
9
|
-
export function hasFlashCookie(cookieHeader) {
|
|
10
|
-
return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
|
|
11
|
-
}
|
|
12
|
-
/** The raw encoded flash payload out of a Cookie header, if present. */
|
|
13
|
-
export function matchFlashCookie(cookieHeader) {
|
|
14
|
-
const match = cookieHeader && cookieHeader.match(FLASH_MATCHER);
|
|
15
|
-
return match ? match[1] : undefined;
|
|
16
|
-
}
|
|
17
|
-
/** The Set-Cookie value clearing the flash cookie after it has been read. */
|
|
18
|
-
export function clearFlashCookie() {
|
|
19
|
-
return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
|
|
20
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { submitServerForm } from "./action.js";
|
package/dist/data/serverForms.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// The delegation fallback's lazy entry (see data/events.ts). Nothing imports
|
|
2
|
-
// this module statically — it exists so the dynamic import has a target that
|
|
3
|
-
// bundlers can keep as a split point: router-only apps never load the action
|
|
4
|
-
// machinery unless a server-action form actually submits.
|
|
5
|
-
export { submitServerForm } from "./action.js";
|