@funstack/router 0.0.5 → 0.0.7-alpha.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.
@@ -0,0 +1,522 @@
1
+ import { CodeBlock } from "../components/CodeBlock.js";
2
+
3
+ export function LearnTypeSafetyPage() {
4
+ return (
5
+ <div className="learn-content">
6
+ <h2>Type Safety</h2>
7
+
8
+ <p className="page-intro">
9
+ FUNSTACK Router provides first-class TypeScript support, allowing you to
10
+ access route params, navigation state, and loader data with full type
11
+ safety. This guide covers two approaches: receiving typed data through
12
+ component props (recommended) and accessing it through hooks.
13
+ </p>
14
+
15
+ <section>
16
+ <h3>Why Type Safety Matters</h3>
17
+ <p>
18
+ Routing is one of the most common sources of runtime errors in web
19
+ applications. Typos in parameter names, incorrect assumptions about
20
+ data shapes, or forgetting to handle navigation state can lead to
21
+ subtle bugs that are hard to track down.
22
+ </p>
23
+ <p>
24
+ With FUNSTACK Router's type-safe approach, the TypeScript compiler
25
+ catches these errors at build time. You get autocomplete for parameter
26
+ names, type checking for loader data, and confidence that your route
27
+ components receive exactly the data they expect.
28
+ </p>
29
+ <p>There are two ways to access typed route data:</p>
30
+ <ul>
31
+ <li>
32
+ <strong>Props (Recommended)</strong> &mdash; Route components
33
+ receive typed data directly as props. This is the simplest and most
34
+ type-safe approach.
35
+ </li>
36
+ <li>
37
+ <strong>Hooks</strong> &mdash; Use hooks like{" "}
38
+ <code>useRouteParams</code> and <code>useRouteData</code> to access
39
+ data anywhere in the component tree. This requires routes to have an{" "}
40
+ <code>id</code> property.
41
+ </li>
42
+ </ul>
43
+ </section>
44
+
45
+ <section>
46
+ <h3>Approach 1: Route Component Props (Recommended)</h3>
47
+
48
+ <h4>Accessing Typed Params via Props</h4>
49
+ <p>
50
+ When you define a route with URL parameters, FUNSTACK Router
51
+ automatically infers the parameter types from the path pattern. Your
52
+ component receives these params as a typed <code>params</code> prop.
53
+ </p>
54
+ <CodeBlock language="tsx">{`import { route } from "@funstack/router";
55
+
56
+ // Route definition with :userId parameter
57
+ const userRoute = route({
58
+ path: "/users/:userId",
59
+ component: UserPage,
60
+ });
61
+
62
+ // Component receives typed params automatically
63
+ function UserPage({ params }: { params: { userId: string } }) {
64
+ return <h1>User: {params.userId}</h1>;
65
+ }`}</CodeBlock>
66
+ <p>
67
+ For explicit type annotations, use the{" "}
68
+ <code>RouteComponentProps</code> type helper with your params type:
69
+ </p>
70
+ <CodeBlock language="tsx">{`import { route, RouteComponentProps } from "@funstack/router";
71
+
72
+ // Define component with explicit props type
73
+ function UserPage({ params }: RouteComponentProps<{ userId: string }>) {
74
+ // params.userId is typed as string
75
+ // params.nonExistent would be a TypeScript error
76
+ return <h1>User: {params.userId}</h1>;
77
+ }
78
+
79
+ // Route definition - TypeScript validates the component props match the path
80
+ const userRoute = route({
81
+ path: "/users/:userId",
82
+ component: UserPage,
83
+ });`}</CodeBlock>
84
+ <p>
85
+ The <code>route()</code> function validates that your component's
86
+ props match the path pattern. If you annotate <code>params</code> with{" "}
87
+ <code>{`{ userId: string }`}</code> but the path is{" "}
88
+ <code>/users/:id</code>, TypeScript will report an error.
89
+ </p>
90
+
91
+ <h4>Routes with Loaders</h4>
92
+ <p>
93
+ When your route has a loader function, the component receives the
94
+ loader's return value as a <code>data</code> prop. The data can be
95
+ wrapped in a Promise, in which case you unwrap it using React's{" "}
96
+ <code>use()</code> hook. Use <code>RouteComponentPropsWithData</code>{" "}
97
+ for routes with loaders.
98
+ </p>
99
+ <CodeBlock language="tsx">{`import { use, Suspense } from "react";
100
+ import { route, RouteComponentPropsWithData } from "@funstack/router";
101
+
102
+ interface User {
103
+ id: string;
104
+ name: string;
105
+ email: string;
106
+ }
107
+
108
+ // Props type: RouteComponentPropsWithData<Params, Data, State?>
109
+ type UserPageProps = RouteComponentPropsWithData<
110
+ { userId: string },
111
+ Promise<User>
112
+ >;
113
+
114
+ // Inner component that uses the data
115
+ function UserPageContent({ params, data }: UserPageProps) {
116
+ const user = use(data); // Unwrap the Promise
117
+ return (
118
+ <div>
119
+ <h1>{user.name}</h1>
120
+ <p>Email: {user.email}</p>
121
+ </div>
122
+ );
123
+ }
124
+
125
+ // Outer component wraps with Suspense
126
+ function UserPage(props: UserPageProps) {
127
+ return (
128
+ <Suspense fallback={<div>Loading user...</div>}>
129
+ <UserPageContent {...props} />
130
+ </Suspense>
131
+ );
132
+ }
133
+
134
+ // Route definition
135
+ const userRoute = route({
136
+ path: "/users/:userId",
137
+ component: UserPage,
138
+ loader: async ({ params }): Promise<User> => {
139
+ const response = await fetch(\`/api/users/\${params.userId}\`);
140
+ return response.json();
141
+ },
142
+ });`}</CodeBlock>
143
+ <p>
144
+ The <code>data</code> prop is typed as{" "}
145
+ <code>Promise&lt;User&gt;</code> based on the loader's return type.
146
+ TypeScript ensures you handle the data shape correctly.
147
+ </p>
148
+
149
+ <h4>Routes with Navigation State</h4>
150
+ <p>
151
+ Navigation state lets you store data in a navigation entry that
152
+ doesn't appear in the URL. Navigation state data is persisted across
153
+ page reloads and history traversals (meaning it is available after
154
+ user goes to another page and then uses the back button to returns to
155
+ the current page). Use the <code>routeState</code> helper to define
156
+ typed state for your routes.
157
+ </p>
158
+ <CodeBlock language="tsx">{`import { route, routeState, RouteComponentProps } from "@funstack/router";
159
+
160
+ // Define the state shape
161
+ interface ProductListState {
162
+ page: number;
163
+ sortBy: "name" | "price" | "date";
164
+ filters: string[];
165
+ }
166
+
167
+ // Props type: RouteComponentProps<Params, State>
168
+ type ProductListProps = RouteComponentProps<
169
+ Record<string, never>, // No params for this route
170
+ ProductListState
171
+ >;
172
+
173
+ function ProductListPage({
174
+ state,
175
+ setState,
176
+ setStateSync,
177
+ resetState,
178
+ }: ProductListProps) {
179
+ // state is typed as ProductListState | undefined
180
+ const page = state?.page ?? 1;
181
+ const sortBy = state?.sortBy ?? "name";
182
+
183
+ const handlePageChange = (newPage: number) => {
184
+ // setState performs a navigation with the new state
185
+ setState({ ...state, page: newPage });
186
+ };
187
+
188
+ const handleSortChange = (newSort: "name" | "price" | "date") => {
189
+ // setStateSync updates state synchronously (replaces current entry)
190
+ setStateSync({ ...state, sortBy: newSort });
191
+ };
192
+
193
+ const handleReset = () => {
194
+ // resetState clears the navigation state
195
+ resetState();
196
+ };
197
+
198
+ return (
199
+ <div>
200
+ <button onClick={() => handlePageChange(page + 1)}>
201
+ Next Page
202
+ </button>
203
+ <button onClick={() => handleSortChange("price")}>
204
+ Sort by Price
205
+ </button>
206
+ <button onClick={handleReset}>Reset Filters</button>
207
+ </div>
208
+ );
209
+ }
210
+
211
+ // Use routeState to create a typed route
212
+ const productListRoute = routeState<ProductListState>()(
213
+ route({
214
+ path: "/products",
215
+ component: ProductListPage,
216
+ })
217
+ );`}</CodeBlock>
218
+ <p>
219
+ The <code>routeState</code> helper adds four props to your component:
220
+ </p>
221
+ <ul>
222
+ <li>
223
+ <code>state</code> &mdash; The current navigation state (or{" "}
224
+ <code>undefined</code> if not set)
225
+ </li>
226
+ <li>
227
+ <code>setState</code> &mdash; Navigate to the same URL with new
228
+ state (creates a new history entry)
229
+ </li>
230
+ <li>
231
+ <code>setStateSync</code> &mdash; Update state synchronously without
232
+ creating a new history entry
233
+ </li>
234
+ <li>
235
+ <code>resetState</code> &mdash; Clear the navigation state
236
+ </li>
237
+ </ul>
238
+
239
+ <h4>Combining Loader and State</h4>
240
+ <p>
241
+ You can use both loaders and navigation state together. The{" "}
242
+ <code>routeState</code> helper works with routes that have loaders.
243
+ </p>
244
+ <CodeBlock language="tsx">{`import { use, Suspense } from "react";
245
+ import { route, routeState, RouteComponentPropsWithData } from "@funstack/router";
246
+
247
+ interface ProductListState {
248
+ sortBy: "name" | "price";
249
+ }
250
+
251
+ interface Product {
252
+ id: string;
253
+ name: string;
254
+ price: number;
255
+ }
256
+
257
+ // Props type: RouteComponentPropsWithData<Params, Data, State>
258
+ type Props = RouteComponentPropsWithData<
259
+ Record<string, never>,
260
+ Promise<Product[]>,
261
+ ProductListState
262
+ >;
263
+
264
+ function ProductListContent({ data, state, setStateSync }: Props) {
265
+ const products = use(data);
266
+ const sortBy = state?.sortBy ?? "name";
267
+
268
+ const sorted = [...products].sort((a, b) =>
269
+ sortBy === "name"
270
+ ? a.name.localeCompare(b.name)
271
+ : a.price - b.price
272
+ );
273
+
274
+ return (
275
+ <div>
276
+ <select
277
+ value={sortBy}
278
+ onChange={(e) =>
279
+ setStateSync({ sortBy: e.target.value as "name" | "price" })
280
+ }
281
+ >
282
+ <option value="name">Sort by Name</option>
283
+ <option value="price">Sort by Price</option>
284
+ </select>
285
+ <ul>
286
+ {sorted.map((product) => (
287
+ <li key={product.id}>
288
+ {product.name} - \${product.price}
289
+ </li>
290
+ ))}
291
+ </ul>
292
+ </div>
293
+ );
294
+ }
295
+
296
+ function ProductListPage(props: Props) {
297
+ return (
298
+ <Suspense fallback={<div>Loading products...</div>}>
299
+ <ProductListContent {...props} />
300
+ </Suspense>
301
+ );
302
+ }
303
+
304
+ // Route definition with both loader and state
305
+ const productListRoute = routeState<ProductListState>()(
306
+ route({
307
+ path: "/products",
308
+ component: ProductListPage,
309
+ loader: async (): Promise<Product[]> => {
310
+ const response = await fetch("/api/products");
311
+ return response.json();
312
+ },
313
+ })
314
+ );`}</CodeBlock>
315
+ </section>
316
+
317
+ <section>
318
+ <h3>Approach 2: Hooks</h3>
319
+
320
+ <h4>When to Use Hooks</h4>
321
+ <p>
322
+ While props are the recommended approach for most cases, hooks are
323
+ useful when:
324
+ </p>
325
+ <ul>
326
+ <li>
327
+ <strong>Avoiding prop drilling</strong> &mdash; Deeply nested
328
+ components need route data without passing props through every level
329
+ </li>
330
+ <li>
331
+ <strong>Accessing parent route data</strong> &mdash; Child routes
332
+ need to read data loaded by ancestor routes
333
+ </li>
334
+ <li>
335
+ <strong>Using React Server Components</strong> &mdash; Route
336
+ components cannot receive props directly
337
+ </li>
338
+ </ul>
339
+ <p>
340
+ <strong>Important:</strong> To use hooks with full type safety, routes
341
+ must have an <code>id</code> property.
342
+ </p>
343
+
344
+ <h4>Setting Up Routes with IDs</h4>
345
+ <p>
346
+ Add an <code>id</code> property to routes you want to access via
347
+ hooks. The ID can be any string, but using a descriptive name helps
348
+ with debugging.
349
+ </p>
350
+ <CodeBlock language="tsx">{`import { route } from "@funstack/router";
351
+
352
+ const userRoute = route({
353
+ id: "user",
354
+ path: "/users/:userId",
355
+ component: UserLayout,
356
+ loader: async ({ params }) => {
357
+ const response = await fetch(\`/api/users/\${params.userId}\`);
358
+ return response.json();
359
+ },
360
+ });
361
+
362
+ const userPostsRoute = route({
363
+ id: "userPosts",
364
+ path: "/posts",
365
+ component: UserPostsPage,
366
+ });
367
+
368
+ // Use these routes in your route tree
369
+ const routes = [
370
+ route({
371
+ path: "/",
372
+ component: Layout,
373
+ children: [
374
+ {
375
+ ...userRoute,
376
+ children: [userPostsRoute],
377
+ },
378
+ ],
379
+ }),
380
+ ];`}</CodeBlock>
381
+
382
+ <h4>useRouteParams</h4>
383
+ <p>
384
+ The <code>useRouteParams</code> hook returns typed params for a
385
+ specific route. It works with the current route or any ancestor route.
386
+ </p>
387
+ <CodeBlock language="tsx">{`import { useRouteParams } from "@funstack/router";
388
+
389
+ // In a deeply nested component
390
+ function UserAvatar() {
391
+ // Pass the route definition to get typed params
392
+ const params = useRouteParams(userRoute);
393
+ // params.userId is typed as string
394
+
395
+ return <img src={\`/avatars/\${params.userId}.png\`} alt="User avatar" />;
396
+ }`}</CodeBlock>
397
+
398
+ <h4>useRouteState</h4>
399
+ <p>
400
+ The <code>useRouteState</code> hook returns the typed navigation state
401
+ for a route. Returns <code>undefined</code> when no state is set.
402
+ </p>
403
+ <CodeBlock language="tsx">{`import { useRouteState } from "@funstack/router";
404
+
405
+ function FilterIndicator() {
406
+ // Get typed state from the product list route
407
+ const state = useRouteState(productListRoute);
408
+ // state is typed as ProductListState | undefined
409
+
410
+ if (!state?.filters?.length) {
411
+ return null;
412
+ }
413
+
414
+ return (
415
+ <div className="filter-badge">
416
+ {state.filters.length} filters active
417
+ </div>
418
+ );
419
+ }`}</CodeBlock>
420
+
421
+ <h4>useRouteData</h4>
422
+ <p>
423
+ The <code>useRouteData</code> hook returns the typed loader data for a
424
+ route. This is particularly useful for accessing parent route data
425
+ from child routes.
426
+ </p>
427
+ <CodeBlock language="tsx">{`import { use } from "react";
428
+ import { useRouteData } from "@funstack/router";
429
+
430
+ // Child route component accessing parent's data
431
+ function UserPostsPage() {
432
+ // Access the parent route's loaded user data
433
+ const userData = useRouteData(userRoute);
434
+ const user = use(userData);
435
+
436
+ return (
437
+ <div>
438
+ <h2>Posts by {user.name}</h2>
439
+ {/* Render posts... */}
440
+ </div>
441
+ );
442
+ }`}</CodeBlock>
443
+ <p>
444
+ This pattern is especially powerful in nested routes where child
445
+ components need access to data loaded by parent routes without prop
446
+ drilling.
447
+ </p>
448
+ </section>
449
+
450
+ <section>
451
+ <h3>Route Definition Best Practices</h3>
452
+ <p>
453
+ To maximize developer experience and maintainability while also
454
+ ensuring type safety, follow the below best practices when defining
455
+ your routes:
456
+ </p>
457
+ <CodeBlock language="tsx">{`interface User {
458
+ name: string;
459
+ }
460
+
461
+ // Define params and data
462
+ type Params = { userId: string };
463
+ type Data = Promise<User>;
464
+
465
+ // Use RouteComponentProps (or RouteComponentPropsWithData) to type your route component
466
+ type UserPageProps = RouteComponentPropsWithData<Params, Data>;
467
+
468
+ // Define the route
469
+ const userRoute = route({
470
+ path: "/users/:userId",
471
+ loader: async ({ params }): Data => {
472
+ const response = await fetch(\`/api/users/\${params.userId}\`);
473
+ return response.json();
474
+ },
475
+ component: UserPage,
476
+ });
477
+
478
+ // Now use it in your component
479
+ function UserPage({ params, data }: UserPageProps) {
480
+ const user = use(data);
481
+ return <h1>User: {user.name} (ID: {params.userId})</h1>;
482
+ }`}</CodeBlock>
483
+ <p>Key techniques demonstrated here include:</p>
484
+ <ul>
485
+ <li>
486
+ Defining explicit <code>Params</code> and <code>Data</code> types
487
+ &mdash; requires minimal type checking effort while improving
488
+ clarity
489
+ </li>
490
+ <li>
491
+ Using <code>RouteComponentPropsWithData</code> to define component
492
+ props
493
+ </li>
494
+ </ul>
495
+ <p>
496
+ TypeScript will validate that the route definition and component props
497
+ remain in sync as you make changes over time.
498
+ </p>
499
+ </section>
500
+
501
+ <section>
502
+ <h3>Key Takeaways</h3>
503
+ <ul>
504
+ <li>
505
+ Use <code>RouteComponentProps&lt;Params, State&gt;</code> or{" "}
506
+ <code>RouteComponentPropsWithData&lt;Params, Data, State&gt;</code>{" "}
507
+ for constructing route component prop types
508
+ </li>
509
+ <li>
510
+ The <code>routeState</code> helper adds typed route state management
511
+ to any route
512
+ </li>
513
+ <li>
514
+ Hooks require routes to have an <code>id</code> property for type
515
+ safety
516
+ </li>
517
+ <li>Use hooks to avoid prop drilling or access parent route data</li>
518
+ </ul>
519
+ </section>
520
+ </div>
521
+ );
522
+ }
@@ -0,0 +1,21 @@
1
+ # FUNSTACK Router Documentation
2
+
3
+ ## Available Documentation
4
+
5
+ - [Getting Started](./GettingStartedPage.tsx)
6
+
7
+ ### API Reference
8
+
9
+ - [Components](./ApiComponentsPage.tsx) - Core components for building routing in your React application.
10
+ - [Hooks](./ApiHooksPage.tsx) - React hooks for accessing router state and navigation.
11
+ - [Types](./ApiTypesPage.tsx) - TypeScript types and interfaces exported by the router.
12
+ - [Utilities](./ApiUtilitiesPage.tsx) - Helper functions for defining routes and managing state.
13
+
14
+ ### Learn
15
+
16
+ - [Navigation API](./LearnNavigationApiPage.tsx) - FUNSTACK Router is built on the Navigation API , a modern browser API that provides a unified way to handle navigation. This guide explains the key differences from the older History API and the benefits this brings to your application.
17
+ - [Nested Routes](./LearnNestedRoutesPage.tsx) - Nested routes let you build complex page layouts where parts of the UI persist across navigation while other parts change. Think of a dashboard with a sidebar that stays in place while the main content area updates&mdash;that's nested routing in action.
18
+ - [React Server Components](./LearnRscPage.tsx) - FUNSTACK Router is designed to work with React Server Components (RSC). The package provides a dedicated server entry point so that route definitions can live in server modules, keeping client bundle sizes small.
19
+ - [Server-Side Rendering](./LearnSsrPage.tsx) - FUNSTACK Router supports server-side rendering with a two-stage model. During SSR, pathless (layout) routes without loaders render to produce an app shell, while path-based routes and loaders activate only after client hydration.
20
+ - [Controlling Transitions](./LearnTransitionsPage.tsx) - FUNSTACK Router wraps every navigation in React's startTransition, which means the old UI may stay visible while the new route loads. This page explains how this works and how to control it.
21
+ - [Type Safety](./LearnTypeSafetyPage.tsx) - FUNSTACK Router provides first-class TypeScript support, allowing you to access route params, navigation state, and loader data with full type safety. This guide covers two approaches: receiving typed data through component props (recommended) and accessing it through hooks.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as LoaderArgs, c as RouteComponentProps, d as RouteDefinition, f as TypefulOpaqueRouteDefinition, i as ExtractRouteState, l as RouteComponentPropsOf, m as routeState, n as ExtractRouteId, o as OpaqueRouteDefinition, p as route, r as ExtractRouteParams, s as PathParams, t as ExtractRouteData, u as RouteComponentPropsWithData } from "./route-Bc8BUlhv.mjs";
1
+ import { a as LoaderArgs, c as RouteComponentProps, d as RouteDefinition, f as TypefulOpaqueRouteDefinition, i as ExtractRouteState, l as RouteComponentPropsOf, m as routeState, n as ExtractRouteId, o as OpaqueRouteDefinition, p as route, r as ExtractRouteParams, s as PathParams, t as ExtractRouteData, u as RouteComponentPropsWithData } from "./route-ClVnhrQD.mjs";
2
2
  import { ComponentType, ReactNode } from "react";
