@richie-router/react 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,552 @@
1
+ // packages/react/src/router.test.ts
2
+ import { describe, expect, test } from "bun:test";
3
+ import React from "react";
4
+ import { renderToStaticMarkup } from "react-dom/server";
5
+ import {
6
+ Link,
7
+ useMatchRoute,
8
+ RouterProvider,
9
+ createLink,
10
+ createFileRoute,
11
+ createMemoryHistory,
12
+ createRootRoute,
13
+ createRouter
14
+ } from "./router.mjs";
15
+ function createTestRouteTree(options) {
16
+ const rootRoute = createRootRoute({
17
+ component: () => null
18
+ });
19
+ const indexRoute = createFileRoute("/")({
20
+ component: () => null
21
+ });
22
+ const aboutRoute = createFileRoute("/about")({
23
+ component: () => null
24
+ });
25
+ aboutRoute._setServerHead(options?.serverHead);
26
+ return rootRoute._addFileChildren({
27
+ index: indexRoute,
28
+ about: aboutRoute
29
+ });
30
+ }
31
+ function createNestedHeadRouteTree() {
32
+ const rootRoute = createRootRoute({
33
+ component: () => null
34
+ });
35
+ const postsRoute = createFileRoute("/posts")({
36
+ component: () => null,
37
+ head: [
38
+ { tag: "title", children: "Posts" }
39
+ ]
40
+ });
41
+ const postRoute = createFileRoute("/posts/$postId")({
42
+ component: () => null
43
+ });
44
+ rootRoute._setServerHead(true);
45
+ postRoute._setServerHead(true);
46
+ postsRoute._addFileChildren({
47
+ post: postRoute
48
+ });
49
+ return rootRoute._addFileChildren({
50
+ posts: postsRoute
51
+ });
52
+ }
53
+ function createRootServerHeadTree() {
54
+ const rootRoute = createRootRoute({
55
+ component: () => null
56
+ });
57
+ const indexRoute = createFileRoute("/")({
58
+ component: () => null
59
+ });
60
+ const aboutRoute = createFileRoute("/about")({
61
+ component: () => null
62
+ });
63
+ rootRoute._setServerHead(true);
64
+ return rootRoute._addFileChildren({
65
+ index: indexRoute,
66
+ about: aboutRoute
67
+ });
68
+ }
69
+ function createNestedServerHeadTree() {
70
+ const rootRoute = createRootRoute({
71
+ component: () => null
72
+ });
73
+ const postsRoute = createFileRoute("/posts")({
74
+ component: () => null
75
+ });
76
+ const postRoute = createFileRoute("/posts/$postId")({
77
+ component: () => null
78
+ });
79
+ rootRoute._setServerHead(true);
80
+ postsRoute._setServerHead(true);
81
+ postRoute._setServerHead(true);
82
+ postsRoute._addFileChildren({
83
+ post: postRoute
84
+ });
85
+ return rootRoute._addFileChildren({
86
+ posts: postsRoute
87
+ });
88
+ }
89
+ function createLinkTestRouteTree(component) {
90
+ const rootRoute = createRootRoute({
91
+ component
92
+ });
93
+ const postRoute = createFileRoute("/post")({
94
+ component: () => null
95
+ });
96
+ const postsRoute = createFileRoute("/posts")({
97
+ component: () => null
98
+ });
99
+ const postDetailRoute = createFileRoute("/posts/$postId")({
100
+ component: () => null
101
+ });
102
+ postsRoute._addFileChildren({
103
+ detail: postDetailRoute
104
+ });
105
+ return rootRoute._addFileChildren({
106
+ post: postRoute,
107
+ posts: postsRoute
108
+ });
109
+ }
110
+ function renderLinkMarkup(initialEntry, component) {
111
+ const history = createMemoryHistory({
112
+ initialEntries: [initialEntry]
113
+ });
114
+ const router = createRouter({
115
+ routeTree: createLinkTestRouteTree(component),
116
+ history
117
+ });
118
+ return renderToStaticMarkup(React.createElement(RouterProvider, { router }));
119
+ }
120
+ describe("createRouter basePath", () => {
121
+ test('treats "/" as the root basePath', () => {
122
+ const history = createMemoryHistory({
123
+ initialEntries: ["/about?tab=team#bio"]
124
+ });
125
+ const router = createRouter({
126
+ routeTree: createTestRouteTree(),
127
+ history,
128
+ basePath: "/"
129
+ });
130
+ expect(router.state.location.pathname).toBe("/about");
131
+ expect(router.state.location.href).toBe("/about?tab=team#bio");
132
+ expect(router.buildHref({ to: "/about" })).toBe("/about");
133
+ });
134
+ test("normalizes a trailing slash in the basePath", async () => {
135
+ const history = createMemoryHistory({
136
+ initialEntries: ["/project/about?tab=team#bio"]
137
+ });
138
+ const router = createRouter({
139
+ routeTree: createTestRouteTree(),
140
+ history,
141
+ basePath: "/project/"
142
+ });
143
+ expect(router.state.location.pathname).toBe("/about");
144
+ expect(router.buildHref({ to: "/about" })).toBe("/project/about");
145
+ await router.navigate({
146
+ to: "/about"
147
+ });
148
+ expect(history.location.href).toBe("/project/about");
149
+ });
150
+ test("strips the basePath from the current history location", () => {
151
+ const history = createMemoryHistory({
152
+ initialEntries: ["/project/about?tab=team#bio"]
153
+ });
154
+ const router = createRouter({
155
+ routeTree: createTestRouteTree(),
156
+ history,
157
+ basePath: "/project"
158
+ });
159
+ expect(router.state.location.pathname).toBe("/about");
160
+ expect(router.state.location.href).toBe("/about?tab=team#bio");
161
+ expect(router.state.matches.at(-1)?.route.fullPath).toBe("/about");
162
+ });
163
+ test("prefixes generated hrefs and history writes with the basePath", async () => {
164
+ const history = createMemoryHistory({
165
+ initialEntries: ["/project"]
166
+ });
167
+ const router = createRouter({
168
+ routeTree: createTestRouteTree(),
169
+ history,
170
+ basePath: "/project"
171
+ });
172
+ expect(router.buildHref({ to: "/about" })).toBe("/project/about");
173
+ await router.navigate({
174
+ to: "/about",
175
+ search: {
176
+ tab: "team"
177
+ }
178
+ });
179
+ expect(history.location.href).toBe("/project/about?tab=team");
180
+ expect(router.state.location.href).toBe("/about?tab=team");
181
+ });
182
+ test("uses the basePath for the default head API endpoint", async () => {
183
+ const history = createMemoryHistory({
184
+ initialEntries: ["/project/about"]
185
+ });
186
+ const fetchCalls = [];
187
+ const originalFetch = globalThis.fetch;
188
+ globalThis.fetch = async (input) => {
189
+ fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
190
+ return new Response(JSON.stringify({
191
+ head: [],
192
+ routeHeads: [
193
+ { routeId: "/about", head: [] }
194
+ ]
195
+ }), {
196
+ status: 200,
197
+ headers: {
198
+ "content-type": "application/json"
199
+ }
200
+ });
201
+ };
202
+ try {
203
+ const router = createRouter({
204
+ routeTree: createTestRouteTree({ serverHead: true }),
205
+ history,
206
+ basePath: "/project"
207
+ });
208
+ await router.load();
209
+ expect(fetchCalls).toHaveLength(1);
210
+ expect(fetchCalls[0]).toBe("/project/head-api?href=%2Fproject%2Fabout");
211
+ } finally {
212
+ globalThis.fetch = originalFetch;
213
+ }
214
+ });
215
+ test("uses one document head request and preserves inline head precedence", async () => {
216
+ const history = createMemoryHistory({
217
+ initialEntries: ["/posts/alpha"]
218
+ });
219
+ const fetchCalls = [];
220
+ const originalFetch = globalThis.fetch;
221
+ globalThis.fetch = async (input) => {
222
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
223
+ fetchCalls.push(url);
224
+ return new Response(JSON.stringify({
225
+ head: [
226
+ { tag: "title", children: "Site" },
227
+ { tag: "title", children: "Alpha" }
228
+ ],
229
+ routeHeads: [
230
+ {
231
+ routeId: "__root__",
232
+ head: [{ tag: "title", children: "Site" }],
233
+ staleTime: 60000
234
+ },
235
+ {
236
+ routeId: "/posts/$postId",
237
+ head: [{ tag: "title", children: "Alpha" }],
238
+ staleTime: 1e4
239
+ }
240
+ ],
241
+ staleTime: 1e4
242
+ }), {
243
+ status: 200,
244
+ headers: {
245
+ "content-type": "application/json"
246
+ }
247
+ });
248
+ };
249
+ try {
250
+ const router = createRouter({
251
+ routeTree: createNestedHeadRouteTree(),
252
+ history
253
+ });
254
+ await router.load();
255
+ expect(fetchCalls).toEqual(["/head-api?href=%2Fposts%2Falpha"]);
256
+ expect(router.state.head).toEqual([
257
+ { tag: "title", children: "Alpha" }
258
+ ]);
259
+ } finally {
260
+ globalThis.fetch = originalFetch;
261
+ }
262
+ });
263
+ test("reuses a fresh root server head across navigations without refetching", async () => {
264
+ const history = createMemoryHistory({
265
+ initialEntries: ["/"]
266
+ });
267
+ const fetchCalls = [];
268
+ const originalFetch = globalThis.fetch;
269
+ globalThis.fetch = async (input) => {
270
+ fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
271
+ return new Response(JSON.stringify({
272
+ head: [
273
+ { tag: "title", children: "Site" }
274
+ ],
275
+ routeHeads: [
276
+ {
277
+ routeId: "__root__",
278
+ head: [{ tag: "title", children: "Site" }],
279
+ staleTime: 60000
280
+ }
281
+ ],
282
+ staleTime: 60000
283
+ }), {
284
+ status: 200,
285
+ headers: {
286
+ "content-type": "application/json"
287
+ }
288
+ });
289
+ };
290
+ try {
291
+ const router = createRouter({
292
+ routeTree: createRootServerHeadTree(),
293
+ history
294
+ });
295
+ await router.load();
296
+ await router.navigate({
297
+ to: "/about"
298
+ });
299
+ expect(fetchCalls).toEqual(["/head-api?href=%2F"]);
300
+ } finally {
301
+ globalThis.fetch = originalFetch;
302
+ }
303
+ });
304
+ test("seeds the route head cache from the dehydrated snapshot", async () => {
305
+ const history = createMemoryHistory({
306
+ initialEntries: ["/about"]
307
+ });
308
+ const fetchCalls = [];
309
+ const originalFetch = globalThis.fetch;
310
+ const globalWithWindow = globalThis;
311
+ const originalWindow = globalWithWindow.window;
312
+ globalThis.fetch = async (input) => {
313
+ fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
314
+ return new Response(JSON.stringify({
315
+ head: [
316
+ { tag: "title", children: "Site" }
317
+ ],
318
+ routeHeads: [
319
+ {
320
+ routeId: "__root__",
321
+ head: [{ tag: "title", children: "Site" }],
322
+ staleTime: 60000
323
+ }
324
+ ],
325
+ staleTime: 60000
326
+ }), {
327
+ status: 200,
328
+ headers: {
329
+ "content-type": "application/json"
330
+ }
331
+ });
332
+ };
333
+ globalWithWindow.window = {
334
+ __RICHIE_ROUTER_HEAD__: {
335
+ href: "/about",
336
+ head: [
337
+ { tag: "title", children: "Site" }
338
+ ],
339
+ routeHeads: [
340
+ {
341
+ routeId: "__root__",
342
+ head: [{ tag: "title", children: "Site" }],
343
+ staleTime: 60000
344
+ }
345
+ ]
346
+ }
347
+ };
348
+ try {
349
+ const router = createRouter({
350
+ routeTree: createRootServerHeadTree(),
351
+ history
352
+ });
353
+ await router.load();
354
+ expect(fetchCalls).toHaveLength(0);
355
+ expect(router.state.head).toEqual([
356
+ { tag: "title", children: "Site" }
357
+ ]);
358
+ } finally {
359
+ globalThis.fetch = originalFetch;
360
+ if (originalWindow === undefined) {
361
+ Reflect.deleteProperty(globalWithWindow, "window");
362
+ } else {
363
+ originalWindow.__RICHIE_ROUTER_HEAD__ = undefined;
364
+ globalWithWindow.window = originalWindow;
365
+ }
366
+ }
367
+ });
368
+ test("reuses the initial merged head snapshot without fetching when the branch has no inline head", async () => {
369
+ const history = createMemoryHistory({
370
+ initialEntries: ["/about"]
371
+ });
372
+ const fetchCalls = [];
373
+ const originalFetch = globalThis.fetch;
374
+ const globalWithWindow = globalThis;
375
+ const originalWindow = globalWithWindow.window;
376
+ globalThis.fetch = async (input) => {
377
+ fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
378
+ return new Response("{}", {
379
+ status: 500
380
+ });
381
+ };
382
+ globalWithWindow.window = {
383
+ __RICHIE_ROUTER_HEAD__: {
384
+ href: "/about",
385
+ head: [
386
+ { tag: "title", children: "About from SSR" }
387
+ ]
388
+ }
389
+ };
390
+ try {
391
+ const router = createRouter({
392
+ routeTree: createTestRouteTree({ serverHead: true }),
393
+ history
394
+ });
395
+ await router.load();
396
+ expect(fetchCalls).toHaveLength(0);
397
+ expect(router.state.head).toEqual([
398
+ { tag: "title", children: "About from SSR" }
399
+ ]);
400
+ } finally {
401
+ globalThis.fetch = originalFetch;
402
+ if (originalWindow === undefined) {
403
+ Reflect.deleteProperty(globalWithWindow, "window");
404
+ } else {
405
+ originalWindow.__RICHIE_ROUTER_HEAD__ = undefined;
406
+ globalWithWindow.window = originalWindow;
407
+ }
408
+ }
409
+ });
410
+ test("uses the merged document head without route fallback requests when the branch has no inline head", async () => {
411
+ const history = createMemoryHistory({
412
+ initialEntries: ["/posts/alpha"]
413
+ });
414
+ const fetchCalls = [];
415
+ const originalFetch = globalThis.fetch;
416
+ globalThis.fetch = async (input) => {
417
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
418
+ fetchCalls.push(url);
419
+ return new Response(JSON.stringify({
420
+ head: [
421
+ { tag: "meta", name: "description", content: "Nested server head" },
422
+ { tag: "title", children: "Alpha" }
423
+ ],
424
+ staleTime: 1e4
425
+ }), {
426
+ status: 200,
427
+ headers: {
428
+ "content-type": "application/json"
429
+ }
430
+ });
431
+ };
432
+ try {
433
+ const router = createRouter({
434
+ routeTree: createNestedServerHeadTree(),
435
+ history
436
+ });
437
+ await router.load();
438
+ expect(fetchCalls).toEqual(["/head-api?href=%2Fposts%2Falpha"]);
439
+ expect(router.state.head).toEqual([
440
+ { tag: "meta", name: "description", content: "Nested server head" },
441
+ { tag: "title", children: "Alpha" }
442
+ ]);
443
+ } finally {
444
+ globalThis.fetch = originalFetch;
445
+ }
446
+ });
447
+ test("keeps loadRouteHead as the route-scoped override path", async () => {
448
+ const history = createMemoryHistory({
449
+ initialEntries: ["/about"]
450
+ });
451
+ const fetchCalls = [];
452
+ const loadRouteHeadCalls = [];
453
+ const originalFetch = globalThis.fetch;
454
+ globalThis.fetch = async (input) => {
455
+ fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
456
+ return new Response("{}", {
457
+ status: 500
458
+ });
459
+ };
460
+ try {
461
+ const router = createRouter({
462
+ routeTree: createTestRouteTree({ serverHead: true }),
463
+ history,
464
+ loadRouteHead: async ({ routeId }) => {
465
+ loadRouteHeadCalls.push(routeId);
466
+ return {
467
+ head: [
468
+ { tag: "title", children: "About override" }
469
+ ],
470
+ staleTime: 1000
471
+ };
472
+ }
473
+ });
474
+ await router.load();
475
+ expect(loadRouteHeadCalls).toEqual(["/about"]);
476
+ expect(fetchCalls).toHaveLength(0);
477
+ expect(router.state.head).toEqual([
478
+ { tag: "title", children: "About override" }
479
+ ]);
480
+ } finally {
481
+ globalThis.fetch = originalFetch;
482
+ }
483
+ });
484
+ });
485
+ describe("Link active state", () => {
486
+ test("keeps parent links active on child routes by default", () => {
487
+ const TestLink = Link;
488
+ const markup = renderLinkMarkup("/posts/alpha", () => React.createElement(TestLink, { to: "/posts", activeProps: { className: "active" } }, "Posts"));
489
+ expect(markup).toContain('class="active"');
490
+ });
491
+ test("supports exact-only active matching", () => {
492
+ const TestLink = Link;
493
+ const markup = renderLinkMarkup("/posts/alpha", () => React.createElement(TestLink, {
494
+ to: "/posts",
495
+ activeOptions: { exact: true },
496
+ activeProps: { className: "active" }
497
+ }, "Posts"));
498
+ expect(markup).not.toContain('class="active"');
499
+ });
500
+ test("matches path segments instead of raw string prefixes", () => {
501
+ const TestLink = Link;
502
+ const markup = renderLinkMarkup("/posts/alpha", () => React.createElement(TestLink, { to: "/post", activeProps: { className: "active" } }, "Post"));
503
+ expect(markup).not.toContain('class="active"');
504
+ });
505
+ test("applies activeOptions in custom links created with createLink", () => {
506
+ const AppLink = createLink((props) => React.createElement("a", props));
507
+ const markup = renderLinkMarkup("/posts/alpha", () => React.createElement(AppLink, {
508
+ to: "/posts",
509
+ activeOptions: { exact: true },
510
+ activeProps: { className: "active" }
511
+ }, "Posts"));
512
+ expect(markup).not.toContain('class="active"');
513
+ });
514
+ });
515
+ describe("useMatchRoute", () => {
516
+ test("returns matched params for exact matches", () => {
517
+ const markup = renderLinkMarkup("/posts/alpha", () => {
518
+ const matchRoute = useMatchRoute();
519
+ const match = matchRoute({ to: "/posts/$postId" });
520
+ return React.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
521
+ });
522
+ expect(markup).toContain("{"postId":"alpha"}");
523
+ });
524
+ test("supports fuzzy parent matching", () => {
525
+ const markup = renderLinkMarkup("/posts/alpha", () => {
526
+ const matchRoute = useMatchRoute();
527
+ const match = matchRoute({ to: "/posts", fuzzy: true });
528
+ return React.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
529
+ });
530
+ expect(markup).toContain("{}");
531
+ });
532
+ test("supports partial param filters", () => {
533
+ const markup = renderLinkMarkup("/posts/alpha", () => {
534
+ const matchRoute = useMatchRoute();
535
+ const match = matchRoute({ to: "/posts/$postId", params: { postId: "beta" } });
536
+ return React.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
537
+ });
538
+ expect(markup).toContain("false");
539
+ });
540
+ test("can include search params in the match", () => {
541
+ const markup = renderLinkMarkup("/posts/alpha?tab=details&count=2", () => {
542
+ const matchRoute = useMatchRoute();
543
+ const match = matchRoute({
544
+ to: "/posts/$postId",
545
+ includeSearch: true,
546
+ search: { tab: "details" }
547
+ });
548
+ return React.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
549
+ });
550
+ expect(markup).toContain("{"postId":"alpha"}");
551
+ });
552
+ });
@@ -67,6 +67,8 @@ type ParamsInput<TParams> = TParams | ((previous: TParams) => TParams);
67
67
  type SearchInput<TSearch> = TSearch | ((previous: TSearch) => TSearch) | true;
