@solidjs/router 1.0.0 → 2.0.0-next.13
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 +606 -697
- package/dist/claims.d.ts +21 -0
- package/dist/claims.js +115 -0
- package/dist/data/action.d.ts +40 -11
- package/dist/data/action.js +307 -101
- package/dist/data/events.d.ts +8 -0
- package/dist/data/events.js +24 -23
- package/dist/data/index.d.ts +2 -4
- package/dist/data/index.js +2 -4
- package/dist/data/query.d.ts +1 -3
- package/dist/data/query.js +75 -32
- package/dist/data/serverForms.d.ts +1 -0
- package/dist/data/serverForms.js +5 -0
- package/dist/fs.d.ts +94 -0
- package/dist/fs.js +51 -0
- package/dist/index.d.ts +24 -3
- package/dist/index.js +1949 -1204
- 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 +12 -26
- package/dist/routers/components.jsx +68 -54
- package/dist/routers/factory.d.ts +121 -0
- package/dist/routers/factory.jsx +154 -0
- package/dist/routers/history.d.ts +26 -0
- package/dist/routers/history.js +180 -0
- package/dist/routers/index.d.ts +5 -11
- package/dist/routers/index.js +2 -6
- package/dist/routers/scrollRestoration.d.ts +10 -1
- package/dist/routers/scrollRestoration.js +49 -11
- package/dist/routing.d.ts +96 -52
- package/dist/routing.js +429 -172
- package/dist/server.d.ts +47 -0
- package/dist/server.js +156 -0
- package/dist/types.d.ts +158 -44
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +2 -0
- package/package.json +12 -7
- package/dist/components.d.ts +0 -31
- package/dist/components.jsx +0 -40
- package/dist/data/createAsync.d.ts +0 -32
- package/dist/data/createAsync.js +0 -96
- package/dist/data/response.d.ts +0 -4
- package/dist/data/response.js +0 -42
- 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 -17
- package/dist/routers/Router.js +0 -59
- 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 -41
package/dist/claims.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
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/action.d.ts
CHANGED
|
@@ -1,17 +1,46 @@
|
|
|
1
|
-
import { JSX } from "
|
|
2
|
-
import type {
|
|
1
|
+
import { type JSX } from "@solidjs/web";
|
|
2
|
+
import type { RouterContext, Submission, NarrowResponse } from "../types.js";
|
|
3
3
|
export type Action<T extends Array<any>, U, V = T> = (T extends [FormData | URLSearchParams] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<NarrowResponse<U>>) & {
|
|
4
4
|
url: string;
|
|
5
5
|
with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => Promise<NarrowResponse<U>>, ...args: A): Action<B, U, V>;
|
|
6
|
+
onSubmit(hook: (...args: V extends Array<any> ? V : T) => void): Action<T, U, V>;
|
|
7
|
+
onSettled(hook: (submission: Submission<V extends Array<any> ? V : T, NarrowResponse<U>>) => void): Action<T, U, V>;
|
|
6
8
|
};
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
type ActionFactory = {
|
|
10
|
+
<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, name?: string): Action<T, U>;
|
|
11
|
+
<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, options?: {
|
|
12
|
+
name?: string;
|
|
13
|
+
}): Action<T, U>;
|
|
10
14
|
};
|
|
11
|
-
export declare
|
|
15
|
+
export declare const actions: Map<string, Action<any, any, any>>;
|
|
16
|
+
/**
|
|
17
|
+
* The document-delegation submit handler for router actions. Lives here —
|
|
18
|
+
* not in events.ts — so the router's event wiring holds no static reference
|
|
19
|
+
* to the action module; `installRouterIntegrations` slots it in when the
|
|
20
|
+
* first action is created on the client.
|
|
21
|
+
*/
|
|
22
|
+
export declare function handleFormAction(evt: SubmitEvent, router: RouterContext, actionBase: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Entry point for delegation's lazy fallback (data/events.ts): when no form
|
|
25
|
+
* handler was ever installed — no action module in the client graph at all —
|
|
26
|
+
* the router intercepts posts to server-action urls synchronously and loads
|
|
27
|
+
* this module to run them. The FormData was captured at submit time; only
|
|
28
|
+
* the enctype conversion and the generic invocation happen here.
|
|
29
|
+
*/
|
|
30
|
+
export declare function submitServerForm(router: RouterContext, url: string, form: HTMLFormElement, data: FormData): void;
|
|
31
|
+
export declare function useSubmissions<T extends Array<any>, U, V>(fn: Action<T, U, V>, filter?: (input: V) => boolean): Submission<V, NarrowResponse<U>>[];
|
|
12
32
|
export declare function useAction<T extends Array<any>, U, V>(action: Action<T, U, V>): (...args: Parameters<Action<T, U, V>>) => Promise<NarrowResponse<U>>;
|
|
13
|
-
export declare
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
33
|
+
export declare const action: ActionFactory;
|
|
34
|
+
/**
|
|
35
|
+
* Registers the router as the single-flight consumer of the server function
|
|
36
|
+
* transport. Subscribing is the opt-in: while registered, the transport
|
|
37
|
+
* sends the `X-Single-Flight` request header on mutations and delivers the
|
|
38
|
+
* folded payload here — fresh route data is seeded into the `query` cache
|
|
39
|
+
* and the envelope metadata (redirect `Location`, `X-Revalidate` keys) is
|
|
40
|
+
* applied, all before the action sees its plain return value. Called by the
|
|
41
|
+
* Router component on the client unless `singleFlight={false}`, which now
|
|
42
|
+
* simply means "never subscribe" — no consumer, no request header, no
|
|
43
|
+
* collection work on the server. Returns the unsubscribe function.
|
|
44
|
+
*/
|
|
45
|
+
export declare function setupFlightDataConsumer(router: RouterContext): () => void;
|
|
46
|
+
export {};
|