3
3
 
4
4
  //#region src/types.d.ts
@@ -136,12 +136,6 @@ declare function useNavigate(): (to: string, options?: NavigateOptions) => void;
136
136
  */
137
137
  declare function useLocation(): Location;
138
138
  //#endregion
139
- //#region src/hooks/useLocationSSR.d.ts
140
- /**
141
- * Returns the current location object, or `null` when the URL is not available (e.g. during SSR).
142
- */
143
- declare function useLocationSSR(): Location | null;
144
- //#endregion
145
139
  //#region src/hooks/useSearchParams.d.ts
146
140
  type SetSearchParams = (params: URLSearchParams | Record<string, string> | ((prev: URLSearchParams) => URLSearchParams | Record<string, string>)) => void;
147
141
  /**
@@ -278,5 +272,5 @@ type LocationEntry = {
278
272
  info: unknown;
279
273
  };
280
274
  //#endregion
281
- export { type ExtractRouteData, type ExtractRouteId, type ExtractRouteParams, type ExtractRouteState, type FallbackMode, type LoaderArgs, type Location, type LocationEntry, type MatchedRoute, type MatchedRouteWithData, type NavigateOptions, type OnNavigateCallback, type OnNavigateInfo, type OpaqueRouteDefinition, Outlet, type PathParams, type RouteComponentProps, type RouteComponentPropsOf, type RouteComponentPropsWithData, type RouteDefinition, Router, type RouterProps, type TypefulOpaqueRouteDefinition, type UseBlockerOptions, route, routeState, useBlocker, useIsPending, useLocation, useLocationSSR, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
275
+ export { type ExtractRouteData, type ExtractRouteId, type ExtractRouteParams, type ExtractRouteState, type FallbackMode, type LoaderArgs, type Location, type LocationEntry, type MatchedRoute, type MatchedRouteWithData, type NavigateOptions, type OnNavigateCallback, type OnNavigateInfo, type OpaqueRouteDefinition, Outlet, type PathParams, type RouteComponentProps, type RouteComponentPropsOf, type RouteComponentPropsWithData, type RouteDefinition, Router, type RouterProps, type TypefulOpaqueRouteDefinition, type UseBlockerOptions, route, routeState, useBlocker, useIsPending, useLocation, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
282
276
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/Router.tsx","../src/Outlet.tsx","../src/hooks/useNavigate.ts","../src/hooks/useLocation.ts","../src/hooks/useLocationSSR.ts","../src/hooks/useSearchParams.ts","../src/hooks/useBlocker.ts","../src/hooks/useRouteParams.ts","../src/hooks/useRouteState.ts","../src/hooks/useRouteData.ts","../src/hooks/useIsPending.ts","../src/core/RouterAdapter.ts"],"mappings":";;;;cAGM,6BAAA;;AAFwD;;;;;;;KAgBlD,uBAAA;EAAA,CACT,6BAAA,UA4Bc;EA1Bf,IAAA,WAwBI;EAtBJ,QAAA,GAAW,uBAAA;EAiCE;;;;;;EA1Bb,KAAA;EAMA;;;;;EAAA,eAAA,YASI;EAHJ,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,+BAKrB;EAHN,SAAA,GACI,aAAA;IACE,IAAA;IACA,MAAA,GAAS,MAAA;IACT,KAAA;IACA,QAAA,IACE,KAAA,cAAmB,IAAA,2BAChB,OAAA;IACL,YAAA,IAAgB,KAAA,cAAmB,IAAA;IACnC,UAAA;IACA,IAAA;EAAA,KAEF,SAAA;AAAA;;;;KAmBM,YAAA;EAMV,oCAJA,KAAA,EAAO,uBAAA,EAIC;EAFR,MAAA,EAAQ,MAAA,kBAQsB;EAN9B,QAAA;AAAA;;AAcF;;KARY,oBAAA,GAAuB,YAAA;EAUH,6DAR9B,IAAA;AAAA;;;;KAMU,cAAA;EAUe,4DARzB,OAAA,WAAkB,YAAA,WAQO;EANzB,YAAA;AAAA;;;;KAMU,eAAA;EAYQ,uDAVlB,OAAA,YAUkB;EARlB,KAAA,YAUA;EARA,IAAA;AAAA;;AAmBF;;KAbY,QAAA;EACV,QAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;AAqBF;;;;KAXY,kBAAA,IACV,KAAA,EAAO,aAAA,EACP,IAAA,EAAM,cAAA;;;;AClGR;;;KD2GY,YAAA;;;KC3GA,WAAA;EACV,MAAA,EAAQ,eAAA;ED/BoC;;;;AAc9C;;;ECyBE,UAAA,GAAa,kBAAA;EDpBF;;;;;;EC2BX,QAAA,GAAW,YAAA;AAAA;AAAA,iBASG,MAAA,CAAA;EACd,MAAA,EAAQ,WAAA;EACR,UAAA;EACA;AAAA,GACC,WAAA,GAAc,SAAA;;;;;;AD7D6C;iBEM9C,MAAA,CAAA,GAAU,SAAA;;;;;;iBCAV,WAAA,CAAA,IAAgB,EAAA,UAAY,OAAA,GAAU,eAAA;;;;;;iBCAtC,WAAA,CAAA,GAAe,QAAA;;;;;;iBCAf,cAAA,CAAA,GAAkB,QAAA;;;KCJ7B,eAAA,IACH,MAAA,EACI,eAAA,GACA,MAAA,qBACE,IAAA,EAAM,eAAA,KAAoB,eAAA,GAAkB,MAAA;;;;iBAMpC,eAAA,CAAA,IAAoB,eAAA,EAAiB,eAAA;;;KCVzC,iBAAA;;;;APFkD;EOO5D,WAAA;AAAA;;;APSF;;;;;;;;;;;;;;;;;;;;;;;;;iBOqBgB,UAAA,CAAW,OAAA,EAAS,iBAAA;;;;;;APrC0B;;;;;AAgB9D;;;;;;;;;;;;iBQSgB,cAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,kBAAA,CAAmB,CAAA;;;;;;ARhC8B;;;;;AAgB9D;;;;;;;;;;;;;iBSUgB,aAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,iBAAA,CAAkB,CAAA;;;;;;ATjC+B;;;;;AAgB9D;;;;;;;;;;;;;;;;iBUagB,YAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,gBAAA,CAAiB,CAAA;;;;;;iBC/Bd,YAAA,CAAA;;;;;;AXL8C;KYSlD,aAAA;wBAEV,GAAA,EAAK,GAAA,EZTuC;EYW5C,GAAA,UZGiC;EYDjC,KAAA,WZEC;EYAD,IAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/Router.tsx","../src/Outlet.tsx","../src/hooks/useNavigate.ts","../src/hooks/useLocation.ts","../src/hooks/useSearchParams.ts","../src/hooks/useBlocker.ts","../src/hooks/useRouteParams.ts","../src/hooks/useRouteState.ts","../src/hooks/useRouteData.ts","../src/hooks/useIsPending.ts","../src/core/RouterAdapter.ts"],"mappings":";;;;cAGM,6BAAA;;AAFwD;;;;;;;KAgBlD,uBAAA;EAAA,CACT,6BAAA,UA4Bc;EA1Bf,IAAA,WAwBI;EAtBJ,QAAA,GAAW,uBAAA;EAiCE;;;;;;EA1Bb,KAAA;EAMA;;;;;EAAA,eAAA,YASI;EAHJ,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,+BAKrB;EAHN,SAAA,GACI,aAAA;IACE,IAAA;IACA,MAAA,GAAS,MAAA;IACT,KAAA;IACA,QAAA,IACE,KAAA,cAAmB,IAAA,2BAChB,OAAA;IACL,YAAA,IAAgB,KAAA,cAAmB,IAAA;IACnC,UAAA;IACA,IAAA;EAAA,KAEF,SAAA;AAAA;;;;KAmBM,YAAA;EAMV,oCAJA,KAAA,EAAO,uBAAA,EAIC;EAFR,MAAA,EAAQ,MAAA,kBAQsB;EAN9B,QAAA;AAAA;;AAcF;;KARY,oBAAA,GAAuB,YAAA;EAUH,6DAR9B,IAAA;AAAA;;;;KAMU,cAAA;EAUe,4DARzB,OAAA,WAAkB,YAAA,WAQO;EANzB,YAAA;AAAA;;;;KAMU,eAAA;EAYQ,uDAVlB,OAAA,YAUkB;EARlB,KAAA,YAUA;EARA,IAAA;AAAA;;AAmBF;;KAbY,QAAA;EACV,QAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;AAqBF;;;;KAXY,kBAAA,IACV,KAAA,EAAO,aAAA,EACP,IAAA,EAAM,cAAA;;;;ACrGR;;;KD8GY,YAAA;;;KC9GA,WAAA;EACV,MAAA,EAAQ,eAAA;ED5BoC;;;;AAc9C;;;ECsBE,UAAA,GAAa,kBAAA;EDjBF;;;;;;ECwBX,QAAA,GAAW,YAAA;AAAA;AAAA,iBAYG,MAAA,CAAA;EACd,MAAA,EAAQ,WAAA;EACR,UAAA;EACA;AAAA,GACC,WAAA,GAAc,SAAA;;;;;;AD7D6C;iBEM9C,MAAA,CAAA,GAAU,SAAA;;;;;;iBCAV,WAAA,CAAA,IAAgB,EAAA,UAAY,OAAA,GAAU,eAAA;;;;;;iBCAtC,WAAA,CAAA,GAAe,QAAA;;;KCJ1B,eAAA,IACH,MAAA,EACI,eAAA,GACA,MAAA,qBACE,IAAA,EAAM,eAAA,KAAoB,eAAA,GAAkB,MAAA;;;;iBAMpC,eAAA,CAAA,IAAoB,eAAA,EAAiB,eAAA;;;KCVzC,iBAAA;;;;ANFkD;EMO5D,WAAA;AAAA;;;ANSF;;;;;;;;;;;;;;;;;;;;;;;;;iBMqBgB,UAAA,CAAW,OAAA,EAAS,iBAAA;;;;;;ANrC0B;;;;;AAgB9D;;;;;;;;;;;;iBOSgB,cAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,kBAAA,CAAmB,CAAA;;;;;;APhC8B;;;;;AAgB9D;;;;;;;;;;;;;iBQUgB,aAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,iBAAA,CAAkB,CAAA;;;;;;ARjC+B;;;;;AAgB9D;;;;;;;;;;;;;;;;iBSagB,YAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,gBAAA,CAAiB,CAAA;;;;;;iBC/Bd,YAAA,CAAA;;;;;;AVL8C;KWSlD,aAAA;wBAEV,GAAA,EAAK,GAAA,EXTuC;EWW5C,GAAA,UXGiC;EWDjC,KAAA,WXEC;EWAD,IAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -417,21 +417,21 @@ function createAdapter(fallback) {
417
417
 
418
418
  //#endregion
419
419
  //#region src/Router.tsx
420
- const noopSubscribe = () => () => {};
421
- const getServerSnapshot = () => null;
422
420
  /**
423
- * Initial value of locationEntry.
424
- * This value means to use the `initialEntry` from `useSyncExternalStore` instead.
421
+ * Special value returned as server snapshot during SSR/hydration.
425
422
  */
