@octanejs/tanstack-router 0.2.3 → 0.2.5
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/package.json +4 -4
- package/src/Asset.tsrx +15 -19
- package/src/Await.tsrx +1 -3
- package/src/CatchBoundary.tsrx +3 -8
- package/src/ClientOnly.tsrx +7 -4
- package/src/Link.tsrx +13 -11
- package/src/Match.tsrx +21 -21
- package/src/MatchRoute.tsrx +8 -7
- package/src/Matches.tsrx +2 -3
- package/src/Outlet.tsrx +2 -4
- package/src/RouteNotFound.tsrx +1 -1
- package/src/RouterProvider.tsrx +4 -5
- package/src/SafeFragment.tsrx +1 -3
- package/src/Transitioner.tsrx +20 -23
- package/src/not-found.tsrx +9 -5
- package/src/scroll-restoration.tsrx +1 -3
- package/src/ssr/RouterClient.tsrx +1 -1
- package/src/useBlocker.tsrx +15 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/tanstack-router",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -58,13 +58,13 @@
|
|
|
58
58
|
"isbot": "^5.1.22"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"octane": "^0.
|
|
61
|
+
"octane": "^0.11.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@tanstack/react-router": "1.170.41",
|
|
65
|
-
"@tsrx/react": "^0.
|
|
65
|
+
"@tsrx/react": "^0.4.5",
|
|
66
66
|
"esbuild": "^0.28.1",
|
|
67
|
-
"octane": "0.
|
|
67
|
+
"octane": "0.11.0",
|
|
68
68
|
"react": "19.2.7",
|
|
69
69
|
"react-dom": "19.2.7",
|
|
70
70
|
"vitest": "^4.1.10"
|
package/src/Asset.tsrx
CHANGED
|
@@ -4,11 +4,10 @@ import type { RouterManagedTag } from '@tanstack/router-core';
|
|
|
4
4
|
import { useRouter } from './context.ts';
|
|
5
5
|
import { useHydrated } from './ClientOnly.tsrx';
|
|
6
6
|
|
|
7
|
-
export type AssetProps =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
7
|
+
export type AssetProps = RouterManagedTag & {
|
|
8
|
+
assetKey: string;
|
|
9
|
+
target: 'head' | 'body';
|
|
10
|
+
};
|
|
12
11
|
|
|
13
12
|
type ManagedAssetProps = AssetProps & { managedKey?: string };
|
|
14
13
|
|
|
@@ -40,8 +39,10 @@ function setAttributes(element: Element, attrs: RouterManagedTag['attrs']) {
|
|
|
40
39
|
}
|
|
41
40
|
for (const [name, value] of Object.entries(attrs)) {
|
|
42
41
|
if (
|
|
43
|
-
name === 'suppressHydrationWarning' ||
|
|
44
|
-
|
|
42
|
+
name === 'suppressHydrationWarning' ||
|
|
43
|
+
value === undefined ||
|
|
44
|
+
value === null ||
|
|
45
|
+
value === false
|
|
45
46
|
) {
|
|
46
47
|
continue;
|
|
47
48
|
}
|
|
@@ -67,8 +68,7 @@ function claim(element: Element) {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function mountAsset(props: ManagedAssetProps) {
|
|
70
|
-
const parent =
|
|
71
|
-
props.target === 'head' ? document.head : document.body;
|
|
71
|
+
const parent = props.target === 'head' ? document.head : document.body;
|
|
72
72
|
const managedKey = props.managedKey ?? props.assetKey;
|
|
73
73
|
const existing = findManagedElement(parent, managedKey);
|
|
74
74
|
if (existing) {
|
|
@@ -95,23 +95,19 @@ export function Asset(props: AssetProps) @{
|
|
|
95
95
|
// every navigation, and an identity dependency would remove and re-add
|
|
96
96
|
// unchanged assets such as the stylesheet.
|
|
97
97
|
const attrsKey = JSON.stringify(props.attrs ?? null);
|
|
98
|
-
useLayoutEffect(
|
|
99
|
-
props
|
|
100
|
-
managedKey,
|
|
101
|
-
|
|
102
|
-
attrsKey,
|
|
103
|
-
props.children,
|
|
104
|
-
props.target,
|
|
105
|
-
]);
|
|
98
|
+
useLayoutEffect(
|
|
99
|
+
() => mountAsset(props),
|
|
100
|
+
[props.assetKey, managedKey, props.tag, attrsKey, props.children, props.target],
|
|
101
|
+
);
|
|
106
102
|
}
|
|
107
103
|
|
|
108
104
|
const visibleTag =
|
|
109
|
-
(isServer ?? router.isServer) || props.target === 'body' && !hydrated ? props.tag : undefined;
|
|
105
|
+
(isServer ?? router.isServer) || (props.target === 'body' && !hydrated) ? props.tag : undefined;
|
|
110
106
|
|
|
111
107
|
@switch (visibleTag) {
|
|
112
108
|
@case 'title': {
|
|
113
109
|
<title {...props.attrs} data-tsr-managed-key={managedKey}>
|
|
114
|
-
{props.children ?? '' as string}
|
|
110
|
+
{props.children ?? ('' as string)}
|
|
115
111
|
</title>
|
|
116
112
|
}
|
|
117
113
|
@case 'meta': {
|
package/src/Await.tsrx
CHANGED
package/src/CatchBoundary.tsrx
CHANGED
|
@@ -24,9 +24,7 @@ export function CatchBoundary(props: {
|
|
|
24
24
|
onCatch?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
25
25
|
}) @{
|
|
26
26
|
@try {
|
|
27
|
-
<>
|
|
28
|
-
{props.children}
|
|
29
|
-
</>
|
|
27
|
+
<>{props.children}</>
|
|
30
28
|
} @catch (error, reset) {
|
|
31
29
|
<CatchErrorView
|
|
32
30
|
error={error}
|
|
@@ -55,8 +53,7 @@ function CatchErrorView(props: {
|
|
|
55
53
|
|
|
56
54
|
// Reset when the reset key rolls (navigation). Skip the mount render — the key
|
|
57
55
|
// captured while catching is the baseline.
|
|
58
|
-
const resetKey =
|
|
59
|
-
props.getResetKey ? props.getResetKey() : undefined;
|
|
56
|
+
const resetKey = props.getResetKey ? props.getResetKey() : undefined;
|
|
60
57
|
const keyRef = useRef(resetKey);
|
|
61
58
|
useLayoutEffect(() => {
|
|
62
59
|
if (keyRef.current !== resetKey) {
|
|
@@ -93,9 +90,7 @@ export function ErrorComponent(props: {
|
|
|
93
90
|
<div style="height:.25rem"></div>
|
|
94
91
|
@if (show) {
|
|
95
92
|
<div>
|
|
96
|
-
<pre
|
|
97
|
-
style="font-size:.7em;border:1px solid red;border-radius:.25rem;padding:.3rem;color:red;overflow:auto"
|
|
98
|
-
>
|
|
93
|
+
<pre style="font-size:.7em;border:1px solid red;border-radius:.25rem;padding:.3rem;color:red;overflow:auto">
|
|
99
94
|
@if (props.error?.message) {
|
|
100
95
|
<code>{props.error.message as string}</code>
|
|
101
96
|
}
|
package/src/ClientOnly.tsrx
CHANGED
|
@@ -13,12 +13,15 @@ const subscribe = () => () => {};
|
|
|
13
13
|
|
|
14
14
|
export function useHydrated(...args: Array<unknown>) {
|
|
15
15
|
const [, slot] = splitSlot(args);
|
|
16
|
-
return useSyncExternalStore(
|
|
16
|
+
return useSyncExternalStore(
|
|
17
|
+
subscribe,
|
|
18
|
+
() => true,
|
|
19
|
+
() => false,
|
|
20
|
+
subSlot(slot, 'hydrated'),
|
|
21
|
+
);
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export function ClientOnly(props: { children?: OctaneNode; fallback?: OctaneNode }) @{
|
|
20
25
|
const hydrated = useHydrated();
|
|
21
|
-
<>
|
|
22
|
-
{hydrated ? props.children : props.fallback ?? null}
|
|
23
|
-
</>
|
|
26
|
+
<>{hydrated ? props.children : (props.fallback ?? null)}</>
|
|
24
27
|
}
|
package/src/Link.tsrx
CHANGED
|
@@ -23,18 +23,20 @@ export function Link<
|
|
|
23
23
|
TMaskFrom extends string = TFrom,
|
|
24
24
|
TMaskTo extends string = '',
|
|
25
25
|
>(props: LinkComponentProps<'a', TRouter, TFrom, TTo, TMaskFrom, TMaskTo>) @{
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const {
|
|
27
|
+
_asChild: AsChild,
|
|
28
|
+
children,
|
|
29
|
+
...rest
|
|
30
|
+
} = props as typeof props & {
|
|
31
|
+
_asChild?: any;
|
|
32
|
+
};
|
|
30
33
|
const linkProps = useLinkProps(
|
|
31
34
|
rest as UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
32
|
-
) as
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
};
|
|
35
|
+
) as OctaneAnchorProps & {
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
'data-status'?: string;
|
|
38
|
+
'data-transitioning'?: string;
|
|
39
|
+
};
|
|
38
40
|
const { disabled: _disabled, ...aProps } = linkProps;
|
|
39
41
|
|
|
40
42
|
const resolvedChildren =
|
|
@@ -48,6 +50,6 @@ export function Link<
|
|
|
48
50
|
@if (AsChild) {
|
|
49
51
|
<AsChild {...linkProps}>{resolvedChildren}</AsChild>
|
|
50
52
|
} @else {
|
|
51
|
-
<a {...aProps as Record<string, any>}>{resolvedChildren}</a>
|
|
53
|
+
<a {...(aProps as Record<string, any>)}>{resolvedChildren}</a>
|
|
52
54
|
}
|
|
53
55
|
}
|
package/src/Match.tsrx
CHANGED
|
@@ -52,15 +52,16 @@ export function Match(props: { routeId: string }) @{
|
|
|
52
52
|
const routeErrorComponent = route.options.errorComponent ?? router.options.defaultErrorComponent;
|
|
53
53
|
const routeOnCatch = route.options.onCatch ?? router.options.defaultOnCatch;
|
|
54
54
|
const routeNotFoundComponent = route.isRoot
|
|
55
|
-
? route.options.notFoundComponent ?? router.options.notFoundRoute?.options.component
|
|
55
|
+
? (route.options.notFoundComponent ?? router.options.notFoundRoute?.options.component)
|
|
56
56
|
: route.options.notFoundComponent;
|
|
57
57
|
const resolvedNoSsr = matchState.ssr === false || matchState.ssr === 'data-only';
|
|
58
58
|
|
|
59
59
|
// Boundary presence, per upstream MatchView. The root route only gets a
|
|
60
60
|
// Suspense boundary when explicitly opted in via wrapInSuspense.
|
|
61
61
|
const suspenseSignal =
|
|
62
|
-
route.options.wrapInSuspense ??
|
|
63
|
-
|
|
62
|
+
route.options.wrapInSuspense ??
|
|
63
|
+
PendingComponent ??
|
|
64
|
+
(routeErrorComponent as ErrorRouteComponent | undefined)?.preload;
|
|
64
65
|
const SuspenseWrap =
|
|
65
66
|
(!route.isRoot || route.options.wrapInSuspense || resolvedNoSsr) &&
|
|
66
67
|
(suspenseSignal || resolvedNoSsr)
|
|
@@ -69,11 +70,10 @@ export function Match(props: { routeId: string }) @{
|
|
|
69
70
|
const CatchWrap = routeErrorComponent ? CatchBoundary : SafeFragment;
|
|
70
71
|
const NotFoundWrap = routeNotFoundComponent ? CatchNotFound : SafeFragment;
|
|
71
72
|
const ShellComponent = route.isRoot
|
|
72
|
-
? (route.options as RootRouteOptions).shellComponent ?? SafeFragment
|
|
73
|
+
? ((route.options as RootRouteOptions).shellComponent ?? SafeFragment)
|
|
73
74
|
: SafeFragment;
|
|
74
75
|
|
|
75
|
-
const pendingElement =
|
|
76
|
-
PendingComponent ? createElement(PendingComponent, {}) : null;
|
|
76
|
+
const pendingElement = PendingComponent ? createElement(PendingComponent, {}) : null;
|
|
77
77
|
const parentRouteId = route.parentRoute?.id;
|
|
78
78
|
|
|
79
79
|
<ShellComponent>
|
|
@@ -95,8 +95,9 @@ export function Match(props: { routeId: string }) @{
|
|
|
95
95
|
(error as any).routeId ??= routeId;
|
|
96
96
|
|
|
97
97
|
if (
|
|
98
|
-
!routeNotFoundComponent ||
|
|
99
|
-
|
|
98
|
+
!routeNotFoundComponent ||
|
|
99
|
+
(error.routeId && error.routeId !== routeId) ||
|
|
100
|
+
(!error.routeId && !route.isRoot)
|
|
100
101
|
) {
|
|
101
102
|
throw error;
|
|
102
103
|
}
|
|
@@ -129,19 +130,18 @@ function MatchInner(props: { routeId: string }) {
|
|
|
129
130
|
|
|
130
131
|
const Comp = route.options.component ?? router.options.defaultComponent;
|
|
131
132
|
const remountFn = route.options.remountDeps ?? router.options.defaultRemountDeps;
|
|
132
|
-
const remountDeps =
|
|
133
|
-
remountFn
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Comp ? createElement(Comp, key !== undefined ? { key } : {}) : createElement(Outlet, {});
|
|
133
|
+
const remountDeps = remountFn
|
|
134
|
+
? remountFn({
|
|
135
|
+
routeId,
|
|
136
|
+
loaderDeps: match.loaderDeps,
|
|
137
|
+
params: match._strictParams,
|
|
138
|
+
search: match._strictSearch,
|
|
139
|
+
})
|
|
140
|
+
: undefined;
|
|
141
|
+
const key = remountDeps ? JSON.stringify(remountDeps) : undefined;
|
|
142
|
+
const out = Comp
|
|
143
|
+
? createElement(Comp, key !== undefined ? { key } : {})
|
|
144
|
+
: createElement(Outlet, {});
|
|
145
145
|
|
|
146
146
|
if (match.status === 'pending') {
|
|
147
147
|
// router-core owns the pending lifecycle (pendingMs, pendingMinMs, redirects
|
package/src/MatchRoute.tsrx
CHANGED
|
@@ -13,10 +13,13 @@ export function useMatchRoute() {
|
|
|
13
13
|
useStore(router.stores.location, (l: any) => l.href as string);
|
|
14
14
|
useStore(router.stores.resolvedLocation, (l: any) => l?.href as string | undefined);
|
|
15
15
|
useStore(router.stores.status, (s: any) => s as string);
|
|
16
|
-
return useCallback(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
return useCallback(
|
|
17
|
+
(opts: Record<string, any>) => {
|
|
18
|
+
const { pending, caseSensitive, fuzzy, includeSearch, ...rest } = opts;
|
|
19
|
+
return router.matchRoute(rest as any, { pending, caseSensitive, fuzzy, includeSearch });
|
|
20
|
+
},
|
|
21
|
+
[router],
|
|
22
|
+
);
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
// Props are permissive (`Record<string, any>` + optional render-prop children) —
|
|
@@ -31,7 +34,5 @@ export function MatchRoute(props: Record<string, any>) @{
|
|
|
31
34
|
: params
|
|
32
35
|
? props.children
|
|
33
36
|
: null;
|
|
34
|
-
<>
|
|
35
|
-
{out}
|
|
36
|
-
</>
|
|
37
|
+
<>{out}</>
|
|
37
38
|
}
|
package/src/Matches.tsrx
CHANGED
|
@@ -24,8 +24,7 @@ export function Matches() @{
|
|
|
24
24
|
const rootRoute = (router.routesById as any)[rootRouteId];
|
|
25
25
|
const PendingComponent =
|
|
26
26
|
rootRoute.options.pendingComponent ?? router.options.defaultPendingComponent;
|
|
27
|
-
const pendingElement =
|
|
28
|
-
PendingComponent ? createElement(PendingComponent, {}) : null;
|
|
27
|
+
const pendingElement = PendingComponent ? createElement(PendingComponent, {}) : null;
|
|
29
28
|
const InnerWrap = router.options.InnerWrap ?? SafeFragment;
|
|
30
29
|
|
|
31
30
|
// `createRouter({ scrollRestoration: true })` wires scroll save/restore here, so
|
|
@@ -49,7 +48,7 @@ function MatchesInner() @{
|
|
|
49
48
|
// navigation transition is pending, render the matches router-core expects
|
|
50
49
|
// (`acknowledgement[0]`) so the committed tree equals what it awaits, then
|
|
51
50
|
// settle that render acknowledgement from a layout effect once it lands.
|
|
52
|
-
const acknowledgement: Array<any> = (router as any)._rendered ??= [];
|
|
51
|
+
const acknowledgement: Array<any> = ((router as any)._rendered ??= []);
|
|
53
52
|
const matches = useStore(router.stores.matches, (m: any) => acknowledgement[0] ?? m);
|
|
54
53
|
const rootMatch = matches[0];
|
|
55
54
|
const routeId = rootMatch?.routeId as string | undefined;
|
package/src/Outlet.tsrx
CHANGED
|
@@ -39,10 +39,8 @@ export function Outlet() @{
|
|
|
39
39
|
// whose fallback is the router's defaultPendingComponent (react-router's
|
|
40
40
|
// Outlet does the same) — the outermost pending UI for the initial load.
|
|
41
41
|
const DefaultPending = router.options.defaultPendingComponent;
|
|
42
|
-
const RootSuspense =
|
|
43
|
-
|
|
44
|
-
const pendingElement =
|
|
45
|
-
DefaultPending ? createElement(DefaultPending, {}) : null;
|
|
42
|
+
const RootSuspense = routeId === rootRouteId ? Suspense : SafeFragment;
|
|
43
|
+
const pendingElement = DefaultPending ? createElement(DefaultPending, {}) : null;
|
|
46
44
|
|
|
47
45
|
@if (notFound.global) {
|
|
48
46
|
<RouteNotFound routeId={routeId} error={notFound.error} />
|
package/src/RouteNotFound.tsrx
CHANGED
|
@@ -16,7 +16,7 @@ export function RouteNotFound(props: { routeId: string; error?: any }) @{
|
|
|
16
16
|
const NotFound = route.options.notFoundComponent ?? router.options.defaultNotFoundComponent;
|
|
17
17
|
|
|
18
18
|
@if (NotFound) {
|
|
19
|
-
<NotFound {...props.error ?? {}} />
|
|
19
|
+
<NotFound {...(props.error ?? {})} />
|
|
20
20
|
} @else {
|
|
21
21
|
<p>Not Found</p>
|
|
22
22
|
}
|
package/src/RouterProvider.tsrx
CHANGED
|
@@ -14,11 +14,10 @@ import { SafeFragment } from './SafeFragment.tsrx';
|
|
|
14
14
|
// Upstream's `RouterProps` is `Omit<RouterOptions<…>, 'context'> & { router }` —
|
|
15
15
|
// extra props reconfigure the instance via `router.update()`. The octane binding
|
|
16
16
|
// keeps the router-typed member strict and the option passthrough permissive.
|
|
17
|
-
export type RouterProps =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} & Record<string, any>;
|
|
17
|
+
export type RouterProps = {
|
|
18
|
+
router: AnyRouter;
|
|
19
|
+
context?: Record<string, any>;
|
|
20
|
+
} & Record<string, any>;
|
|
22
21
|
|
|
23
22
|
export function RouterContextProvider(props: RouterProps & { children?: OctaneNode }) @{
|
|
24
23
|
const { router, children, ...rest } = props;
|
package/src/SafeFragment.tsrx
CHANGED
|
@@ -9,7 +9,5 @@
|
|
|
9
9
|
// it stands in for Suspense/CatchBoundary/CatchNotFound/shellComponent at dynamic
|
|
10
10
|
// boundary slots, so it must accept any boundary's props and render only children.
|
|
11
11
|
export function SafeFragment(props: any) @{
|
|
12
|
-
<>
|
|
13
|
-
{props.children}
|
|
14
|
-
</>
|
|
12
|
+
<>{props.children}</>
|
|
15
13
|
}
|
package/src/Transitioner.tsrx
CHANGED
|
@@ -33,19 +33,18 @@ export function useTransitioner() {
|
|
|
33
33
|
const router = useRouter() as any;
|
|
34
34
|
const [, forceRender] = useState<object>();
|
|
35
35
|
|
|
36
|
-
const acknowledgement: Array<any> = router._rendered ??= [];
|
|
36
|
+
const acknowledgement: Array<any> = (router._rendered ??= []);
|
|
37
37
|
|
|
38
|
-
router.startTransition = (fn: () => void, expected: unknown) =>
|
|
39
|
-
resolve
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
38
|
+
router.startTransition = (fn: () => void, expected: unknown) =>
|
|
39
|
+
new Promise<boolean>((resolve) => {
|
|
40
|
+
// A newer transition supersedes an unrendered older one.
|
|
41
|
+
settleOwner(acknowledgement, false);
|
|
42
|
+
acknowledgement.push(expected, resolve);
|
|
43
|
+
// Force `MatchesInner` to re-read: it renders `acknowledgement[0]` and
|
|
44
|
+
// settles this promise from a layout effect once that commit lands.
|
|
45
|
+
forceRender({});
|
|
46
|
+
startTransition(fn);
|
|
47
|
+
});
|
|
49
48
|
|
|
50
49
|
useLayoutEffect(() => {
|
|
51
50
|
const unsub = router.history.subscribe(() => router.load());
|
|
@@ -58,16 +57,14 @@ export function useTransitioner() {
|
|
|
58
57
|
// the swapped-in router's load.
|
|
59
58
|
router.updateLatestLocation?.();
|
|
60
59
|
const location = router.latestLocation;
|
|
61
|
-
const nextLocation = router.buildLocation(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} as any,
|
|
70
|
-
);
|
|
60
|
+
const nextLocation = router.buildLocation({
|
|
61
|
+
to: location.pathname,
|
|
62
|
+
search: true,
|
|
63
|
+
params: true,
|
|
64
|
+
hash: true,
|
|
65
|
+
state: true,
|
|
66
|
+
_includeValidateSearch: true,
|
|
67
|
+
} as any);
|
|
71
68
|
if (trimPathRight(location.publicHref) !== trimPathRight(nextLocation.publicHref)) {
|
|
72
69
|
router.commitLocation({ ...nextLocation, replace: true, ignoreBlocker: true });
|
|
73
70
|
return unsub;
|
|
@@ -76,7 +73,7 @@ export function useTransitioner() {
|
|
|
76
73
|
const resolvedLocation = router.stores.resolvedLocation.get();
|
|
77
74
|
if (
|
|
78
75
|
resolvedLocation?.href === location.href &&
|
|
79
|
-
|
|
76
|
+
resolvedLocation.state.__TSR_key === location.state.__TSR_key
|
|
80
77
|
) {
|
|
81
78
|
// Already resolved (SSR hydration): emit `onRendered` for scroll
|
|
82
79
|
// restoration and devtools. The match tree has already committed by the
|
package/src/not-found.tsrx
CHANGED
|
@@ -40,11 +40,15 @@ export function CatchNotFound(props: {
|
|
|
40
40
|
throw error;
|
|
41
41
|
}
|
|
42
42
|
}}
|
|
43
|
-
errorComponent={(p: ErrorComponentProps) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
errorComponent={(p: ErrorComponentProps) =>
|
|
44
|
+
NotFoundFallback({
|
|
45
|
+
error: p.error,
|
|
46
|
+
render: props.fallback,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
{props.children}
|
|
51
|
+
</CatchBoundary>
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
export function DefaultGlobalNotFound() @{
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
// itself before React hydrates. Octane hydration claims the server's arm, so the
|
|
6
6
|
// client takes the arm the server took and ScriptOnce adopts, then drops, the
|
|
7
7
|
// server's script.
|
|
8
|
-
import {
|
|
9
|
-
getScrollRestorationScriptForRouter,
|
|
10
|
-
} from '@tanstack/router-core/scroll-restoration-script';
|
|
8
|
+
import { getScrollRestorationScriptForRouter } from '@tanstack/router-core/scroll-restoration-script';
|
|
11
9
|
import type { AnyRouter } from '@tanstack/router-core';
|
|
12
10
|
import { useRouter } from './context.ts';
|
|
13
11
|
import { ScriptOnce } from './ScriptOnce.tsrx';
|
|
@@ -16,7 +16,7 @@ const ready = {
|
|
|
16
16
|
function getHydrationPromise(router: AnyRouter) {
|
|
17
17
|
let promise = hydrationPromises.get(router);
|
|
18
18
|
if (!promise) {
|
|
19
|
-
promise = router.stores.ids.get().length ? ready as PromiseLike<void> : hydrate(router);
|
|
19
|
+
promise = router.stores.ids.get().length ? (ready as PromiseLike<void>) : hydrate(router);
|
|
20
20
|
hydrationPromises.set(router, promise);
|
|
21
21
|
}
|
|
22
22
|
return promise;
|
package/src/useBlocker.tsrx
CHANGED
|
@@ -108,8 +108,12 @@ export function useBlocker(
|
|
|
108
108
|
opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
|
|
109
109
|
condition?: boolean | any,
|
|
110
110
|
): BlockerResolver {
|
|
111
|
-
const {
|
|
112
|
-
|
|
111
|
+
const {
|
|
112
|
+
shouldBlockFn,
|
|
113
|
+
enableBeforeUnload = true,
|
|
114
|
+
disabled = false,
|
|
115
|
+
withResolver = false,
|
|
116
|
+
} = _resolveBlockerOpts(opts, condition);
|
|
113
117
|
|
|
114
118
|
const router = useRouter();
|
|
115
119
|
const { history } = router;
|
|
@@ -182,23 +186,19 @@ export function useBlocker(
|
|
|
182
186
|
|
|
183
187
|
// Upstream's PromptProps: the blocker options plus optional children (a render
|
|
184
188
|
// prop receiving the resolver, or plain renderables).
|
|
185
|
-
export type PromptProps =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
};
|
|
189
|
+
export type PromptProps = (UseBlockerOpts | LegacyBlockerOpts) & {
|
|
190
|
+
children?: OctaneNode | ((params: BlockerResolver) => OctaneNode);
|
|
191
|
+
};
|
|
189
192
|
|
|
190
193
|
// Declarative blocker: registers useBlocker and renders children (optionally a
|
|
191
194
|
// render prop receiving the resolver).
|
|
192
195
|
export function Block(props: PromptProps) @{
|
|
193
196
|
const { children, ...rest } = props;
|
|
194
197
|
const resolver = useBlocker(rest);
|
|
195
|
-
const out =
|
|
196
|
-
children
|
|
197
|
-
?
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<>
|
|
202
|
-
{out}
|
|
203
|
-
</>
|
|
198
|
+
const out = children
|
|
199
|
+
? typeof children === 'function' && !isChildrenBlock(children)
|
|
200
|
+
? children(resolver)
|
|
201
|
+
: children
|
|
202
|
+
: null;
|
|
203
|
+
<>{out}</>
|
|
204
204
|
}
|