@solidjs/router 2.0.0-next.35 → 2.0.0-next.37
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 +30 -7
- package/dist/claims.d.ts +5 -3
- package/dist/claims.js +14 -14
- package/dist/data/action.js +4 -1
- package/dist/data/events.js +9 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +245 -110
- package/dist/index.jsx +1 -0
- package/dist/paths.d.ts +23 -3
- package/dist/pending.d.ts +25 -0
- package/dist/pending.js +67 -0
- package/dist/routers/components.jsx +7 -4
- package/dist/routers/factory.d.ts +17 -2
- package/dist/routers/factory.jsx +22 -13
- package/dist/routers/scrollRestoration.d.ts +29 -5
- package/dist/routers/scrollRestoration.js +77 -32
- package/dist/routing.d.ts +7 -1
- package/dist/routing.js +28 -38
- package/dist/serverRouteComponent.js +5 -4
- package/dist/types.d.ts +27 -10
- package/package.json +6 -6
package/dist/routing.js
CHANGED
|
@@ -2,10 +2,11 @@ import { runWithOwner } from "solid-js";
|
|
|
2
2
|
// standalone import: `DEV` is undefined in solid's production build, so app
|
|
3
3
|
// bundlers fold `DEV &&` diagnostics out of shipped bundles
|
|
4
4
|
import { DEV } from "solid-js";
|
|
5
|
-
import { createComponent, createContext, createMemo, createSignal, getOwner,
|
|
5
|
+
import { createComponent, createContext, createMemo, createSignal, getOwner, NotReadyError, untrack, useContext } from "solid-js";
|
|
6
6
|
import { clearFlashCookie, getRequestEvent, hasFlashCookie, isServer } from "@solidjs/web";
|
|
7
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
|
+
import { linkPending, routingState } from "./pending.js";
|
|
9
10
|
import { serverRouteOf, serverRouteArgs, serverRouteArgsEqual } from "./serverRouteShared.js";
|
|
10
11
|
const MAX_REDIRECTS = 100;
|
|
11
12
|
/**
|
|
@@ -81,6 +82,8 @@ export const useLocation = () => useRouter().location;
|
|
|
81
82
|
/**
|
|
82
83
|
* Retrieves a signal that indicates whether the router is currently processing a navigation.
|
|
83
84
|
* Useful for showing pending navigation state while the next route and its data settle.
|
|
85
|
+
* This is the way to read routing state: `RouterContext` doesn't carry `isRouting`.
|
|
86
|
+
* For the in-flight destination, read the location with Solid's `isPending`/`latest`.
|
|
84
87
|
*
|
|
85
88
|
* @example
|
|
86
89
|
* ```js
|
|
@@ -93,7 +96,7 @@ export const useLocation = () => useRouter().location;
|
|
|
93
96
|
* );
|
|
94
97
|
* ```
|
|
95
98
|
*/
|
|
96
|
-
export const useIsRouting = () => useRouter()
|
|
99
|
+
export const useIsRouting = () => routingState(useRouter());
|
|
97
100
|
/**
|
|
98
101
|
* `useMatch` takes an accessor that returns the path and creates a `Memo` that returns match information if the current path matches the provided path.
|
|
99
102
|
* Useful for determining if a given path matches the current route.
|
|
@@ -237,10 +240,7 @@ export const useLinkState = (href, options = {}) => {
|
|
|
237
240
|
// observe the committed location during a transition
|
|
238
241
|
pending: createMemo(() => {
|
|
239
242
|
state(); // location dependency: mid-flight target swaps recompute
|
|
240
|
-
return (router
|
|
241
|
-
!!router.pendingTarget &&
|
|
242
|
-
matchLink({ pathname: router.pendingTarget.value, search: "" }, to(), base, options.end)
|
|
243
|
-
.active);
|
|
243
|
+
return linkPending(router, to(), base, options.end);
|
|
244
244
|
})
|
|
245
245
|
};
|
|
246
246
|
};
|
|
@@ -577,7 +577,11 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
577
577
|
_navigation: isServer ? undefined : 1
|
|
578
578
|
});
|
|
579
579
|
}
|
|
580
|
-
|
|
580
|
+
// The flushed world — what `latest(source)` answers — as the location's own
|
|
581
|
+
// memo sees it: it recomputes under the transition that carries a write,
|
|
582
|
+
// never for a write of this same tick (A28).
|
|
583
|
+
let flushed = initialSource;
|
|
584
|
+
const location = createLocation(() => (flushed = source()).value, () => source().state, utils.queryWrapper);
|
|
581
585
|
// The flash cookie is consumed eagerly: its one-shot clear (Set-Cookie)
|
|
582
586
|
// must be appended before streaming flushes the response headers, and an
|
|
583
587
|
// unread outcome must not haunt a later request's render. Only detection
|
|
@@ -643,7 +647,9 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
643
647
|
// NotReadyError's source must be a reactive async node, not the raw
|
|
644
648
|
// Promise. Keep one reader per boundary for this router owner so rejection
|
|
645
649
|
// is delivered through the graph's error channel and resolution wakes the
|
|
646
|
-
// parked matches computation.
|
|
650
|
+
// parked matches computation. `transparent`: the reader exists on the
|
|
651
|
+
// client only (the server parks `matches` on the promise directly), so it
|
|
652
|
+
// must not take a hydration id the server never allocated.
|
|
647
653
|
const routerOwner = getOwner();
|
|
648
654
|
const lazyReaders = new WeakMap();
|
|
649
655
|
const readLazySubtree = (record) => {
|
|
@@ -652,7 +658,7 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
652
658
|
read = runWithOwner(routerOwner, () => createMemo(() => {
|
|
653
659
|
const result = resolveLazySubtree(record);
|
|
654
660
|
return result instanceof Promise ? result.then(() => undefined) : undefined;
|
|
655
|
-
}, DEV && { name: "lazyRoutes" }));
|
|
661
|
+
}, { transparent: true, ...(DEV && { name: "lazyRoutes" }) }));
|
|
656
662
|
lazyReaders.set(record, read);
|
|
657
663
|
}
|
|
658
664
|
return read();
|
|
@@ -688,30 +694,16 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
688
694
|
}
|
|
689
695
|
return m;
|
|
690
696
|
}, DEV && { name: "matches" });
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
catch (e) {
|
|
696
|
-
if (e instanceof NotReadyError)
|
|
697
|
-
throw e;
|
|
698
|
-
}
|
|
699
|
-
location.search;
|
|
700
|
-
location.hash;
|
|
701
|
-
}), DEV && { name: "routingPending" });
|
|
702
|
-
const isRouting = () => routingPending() || isPending(source);
|
|
697
|
+
const headedLocation = () => (isServer ? untrack(source) : flushed);
|
|
698
|
+
// A flushed navigation write whose settle has not run is still in flight.
|
|
699
|
+
const unsettled = (change) => change._navigation !== undefined && !!integration.settled && !integration.settled(change);
|
|
703
700
|
const transitionIntent = () => {
|
|
704
|
-
|
|
701
|
+
const head = headedLocation();
|
|
702
|
+
if (!unsettled(head))
|
|
705
703
|
return;
|
|
706
|
-
const navigation =
|
|
704
|
+
const navigation = head._navigation;
|
|
707
705
|
return navigation === -1 ? "native" : navigation && navigation > 0 ? "navigate" : undefined;
|
|
708
706
|
};
|
|
709
|
-
const pendingNavigation = () => {
|
|
710
|
-
if (!isRouting())
|
|
711
|
-
return;
|
|
712
|
-
const target = latest(source);
|
|
713
|
-
return target._navigation && target._navigation > 0 ? target : undefined;
|
|
714
|
-
};
|
|
715
707
|
const buildParams = () => mergeParams(matches());
|
|
716
708
|
const wrapParams = utils.paramsWrapper
|
|
717
709
|
? (getParams) => utils.paramsWrapper(getParams, branches)
|
|
@@ -731,11 +723,9 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
731
723
|
location,
|
|
732
724
|
params,
|
|
733
725
|
wrapParams,
|
|
734
|
-
isRouting,
|
|
735
726
|
intent: transitionIntent,
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
},
|
|
727
|
+
_source: source,
|
|
728
|
+
_owner: routerOwner,
|
|
739
729
|
renderPath,
|
|
740
730
|
parsePath,
|
|
741
731
|
navigatorFactory,
|
|
@@ -795,10 +785,10 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
795
785
|
// the destination the leave guard is told all read it. A write of this
|
|
796
786
|
// same tick is not in it (A28) — see `compose` below.
|
|
797
787
|
//
|
|
798
|
-
// A redirect hop: the previous navigation
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
const headed =
|
|
788
|
+
// A redirect hop: the previous navigation's write has not settled — it
|
|
789
|
+
// is still held, or a guard is redirecting in its landing flush — so its
|
|
790
|
+
// destination was never shown.
|
|
791
|
+
const headed = headedLocation();
|
|
802
792
|
// A composed target (`setSearchParams`) is a function of where the
|
|
803
793
|
// router is heading, an unflushed write of this tick included. Only
|
|
804
794
|
// the integration's functional updater serves that value, so the
|
|
@@ -834,7 +824,7 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
834
824
|
const navigationDepth = !isServer &&
|
|
835
825
|
headed._navigation !== undefined &&
|
|
836
826
|
headed._navigation > 0 &&
|
|
837
|
-
(
|
|
827
|
+
unsettled(headed)
|
|
838
828
|
? headed._navigation
|
|
839
829
|
: 0;
|
|
840
830
|
if (navigationDepth >= MAX_REDIRECTS) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// // before: a client component exists only to make the call
|
|
9
9
|
// const getStory = query(storyView, "story");
|
|
10
10
|
// component: props => {
|
|
11
|
-
// const View =
|
|
11
|
+
// const View = dynamicComponent(() => getStory(props.params.id));
|
|
12
12
|
// return <View>{props.children}</View>;
|
|
13
13
|
// }
|
|
14
14
|
// // after
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
// A server component that takes other client positions — handlers, refs,
|
|
37
37
|
// named slots — has a client half, and that half is a client component; the
|
|
38
38
|
// helper does not pretend otherwise. Write the wrapper for that route.
|
|
39
|
-
import {
|
|
39
|
+
import { dynamicComponent } from "@solidjs/web";
|
|
40
40
|
import { createComponent, DEV } from "solid-js";
|
|
41
41
|
import { SERVER_ROUTE } from "./serverRouteShared.js";
|
|
42
42
|
/**
|
|
@@ -77,8 +77,9 @@ source) {
|
|
|
77
77
|
};
|
|
78
78
|
function render(args, routeProps) {
|
|
79
79
|
// The source may answer a component, a promise of one, or (a live query)
|
|
80
|
-
// successive components; `
|
|
81
|
-
|
|
80
|
+
// successive components; `dynamicComponent`'s memo lands each the same
|
|
81
|
+
// way. Never a tag, so the element runtime `dynamic` retains stays out.
|
|
82
|
+
const View = dynamicComponent(() => call(args()));
|
|
82
83
|
return createComponent(View, {
|
|
83
84
|
get children() {
|
|
84
85
|
return routeProps.children;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Component, Signal } from "solid-js";
|
|
1
|
+
import type { Component, Owner, Signal } from "solid-js";
|
|
2
2
|
import type { JSX, ResponseEnvelope } from "@solidjs/web";
|
|
3
3
|
declare module "@solidjs/web" {
|
|
4
4
|
interface RequestEvent {
|
|
@@ -118,13 +118,13 @@ export interface LocationWrite<S = unknown> extends Omit<LocationChange<S>, "val
|
|
|
118
118
|
export interface RouterIntegration {
|
|
119
119
|
signal: [get: () => LocationChange, set: (next: LocationWrite) => void];
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* in
|
|
124
|
-
* guard redirecting as the held route
|
|
125
|
-
*
|
|
121
|
+
* Whether a navigation write's settle has run: Solid's `onSettled`,
|
|
122
|
+
* registered beside the write, fired for the transition that carried it.
|
|
123
|
+
* Until then the write is in flight: its destination was never shown, so a
|
|
124
|
+
* `navigate()` issued meanwhile (a guard redirecting as the held route
|
|
125
|
+
* lands) is a hop of that navigation rather than a new one.
|
|
126
126
|
*/
|
|
127
|
-
|
|
127
|
+
settled?: (write: LocationChange) => boolean;
|
|
128
128
|
utils?: Partial<RouterUtils>;
|
|
129
129
|
}
|
|
130
130
|
export type Intent = "initial" | "native" | "navigate" | "preload";
|
|
@@ -330,11 +330,12 @@ export interface RouterContext {
|
|
|
330
330
|
params: Params;
|
|
331
331
|
wrapParams: (getParams: () => Params) => Params;
|
|
332
332
|
navigatorFactory: NavigatorFactory;
|
|
333
|
-
isRouting: () => boolean;
|
|
334
333
|
/** @internal Intent of the in-flight canonical location transition. */
|
|
335
334
|
intent?: () => Intent | undefined;
|
|
336
|
-
/** The
|
|
337
|
-
|
|
335
|
+
/** @internal The integration's location signal: the last flushed write. */
|
|
336
|
+
_source: () => LocationChange;
|
|
337
|
+
/** @internal The owner the router's own computations live under. */
|
|
338
|
+
_owner: Owner | null;
|
|
338
339
|
matches: () => RouteMatch[];
|
|
339
340
|
renderPath(path: string): string;
|
|
340
341
|
parsePath(str: string): string;
|
|
@@ -343,6 +344,22 @@ export interface RouterContext {
|
|
|
343
344
|
singleFlight: boolean;
|
|
344
345
|
submissions: Signal<Submission<any, any>[]>;
|
|
345
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* A plugin for the router's `links` option that extends the state it writes
|
|
349
|
+
* onto claimed anchors. Built once per router with its base path. The router
|
|
350
|
+
* ships `pendingLinks`, which adds `data-pending`.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```ts
|
|
354
|
+
* const Router = createRouter({ routes, links: pendingLinks });
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
export type LinksPlugin = (router: RouterContext, base: string) => {
|
|
358
|
+
/** The reactive read the claims sweep re-runs on. */
|
|
359
|
+
track(): unknown;
|
|
360
|
+
/** Whether an anchor resolving to `target` is the in-flight destination; read when `track()` is truthy. */
|
|
361
|
+
pending(target: string | undefined): boolean;
|
|
362
|
+
};
|
|
346
363
|
export interface BeforeLeaveEventArgs {
|
|
347
364
|
from: Location;
|
|
348
365
|
to: string | number;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "2.0.0-next.
|
|
9
|
+
"version": "2.0.0-next.37",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -37,23 +37,23 @@
|
|
|
37
37
|
"@rollup/plugin-babel": "6.0.4",
|
|
38
38
|
"@rollup/plugin-node-resolve": "15.3.0",
|
|
39
39
|
"@rollup/plugin-terser": "0.4.4",
|
|
40
|
-
"@solidjs/vite-plugin": "3.0.0-next.
|
|
41
|
-
"@solidjs/web": "^2.0.0-rc.
|
|
40
|
+
"@solidjs/vite-plugin": "3.0.0-next.47",
|
|
41
|
+
"@solidjs/web": "^2.0.0-rc.14",
|
|
42
42
|
"@types/jest": "^29.5.14",
|
|
43
43
|
"@types/node": "^22.10.0",
|
|
44
44
|
"babel-preset-solid": "^2.0.0-rc.2",
|
|
45
45
|
"jsdom": "^25.0.1",
|
|
46
46
|
"prettier": "^3.4.1",
|
|
47
47
|
"rollup": "^4.27.4",
|
|
48
|
-
"solid-js": "^2.0.0-rc.
|
|
48
|
+
"solid-js": "^2.0.0-rc.14",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vite": "^8.2.2",
|
|
51
51
|
"vitest": "^4.1.11"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@solidjs/web": "^2.0.0-rc.
|
|
54
|
+
"@solidjs/web": "^2.0.0-rc.14",
|
|
55
55
|
"filesystem-routing": ">=0.4.0",
|
|
56
|
-
"solid-js": "^2.0.0-rc.
|
|
56
|
+
"solid-js": "^2.0.0-rc.14"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"filesystem-routing": {
|