@tschk/moonshine-react-router 0.4.0 → 0.4.4
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 +4 -5
- package/package.json +11 -3
- package/src/link.ts +12 -8
- package/src/location.ts +12 -9
- package/src/navigation.ts +13 -11
package/README.md
CHANGED
|
@@ -65,12 +65,11 @@ provided — do not expect them to work by aliasing:
|
|
|
65
65
|
`<OutletProvider>` rather than derived from nested config.
|
|
66
66
|
- **Progressive enhancement**: `<Form>`, `<Scripts>`, `<Meta>`, `<Links>`,
|
|
67
67
|
`<ScrollRestoration>`, `<Await>`, `useFormAction`.
|
|
68
|
-
- **History
|
|
69
|
-
|
|
70
|
-
and `useNavigationType()` always returns `"POP"`.
|
|
68
|
+
- **History keys**: `location.state` and PUSH/POP/REPLACE navigation types are
|
|
69
|
+
supported, but `location.key` is URL-derived rather than unique per entry.
|
|
71
70
|
- **Error boundaries**: `useRouteError`, `errorElement`. `ErrorResponse` and
|
|
72
71
|
`isRouteErrorResponse` exist as value types only.
|
|
73
|
-
- `useSearchParams` reads
|
|
74
|
-
|
|
72
|
+
- `useSearchParams` reads the owning router's pathname-plus-query signal,
|
|
73
|
+
including its scoped server snapshot.
|
|
75
74
|
|
|
76
75
|
See [Moonshine README](../../README.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tschk/moonshine-react-router",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "react-router / Remix client API reimplemented on moonshine — drop react-router, keep the imports",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"type": "module",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"./aliases": "./src/aliases.ts"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@tschk/moonshine": "^0.
|
|
21
|
-
"@tschk/moonshine-router": "^0.
|
|
20
|
+
"@tschk/moonshine": "^0.4.4",
|
|
21
|
+
"@tschk/moonshine-router": "^0.4.4"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"react": ">=18",
|
|
@@ -39,5 +39,13 @@
|
|
|
39
39
|
"lint": "bunx oxlint .",
|
|
40
40
|
"typecheck": "bunx tsc --noEmit",
|
|
41
41
|
"test": "bun test"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"react": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"react-dom": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
42
50
|
}
|
|
43
51
|
}
|
package/src/link.ts
CHANGED
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
type MouseEvent,
|
|
18
18
|
type ReactNode,
|
|
19
19
|
} from "react";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
isExternal,
|
|
22
|
+
useNavigate as useMoonshineNavigate,
|
|
23
|
+
} from "@tschk/moonshine/router";
|
|
21
24
|
import { toHref, type To } from "./location";
|
|
22
25
|
import { useLocation } from "./navigation";
|
|
23
26
|
|
|
@@ -28,7 +31,6 @@ export type LinkProps = Omit<
|
|
|
28
31
|
to: To;
|
|
29
32
|
replace?: boolean;
|
|
30
33
|
preventScrollReset?: boolean;
|
|
31
|
-
/** Accepted for source compatibility; history state is not tracked. */
|
|
32
34
|
state?: unknown;
|
|
33
35
|
reloadDocument?: boolean;
|
|
34
36
|
/** `"intent"` warms the URL on hover, `"render"` on mount. */
|
|
@@ -51,7 +53,7 @@ export function Link({
|
|
|
51
53
|
to,
|
|
52
54
|
replace,
|
|
53
55
|
preventScrollReset,
|
|
54
|
-
state
|
|
56
|
+
state,
|
|
55
57
|
reloadDocument,
|
|
56
58
|
prefetch = "intent",
|
|
57
59
|
children,
|
|
@@ -59,8 +61,9 @@ export function Link({
|
|
|
59
61
|
onMouseEnter,
|
|
60
62
|
...rest
|
|
61
63
|
}: LinkProps): ReactNode {
|
|
64
|
+
const navigate = useMoonshineNavigate();
|
|
62
65
|
const href = toHref(to);
|
|
63
|
-
const external = isExternal(href, rest.target);
|
|
66
|
+
const external = isExternal(href, rest.target, rest.download);
|
|
64
67
|
const ref = useRef<HTMLAnchorElement>(null);
|
|
65
68
|
// Navigation runs in a transition so a slow subtree cannot block the click.
|
|
66
69
|
const [, startTransition] = useTransition();
|
|
@@ -122,7 +125,7 @@ export function Link({
|
|
|
122
125
|
}
|
|
123
126
|
event.preventDefault();
|
|
124
127
|
startTransition(() => {
|
|
125
|
-
navigate(href, { replace });
|
|
128
|
+
navigate(href, { replace, state });
|
|
126
129
|
if (preventScrollReset !== true) globalThis.scrollTo?.(0, 0);
|
|
127
130
|
});
|
|
128
131
|
},
|
|
@@ -190,11 +193,12 @@ export type NavigateProps = {
|
|
|
190
193
|
};
|
|
191
194
|
|
|
192
195
|
/** Redirects on mount, the declarative form of `useNavigate()`. */
|
|
193
|
-
export function Navigate({ to, replace }: NavigateProps): null {
|
|
196
|
+
export function Navigate({ to, replace, state }: NavigateProps): null {
|
|
197
|
+
const navigate = useMoonshineNavigate();
|
|
194
198
|
const href = toHref(to);
|
|
195
199
|
useEffect(() => {
|
|
196
|
-
navigate(href, { replace });
|
|
197
|
-
}, [href, replace]);
|
|
200
|
+
navigate(href, { replace, state });
|
|
201
|
+
}, [navigate, href, replace, state]);
|
|
198
202
|
return null;
|
|
199
203
|
}
|
|
200
204
|
|
package/src/location.ts
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Location shape shared by the react-router hooks.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* are read back from `window.location` on each render.
|
|
6
|
+
* History state is subscribed independently so same-path navigations update.
|
|
8
7
|
*/
|
|
9
8
|
import { useMemo, useReducer } from "react";
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
useLocation as useMoonshineLocation,
|
|
11
|
+
useHistory as useMoonshineHistory,
|
|
12
|
+
} from "@tschk/moonshine/router";
|
|
11
13
|
|
|
12
14
|
export type Location<S = unknown> = {
|
|
13
15
|
pathname: string;
|
|
@@ -35,7 +37,7 @@ export function toHref(to: To): string {
|
|
|
35
37
|
return `${to.pathname ?? ""}${search}${hash}`;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
function browserPart(part: "
|
|
40
|
+
function browserPart(part: "hash"): string {
|
|
39
41
|
if (typeof window === "undefined") return "";
|
|
40
42
|
return window.location[part];
|
|
41
43
|
}
|
|
@@ -47,18 +49,19 @@ export function useRerender(): () => void {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export function useLocationObject(): Location {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
+
const source = useMoonshineLocation();
|
|
53
|
+
const pathname = source.split("?", 1)[0]!;
|
|
54
|
+
const history = useMoonshineHistory();
|
|
55
|
+
const search = source.includes("?") ? source.slice(source.indexOf("?")) : "";
|
|
52
56
|
const hash = browserPart("hash");
|
|
53
57
|
return useMemo(
|
|
54
58
|
() => ({
|
|
55
59
|
pathname,
|
|
56
60
|
search,
|
|
57
61
|
hash,
|
|
58
|
-
|
|
59
|
-
state: null,
|
|
62
|
+
state: history.state,
|
|
60
63
|
key: `${pathname}${search}${hash}`,
|
|
61
64
|
}),
|
|
62
|
-
[pathname, search, hash],
|
|
65
|
+
[pathname, search, hash, history],
|
|
63
66
|
);
|
|
64
67
|
}
|
package/src/navigation.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { useCallback, useMemo } from "react";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
useNavigate as useMoonshineNavigate,
|
|
13
|
+
useHistory as useMoonshineHistory,
|
|
13
14
|
useParams as useMoonshineParams,
|
|
14
15
|
} from "@tschk/moonshine/router";
|
|
15
16
|
import {
|
|
@@ -22,7 +23,6 @@ import {
|
|
|
22
23
|
|
|
23
24
|
export type NavigateOptions = {
|
|
24
25
|
replace?: boolean;
|
|
25
|
-
/** Accepted for source compatibility; history state is not tracked. */
|
|
26
26
|
state?: unknown;
|
|
27
27
|
preventScrollReset?: boolean;
|
|
28
28
|
};
|
|
@@ -37,6 +37,7 @@ export function useLocation(): Location {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export function useNavigate(): NavigateFunction {
|
|
40
|
+
const navigate = useMoonshineNavigate();
|
|
40
41
|
const rerender = useRerender();
|
|
41
42
|
return useCallback(
|
|
42
43
|
(to: To | number, options?: NavigateOptions) => {
|
|
@@ -44,11 +45,14 @@ export function useNavigate(): NavigateFunction {
|
|
|
44
45
|
globalThis.history?.go(to);
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
|
-
navigate(toHref(to), {
|
|
48
|
+
navigate(toHref(to), {
|
|
49
|
+
replace: options?.replace,
|
|
50
|
+
state: options?.state,
|
|
51
|
+
});
|
|
48
52
|
if (options?.preventScrollReset !== true) globalThis.scrollTo?.(0, 0);
|
|
49
53
|
rerender();
|
|
50
54
|
},
|
|
51
|
-
[rerender],
|
|
55
|
+
[navigate, rerender],
|
|
52
56
|
) as NavigateFunction;
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -67,6 +71,7 @@ export type SetURLSearchParams = (
|
|
|
67
71
|
) => void;
|
|
68
72
|
|
|
69
73
|
export function useSearchParams(): [URLSearchParams, SetURLSearchParams] {
|
|
74
|
+
const navigate = useMoonshineNavigate();
|
|
70
75
|
const location = useLocationObject();
|
|
71
76
|
const rerender = useRerender();
|
|
72
77
|
const params = useMemo(
|
|
@@ -85,11 +90,11 @@ export function useSearchParams(): [URLSearchParams, SetURLSearchParams] {
|
|
|
85
90
|
const query = resolved.toString();
|
|
86
91
|
navigate(`${location.pathname}${query ? `?${query}` : ""}`, {
|
|
87
92
|
replace: options?.replace,
|
|
93
|
+
state: options?.state,
|
|
88
94
|
});
|
|
89
|
-
// A query-only navigation leaves the pathname signal unchanged.
|
|
90
95
|
rerender();
|
|
91
96
|
},
|
|
92
|
-
[location.pathname, location.search, rerender],
|
|
97
|
+
[navigate, location.pathname, location.search, rerender],
|
|
93
98
|
);
|
|
94
99
|
|
|
95
100
|
return [params, setSearchParams];
|
|
@@ -129,10 +134,7 @@ export function useHref(to: To): string {
|
|
|
129
134
|
|
|
130
135
|
export type NavigationType = "POP" | "PUSH" | "REPLACE";
|
|
131
136
|
|
|
132
|
-
/**
|
|
133
|
-
* Moonshine does not record how the current entry was reached, so this always
|
|
134
|
-
* reports `POP`.
|
|
135
|
-
*/
|
|
137
|
+
/** How the current history entry was reached. */
|
|
136
138
|
export function useNavigationType(): NavigationType {
|
|
137
|
-
return
|
|
139
|
+
return useMoonshineHistory().action;
|
|
138
140
|
}
|