426
- const entryInitValue = Symbol();
423
+ const serverSnapshotSymbol = Symbol();
424
+ const noopSubscribe = () => () => {};
425
+ const getServerSnapshot = () => serverSnapshotSymbol;
427
426
  function Router({ routes: inputRoutes, onNavigate, fallback = "none" }) {
428
427
  const routes = internalRoutes(inputRoutes);
429
428
  const adapter = useMemo(() => createAdapter(fallback), [fallback]);
430
429
  const [blockerRegistry] = useState(() => createBlockerRegistry());
431
430
  const initialEntry = useSyncExternalStore(noopSubscribe, useCallback(() => adapter.getSnapshot(), [adapter]), getServerSnapshot);
432
431
  const [isPending, startTransition] = useTransition();
433
- const [locationEntryInternal, setLocationEntry] = useState(entryInitValue);
434
- const locationEntry = locationEntryInternal === entryInitValue ? initialEntry : locationEntryInternal;
432
+ const [locationEntryInternal, setLocationEntry] = useState(initialEntry);
433
+ const locationEntry = locationEntryInternal === serverSnapshotSymbol ? null : locationEntryInternal;
434
+ if (locationEntryInternal === serverSnapshotSymbol && initialEntry !== serverSnapshotSymbol) setLocationEntry(initialEntry);
435
435
  useEffect(() => {
436
436
  return adapter.subscribe(() => {
437
437
  startTransition(() => {
@@ -649,25 +649,6 @@ function useLocation() {
649
649
  }, [url]);
650
650
  }
651
651
 
652
- //#endregion
653
- //#region src/hooks/useLocationSSR.ts
654
- /**
655
- * Returns the current location object, or `null` when the URL is not available (e.g. during SSR).
656
- */
657
- function useLocationSSR() {
658
- const context = useContext(RouterContext);
659
- if (!context) throw new Error("useLocationSSR must be used within a Router");
660
- const { url } = context;
661
- return useMemo(() => {
662
- if (url === null) return null;
663
- return {
664
- pathname: url.pathname,
665
- search: url.search,
666
- hash: url.hash
667
- };
668
- }, [url]);
669
- }
670
-
671
652
  //#endregion
672
653
  //#region src/hooks/useSearchParams.ts
673
654
  /**
@@ -855,5 +836,5 @@ function useIsPending() {
855
836
  }
856
837
 
857
838
  //#endregion
858
- export { Outlet, Router, route, routeState, useBlocker, useIsPending, useLocation, useLocationSSR, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
839
+ export { Outlet, Router, route, routeState, useBlocker, useIsPending, useLocation, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
859
840
  //# sourceMappingURL=index.mjs.map