@octanejs/tanstack-router 0.2.9 → 0.3.0
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 +23 -7
- package/package.json +4 -3
- package/src/Asset.tsrx +1 -1
- package/src/Await.tsrx +1 -1
- package/src/CatchBoundary.tsrx +11 -41
- package/src/ClientOnly.tsrx +1 -1
- package/src/ErrorComponent.tsrx +38 -0
- package/src/HeadContent.tsrx +4 -2
- package/src/Link.tsrx +3 -1
- package/src/Link.tsrx.d.ts +3 -0
- package/src/Match.tsrx +3 -3
- package/src/MatchRoute.tsrx +5 -5
- package/src/Matches.tsrx +6 -19
- package/src/Navigate.tsrx +1 -1
- package/src/Outlet.tsrx +2 -2
- package/src/RouteNotFound.tsrx +1 -1
- package/src/RouterProvider.tsrx +1 -1
- package/src/ScriptOnce.tsrx +14 -15
- package/src/Scripts.tsrx +1 -1
- package/src/ScrollRestoration.tsrx +1 -1
- package/src/Transitioner.tsrx +80 -62
- package/src/assetComponent.tsrx +1 -1
- package/src/compiled/Document.tsrx +2 -0
- package/src/compiled/Link.tsrx +1 -3
- package/src/compiled/Matches.tsrx +128 -120
- package/src/compiled/RouterProvider.tsrx +27 -0
- package/src/compiled.ts +6 -3
- package/src/core.ts +8 -0
- package/src/externalHydration.ts +2 -2
- package/src/fileRoute.ts +1 -1
- package/src/headContentBinding.ts +10 -15
- package/src/history.ts +16 -8
- package/src/historyState.ts +37 -0
- package/src/hookBindings.ts +2 -0
- package/src/index.ts +4 -4
- package/src/{link.ts → linkApi.ts} +1 -3
- package/src/linkBinding.ts +14 -10
- package/src/linkComponent.tsrx +3 -3
- package/src/not-found.tsrx +4 -4
- package/src/route.ts +2 -1
- package/src/router.ts +5 -0
- package/src/routerStateBinding.ts +2 -0
- package/src/scriptComponent.tsrx +22 -15
- package/src/scroll-restoration.tsrx +1 -1
- package/src/ssr/RouterClient.tsrx +1 -1
- package/src/ssr/renderRouterToStream.ts +13 -4
- package/src/useBlocker.tsrx +1 -1
- package/src/useStore.ts +2 -0
package/src/compiled/Link.tsrx
CHANGED
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
import { createUseLinkProps } from '../linkBinding';
|
|
4
4
|
import { createLinkComponent } from '../linkComponent.tsrx';
|
|
5
5
|
import { useRouter } from './bindings';
|
|
6
|
-
import type { createLink as StandardCreateLink } from '../
|
|
6
|
+
import type { createLink as StandardCreateLink } from '../linkApi';
|
|
7
7
|
|
|
8
|
-
// The shared hook intentionally retains flushSync's synchronous navigation cue.
|
|
9
|
-
// The compiled entry does not relax its observable event timing.
|
|
10
8
|
export const useLinkProps = createUseLinkProps(useRouter);
|
|
11
9
|
export const Link = createLinkComponent(useLinkProps);
|
|
12
10
|
|
|
@@ -1,34 +1,31 @@
|
|
|
1
|
+
'use strong';
|
|
2
|
+
|
|
1
3
|
import {
|
|
4
|
+
__runOnCatch,
|
|
2
5
|
CompiledOutlet as CompiledComponent,
|
|
3
6
|
CompiledContext,
|
|
7
|
+
Hydrate,
|
|
8
|
+
trustHTML,
|
|
4
9
|
useContext,
|
|
5
10
|
useLayoutEffect,
|
|
6
11
|
useRef,
|
|
7
12
|
} from 'octane';
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
13
|
+
import { never } from 'octane/hydration';
|
|
14
|
+
import type { AnyRouter, NotFoundError } from '@tanstack/router-core';
|
|
15
|
+
import { isNotFound, rootRouteId, setupScrollRestoration } from '@tanstack/router-core';
|
|
11
16
|
import { isServer } from '@tanstack/router-core/isServer';
|
|
17
|
+
import { getScrollRestorationScriptForRouter } from '@tanstack/router-core/scroll-restoration-script';
|
|
12
18
|
import { routerContext, matchContext, useRouter } from './bindings';
|
|
13
19
|
import { useStore } from '../useStore';
|
|
14
|
-
import { useTransitioner,
|
|
20
|
+
import { useTransitioner, usePresentedMatches } from '../Transitioner.tsrx';
|
|
15
21
|
import { useHydrated } from '../ClientOnly.tsrx';
|
|
16
|
-
import { ErrorComponent } from '../
|
|
17
|
-
import { createLazyRouteLoader } from '../lazyRouteLoader';
|
|
18
|
-
import type { RouterProps } from '../RouterProvider.tsrx';
|
|
22
|
+
import { ErrorComponent } from '../ErrorComponent.tsrx';
|
|
19
23
|
|
|
20
|
-
export function
|
|
21
|
-
const
|
|
22
|
-
if (hasKeys(rest)) {
|
|
23
|
-
router.update({
|
|
24
|
-
...router.options,
|
|
25
|
-
...rest,
|
|
26
|
-
context: { ...router.options.context, ...rest.context },
|
|
27
|
-
});
|
|
28
|
-
}
|
|
24
|
+
export function PreconfiguredRouterProvider(props: { router: AnyRouter }) @{
|
|
25
|
+
const router = props.router;
|
|
29
26
|
if (router.options.Wrap || router.options.InnerWrap) {
|
|
30
27
|
throw new Error(
|
|
31
|
-
'
|
|
28
|
+
'Compiled providers do not accept Wrap or InnerWrap. Use RouterProvider from @octanejs/tanstack-router for renderable wrappers.',
|
|
32
29
|
);
|
|
33
30
|
}
|
|
34
31
|
<CompiledContext context={routerContext} value={router}>
|
|
@@ -36,41 +33,36 @@ export function CompiledRouterProvider(props: RouterProps) @{
|
|
|
36
33
|
</CompiledContext>
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
<CompiledComponent component={props.component} props={{ children: props.children }} />
|
|
42
|
-
} @else {
|
|
43
|
-
<>{props.children}</>
|
|
44
|
-
}
|
|
36
|
+
function setupRouterScrollRestoration(router: AnyRouter) {
|
|
37
|
+
if (router.options.scrollRestoration) setupScrollRestoration(router);
|
|
45
38
|
}
|
|
46
39
|
|
|
47
40
|
function CompiledMatches() @{
|
|
48
41
|
const router = useRouter();
|
|
49
42
|
const rootRoute = (router.routesById as any)[rootRouteId];
|
|
50
|
-
useLayoutEffect(() =>
|
|
51
|
-
|
|
52
|
-
}, [router]);
|
|
53
|
-
const expectedMatches = useTransitioner(router);
|
|
43
|
+
useLayoutEffect(() => setupRouterScrollRestoration(router));
|
|
44
|
+
useTransitioner(router);
|
|
54
45
|
@try {
|
|
55
|
-
<CompiledMatchesInner
|
|
46
|
+
<CompiledMatchesInner />
|
|
56
47
|
} @pending {
|
|
57
48
|
<CompiledPending route={rootRoute} />
|
|
58
49
|
} @catch (error, reset) {
|
|
59
50
|
if (router.options.disableGlobalCatchBoundary) throw error;
|
|
60
|
-
|
|
51
|
+
const failure = resolveFailure(error, rootRoute, router, true);
|
|
52
|
+
<CompiledFailure
|
|
53
|
+
error={error}
|
|
54
|
+
reset={reset}
|
|
55
|
+
route={rootRoute}
|
|
56
|
+
router={router}
|
|
57
|
+
failure={failure}
|
|
58
|
+
/>
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
function CompiledMatchesInner(
|
|
62
|
+
function CompiledMatchesInner() @{
|
|
65
63
|
const router = useRouter();
|
|
66
|
-
const
|
|
67
|
-
const matches = useStore(router.stores.matches, (m: any) =>
|
|
68
|
-
acknowledgement[0] === props.expectedMatches ? (props.expectedMatches ?? m) : m,
|
|
69
|
-
) as any;
|
|
64
|
+
const matches = usePresentedMatches(router);
|
|
70
65
|
const rootMatch = matches[0];
|
|
71
|
-
useLayoutEffect(() => {
|
|
72
|
-
if (acknowledgement[0] === matches) settleOwner(acknowledgement, true);
|
|
73
|
-
}, [acknowledgement, matches]);
|
|
74
66
|
@if (rootMatch) {
|
|
75
67
|
<CompiledContext context={matchContext} value={rootMatch.routeId}>
|
|
76
68
|
<CompiledMatch routeId={rootMatch.routeId} />
|
|
@@ -78,7 +70,31 @@ function CompiledMatchesInner(props: { expectedMatches: unknown }) @{
|
|
|
78
70
|
}
|
|
79
71
|
}
|
|
80
72
|
|
|
81
|
-
function
|
|
73
|
+
function CompiledPending(props: { route?: any; waitForTransition?: boolean }) @{
|
|
74
|
+
const router = useRouter();
|
|
75
|
+
if (props.waitForTransition) {
|
|
76
|
+
// Observe the transaction with the pending match it owns, not through the
|
|
77
|
+
// stable router identity during Strong render.
|
|
78
|
+
const transition = useStore(router.stores.getMatchStore(props.route.id), () => router._tx?.[5]);
|
|
79
|
+
if (transition) throw transition;
|
|
80
|
+
}
|
|
81
|
+
const Pending = props.route?.options.pendingComponent ?? router.options.defaultPendingComponent;
|
|
82
|
+
@if (Pending) {
|
|
83
|
+
<CompiledComponent component={Pending} props={{}} />
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Route IDs enter this tree from published match snapshots. Router-core has
|
|
88
|
+
// already created their stable presentation stores before notifying readers.
|
|
89
|
+
function CompiledWrap(props: { component?: any; children?: unknown }) @{
|
|
90
|
+
@if (props.component) {
|
|
91
|
+
<CompiledComponent component={props.component} props={{ children: props.children }} />
|
|
92
|
+
} @else {
|
|
93
|
+
<>{props.children}</>
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function CompiledMatch(props: { routeId: string }) @{
|
|
82
98
|
const router = useRouter();
|
|
83
99
|
const store = router.stores.getMatchStore(props.routeId);
|
|
84
100
|
const ssr = useStore(store, (match: any) => match.ssr);
|
|
@@ -107,16 +123,88 @@ function CompiledMatch(props: { routeId: string }) @{
|
|
|
107
123
|
</CompiledWrap>
|
|
108
124
|
}
|
|
109
125
|
|
|
126
|
+
function CompiledScrollRestoration() @{
|
|
127
|
+
const router = useRouter();
|
|
128
|
+
const hydrated = useHydrated();
|
|
129
|
+
const option = router.options.scrollRestoration;
|
|
130
|
+
const script = getScrollRestorationScriptForRouter(router);
|
|
131
|
+
// Keep the location predicate live: the router identity survives location changes.
|
|
132
|
+
let rendersScript = true;
|
|
133
|
+
if (typeof option === 'function') rendersScript = option({ location: router.latestLocation });
|
|
134
|
+
@if ((script !== null || rendersScript) && ((isServer ?? router.isServer) || !hydrated)) {
|
|
135
|
+
<Hydrate split={false} when={never()}>
|
|
136
|
+
<script nonce={router.options.ssr?.nonce} dangerouslySetInnerHTML={trustHTML(script ?? '')} />
|
|
137
|
+
</Hydrate>
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
110
141
|
// Without a configured Suspense boundary, both lazy imports and loader promises
|
|
111
142
|
// pass through to the next ancestor boundary. The match still owns its errors.
|
|
112
143
|
function CompiledMatchContent(props: { route: any; routeId: string; noSsr: boolean }) @{
|
|
113
144
|
@try {
|
|
114
145
|
<CompiledMatchInner routeId={props.routeId} noSsr={props.noSsr} />
|
|
115
146
|
} @catch (error, reset) {
|
|
116
|
-
|
|
147
|
+
const router = useRouter();
|
|
148
|
+
const failure = resolveFailure(error, props.route, router, false);
|
|
149
|
+
<CompiledFailure
|
|
150
|
+
error={error}
|
|
151
|
+
reset={reset}
|
|
152
|
+
route={props.route}
|
|
153
|
+
router={router}
|
|
154
|
+
failure={failure}
|
|
155
|
+
/>
|
|
117
156
|
}
|
|
118
157
|
}
|
|
119
158
|
|
|
159
|
+
function resolveFailure(error: any, route: any, router: AnyRouter, global: boolean) {
|
|
160
|
+
const errorComponent = global
|
|
161
|
+
? ErrorComponent
|
|
162
|
+
: (route.options.errorComponent ?? router.options.defaultErrorComponent);
|
|
163
|
+
const notFoundComponent = route.isRoot
|
|
164
|
+
? (route.options.notFoundComponent ?? router.options.notFoundRoute?.options.component)
|
|
165
|
+
: route.options.notFoundComponent;
|
|
166
|
+
const missing = isNotFound(error);
|
|
167
|
+
if (missing) {
|
|
168
|
+
error.routeId ??= route.id;
|
|
169
|
+
if (!notFoundComponent || error.routeId !== route.id) throw error;
|
|
170
|
+
} else {
|
|
171
|
+
if (!errorComponent) throw error;
|
|
172
|
+
// The compatibility owner initializes its reported-error ref to null.
|
|
173
|
+
if (!global && error !== null) {
|
|
174
|
+
__runOnCatch(() => {
|
|
175
|
+
const onCatch = route.options.onCatch ?? router.options.defaultOnCatch;
|
|
176
|
+
onCatch?.(error, { componentStack: '' });
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { component: missing ? notFoundComponent : errorComponent, missing };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function CompiledFailure(props: {
|
|
184
|
+
error: any;
|
|
185
|
+
reset: () => void;
|
|
186
|
+
route: any;
|
|
187
|
+
router: AnyRouter;
|
|
188
|
+
failure: ReturnType<typeof resolveFailure>;
|
|
189
|
+
}) @{
|
|
190
|
+
const match = useStore(props.router.stores.getMatchStore(props.route.id), (value: any) => value);
|
|
191
|
+
const firstMatch = useRef(match);
|
|
192
|
+
useLayoutEffect(() => {
|
|
193
|
+
if (firstMatch.current !== match) {
|
|
194
|
+
firstMatch.current = match;
|
|
195
|
+
props.reset();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
<CompiledComponent
|
|
199
|
+
component={props.failure.component}
|
|
200
|
+
props={
|
|
201
|
+
props.failure.missing
|
|
202
|
+
? props.error
|
|
203
|
+
: { error: props.error, reset: props.reset, info: { componentStack: '' } }
|
|
204
|
+
}
|
|
205
|
+
/>
|
|
206
|
+
}
|
|
207
|
+
|
|
120
208
|
function CompiledMatchInner(props: { routeId: string; noSsr: boolean }) @{
|
|
121
209
|
const router = useRouter();
|
|
122
210
|
const match = useStore(router.stores.getMatchStore(props.routeId), (m: any) => m);
|
|
@@ -131,11 +219,8 @@ function CompiledMatchInner(props: { routeId: string; noSsr: boolean }) @{
|
|
|
131
219
|
search: match._strictSearch,
|
|
132
220
|
});
|
|
133
221
|
const key = remountDeps ? JSON.stringify(remountDeps) : undefined;
|
|
134
|
-
if (match.status === 'pending' && !props.noSsr && (router as any)._tx) {
|
|
135
|
-
throw (router as any)._tx[5];
|
|
136
|
-
}
|
|
137
222
|
@if ((props.noSsr && !hydrated) || match.status === 'pending') {
|
|
138
|
-
<CompiledPending route={route} />
|
|
223
|
+
<CompiledPending route={route} waitForTransition={match.status === 'pending' && !props.noSsr} />
|
|
139
224
|
} @else if (match.status === 'notFound') {
|
|
140
225
|
<CompiledNotFound route={route} error={match.error} />
|
|
141
226
|
} @else if (match.status === 'error') {
|
|
@@ -187,14 +272,6 @@ export function CompiledOutlet() @{
|
|
|
187
272
|
}
|
|
188
273
|
}
|
|
189
274
|
|
|
190
|
-
function CompiledPending(props: { route?: any }) @{
|
|
191
|
-
const router = useRouter();
|
|
192
|
-
const Pending = props.route?.options.pendingComponent ?? router.options.defaultPendingComponent;
|
|
193
|
-
@if (Pending) {
|
|
194
|
-
<CompiledComponent component={Pending} props={{}} />
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
275
|
function CompiledNotFound(props: { route: any; error?: NotFoundError }) @{
|
|
199
276
|
const router = useRouter();
|
|
200
277
|
const NotFound = props.route.options.notFoundComponent ?? router.options.defaultNotFoundComponent;
|
|
@@ -204,72 +281,3 @@ function CompiledNotFound(props: { route: any; error?: NotFoundError }) @{
|
|
|
204
281
|
<p>Not Found</p>
|
|
205
282
|
}
|
|
206
283
|
}
|
|
207
|
-
|
|
208
|
-
function CompiledFailure(props: { error: any; reset: () => void; route: any; global?: boolean }) @{
|
|
209
|
-
const router = useRouter();
|
|
210
|
-
const errorComponent = props.global
|
|
211
|
-
? ErrorComponent
|
|
212
|
-
: (props.route.options.errorComponent ?? router.options.defaultErrorComponent);
|
|
213
|
-
const notFoundComponent = props.route.isRoot
|
|
214
|
-
? (props.route.options.notFoundComponent ?? router.options.notFoundRoute?.options.component)
|
|
215
|
-
: props.route.options.notFoundComponent;
|
|
216
|
-
const isMissing = isNotFound(props.error);
|
|
217
|
-
if (isMissing) {
|
|
218
|
-
const missing = props.error as NotFoundError;
|
|
219
|
-
missing.routeId ??= props.route.id;
|
|
220
|
-
if (!notFoundComponent || missing.routeId !== props.route.id) throw missing;
|
|
221
|
-
} else if (!errorComponent) {
|
|
222
|
-
throw props.error;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Report an error only at the boundary that owns it and retry after its
|
|
226
|
-
// TanStack match identity changes, just like the normal binding.
|
|
227
|
-
const reported = useRef<unknown>(null);
|
|
228
|
-
if (!isMissing && !props.global && reported.current !== props.error) {
|
|
229
|
-
reported.current = props.error;
|
|
230
|
-
const onCatch = props.route.options.onCatch ?? router.options.defaultOnCatch;
|
|
231
|
-
onCatch?.(props.error, { componentStack: '' });
|
|
232
|
-
}
|
|
233
|
-
const store = router.stores.getMatchStore(props.route.id);
|
|
234
|
-
const match = useStore(store, (m: any) => m);
|
|
235
|
-
const firstMatch = useRef(match);
|
|
236
|
-
useLayoutEffect(() => {
|
|
237
|
-
if (firstMatch.current !== match) {
|
|
238
|
-
firstMatch.current = match;
|
|
239
|
-
props.reset();
|
|
240
|
-
}
|
|
241
|
-
}, [match]);
|
|
242
|
-
|
|
243
|
-
@if (isMissing) {
|
|
244
|
-
<CompiledComponent component={notFoundComponent} props={props.error} />
|
|
245
|
-
} @else {
|
|
246
|
-
<CompiledComponent
|
|
247
|
-
component={errorComponent}
|
|
248
|
-
props={{ error: props.error, reset: props.reset, info: { componentStack: '' } }}
|
|
249
|
-
/>
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function CompiledScrollRestoration() @{
|
|
254
|
-
const router = useRouter();
|
|
255
|
-
const hydrated = useHydrated();
|
|
256
|
-
const option = router.options.scrollRestoration;
|
|
257
|
-
const script = getScrollRestorationScriptForRouter(router);
|
|
258
|
-
const rendersScript = typeof option !== 'function' || option({ location: router.latestLocation });
|
|
259
|
-
@if ((script !== null || rendersScript) && ((isServer ?? router.isServer) || !hydrated)) {
|
|
260
|
-
<script
|
|
261
|
-
nonce={router.options.ssr?.nonce}
|
|
262
|
-
suppressHydrationWarning
|
|
263
|
-
dangerouslySetInnerHTML={{ __html: script ?? '' }}
|
|
264
|
-
/>
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export function lazyCompiledRouteComponent(importer: () => Promise<any>, exportName?: string) {
|
|
269
|
-
const loader = createLazyRouteLoader(importer, exportName);
|
|
270
|
-
function Lazy(props: any) @{
|
|
271
|
-
<CompiledComponent component={loader.read()} props={props} />
|
|
272
|
-
}
|
|
273
|
-
Lazy.preload = loader.preload;
|
|
274
|
-
return Lazy;
|
|
275
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CompiledOutlet as CompiledComponent } from 'octane';
|
|
2
|
+
import { hasKeys } from '@tanstack/router-core';
|
|
3
|
+
import { createLazyRouteLoader } from '../lazyRouteLoader';
|
|
4
|
+
import type { RouterProps } from '../RouterProvider.tsrx';
|
|
5
|
+
import { PreconfiguredRouterProvider } from './Matches.tsrx';
|
|
6
|
+
|
|
7
|
+
export function CompiledRouterProvider(props: RouterProps) @{
|
|
8
|
+
const { router, ...rest } = props;
|
|
9
|
+
// Option props must apply before descendants render, including during SSR.
|
|
10
|
+
if (hasKeys(rest)) {
|
|
11
|
+
router.update({
|
|
12
|
+
...router.options,
|
|
13
|
+
...rest,
|
|
14
|
+
context: { ...router.options.context, ...rest.context },
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
<PreconfiguredRouterProvider router={router} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function lazyCompiledRouteComponent(importer: () => Promise<any>, exportName?: string) {
|
|
21
|
+
const loader = createLazyRouteLoader(importer, exportName);
|
|
22
|
+
function Lazy(props: any) @{
|
|
23
|
+
<CompiledComponent component={loader.read()} props={props} />
|
|
24
|
+
}
|
|
25
|
+
Lazy.preload = loader.preload;
|
|
26
|
+
return Lazy;
|
|
27
|
+
}
|
package/src/compiled.ts
CHANGED
|
@@ -37,13 +37,16 @@ export {
|
|
|
37
37
|
} from './compiled/bindings';
|
|
38
38
|
export type { UseRouterStateOptions, UseRouterStateResult } from './routerStateBinding';
|
|
39
39
|
export {
|
|
40
|
-
CompiledRouterProvider,
|
|
41
|
-
CompiledRouterProvider as RouterProvider,
|
|
42
40
|
CompiledOutlet,
|
|
43
41
|
CompiledOutlet as Outlet,
|
|
42
|
+
PreconfiguredRouterProvider,
|
|
43
|
+
} from './compiled/Matches.tsrx';
|
|
44
|
+
export {
|
|
45
|
+
CompiledRouterProvider,
|
|
46
|
+
CompiledRouterProvider as RouterProvider,
|
|
44
47
|
lazyCompiledRouteComponent,
|
|
45
48
|
lazyCompiledRouteComponent as lazyRouteComponent,
|
|
46
|
-
} from './compiled/
|
|
49
|
+
} from './compiled/RouterProvider.tsrx';
|
|
47
50
|
export {
|
|
48
51
|
createRoute,
|
|
49
52
|
createRootRoute,
|
package/src/core.ts
ADDED
package/src/externalHydration.ts
CHANGED
|
@@ -2,8 +2,8 @@ const EXTERNAL_HYDRATION_PROMISE = Symbol.for('octane.external-hydration-promise
|
|
|
2
2
|
|
|
3
3
|
type ExternalHydrationThenable<T> = PromiseLike<T> & {
|
|
4
4
|
[EXTERNAL_HYDRATION_PROMISE]: true;
|
|
5
|
-
status?: ThenableStatus;
|
|
6
|
-
value?: T;
|
|
5
|
+
status?: ThenableStatus | undefined;
|
|
6
|
+
value?: T | undefined;
|
|
7
7
|
reason?: unknown;
|
|
8
8
|
};
|
|
9
9
|
|
package/src/fileRoute.ts
CHANGED
|
@@ -70,7 +70,7 @@ export class FileRoute<
|
|
|
70
70
|
TPath extends RouteConstraints['TPath'] = FileRoutesByPath[TFilePath]['path'],
|
|
71
71
|
TFullPath extends RouteConstraints['TFullPath'] = FileRoutesByPath[TFilePath]['fullPath'],
|
|
72
72
|
> {
|
|
73
|
-
silent?: boolean;
|
|
73
|
+
silent?: boolean | undefined;
|
|
74
74
|
|
|
75
75
|
constructor(
|
|
76
76
|
public path?: TFilePath,
|
|
@@ -111,10 +111,11 @@ function buildTagsFromMatches(
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
if (manifest.inlineStyle) {
|
|
114
|
+
const children = manifest.inlineStyle.children;
|
|
114
115
|
manifestTags.push({
|
|
115
116
|
tag: 'style',
|
|
116
117
|
attrs: { ...manifest.inlineStyle.attrs, nonce },
|
|
117
|
-
children:
|
|
118
|
+
...(children === undefined ? undefined : { children }),
|
|
118
119
|
inlineCss: true,
|
|
119
120
|
});
|
|
120
121
|
}
|
|
@@ -123,25 +124,19 @@ function buildTagsFromMatches(
|
|
|
123
124
|
const styles = matches
|
|
124
125
|
.flatMap((match) => match.styles ?? [])
|
|
125
126
|
.filter((style) => style !== undefined)
|
|
126
|
-
.map(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
attrs: { ...attrs, nonce },
|
|
131
|
-
children: children,
|
|
132
|
-
}) satisfies RouterManagedTag,
|
|
127
|
+
.map(({ children, ...attrs }): RouterManagedTag =>
|
|
128
|
+
children === undefined
|
|
129
|
+
? { tag: 'style', attrs: { ...attrs, nonce } }
|
|
130
|
+
: { tag: 'style', attrs: { ...attrs, nonce }, children },
|
|
133
131
|
);
|
|
134
132
|
|
|
135
133
|
const headScripts = matches
|
|
136
134
|
.flatMap((match) => match.headScripts ?? [])
|
|
137
135
|
.filter((script) => script !== undefined)
|
|
138
|
-
.map(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
attrs: { ...attrs, nonce },
|
|
143
|
-
children: children,
|
|
144
|
-
}) satisfies RouterManagedTag,
|
|
136
|
+
.map(({ children, ...attrs }): RouterManagedTag =>
|
|
137
|
+
children === undefined
|
|
138
|
+
? { tag: 'script', attrs: { ...attrs, nonce } }
|
|
139
|
+
: { tag: 'script', attrs: { ...attrs, nonce }, children },
|
|
145
140
|
);
|
|
146
141
|
|
|
147
142
|
const tags: Array<RouterManagedTag> = [];
|
package/src/history.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
createHistory,
|
|
6
|
-
createBrowserHistory,
|
|
7
|
-
createHashHistory,
|
|
8
|
-
createMemoryHistory,
|
|
1
|
+
import {
|
|
2
|
+
createBrowserHistory as createTanStackBrowserHistory,
|
|
3
|
+
createHashHistory as createTanStackHashHistory,
|
|
9
4
|
} from '@tanstack/history';
|
|
5
|
+
import { prepareInitialHistoryState } from './historyState';
|
|
6
|
+
|
|
7
|
+
export { createHistory, createMemoryHistory } from '@tanstack/history';
|
|
10
8
|
export type * from '@tanstack/history';
|
|
9
|
+
|
|
10
|
+
export const createBrowserHistory: typeof createTanStackBrowserHistory = (options) => {
|
|
11
|
+
prepareInitialHistoryState(options?.window);
|
|
12
|
+
return createTanStackBrowserHistory(options);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const createHashHistory: typeof createTanStackHashHistory = (options) => {
|
|
16
|
+
prepareInitialHistoryState(options?.window);
|
|
17
|
+
return createTanStackHashHistory(options);
|
|
18
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { parseHref } from '@tanstack/history';
|
|
2
|
+
|
|
3
|
+
type NavigationWindow = Window & {
|
|
4
|
+
navigation?: {
|
|
5
|
+
currentEntry?: { key: string; getState(): unknown } | null;
|
|
6
|
+
updateCurrentEntry(options: { state: unknown }): void;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function prepareInitialHistoryState(
|
|
11
|
+
win: NavigationWindow | undefined = typeof document === 'undefined' ? undefined : window,
|
|
12
|
+
): void {
|
|
13
|
+
if (!win) return;
|
|
14
|
+
const state = win.history.state;
|
|
15
|
+
if (state?.__TSR_key || state?.key) return;
|
|
16
|
+
|
|
17
|
+
const navigation = win.navigation;
|
|
18
|
+
const entry = navigation?.currentEntry;
|
|
19
|
+
const navigationState =
|
|
20
|
+
entry && typeof navigation?.updateCurrentEntry === 'function' ? entry.getState() : undefined;
|
|
21
|
+
if (state == null && navigationState === undefined) return;
|
|
22
|
+
const entryKey = entry?.key;
|
|
23
|
+
|
|
24
|
+
// Seed TanStack's metadata before its constructor replaces the entry. The
|
|
25
|
+
// History API and Navigation API store independent state for the same entry.
|
|
26
|
+
const metadata = parseHref('/', undefined).state;
|
|
27
|
+
win.history.replaceState(
|
|
28
|
+
{ ...state, ...metadata, __TSR_index: state?.__TSR_index ?? metadata.__TSR_index },
|
|
29
|
+
'',
|
|
30
|
+
);
|
|
31
|
+
if (navigation && navigationState !== undefined) {
|
|
32
|
+
const currentEntry = navigation.currentEntry;
|
|
33
|
+
if (currentEntry && currentEntry.key === entryKey && currentEntry.getState() === undefined) {
|
|
34
|
+
navigation.updateCurrentEntry({ state: navigationState });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/hookBindings.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -122,8 +122,8 @@ export type {
|
|
|
122
122
|
} from './routeHookTypes';
|
|
123
123
|
export { useAwaited } from './useAwaited';
|
|
124
124
|
export type { AwaitOptions } from './useAwaited';
|
|
125
|
-
export { useLinkProps, createLink, linkOptions } from './
|
|
126
|
-
export type { LinkOptionsFn, LinkOptionsFnOptions } from './
|
|
125
|
+
export { useLinkProps, createLink, linkOptions } from './linkApi';
|
|
126
|
+
export type { LinkOptionsFn, LinkOptionsFnOptions } from './linkApi';
|
|
127
127
|
export type {
|
|
128
128
|
ActiveLinkOptionProps,
|
|
129
129
|
ActiveLinkOptions,
|
|
@@ -166,8 +166,8 @@ export { Scripts } from './Scripts.tsrx';
|
|
|
166
166
|
export { ScriptOnce } from './ScriptOnce.tsrx';
|
|
167
167
|
export { Asset } from './Asset.tsrx';
|
|
168
168
|
export type { AssetProps } from './Asset.tsrx';
|
|
169
|
-
export { getAssetKeys } from './assetKeys
|
|
170
|
-
export type { AssetKeys } from './assetKeys
|
|
169
|
+
export { getAssetKeys } from './assetKeys';
|
|
170
|
+
export type { AssetKeys } from './assetKeys';
|
|
171
171
|
export { useTags } from './headContentUtils';
|
|
172
172
|
export { Html } from './Html';
|
|
173
173
|
export type { HtmlProps } from './Html';
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { createElement } from 'octane';
|
|
2
2
|
import type { ComponentBody } from 'octane';
|
|
3
3
|
import type { AnyRouter, RegisteredRouter } from '@tanstack/router-core';
|
|
4
|
-
import { useRouter } from './context';
|
|
5
4
|
import { Link } from './Link.tsrx';
|
|
6
|
-
import { createUseLinkProps } from './linkBinding';
|
|
7
5
|
import type { LinkComponent } from './linkTypes';
|
|
8
6
|
import type { ValidateLinkOptions, ValidateLinkOptionsArray } from './typePrimitives';
|
|
9
7
|
|
|
10
|
-
export
|
|
8
|
+
export { useLinkProps } from './Link.tsrx';
|
|
11
9
|
|
|
12
10
|
// Wrap a design-system component so it navigates like <Link> — the component
|
|
13
11
|
// receives the fully-built link props (href, handlers, data-status, …).
|
package/src/linkBinding.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strong';
|
|
2
|
+
|
|
1
3
|
// Shared useLinkProps behavior — port of react-router's link.tsx
|
|
2
4
|
// (client path; SSR link rendering arrives with the SSR entries). All of Link's
|
|
3
5
|
// behavior lives here so both `<Link>` and custom `createLink` components share
|
|
@@ -8,7 +10,7 @@
|
|
|
8
10
|
// IntersectionObserver, 'render' once on mount), the click handler (navigate
|
|
9
11
|
// with replace/resetScroll/hashScrollIntoView/viewTransition/startTransition/
|
|
10
12
|
// ignoreBlocker forwarded), and data-status/data-transitioning reflection.
|
|
11
|
-
import {
|
|
13
|
+
import { __useCommittedState, useRef, useEffect, useCallback } from 'octane';
|
|
12
14
|
import {
|
|
13
15
|
deepEqual,
|
|
14
16
|
exactPathTest,
|
|
@@ -216,21 +218,24 @@ export function createUseLinkProps(useRouter: typeof UseRouter) {
|
|
|
216
218
|
// Class composes clsx-style (octane normalizeClass folds arrays + falsy).
|
|
217
219
|
const resolvedClass = [
|
|
218
220
|
klass ?? className,
|
|
219
|
-
resolvedActiveProps
|
|
220
|
-
resolvedInactiveProps
|
|
221
|
+
resolvedActiveProps['class'] ?? resolvedActiveProps['className'],
|
|
222
|
+
resolvedInactiveProps['class'] ?? resolvedInactiveProps['className'],
|
|
221
223
|
].filter(Boolean);
|
|
222
224
|
const resolvedStyle = mergeStyles(
|
|
223
225
|
style,
|
|
224
|
-
resolvedActiveProps
|
|
225
|
-
resolvedInactiveProps
|
|
226
|
+
resolvedActiveProps['style'],
|
|
227
|
+
resolvedInactiveProps['style'],
|
|
226
228
|
);
|
|
227
229
|
|
|
228
|
-
const [isTransitioning, setIsTransitioning] =
|
|
230
|
+
const [isTransitioning, setIsTransitioning, commitIsTransitioning] = __useCommittedState(
|
|
231
|
+
false,
|
|
232
|
+
subSlot(slot, 'lp:t'),
|
|
233
|
+
);
|
|
229
234
|
const hasRenderFetched = useRef(false, subSlot(slot, 'lp:rf'));
|
|
230
235
|
const elRef = useRef<Element | null>(null, subSlot(slot, 'lp:el'));
|
|
231
236
|
|
|
232
237
|
const preload =
|
|
233
|
-
options
|
|
238
|
+
options['reloadDocument'] || externalLink
|
|
234
239
|
? false
|
|
235
240
|
: (userPreload ?? router.options.defaultPreload);
|
|
236
241
|
const preloadDelay = userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0;
|
|
@@ -246,6 +251,7 @@ export function createUseLinkProps(useRouter: typeof UseRouter) {
|
|
|
246
251
|
subSlot(slot, 'lp:dp'),
|
|
247
252
|
);
|
|
248
253
|
|
|
254
|
+
// This manually slotted module retains explicit effect dependencies.
|
|
249
255
|
// preload="viewport": preload when the anchor scrolls into view (100px margin).
|
|
250
256
|
useEffect(
|
|
251
257
|
() => {
|
|
@@ -292,9 +298,7 @@ export function createUseLinkProps(useRouter: typeof UseRouter) {
|
|
|
292
298
|
) {
|
|
293
299
|
e.preventDefault();
|
|
294
300
|
|
|
295
|
-
|
|
296
|
-
setIsTransitioning(true);
|
|
297
|
-
});
|
|
301
|
+
commitIsTransitioning(true);
|
|
298
302
|
const unsub = router.subscribe('onResolved', () => {
|
|
299
303
|
unsub();
|
|
300
304
|
setIsTransitioning(false);
|
package/src/linkComponent.tsrx
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
// no forwardRef); `disabled` renders without an href + role="link" (upstream
|
|
12
12
|
// drops the anchor's disabled attribute; the aria props carry the state).
|
|
13
13
|
import { isChildrenBlock } from 'octane';
|
|
14
|
-
import type { useLinkProps as UseLinkProps } from './
|
|
14
|
+
import type { useLinkProps as UseLinkProps } from './linkApi';
|
|
15
15
|
import type { AnyRouter, RegisteredRouter } from '@tanstack/router-core';
|
|
16
|
-
import type { LinkComponentProps, OctaneAnchorProps, UseLinkPropsOptions } from './linkTypes
|
|
16
|
+
import type { LinkComponentProps, OctaneAnchorProps, UseLinkPropsOptions } from './linkTypes';
|
|
17
17
|
|
|
18
18
|
// Keep the generic implementation signature visible to `.tsrx` consumers so
|
|
19
19
|
// route-aware `to`/`params`/`search` inference and native anchor event types are
|
|
@@ -51,7 +51,7 @@ export function createLinkComponent(useLinkProps: typeof UseLinkProps) {
|
|
|
51
51
|
: children;
|
|
52
52
|
|
|
53
53
|
@if (AsChild) {
|
|
54
|
-
<AsChild {...linkProps}
|
|
54
|
+
<AsChild {...linkProps} children={resolvedChildren} />
|
|
55
55
|
} @else {
|
|
56
56
|
<a {...(aProps as Record<string, any>)}>{resolvedChildren}</a>
|
|
57
57
|
}
|