@octanejs/tanstack-router 0.1.9 → 0.1.11
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 +24 -11
- package/package.json +22 -7
- package/src/Asset.tsrx +121 -0
- package/src/Asset.tsrx.d.ts +9 -0
- package/src/Await.tsrx +9 -3
- package/src/Await.tsrx.d.ts +8 -6
- package/src/Body.ts +31 -0
- package/src/CatchBoundary.tsrx +21 -4
- package/src/CatchBoundary.tsrx.d.ts +10 -4
- package/src/ClientOnly.tsrx +6 -3
- package/src/Head.ts +22 -0
- package/src/HeadContent.tsrx +41 -0
- package/src/HeadContent.tsrx.d.ts +8 -0
- package/src/Html.ts +19 -0
- package/src/Link.tsrx +19 -4
- package/src/Link.tsrx.d.ts +3 -4
- package/src/Match.tsrx +61 -29
- package/src/MatchRoute.tsrx +6 -3
- package/src/Matches.tsrx +7 -7
- package/src/Navigate.tsrx +2 -1
- package/src/Outlet.tsrx +8 -6
- package/src/RouteNotFound.tsrx +2 -2
- package/src/RouterProvider.tsrx +12 -2
- package/src/RouterProvider.tsrx.d.ts +9 -5
- package/src/SafeFragment.tsrx +4 -1
- package/src/ScriptOnce.tsrx +23 -0
- package/src/ScriptOnce.tsrx.d.ts +3 -0
- package/src/Scripts.tsrx +42 -0
- package/src/Scripts.tsrx.d.ts +3 -0
- package/src/Transitioner.tsrx +15 -13
- package/src/assetKeys.ts +11 -0
- package/src/context.ts +5 -1
- package/src/externalHydration.ts +77 -0
- package/src/fileRoute.ts +277 -0
- package/src/frameworkTypes.ts +42 -0
- package/src/generator-plugin.d.ts +20 -0
- package/src/generator-plugin.js +100 -0
- package/src/headContentUtils.ts +172 -0
- package/src/hooks.ts +151 -0
- package/src/index.ts +95 -3
- package/src/lazyRouteComponent.ts +2 -1
- package/src/link.ts +25 -4
- package/src/linkTypes.ts +96 -0
- package/src/not-found.tsrx +19 -6
- package/src/not-found.tsrx.d.ts +5 -2
- package/src/octane-compiler.d.ts +12 -0
- package/src/route.ts +473 -36
- package/src/routeHookTypes.ts +228 -0
- package/src/router.ts +31 -6
- package/src/scriptContentUtils.ts +64 -0
- package/src/scroll-restoration.tsrx +16 -0
- package/src/scroll-restoration.tsrx.d.ts +3 -0
- package/src/ssr/RouterClient.tsrx +36 -0
- package/src/ssr/RouterClient.tsrx.d.ts +4 -0
- package/src/ssr/RouterServer.tsrx +6 -0
- package/src/ssr/RouterServer.tsrx.d.ts +4 -0
- package/src/ssr/client.ts +4 -0
- package/src/ssr/defaultRenderHandler.ts +11 -0
- package/src/ssr/defaultStreamHandler.ts +12 -0
- package/src/ssr/renderRouterToStream.ts +195 -0
- package/src/ssr/renderRouterToString.ts +58 -0
- package/src/ssr/server.ts +8 -0
- package/src/structuralSharing.ts +41 -0
- package/src/typePrimitives.ts +77 -0
- package/src/useAwaited.ts +7 -2
- package/src/useBlocker.tsrx +73 -8
- package/src/useBlocker.tsrx.d.ts +58 -2
- package/src/useRouterState.ts +20 -0
- package/src/useStore.ts +17 -6
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { renderToString as octaneRenderToString } from 'octane/server';
|
|
2
|
+
import type { ComponentBody } from 'octane';
|
|
3
|
+
import type { AnyRouter } from '@tanstack/router-core';
|
|
4
|
+
|
|
5
|
+
type RouterApp = ComponentBody<{ router: AnyRouter }>;
|
|
6
|
+
type ServerComponent = Parameters<typeof octaneRenderToString>[0];
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- framework render handlers share an async contract
|
|
9
|
+
export async function renderRouterToString({
|
|
10
|
+
router,
|
|
11
|
+
responseHeaders,
|
|
12
|
+
App,
|
|
13
|
+
}: {
|
|
14
|
+
router: AnyRouter;
|
|
15
|
+
responseHeaders: Headers;
|
|
16
|
+
App: RouterApp;
|
|
17
|
+
}) {
|
|
18
|
+
try {
|
|
19
|
+
const result = octaneRenderToString(
|
|
20
|
+
App as unknown as ServerComponent,
|
|
21
|
+
{ router },
|
|
22
|
+
{ nonce: router.options.ssr?.nonce },
|
|
23
|
+
);
|
|
24
|
+
router.serverSsr!.setRenderFinished();
|
|
25
|
+
|
|
26
|
+
return new Response(
|
|
27
|
+
finalizeBufferedHtml(result.html, result.css, router.serverSsr!.takeBufferedHtml()),
|
|
28
|
+
{
|
|
29
|
+
status: router.stores.statusCode.get(),
|
|
30
|
+
headers: responseHeaders,
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Render to string error:', error);
|
|
35
|
+
return new Response('Internal Server Error', {
|
|
36
|
+
status: 500,
|
|
37
|
+
headers: responseHeaders,
|
|
38
|
+
});
|
|
39
|
+
} finally {
|
|
40
|
+
router.serverSsr?.cleanup();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function finalizeBufferedHtml(renderedHtml: string, css: string, injectedHtml?: string) {
|
|
45
|
+
let html = renderedHtml;
|
|
46
|
+
|
|
47
|
+
if (css) {
|
|
48
|
+
html = html.includes('</head>') ? html.replace('</head>', `${css}</head>`) : `${css}${html}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (injectedHtml) {
|
|
52
|
+
html = html.includes('</body>')
|
|
53
|
+
? html.replace('</body>', `${injectedHtml}</body>`)
|
|
54
|
+
: `${html}${injectedHtml}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return `<!DOCTYPE html>${html}`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import '../frameworkTypes';
|
|
2
|
+
|
|
3
|
+
export { RouterServer } from './RouterServer.tsrx';
|
|
4
|
+
export { defaultRenderHandler } from './defaultRenderHandler';
|
|
5
|
+
export { defaultStreamHandler } from './defaultStreamHandler';
|
|
6
|
+
export { renderRouterToStream } from './renderRouterToStream';
|
|
7
|
+
export { renderRouterToString } from './renderRouterToString';
|
|
8
|
+
export * from '@tanstack/router-core/ssr/server';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnyRouter,
|
|
3
|
+
Constrain,
|
|
4
|
+
OptionalStructuralSharing,
|
|
5
|
+
ValidateJSON,
|
|
6
|
+
} from '@tanstack/router-core';
|
|
7
|
+
|
|
8
|
+
export type DefaultStructuralSharingEnabled<TRouter extends AnyRouter> =
|
|
9
|
+
boolean extends TRouter['options']['defaultStructuralSharing']
|
|
10
|
+
? false
|
|
11
|
+
: NonNullable<TRouter['options']['defaultStructuralSharing']>;
|
|
12
|
+
|
|
13
|
+
export interface RequiredStructuralSharing<TStructuralSharing, TConstraint> {
|
|
14
|
+
readonly structuralSharing: Constrain<TStructuralSharing, TConstraint>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type StructuralSharingOption<
|
|
18
|
+
TRouter extends AnyRouter,
|
|
19
|
+
TSelected,
|
|
20
|
+
TStructuralSharing,
|
|
21
|
+
> = unknown extends TSelected
|
|
22
|
+
? OptionalStructuralSharing<TStructuralSharing, boolean>
|
|
23
|
+
: unknown extends TRouter['routeTree']
|
|
24
|
+
? OptionalStructuralSharing<TStructuralSharing, boolean>
|
|
25
|
+
: TSelected extends ValidateJSON<TSelected>
|
|
26
|
+
? OptionalStructuralSharing<TStructuralSharing, boolean>
|
|
27
|
+
: DefaultStructuralSharingEnabled<TRouter> extends true
|
|
28
|
+
? RequiredStructuralSharing<TStructuralSharing, false>
|
|
29
|
+
: OptionalStructuralSharing<TStructuralSharing, false>;
|
|
30
|
+
|
|
31
|
+
export type StructuralSharingEnabled<
|
|
32
|
+
TRouter extends AnyRouter,
|
|
33
|
+
TStructuralSharing,
|
|
34
|
+
> = boolean extends TStructuralSharing
|
|
35
|
+
? DefaultStructuralSharingEnabled<TRouter>
|
|
36
|
+
: TStructuralSharing;
|
|
37
|
+
|
|
38
|
+
export type ValidateSelected<TRouter extends AnyRouter, TSelected, TStructuralSharing> =
|
|
39
|
+
StructuralSharingEnabled<TRouter, TStructuralSharing> extends true
|
|
40
|
+
? ValidateJSON<TSelected>
|
|
41
|
+
: TSelected;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnyRouter,
|
|
3
|
+
Constrain,
|
|
4
|
+
InferFrom,
|
|
5
|
+
InferMaskFrom,
|
|
6
|
+
InferMaskTo,
|
|
7
|
+
InferSelected,
|
|
8
|
+
InferShouldThrow,
|
|
9
|
+
InferStrict,
|
|
10
|
+
InferTo,
|
|
11
|
+
RegisteredRouter,
|
|
12
|
+
} from '@tanstack/router-core';
|
|
13
|
+
import type { LinkComponentProps } from './linkTypes';
|
|
14
|
+
import type { UseParamsOptions, UseSearchOptions } from './routeHookTypes';
|
|
15
|
+
|
|
16
|
+
export type ValidateLinkOptions<
|
|
17
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
18
|
+
TOptions = unknown,
|
|
19
|
+
TDefaultFrom extends string = string,
|
|
20
|
+
TComp = 'a',
|
|
21
|
+
> = Constrain<
|
|
22
|
+
TOptions,
|
|
23
|
+
LinkComponentProps<
|
|
24
|
+
TComp,
|
|
25
|
+
TRouter,
|
|
26
|
+
InferFrom<TOptions, TDefaultFrom>,
|
|
27
|
+
InferTo<TOptions>,
|
|
28
|
+
InferMaskFrom<TOptions>,
|
|
29
|
+
InferMaskTo<TOptions>
|
|
30
|
+
>
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
/** @private */
|
|
34
|
+
export type InferStructuralSharing<TOptions> = TOptions extends {
|
|
35
|
+
structuralSharing: infer TStructuralSharing;
|
|
36
|
+
}
|
|
37
|
+
? TStructuralSharing
|
|
38
|
+
: unknown;
|
|
39
|
+
|
|
40
|
+
export type ValidateUseSearchOptions<
|
|
41
|
+
TOptions,
|
|
42
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
43
|
+
> = Constrain<
|
|
44
|
+
TOptions,
|
|
45
|
+
UseSearchOptions<
|
|
46
|
+
TRouter,
|
|
47
|
+
InferFrom<TOptions>,
|
|
48
|
+
InferStrict<TOptions>,
|
|
49
|
+
InferShouldThrow<TOptions>,
|
|
50
|
+
InferSelected<TOptions>,
|
|
51
|
+
InferStructuralSharing<TOptions>
|
|
52
|
+
>
|
|
53
|
+
>;
|
|
54
|
+
|
|
55
|
+
export type ValidateUseParamsOptions<
|
|
56
|
+
TOptions,
|
|
57
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
58
|
+
> = Constrain<
|
|
59
|
+
TOptions,
|
|
60
|
+
UseParamsOptions<
|
|
61
|
+
TRouter,
|
|
62
|
+
InferFrom<TOptions>,
|
|
63
|
+
InferStrict<TOptions>,
|
|
64
|
+
InferShouldThrow<TOptions>,
|
|
65
|
+
InferSelected<TOptions>,
|
|
66
|
+
InferStructuralSharing<TOptions>
|
|
67
|
+
>
|
|
68
|
+
>;
|
|
69
|
+
|
|
70
|
+
export type ValidateLinkOptionsArray<
|
|
71
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
72
|
+
TOptions extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
|
|
73
|
+
TDefaultFrom extends string = string,
|
|
74
|
+
TComp = 'a',
|
|
75
|
+
> = {
|
|
76
|
+
[K in keyof TOptions]: ValidateLinkOptions<TRouter, TOptions[K], TDefaultFrom, TComp>;
|
|
77
|
+
};
|
package/src/useAwaited.ts
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
// this is a one-liner. The promise is typically produced by router-core's `defer()`
|
|
4
4
|
// in a loader and streamed to the client.
|
|
5
5
|
import { use } from 'octane';
|
|
6
|
+
import { toExternalHydrationThenable } from './externalHydration';
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
-
|
|
8
|
+
export type AwaitOptions<T> = {
|
|
9
|
+
promise: Promise<T>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function useAwaited<T>(opts: AwaitOptions<T>): T {
|
|
13
|
+
return use(toExternalHydrationThenable(opts.promise));
|
|
9
14
|
}
|
package/src/useBlocker.tsrx
CHANGED
|
@@ -7,9 +7,62 @@
|
|
|
7
7
|
// in .tsrx so the compiler slots the internal useState/useEffect and callers'
|
|
8
8
|
// withSlot wrapping keeps per-call-site state independent.
|
|
9
9
|
import { useState, useEffect, isChildrenBlock } from 'octane';
|
|
10
|
+
import type { OctaneNode } from 'octane';
|
|
11
|
+
import type { BlockerFnArgs, HistoryAction, HistoryLocation } from '@tanstack/history';
|
|
10
12
|
import { useRouter } from './context.ts';
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
// Blocker types, ported from react-router's useBlocker.tsx. Upstream derives the
|
|
15
|
+
// location shapes from the registered route tree's generics; the octane binding's
|
|
16
|
+
// route factories are untyped (`createRoute(options: any)`), so the union of
|
|
17
|
+
// matched-route shapes collapses to the loosely-typed equivalent.
|
|
18
|
+
export interface ShouldBlockFnLocation {
|
|
19
|
+
routeId: string;
|
|
20
|
+
fullPath: string;
|
|
21
|
+
pathname: string;
|
|
22
|
+
params: Record<string, string>;
|
|
23
|
+
search: Record<string, any>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type BlockerResolver =
|
|
27
|
+
| {
|
|
28
|
+
status: 'blocked';
|
|
29
|
+
current: ShouldBlockFnLocation;
|
|
30
|
+
next: ShouldBlockFnLocation;
|
|
31
|
+
action: HistoryAction;
|
|
32
|
+
proceed: () => void;
|
|
33
|
+
reset: () => void;
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
status: 'idle';
|
|
37
|
+
current: undefined;
|
|
38
|
+
next: undefined;
|
|
39
|
+
action: undefined;
|
|
40
|
+
proceed: undefined;
|
|
41
|
+
reset: undefined;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ShouldBlockFnArgs = {
|
|
45
|
+
current: ShouldBlockFnLocation;
|
|
46
|
+
next: ShouldBlockFnLocation;
|
|
47
|
+
action: HistoryAction;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ShouldBlockFn = (args: ShouldBlockFnArgs) => boolean | Promise<boolean>;
|
|
51
|
+
|
|
52
|
+
export type UseBlockerOpts = {
|
|
53
|
+
shouldBlockFn: ShouldBlockFn;
|
|
54
|
+
enableBeforeUnload?: boolean | (() => boolean);
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
withResolver?: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
type LegacyBlockerFn = () => Promise<any> | any;
|
|
60
|
+
type LegacyBlockerOpts = {
|
|
61
|
+
blockerFn?: LegacyBlockerFn;
|
|
62
|
+
condition?: boolean | any;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const IDLE_RESOLVER: BlockerResolver = {
|
|
13
66
|
status: 'idle',
|
|
14
67
|
current: undefined,
|
|
15
68
|
next: undefined,
|
|
@@ -18,7 +71,10 @@ const IDLE_RESOLVER = {
|
|
|
18
71
|
reset: undefined,
|
|
19
72
|
};
|
|
20
73
|
|
|
21
|
-
function _resolveBlockerOpts(
|
|
74
|
+
function _resolveBlockerOpts(
|
|
75
|
+
opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
|
|
76
|
+
condition?: boolean | any,
|
|
77
|
+
): UseBlockerOpts {
|
|
22
78
|
if (opts === undefined) {
|
|
23
79
|
return { shouldBlockFn: () => true, withResolver: false };
|
|
24
80
|
}
|
|
@@ -48,18 +104,21 @@ function _resolveBlockerOpts(opts, condition) {
|
|
|
48
104
|
};
|
|
49
105
|
}
|
|
50
106
|
|
|
51
|
-
export function useBlocker(
|
|
107
|
+
export function useBlocker(
|
|
108
|
+
opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
|
|
109
|
+
condition?: boolean | any,
|
|
110
|
+
): BlockerResolver {
|
|
52
111
|
const { shouldBlockFn, enableBeforeUnload = true, disabled = false, withResolver = false } =
|
|
53
112
|
_resolveBlockerOpts(opts, condition);
|
|
54
113
|
|
|
55
114
|
const router = useRouter();
|
|
56
115
|
const { history } = router;
|
|
57
116
|
|
|
58
|
-
const [resolver, setResolver] = useState(IDLE_RESOLVER);
|
|
117
|
+
const [resolver, setResolver] = useState<BlockerResolver>(IDLE_RESOLVER);
|
|
59
118
|
|
|
60
119
|
useEffect(() => {
|
|
61
|
-
const blockerFnComposed = async (blockerFnArgs) => {
|
|
62
|
-
function getLocation(location) {
|
|
120
|
+
const blockerFnComposed = async (blockerFnArgs: BlockerFnArgs) => {
|
|
121
|
+
function getLocation(location: HistoryLocation): ShouldBlockFnLocation {
|
|
63
122
|
const parsedLocation = router.parseLocation(location);
|
|
64
123
|
const matchedRoutes = router.getMatchedRoutes(parsedLocation.pathname);
|
|
65
124
|
if (matchedRoutes.foundRoute === undefined) {
|
|
@@ -95,7 +154,7 @@ export function useBlocker(opts, condition) {
|
|
|
95
154
|
if (!withResolver) return shouldBlock;
|
|
96
155
|
if (!shouldBlock) return false;
|
|
97
156
|
|
|
98
|
-
const promise = new Promise((resolve) => {
|
|
157
|
+
const promise = new Promise<boolean>((resolve) => {
|
|
99
158
|
setResolver({
|
|
100
159
|
status: 'blocked',
|
|
101
160
|
current,
|
|
@@ -119,9 +178,15 @@ export function useBlocker(opts, condition) {
|
|
|
119
178
|
return resolver;
|
|
120
179
|
}
|
|
121
180
|
|
|
181
|
+
// Upstream's PromptProps: the blocker options plus optional children (a render
|
|
182
|
+
// prop receiving the resolver, or plain renderables).
|
|
183
|
+
export type PromptProps = (UseBlockerOpts | LegacyBlockerOpts) & {
|
|
184
|
+
children?: OctaneNode | ((params: BlockerResolver) => OctaneNode);
|
|
185
|
+
};
|
|
186
|
+
|
|
122
187
|
// Declarative blocker: registers useBlocker and renders children (optionally a
|
|
123
188
|
// render prop receiving the resolver).
|
|
124
|
-
export function Block(props) @{
|
|
189
|
+
export function Block(props: PromptProps) @{
|
|
125
190
|
const { children, ...rest } = props;
|
|
126
191
|
const resolver = useBlocker(rest);
|
|
127
192
|
const out =
|
package/src/useBlocker.tsrx.d.ts
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
1
|
// Type declaration for the .tsrx module (resolved by relative path).
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import type { HistoryAction } from '@tanstack/history';
|
|
3
|
+
|
|
4
|
+
export interface ShouldBlockFnLocation {
|
|
5
|
+
routeId: string;
|
|
6
|
+
fullPath: string;
|
|
7
|
+
pathname: string;
|
|
8
|
+
params: Record<string, string>;
|
|
9
|
+
search: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type BlockerResolver =
|
|
13
|
+
| {
|
|
14
|
+
status: 'blocked';
|
|
15
|
+
current: ShouldBlockFnLocation;
|
|
16
|
+
next: ShouldBlockFnLocation;
|
|
17
|
+
action: HistoryAction;
|
|
18
|
+
proceed: () => void;
|
|
19
|
+
reset: () => void;
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
status: 'idle';
|
|
23
|
+
current: undefined;
|
|
24
|
+
next: undefined;
|
|
25
|
+
action: undefined;
|
|
26
|
+
proceed: undefined;
|
|
27
|
+
reset: undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ShouldBlockFnArgs = {
|
|
31
|
+
current: ShouldBlockFnLocation;
|
|
32
|
+
next: ShouldBlockFnLocation;
|
|
33
|
+
action: HistoryAction;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ShouldBlockFn = (args: ShouldBlockFnArgs) => boolean | Promise<boolean>;
|
|
37
|
+
|
|
38
|
+
export type UseBlockerOpts = {
|
|
39
|
+
shouldBlockFn: ShouldBlockFn;
|
|
40
|
+
enableBeforeUnload?: boolean | (() => boolean);
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
withResolver?: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type LegacyBlockerFn = () => Promise<any> | any;
|
|
46
|
+
type LegacyBlockerOpts = {
|
|
47
|
+
blockerFn?: LegacyBlockerFn;
|
|
48
|
+
condition?: boolean | any;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type PromptProps = (UseBlockerOpts | LegacyBlockerOpts) & {
|
|
52
|
+
children?: unknown | ((params: BlockerResolver) => unknown);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export declare const useBlocker: (
|
|
56
|
+
opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
|
|
57
|
+
condition?: boolean | any,
|
|
58
|
+
) => BlockerResolver;
|
|
59
|
+
export declare const Block: (props: PromptProps) => unknown;
|
package/src/useRouterState.ts
CHANGED
|
@@ -3,7 +3,27 @@
|
|
|
3
3
|
import { useStore } from './useStore';
|
|
4
4
|
import { useRouter } from './context';
|
|
5
5
|
import { splitSlot, subSlot } from './internal';
|
|
6
|
+
import type { AnyRouter, RegisteredRouter, RouterState } from '@tanstack/router-core';
|
|
7
|
+
import type { StructuralSharingOption, ValidateSelected } from './structuralSharing';
|
|
6
8
|
|
|
9
|
+
export type UseRouterStateOptions<TRouter extends AnyRouter, TSelected, TStructuralSharing> = {
|
|
10
|
+
router?: TRouter;
|
|
11
|
+
select?: (
|
|
12
|
+
state: RouterState<TRouter['routeTree']>,
|
|
13
|
+
) => ValidateSelected<TRouter, TSelected, TStructuralSharing>;
|
|
14
|
+
} & StructuralSharingOption<TRouter, TSelected, TStructuralSharing>;
|
|
15
|
+
|
|
16
|
+
export type UseRouterStateResult<TRouter extends AnyRouter, TSelected> = unknown extends TSelected
|
|
17
|
+
? RouterState<TRouter['routeTree']>
|
|
18
|
+
: TSelected;
|
|
19
|
+
|
|
20
|
+
export function useRouterState<
|
|
21
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
22
|
+
TSelected = unknown,
|
|
23
|
+
TStructuralSharing extends boolean = boolean,
|
|
24
|
+
>(
|
|
25
|
+
opts?: UseRouterStateOptions<TRouter, TSelected, TStructuralSharing>,
|
|
26
|
+
): UseRouterStateResult<TRouter, TSelected>;
|
|
7
27
|
export function useRouterState(...args: any[]): any {
|
|
8
28
|
const [user, slot] = splitSlot(args);
|
|
9
29
|
const opts = (user[0] ?? {}) as { select?: (s: any) => any; router?: any };
|
package/src/useStore.ts
CHANGED
|
@@ -13,11 +13,22 @@ interface Atom<T> {
|
|
|
13
13
|
get: () => T;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// Public signature mirrors @tanstack/react-store's `useStore(store, selector,
|
|
17
|
+
// compare)`; the store parameter is the structural `{ get }` shape so router-core's
|
|
18
|
+
// `RouterReadableStore`/`RouterWritableStore` atoms (whose types omit `subscribe`)
|
|
19
|
+
// infer `T` directly. The trailing `slot` is the binding's forwarded call-site slot
|
|
20
|
+
// (see internal.ts) — the implementation splits it off the raw argument list.
|
|
21
|
+
export function useStore<T, S = T>(
|
|
22
|
+
atom: { get: () => T },
|
|
23
|
+
selector?: (state: T) => S,
|
|
24
|
+
compare?: (a: S, b: S) => boolean,
|
|
25
|
+
slot?: symbol,
|
|
26
|
+
): S;
|
|
27
|
+
export function useStore(...args: any[]): any {
|
|
17
28
|
const [user, slot] = splitSlot(args);
|
|
18
|
-
const atom = user[0] as Atom<
|
|
19
|
-
const selector = (user[1] ?? ((s:
|
|
20
|
-
const compare = (user[2] ?? Object.is) as (a:
|
|
29
|
+
const atom = user[0] as Atom<unknown>;
|
|
30
|
+
const selector = (user[1] ?? ((s: unknown) => s)) as (s: unknown) => unknown;
|
|
31
|
+
const compare = (user[2] ?? Object.is) as (a: unknown, b: unknown) => boolean;
|
|
21
32
|
|
|
22
33
|
// Re-subscribe only when the atom identity changes (it's stable across renders).
|
|
23
34
|
const subscribe = useCallback(
|
|
@@ -28,8 +39,8 @@ export function useStore<T, S = T>(...args: any[]): S {
|
|
|
28
39
|
|
|
29
40
|
// Memoize selector output: same store input → same output; structurally-equal
|
|
30
41
|
// output keeps its previous reference (so useSyncExternalStore doesn't loop).
|
|
31
|
-
const cache = useRef<{ in:
|
|
32
|
-
const getSnapshot = ():
|
|
42
|
+
const cache = useRef<{ in: unknown; out: unknown } | null>(null, subSlot(slot, 'us:cache'));
|
|
43
|
+
const getSnapshot = (): unknown => {
|
|
33
44
|
const input = atom.get();
|
|
34
45
|
const prev = cache.current;
|
|
35
46
|
if (prev && Object.is(prev.in, input)) return prev.out;
|