68
68
  type ParamsOption<TParams> = keyof TParams extends never ? {
69
69
  params?: never;
70
+ } : string extends keyof TParams ? {
71
+ params?: ParamsInput<TParams>;
70
72
  } : {
71
73
  params: ParamsInput<TParams>;
72
74
  };
@@ -107,8 +109,22 @@ export type NavigateOptions<TTo extends RoutePaths = RoutePaths> = {
107
109
  search?: SearchInput<SearchForTo<TTo>>;
108
110
  } & NavigateBaseOptions & ParamsOption<ParamsForTo<TTo>>;
109
111
  export type NavigateFn = <TTo extends RoutePaths>(options: NavigateOptions<TTo>) => Promise<void>;
112
+ export interface MatchRouteOptions {
113
+ fuzzy?: boolean;
114
+ includeSearch?: boolean;
115
+ }
116
+ export type UseMatchRouteOptions<TTo extends RoutePaths = RoutePaths> = {
117
+ to: TTo;
118
+ params?: Partial<ParamsForTo<TTo>>;
119
+ search?: Record<string, unknown>;
120
+ } & MatchRouteOptions;
121
+ export type MatchRouteFn = <TTo extends RoutePaths>(options: UseMatchRouteOptions<TTo>) => false | ParamsForTo<TTo>;
122
+ export interface ActiveOptions {
123
+ exact?: boolean;
124
+ }
110
125
  export type LinkOwnProps<TTo extends RoutePaths> = NavigateOptions<TTo> & {
111
126
  preload?: 'intent' | 'render' | false;
127
+ activeOptions?: ActiveOptions;
112
128
  activeProps?: React.AnchorHTMLAttributes<HTMLAnchorElement>;
113
129
  children?: React.ReactNode | ((ctx: {
114
130
  isActive: boolean;
@@ -125,6 +141,7 @@ export interface RouterState {
125
141
  export interface RouterOptions<TRouteTree extends AnyRoute> {
126
142
  routeTree: TRouteTree;
127
143
  history?: RouterHistory;
144
+ basePath?: string;
128
145
  defaultPreload?: 'intent' | 'render' | false;
129
146
  defaultPreloadDelay?: number;
130
147
  defaultPendingMs?: number;
@@ -170,6 +187,8 @@ export declare class Router<TRouteTree extends AnyRoute> {
170
187
  private readonly headCache;
171
188
  private readonly parseSearch;
172
189
  private readonly stringifySearch;
190
+ private readonly basePath;
191
+ private initialHeadSnapshot?;
173
192
  private started;
174
193
  private unsubscribeHistory?;
175
194
  constructor(options: RouterOptions<TRouteTree>);
@@ -184,6 +203,8 @@ export declare class Router<TRouteTree extends AnyRoute> {
184
203
  preloadRoute<TTo extends RoutePaths>(options: NavigateOptions<TTo>): Promise<void>;
185
204
  invalidate(): Promise<void>;
186
205
  buildHref<TTo extends RoutePaths>(options: NavigateOptions<TTo>): string;
206
+ private buildLocation;
207
+ private buildLocationHref;
187
208
  private readLocation;
188
209
  private applyTrailingSlash;
189
210
  private notify;
@@ -192,21 +213,28 @@ export declare class Router<TRouteTree extends AnyRoute> {
192
213
  private resolveSearch;
193
214
  resolveLocation(location: ParsedLocation, options?: {
194
215
  request?: Request;
216
+ initialHeadSnapshot?: DehydratedHeadState;
195
217
  }): Promise<{
196
218
  matches: InternalRouteMatch[];
197
219
  head: HeadConfig;
198
220
  error: unknown;
199
221
  }>;
200
222
  private resolveLocationHead;
223
+ private getRouteHeadCacheKey;
224
+ private getCachedRouteHead;
225
+ private setRouteHeadCache;
226
+ private seedHeadCacheFromRouteHeads;
227
+ private cacheRouteHeadsFromDocument;
201
228
  private loadRouteHead;
202
229
  private fetchRouteHead;
230
+ private fetchDocumentHead;
203
231
  private commitLocation;
204
232
  private restoreScroll;
205
233
  private handleHistoryChange;
206
234
  }
207
235
  export declare function createRouter<TRouteTree extends AnyRoute>(options: RouterOptions<TRouteTree>): Router<TRouteTree>;
208
- export declare function RouterProvider({ router }: {
209
- router: RegisteredRouter;
236
+ export declare function RouterProvider<TRouteTree extends AnyRoute>({ router }: {
237
+ router: Router<TRouteTree>;
210
238
  }): React.ReactElement;
211
239
  export declare function Outlet(): React.ReactNode;
212
240
  export declare function useRouter(): RegisteredRouter;
@@ -221,6 +249,7 @@ export declare function useSearch<TFrom extends RoutePaths>(options?: {
221
249
  from?: TFrom;
222
250
  }): SearchOfRoute<RouteById<TFrom>>;
223
251
  export declare function useNavigate(): NavigateFn;
252
+ export declare function useMatchRoute(): MatchRouteFn;
224
253
  export declare function useLocation(): ParsedLocation;
225
254
  export declare function useRouterState<TSelection>(options: {
226
255
  select: Selector<TSelection>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richie-router/react",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "React runtime, components, and hooks for Richie Router",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -13,7 +13,7 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "@richie-router/core": "^0.1.2"
16
+ "@richie-router/core": "^0.1.3"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "react": "^19"