@solidjs/router 2.0.0-next.33 → 2.0.0-next.35
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 +24 -7
- package/dist/claims.d.ts +14 -6
- package/dist/claims.js +64 -28
- package/dist/data/action.js +201 -54
- package/dist/data/events.js +2 -2
- package/dist/index.js +382 -128
- package/dist/routing.d.ts +10 -2
- package/dist/routing.js +40 -45
- package/dist/utils.d.ts +29 -1
- package/dist/utils.js +48 -2
- package/package.json +1 -1
package/dist/routing.d.ts
CHANGED
|
@@ -165,9 +165,17 @@ export declare function useSearchParams<T extends SearchParams>(): [
|
|
|
165
165
|
(params: SetSearchParams, options?: Partial<NavigateOptions>) => void
|
|
166
166
|
];
|
|
167
167
|
export interface LinkState {
|
|
168
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* The location's pathname matches this link's or lives under it; the query
|
|
170
|
+
* is ignored. A link to the router's root (`/`, under the router's `base`)
|
|
171
|
+
* is exact-only, as is every link with `end`.
|
|
172
|
+
* Styling: `data-active`.
|
|
173
|
+
*/
|
|
169
174
|
active: () => boolean;
|
|
170
|
-
/**
|
|
175
|
+
/**
|
|
176
|
+
* The location matches this link exactly: same pathname and same query,
|
|
177
|
+
* parameter order and hash aside — what `aria-current="page"` reflects.
|
|
178
|
+
*/
|
|
171
179
|
current: () => boolean;
|
|
172
180
|
/** This link is the target of an in-flight navigation. Styling: `data-pending`. */
|
|
173
181
|
pending: () => boolean;
|
package/dist/routing.js
CHANGED
|
@@ -4,7 +4,7 @@ import { runWithOwner } from "solid-js";
|
|
|
4
4
|
import { DEV } from "solid-js";
|
|
5
5
|
import { createComponent, createContext, createMemo, createSignal, getOwner, isPending, latest, NotReadyError, untrack, useContext } from "solid-js";
|
|
6
6
|
import { clearFlashCookie, getRequestEvent, hasFlashCookie, isServer } from "@solidjs/web";
|
|
7
|
-
import { mockBase,
|
|
7
|
+
import { mockBase, createMemoObject, extractSearchParams, invariant, matchLink, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals, validateSearch } from "./utils.js";
|
|
8
8
|
import { HREF } from "./paths.js";
|
|
9
9
|
import { serverRouteOf, serverRouteArgs, serverRouteArgsEqual } from "./serverRouteShared.js";
|
|
10
10
|
const MAX_REDIRECTS = 100;
|
|
@@ -227,22 +227,11 @@ export const useLinkState = (href, options = {}) => {
|
|
|
227
227
|
const router = useRouter();
|
|
228
228
|
const location = router.location;
|
|
229
229
|
const to = useResolvedPath(() => String(href()));
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
const to_ = to();
|
|
233
|
-
return to_ === undefined ? undefined : comparablePath(to_);
|
|
234
|
-
});
|
|
235
|
-
const matches = (loc) => {
|
|
236
|
-
const path_ = path();
|
|
237
|
-
if (path_ === undefined)
|
|
238
|
-
return [false, false];
|
|
239
|
-
const exact = loc === path_;
|
|
240
|
-
return [exact || (!options.end && loc.startsWith(path_ + "/")), exact];
|
|
241
|
-
};
|
|
242
|
-
const state = createMemo(() => matches(decodeURI(comparablePath(location.pathname))));
|
|
230
|
+
const base = router.base.path();
|
|
231
|
+
const state = createMemo(() => matchLink(location, to(), base, options.end));
|
|
243
232
|
return {
|
|
244
|
-
active: () => state()
|
|
245
|
-
current: () => state()
|
|
233
|
+
active: createMemo(() => state().active),
|
|
234
|
+
current: createMemo(() => state().current),
|
|
246
235
|
// match the in-flight target explicitly (rather than active-while-routing)
|
|
247
236
|
// so the answer is the same from pure reads and from effects, which
|
|
248
237
|
// observe the committed location during a transition
|
|
@@ -250,7 +239,8 @@ export const useLinkState = (href, options = {}) => {
|
|
|
250
239
|
state(); // location dependency: mid-flight target swaps recompute
|
|
251
240
|
return (router.isRouting() &&
|
|
252
241
|
!!router.pendingTarget &&
|
|
253
|
-
|
|
242
|
+
matchLink({ pathname: router.pendingTarget.value, search: "" }, to(), base, options.end)
|
|
243
|
+
.active);
|
|
254
244
|
})
|
|
255
245
|
};
|
|
256
246
|
};
|
|
@@ -556,6 +546,7 @@ let flashDecoder;
|
|
|
556
546
|
export function provideFlashDecoder(decoder) {
|
|
557
547
|
flashDecoder || (flashDecoder = decoder);
|
|
558
548
|
}
|
|
549
|
+
const flashStates = new WeakMap();
|
|
559
550
|
let preloadIntent;
|
|
560
551
|
export function getIntent() {
|
|
561
552
|
return preloadIntent || useOptionalContext(RouterContextObj)?.intent?.();
|
|
@@ -592,56 +583,60 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
592
583
|
// unread outcome must not haunt a later request's render. Only detection
|
|
593
584
|
// and clearing happen here (the runtime's isomorphic half); the raw header
|
|
594
585
|
// is stashed and decoding waits for the action-provided codec, read when
|
|
595
|
-
// the lazily allocated submissions signal below first initializes.
|
|
596
|
-
|
|
586
|
+
// the lazily allocated submissions signal below first initializes. Both
|
|
587
|
+
// happen once per request: a router re-created for the same request finds
|
|
588
|
+
// the request's flash state (see `flashStates`) and neither clears again
|
|
589
|
+
// nor decodes again.
|
|
590
|
+
let flash;
|
|
597
591
|
if (isServer) {
|
|
598
592
|
const e = getRequestEvent();
|
|
599
593
|
if (e && !(e.router && e.router.submission)) {
|
|
600
|
-
|
|
601
|
-
if (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
594
|
+
flash = flashStates.get(e);
|
|
595
|
+
if (!flash) {
|
|
596
|
+
const cookieHeader = e.request.headers.get("cookie");
|
|
597
|
+
if (hasFlashCookie(cookieHeader)) {
|
|
598
|
+
flashStates.set(e, (flash = { cookieHeader }));
|
|
599
|
+
// one-shot: clear it even when unreadable so it can't haunt later renders
|
|
600
|
+
if (e.response && e.response.headers)
|
|
601
|
+
e.response.headers.append("Set-Cookie", clearFlashCookie());
|
|
602
|
+
}
|
|
606
603
|
}
|
|
607
604
|
}
|
|
608
605
|
}
|
|
609
|
-
// The decode, at most once per request: the decoder may answer with a
|
|
610
|
-
// Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
|
|
611
|
-
// async), and this cache is what keeps the parked read's rerun from
|
|
612
|
-
// restarting it — resumption finds the settled outcome and just reads it.
|
|
613
|
-
// A decoder that rejects reads as "no flash", matching the runtime's own
|
|
614
|
-
// malformed-cookie semantics.
|
|
615
|
-
let flashDecode;
|
|
616
606
|
// The seeding read, as a memo: NotReadyError must surface from a reactive
|
|
617
607
|
// node the graph can park and retry — never from router setup, which no
|
|
618
|
-
// boundary guards — and the memo bounds the recompute to this function
|
|
619
|
-
//
|
|
620
|
-
//
|
|
621
|
-
//
|
|
608
|
+
// boundary guards — and the memo bounds the recompute to this function.
|
|
609
|
+
// The decode runs at most once per request: the decoder may answer with a
|
|
610
|
+
// Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
|
|
611
|
+
// async), and the request's cached state is what keeps a parked read's
|
|
612
|
+
// rerun — or a re-created router's first read — from restarting it. A
|
|
613
|
+
// decoder that rejects reads as "no flash", matching the runtime's own
|
|
614
|
+
// malformed-cookie semantics. Created only when a flash cookie actually
|
|
615
|
+
// arrived (server-only by construction: `flash` is only ever set there), and
|
|
622
616
|
// - `lazy`: server memos compute eagerly by default — deferred to first
|
|
623
617
|
// read, a request whose submissions are never read never decodes;
|
|
624
618
|
// - `transparent`: the memo exists on the server only, so its owner
|
|
625
619
|
// must not consume a hydration-id slot — the client, which seeds
|
|
626
620
|
// submissions as [] without ever creating this memo, would miss it
|
|
627
621
|
// and every sibling id would shift.
|
|
628
|
-
const flashSubmission =
|
|
622
|
+
const flashSubmission = flash !== undefined
|
|
629
623
|
? createMemo(() => {
|
|
624
|
+
const state = flash;
|
|
630
625
|
if (!flashDecoder)
|
|
631
626
|
return undefined;
|
|
632
|
-
if (!
|
|
633
|
-
const promise = flashDecoder(
|
|
634
|
-
|
|
627
|
+
if (!state.decode) {
|
|
628
|
+
const promise = flashDecoder(state.cookieHeader).then(value => {
|
|
629
|
+
state.decode = { done: true, value };
|
|
635
630
|
}, () => {
|
|
636
|
-
|
|
631
|
+
state.decode = { done: true, value: undefined };
|
|
637
632
|
});
|
|
638
|
-
|
|
633
|
+
state.decode = { done: false, promise };
|
|
639
634
|
}
|
|
640
635
|
// SSR carries the Promise through NotReadyError so the parked
|
|
641
636
|
// reader can resume, exactly like the lazy matches above.
|
|
642
|
-
if (!
|
|
643
|
-
throw new NotReadyError(
|
|
644
|
-
return
|
|
637
|
+
if (!state.decode.done)
|
|
638
|
+
throw new NotReadyError(state.decode.promise);
|
|
639
|
+
return state.decode.value;
|
|
645
640
|
}, { lazy: true, transparent: true })
|
|
646
641
|
: undefined;
|
|
647
642
|
let submissions;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
import type { StandardSchemaV1, 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
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Pathname stripped of search/hash and trailing slash, percent-encoded the
|
|
6
|
+
* way `URL` (and so `location.pathname`) reports it, lowercased — the form
|
|
7
|
+
* link matching compares. Raw and encoded spellings (`/café`, `/caf%C3%A9`)
|
|
8
|
+
* meet; escapes are never decoded, so `%2F` is not a path separator.
|
|
9
|
+
*/
|
|
5
10
|
export declare const comparablePath: (path: string) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a URL pathname is the router's base path or under it, on a segment
|
|
13
|
+
* boundary: base `/app` covers `/app`, `/app/` and `/app/x`, not `/apple`.
|
|
14
|
+
* Case-insensitive; no base (`""` or `/`) covers every path.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isUnderBase(pathname: string, base: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The link-state rule shared by claimed anchors and `useLinkState`, given
|
|
19
|
+
* the location and a link's already-resolved target (path, optional query
|
|
20
|
+
* and hash):
|
|
21
|
+
*
|
|
22
|
+
* - `current` — same pathname and same query (parameter order and hash aside)
|
|
23
|
+
* - `active` — same pathname or one under it; the router's root (`base`, or
|
|
24
|
+
* `/` without one) only matches exactly, since it is a prefix of every
|
|
25
|
+
* page. `end` makes every link exact-only.
|
|
26
|
+
*/
|
|
27
|
+
export declare function matchLink(location: {
|
|
28
|
+
pathname: string;
|
|
29
|
+
search: string;
|
|
30
|
+
}, target: string | undefined, base: string, end?: boolean): {
|
|
31
|
+
active: boolean;
|
|
32
|
+
current: boolean;
|
|
33
|
+
};
|
|
6
34
|
export declare function resolvePath(base: string, path: string, from?: string): string | undefined;
|
|
7
35
|
export declare function invariant<T>(value: T | null | undefined, message: string): T;
|
|
8
36
|
export declare function joinPaths(from: string, to: string): string;
|
package/dist/utils.js
CHANGED
|
@@ -6,8 +6,54 @@ 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
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Pathname stripped of search/hash and trailing slash, percent-encoded the
|
|
11
|
+
* way `URL` (and so `location.pathname`) reports it, lowercased — the form
|
|
12
|
+
* link matching compares. Raw and encoded spellings (`/café`, `/caf%C3%A9`)
|
|
13
|
+
* meet; escapes are never decoded, so `%2F` is not a path separator.
|
|
14
|
+
*/
|
|
15
|
+
export const comparablePath = (path) => new URL(mockBase + normalizePath(path.split(/[?#]/, 1)[0])).pathname
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/\/$/, "");
|
|
18
|
+
/**
|
|
19
|
+
* Whether a URL pathname is the router's base path or under it, on a segment
|
|
20
|
+
* boundary: base `/app` covers `/app`, `/app/` and `/app/x`, not `/apple`.
|
|
21
|
+
* Case-insensitive; no base (`""` or `/`) covers every path.
|
|
22
|
+
*/
|
|
23
|
+
export function isUnderBase(pathname, base) {
|
|
24
|
+
const b = base.toLowerCase().replace(/\/+$/, "");
|
|
25
|
+
const p = pathname.toLowerCase();
|
|
26
|
+
return !b || !p || p === b || p.startsWith(b + "/");
|
|
27
|
+
}
|
|
28
|
+
/** A query string as an order-independent comparable string. */
|
|
29
|
+
const comparableQuery = (search) => {
|
|
30
|
+
const params = new URLSearchParams(search);
|
|
31
|
+
params.sort();
|
|
32
|
+
return params.toString();
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The link-state rule shared by claimed anchors and `useLinkState`, given
|
|
36
|
+
* the location and a link's already-resolved target (path, optional query
|
|
37
|
+
* and hash):
|
|
38
|
+
*
|
|
39
|
+
* - `current` — same pathname and same query (parameter order and hash aside)
|
|
40
|
+
* - `active` — same pathname or one under it; the router's root (`base`, or
|
|
41
|
+
* `/` without one) only matches exactly, since it is a prefix of every
|
|
42
|
+
* page. `end` makes every link exact-only.
|
|
43
|
+
*/
|
|
44
|
+
export function matchLink(location, target, base, end) {
|
|
45
|
+
if (target === undefined)
|
|
46
|
+
return { active: false, current: false };
|
|
47
|
+
const loc = comparablePath(location.pathname);
|
|
48
|
+
const path = comparablePath(target);
|
|
49
|
+
const exact = loc === path;
|
|
50
|
+
const hashless = target.split("#", 1)[0];
|
|
51
|
+
const q = hashless.indexOf("?");
|
|
52
|
+
return {
|
|
53
|
+
active: exact || (!end && path !== "" && path !== comparablePath(base) && loc.startsWith(path + "/")),
|
|
54
|
+
current: exact && comparableQuery(location.search) === comparableQuery(q < 0 ? "" : hashless.slice(q))
|
|
55
|
+
};
|
|
56
|
+
}
|
|
11
57
|
export function resolvePath(base, path, from) {
|
|
12
58
|
if (hasSchemeRegex.test(path)) {
|
|
13
59
|
return undefined;
|