@octanejs/tanstack-router 0.2.7 → 0.2.8
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 +48 -0
- package/package.json +3 -2
- package/src/Matches.tsrx +1 -1
- package/src/Transitioner.tsrx +3 -3
- package/src/compiled/Matches.tsrx +266 -0
- package/src/compiled/bindings.ts +40 -0
- package/src/compiled.ts +32 -0
- package/src/fileRoute.ts +3 -2
- package/src/frameworkTypes.ts +11 -8
- package/src/hookBindings.ts +396 -0
- package/src/hooks.ts +17 -374
- package/src/index.ts +2 -0
- package/src/lazyRouteComponent.ts +6 -50
- package/src/lazyRouteLoader.ts +49 -0
- package/src/route.ts +181 -0
- package/src/routerStateBinding.ts +41 -0
- package/src/useRouterState.ts +3 -35
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// The read hooks — ports of react-router's useMatch.tsx / useParams / useSearch /
|
|
2
|
+
// useLoaderData / useLoaderDeps / useRouteContext / useNavigate / useCanGoBack /
|
|
3
|
+
// Matches.tsx (useMatches / useParentMatches / useChildMatches). Everything match-
|
|
4
|
+
// shaped funnels through `useMatch`, which subscribes to ONE match store via
|
|
5
|
+
// `router.stores.getMatchStore(routeId)`:
|
|
6
|
+
// - `from` given → that routeId's match store;
|
|
7
|
+
// - no `from` → the NEAREST match via `matchContext` (the match id the enclosing
|
|
8
|
+
// `<Match>` provided) — NOT the leaf match.
|
|
9
|
+
// A missing match throws unless `shouldThrow: false` (upstream invariant).
|
|
10
|
+
// Selectors run through `useStructuralSharing` (replaceEqualDeep against the
|
|
11
|
+
// previous selection when `structuralSharing ?? defaultStructuralSharing`).
|
|
12
|
+
import { useRef, useCallback } from 'octane';
|
|
13
|
+
import { replaceEqualDeep } from '@tanstack/router-core';
|
|
14
|
+
import type { useRouter as StandardUseRouter } from './context';
|
|
15
|
+
import { useStore } from './useStore';
|
|
16
|
+
import { splitSlot, subSlot } from './internal';
|
|
17
|
+
import type {
|
|
18
|
+
AnyRouter,
|
|
19
|
+
FromPathOption,
|
|
20
|
+
RegisteredRouter,
|
|
21
|
+
ThrowConstraint,
|
|
22
|
+
ThrowOrOptional,
|
|
23
|
+
UseLoaderDataResult,
|
|
24
|
+
UseLoaderDepsResult,
|
|
25
|
+
UseNavigateResult,
|
|
26
|
+
UseParamsResult,
|
|
27
|
+
UseRouteContextOptions,
|
|
28
|
+
UseRouteContextResult,
|
|
29
|
+
UseSearchResult,
|
|
30
|
+
} from '@tanstack/router-core';
|
|
31
|
+
import type {
|
|
32
|
+
UseLoaderDataOptions,
|
|
33
|
+
UseLoaderDepsOptions,
|
|
34
|
+
UseLocationBaseOptions,
|
|
35
|
+
UseLocationResult,
|
|
36
|
+
UseMatchOptions,
|
|
37
|
+
UseMatchResult,
|
|
38
|
+
UseMatchesBaseOptions,
|
|
39
|
+
UseMatchesResult,
|
|
40
|
+
UseParamsOptions,
|
|
41
|
+
UseSearchOptions,
|
|
42
|
+
} from './routeHookTypes';
|
|
43
|
+
import type { StructuralSharingOption } from './structuralSharing';
|
|
44
|
+
|
|
45
|
+
// Sentinel store + selection for "no match at this id" (upstream's dummyStore).
|
|
46
|
+
const dummyStore = {
|
|
47
|
+
get() {},
|
|
48
|
+
subscribe() {
|
|
49
|
+
return { unsubscribe() {} };
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Selector wrapper honoring structural sharing: when enabled, the selection is
|
|
54
|
+
// replaceEqualDeep'd against the previous one so deep-equal slices keep their
|
|
55
|
+
// reference (no re-render). Port of react-router's useStructuralSharing.
|
|
56
|
+
function useStructuralSharing(opts: any, router: any, slot: symbol | undefined) {
|
|
57
|
+
const previousResult = useRef<any>(undefined, subSlot(slot, 'ss'));
|
|
58
|
+
return (slice: any) => {
|
|
59
|
+
const selected = opts?.select ? opts.select(slice) : slice;
|
|
60
|
+
if (opts?.structuralSharing ?? router.options.defaultStructuralSharing) {
|
|
61
|
+
return (previousResult.current = replaceEqualDeep(previousResult.current, selected));
|
|
62
|
+
}
|
|
63
|
+
return selected;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Both rendering entries own their contexts; the selectors and slot forwarding stay shared.
|
|
68
|
+
export function createRouterHooks(
|
|
69
|
+
useRouter: typeof StandardUseRouter,
|
|
70
|
+
useNearestMatch: () => string | undefined,
|
|
71
|
+
) {
|
|
72
|
+
function useMatch<
|
|
73
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
74
|
+
const TFrom extends string | undefined = undefined,
|
|
75
|
+
TStrict extends boolean = true,
|
|
76
|
+
TThrow extends boolean = true,
|
|
77
|
+
TSelected = unknown,
|
|
78
|
+
TStructuralSharing extends boolean = boolean,
|
|
79
|
+
>(
|
|
80
|
+
opts: UseMatchOptions<
|
|
81
|
+
TRouter,
|
|
82
|
+
TFrom,
|
|
83
|
+
TStrict,
|
|
84
|
+
ThrowConstraint<TStrict, TThrow>,
|
|
85
|
+
TSelected,
|
|
86
|
+
TStructuralSharing
|
|
87
|
+
>,
|
|
88
|
+
): ThrowOrOptional<UseMatchResult<TRouter, TFrom, TStrict, TSelected>, TThrow>;
|
|
89
|
+
function useMatch(opts: any, slot: symbol | undefined): any;
|
|
90
|
+
function useMatch(...args: any[]): any {
|
|
91
|
+
const [user, slot] = splitSlot(args);
|
|
92
|
+
const opts = user[0] ?? {};
|
|
93
|
+
const router = useRouter();
|
|
94
|
+
// octane has no rules of hooks, so the nearest-match context is read
|
|
95
|
+
// unconditionally (upstream reads a dummy context when `from` is given).
|
|
96
|
+
const nearestMatchId = useNearestMatch();
|
|
97
|
+
const routeId = opts.from ?? (nearestMatchId as string);
|
|
98
|
+
const matchStore = router.stores.getMatchStore(routeId);
|
|
99
|
+
|
|
100
|
+
const selector = useStructuralSharing(opts, router, subSlot(slot, 'm'));
|
|
101
|
+
const matchSelection = useStore(
|
|
102
|
+
matchStore ?? dummyStore,
|
|
103
|
+
(match: any) => (match ? selector(match) : dummyStore),
|
|
104
|
+
undefined,
|
|
105
|
+
subSlot(slot, 'm:us'),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (matchSelection !== dummyStore) return matchSelection;
|
|
109
|
+
if (opts.shouldThrow ?? true) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Invariant failed: Could not find ${
|
|
112
|
+
opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'
|
|
113
|
+
}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function useParams<
|
|
120
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
121
|
+
const TFrom extends string | undefined = undefined,
|
|
122
|
+
TStrict extends boolean = true,
|
|
123
|
+
TThrow extends boolean = true,
|
|
124
|
+
TSelected = unknown,
|
|
125
|
+
TStructuralSharing extends boolean = boolean,
|
|
126
|
+
>(
|
|
127
|
+
opts: UseParamsOptions<
|
|
128
|
+
TRouter,
|
|
129
|
+
TFrom,
|
|
130
|
+
TStrict,
|
|
131
|
+
ThrowConstraint<TStrict, TThrow>,
|
|
132
|
+
TSelected,
|
|
133
|
+
TStructuralSharing
|
|
134
|
+
>,
|
|
135
|
+
): ThrowOrOptional<UseParamsResult<TRouter, TFrom, TStrict, TSelected>, TThrow>;
|
|
136
|
+
function useParams(opts: any, slot: symbol | undefined): any;
|
|
137
|
+
function useParams(...args: any[]): any {
|
|
138
|
+
const [user, slot] = splitSlot(args);
|
|
139
|
+
const opts = user[0] ?? {};
|
|
140
|
+
return useMatch(
|
|
141
|
+
{
|
|
142
|
+
from: opts.from,
|
|
143
|
+
strict: opts.strict,
|
|
144
|
+
shouldThrow: opts.shouldThrow,
|
|
145
|
+
structuralSharing: opts.structuralSharing,
|
|
146
|
+
select: (match: any) => {
|
|
147
|
+
const params = opts.strict === false ? match.params : match._strictParams;
|
|
148
|
+
return opts.select ? opts.select(params) : params;
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
subSlot(slot, 'params'),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function useSearch<
|
|
156
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
157
|
+
const TFrom extends string | undefined = undefined,
|
|
158
|
+
TStrict extends boolean = true,
|
|
159
|
+
TThrow extends boolean = true,
|
|
160
|
+
TSelected = unknown,
|
|
161
|
+
TStructuralSharing extends boolean = boolean,
|
|
162
|
+
>(
|
|
163
|
+
opts: UseSearchOptions<
|
|
164
|
+
TRouter,
|
|
165
|
+
TFrom,
|
|
166
|
+
TStrict,
|
|
167
|
+
ThrowConstraint<TStrict, TThrow>,
|
|
168
|
+
TSelected,
|
|
169
|
+
TStructuralSharing
|
|
170
|
+
>,
|
|
171
|
+
): ThrowOrOptional<UseSearchResult<TRouter, TFrom, TStrict, TSelected>, TThrow>;
|
|
172
|
+
function useSearch(opts: any, slot: symbol | undefined): any;
|
|
173
|
+
function useSearch(...args: any[]): any {
|
|
174
|
+
const [user, slot] = splitSlot(args);
|
|
175
|
+
const opts = user[0] ?? {};
|
|
176
|
+
return useMatch(
|
|
177
|
+
{
|
|
178
|
+
from: opts.from,
|
|
179
|
+
strict: opts.strict,
|
|
180
|
+
shouldThrow: opts.shouldThrow,
|
|
181
|
+
structuralSharing: opts.structuralSharing,
|
|
182
|
+
select: (match: any) => (opts.select ? opts.select(match.search) : match.search),
|
|
183
|
+
},
|
|
184
|
+
subSlot(slot, 'search'),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function useLoaderData<
|
|
189
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
190
|
+
const TFrom extends string | undefined = undefined,
|
|
191
|
+
TStrict extends boolean = true,
|
|
192
|
+
TSelected = unknown,
|
|
193
|
+
TStructuralSharing extends boolean = boolean,
|
|
194
|
+
>(
|
|
195
|
+
opts: UseLoaderDataOptions<TRouter, TFrom, TStrict, TSelected, TStructuralSharing>,
|
|
196
|
+
): UseLoaderDataResult<TRouter, TFrom, TStrict, TSelected>;
|
|
197
|
+
function useLoaderData(opts: any, slot: symbol | undefined): any;
|
|
198
|
+
function useLoaderData(...args: any[]): any {
|
|
199
|
+
const [user, slot] = splitSlot(args);
|
|
200
|
+
const opts = user[0] ?? {};
|
|
201
|
+
return useMatch(
|
|
202
|
+
{
|
|
203
|
+
from: opts.from,
|
|
204
|
+
strict: opts.strict,
|
|
205
|
+
structuralSharing: opts.structuralSharing,
|
|
206
|
+
select: (match: any) => (opts.select ? opts.select(match.loaderData) : match.loaderData),
|
|
207
|
+
},
|
|
208
|
+
subSlot(slot, 'loader'),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function useLoaderDeps<
|
|
213
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
214
|
+
const TFrom extends string | undefined = undefined,
|
|
215
|
+
TSelected = unknown,
|
|
216
|
+
TStructuralSharing extends boolean = boolean,
|
|
217
|
+
>(
|
|
218
|
+
opts: UseLoaderDepsOptions<TRouter, TFrom, TSelected, TStructuralSharing>,
|
|
219
|
+
): UseLoaderDepsResult<TRouter, TFrom, TSelected>;
|
|
220
|
+
function useLoaderDeps(opts: any, slot: symbol | undefined): any;
|
|
221
|
+
function useLoaderDeps(...args: any[]): any {
|
|
222
|
+
const [user, slot] = splitSlot(args);
|
|
223
|
+
const opts = user[0] ?? {};
|
|
224
|
+
const { select, ...rest } = opts;
|
|
225
|
+
return useMatch(
|
|
226
|
+
{
|
|
227
|
+
...rest,
|
|
228
|
+
select: (match: any) => (select ? select(match.loaderDeps) : match.loaderDeps),
|
|
229
|
+
},
|
|
230
|
+
subSlot(slot, 'deps'),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function useRouteContext<
|
|
235
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
236
|
+
const TFrom extends string | undefined = undefined,
|
|
237
|
+
TStrict extends boolean = true,
|
|
238
|
+
TSelected = unknown,
|
|
239
|
+
>(
|
|
240
|
+
opts: UseRouteContextOptions<TRouter, TFrom, TStrict, TSelected>,
|
|
241
|
+
): UseRouteContextResult<TRouter, TFrom, TStrict, TSelected>;
|
|
242
|
+
function useRouteContext(opts: any, slot: symbol | undefined): any;
|
|
243
|
+
function useRouteContext(...args: any[]): any {
|
|
244
|
+
const [user, slot] = splitSlot(args);
|
|
245
|
+
const opts = user[0] ?? {};
|
|
246
|
+
return useMatch(
|
|
247
|
+
{
|
|
248
|
+
...opts,
|
|
249
|
+
select: (match: any) => (opts.select ? opts.select(match.context) : match.context),
|
|
250
|
+
},
|
|
251
|
+
subSlot(slot, 'ctx'),
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function useLocation<
|
|
256
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
257
|
+
TSelected = unknown,
|
|
258
|
+
TStructuralSharing extends boolean = boolean,
|
|
259
|
+
>(
|
|
260
|
+
opts?: UseLocationBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
261
|
+
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
262
|
+
): UseLocationResult<TRouter, TSelected>;
|
|
263
|
+
function useLocation(opts: any, slot: symbol | undefined): any;
|
|
264
|
+
function useLocation(...args: any[]): any {
|
|
265
|
+
const [user, slot] = splitSlot(args);
|
|
266
|
+
const opts = user[0] ?? {};
|
|
267
|
+
const router = useRouter();
|
|
268
|
+
return useStore(
|
|
269
|
+
router.stores.location,
|
|
270
|
+
useStructuralSharing(opts, router, subSlot(slot, 'loc')),
|
|
271
|
+
undefined,
|
|
272
|
+
subSlot(slot, 'loc:us'),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function useMatches<
|
|
277
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
278
|
+
TSelected = unknown,
|
|
279
|
+
TStructuralSharing extends boolean = boolean,
|
|
280
|
+
>(
|
|
281
|
+
opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
282
|
+
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
283
|
+
): UseMatchesResult<TRouter, TSelected>;
|
|
284
|
+
function useMatches(opts: any, slot: symbol | undefined): any;
|
|
285
|
+
function useMatches(...args: any[]): any {
|
|
286
|
+
const [user, slot] = splitSlot(args);
|
|
287
|
+
const opts = user[0] ?? {};
|
|
288
|
+
const router = useRouter();
|
|
289
|
+
return useStore(
|
|
290
|
+
router.stores.matches,
|
|
291
|
+
useStructuralSharing(opts, router, subSlot(slot, 'matches')),
|
|
292
|
+
undefined,
|
|
293
|
+
subSlot(slot, 'matches:us'),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function useParentMatches<
|
|
298
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
299
|
+
TSelected = unknown,
|
|
300
|
+
TStructuralSharing extends boolean = boolean,
|
|
301
|
+
>(
|
|
302
|
+
opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
303
|
+
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
304
|
+
): UseMatchesResult<TRouter, TSelected>;
|
|
305
|
+
function useParentMatches(opts: any, slot: symbol | undefined): any;
|
|
306
|
+
function useParentMatches(...args: any[]): any {
|
|
307
|
+
const [user, slot] = splitSlot(args);
|
|
308
|
+
const opts = user[0] ?? {};
|
|
309
|
+
const contextRouteId = useNearestMatch();
|
|
310
|
+
return useMatches(
|
|
311
|
+
{
|
|
312
|
+
select: (matches: any[]) => {
|
|
313
|
+
matches = matches.slice(
|
|
314
|
+
0,
|
|
315
|
+
matches.findIndex((d: any) => d.routeId === contextRouteId),
|
|
316
|
+
);
|
|
317
|
+
return opts.select ? opts.select(matches) : matches;
|
|
318
|
+
},
|
|
319
|
+
structuralSharing: opts.structuralSharing,
|
|
320
|
+
},
|
|
321
|
+
subSlot(slot, 'parents'),
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function useChildMatches<
|
|
326
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
327
|
+
TSelected = unknown,
|
|
328
|
+
TStructuralSharing extends boolean = boolean,
|
|
329
|
+
>(
|
|
330
|
+
opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
331
|
+
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
332
|
+
): UseMatchesResult<TRouter, TSelected>;
|
|
333
|
+
function useChildMatches(opts: any, slot: symbol | undefined): any;
|
|
334
|
+
function useChildMatches(...args: any[]): any {
|
|
335
|
+
const [user, slot] = splitSlot(args);
|
|
336
|
+
const opts = user[0] ?? {};
|
|
337
|
+
const contextRouteId = useNearestMatch();
|
|
338
|
+
return useMatches(
|
|
339
|
+
{
|
|
340
|
+
select: (matches: any[]) => {
|
|
341
|
+
matches = matches.slice(matches.findIndex((d: any) => d.routeId === contextRouteId) + 1);
|
|
342
|
+
return opts.select ? opts.select(matches) : matches;
|
|
343
|
+
},
|
|
344
|
+
structuralSharing: opts.structuralSharing,
|
|
345
|
+
},
|
|
346
|
+
subSlot(slot, 'children'),
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Returns a STABLE navigate function (upstream useCallback([from, router])) that
|
|
351
|
+
// forwards to `router.navigate`, defaulting `from` to the hook's option.
|
|
352
|
+
function useNavigate<
|
|
353
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
354
|
+
TDefaultFrom extends string = string,
|
|
355
|
+
>(options?: { from?: FromPathOption<TRouter, TDefaultFrom> }): UseNavigateResult<TDefaultFrom>;
|
|
356
|
+
function useNavigate(options: any, slot: symbol | undefined): (to: any) => any;
|
|
357
|
+
function useNavigate(...args: any[]): (to: any) => any {
|
|
358
|
+
const [user, slot] = splitSlot(args);
|
|
359
|
+
const opts = user[0] ?? {};
|
|
360
|
+
const router = useRouter(opts.router ? { router: opts.router } : undefined);
|
|
361
|
+
return useCallback(
|
|
362
|
+
(options: any) => router.navigate({ ...options, from: options?.from ?? opts.from }),
|
|
363
|
+
[opts.from, router],
|
|
364
|
+
subSlot(slot, 'nav'),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// True when the current history entry isn't the first (there is somewhere to go
|
|
369
|
+
// back to) — per upstream useCanGoBack (location.state.__TSR_index !== 0).
|
|
370
|
+
function useCanGoBack(): boolean;
|
|
371
|
+
function useCanGoBack(...args: any[]): boolean {
|
|
372
|
+
const [, slot] = splitSlot(args);
|
|
373
|
+
const router = useRouter();
|
|
374
|
+
return useStore(
|
|
375
|
+
router.stores.location,
|
|
376
|
+
(location: any) => location.state.__TSR_index !== 0,
|
|
377
|
+
undefined,
|
|
378
|
+
subSlot(slot, 'back'),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
useMatch,
|
|
384
|
+
useParams,
|
|
385
|
+
useSearch,
|
|
386
|
+
useLoaderData,
|
|
387
|
+
useLoaderDeps,
|
|
388
|
+
useRouteContext,
|
|
389
|
+
useLocation,
|
|
390
|
+
useMatches,
|
|
391
|
+
useParentMatches,
|
|
392
|
+
useChildMatches,
|
|
393
|
+
useNavigate,
|
|
394
|
+
useCanGoBack,
|
|
395
|
+
};
|
|
396
|
+
}
|