@typeroute/router 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -28
- package/dist/index.d.ts +8 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,25 +9,31 @@
|
|
|
9
9
|
<div align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/@typeroute/router">
|
|
11
11
|
<img
|
|
12
|
-
src="https://img.shields.io/npm/v/%40typeroute%2Frouter?
|
|
12
|
+
src="https://img.shields.io/npm/v/%40typeroute%2Frouter?color=0d1117&labelColor=0d1117"
|
|
13
13
|
alt="npm version"
|
|
14
14
|
/>
|
|
15
15
|
</a>
|
|
16
16
|
<a href="https://www.npmjs.com/package/@typeroute/router">
|
|
17
17
|
<img
|
|
18
|
-
src="https://img.
|
|
18
|
+
src="https://img.shields.io/npm/dw/%40typeroute%2Frouter?color=0d1117&labelColor=0d1117"
|
|
19
|
+
alt="weekly downloads"
|
|
20
|
+
/>
|
|
21
|
+
</a>
|
|
22
|
+
<a href="https://www.npmjs.com/package/@typeroute/router">
|
|
23
|
+
<img
|
|
24
|
+
src="https://img.badgesize.io/https://cdn.jsdelivr.net/npm/@typeroute/router/dist/index.js?compression=gzip&label=gzip&color=0d1117&labelColor=0d1117"
|
|
19
25
|
alt="gzip size"
|
|
20
26
|
/>
|
|
21
27
|
</a>
|
|
22
28
|
<a href="https://github.com/strblr/typeroute/blob/master/LICENSE">
|
|
23
29
|
<img
|
|
24
|
-
src="https://img.shields.io/npm/l/%40typeroute%2Frouter?
|
|
30
|
+
src="https://img.shields.io/npm/l/%40typeroute%2Frouter?color=0d1117&labelColor=0d1117"
|
|
25
31
|
alt="license"
|
|
26
32
|
/>
|
|
27
33
|
</a>
|
|
28
34
|
<a href="https://github.com/sponsors/strblr">
|
|
29
35
|
<img
|
|
30
|
-
src="https://img.shields.io/github/sponsors/strblr?
|
|
36
|
+
src="https://img.shields.io/github/sponsors/strblr?color=0d1117&labelColor=0d1117"
|
|
31
37
|
alt="sponsors"
|
|
32
38
|
/>
|
|
33
39
|
</a>
|
|
@@ -100,8 +106,8 @@ If you believe there's a mistake in the comparison table, please [open an issue]
|
|
|
100
106
|
# Table of contents
|
|
101
107
|
|
|
102
108
|
- [Comparison](#comparison)
|
|
103
|
-
- [Showcase](#showcase)
|
|
104
109
|
- [Installation](#installation)
|
|
110
|
+
- [Showcase](#showcase)
|
|
105
111
|
- [Defining routes](#defining-routes)
|
|
106
112
|
- [Nested routes and layouts](#nested-routes-and-layouts)
|
|
107
113
|
- [Setting up the router](#setting-up-the-router)
|
|
@@ -135,6 +141,7 @@ If you believe there's a mistake in the comparison table, please [open an issue]
|
|
|
135
141
|
- [Global link configuration](#global-link-configuration)
|
|
136
142
|
- [History middleware](#history-middleware)
|
|
137
143
|
- [View transitions](#view-transitions)
|
|
144
|
+
- [Dynamic page titles](#dynamic-page-titles)
|
|
138
145
|
- [API reference](#api-reference)
|
|
139
146
|
- [Router class](#router-class)
|
|
140
147
|
- [Route class](#route-class)
|
|
@@ -148,6 +155,16 @@ If you believe there's a mistake in the comparison table, please [open an issue]
|
|
|
148
155
|
|
|
149
156
|
---
|
|
150
157
|
|
|
158
|
+
# Installation
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm install @typeroute/router
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
TypeRoute requires React 18 or higher.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
151
168
|
# Showcase
|
|
152
169
|
|
|
153
170
|
Here's what routing looks like with TypeRoute:
|
|
@@ -155,12 +172,12 @@ Here's what routing looks like with TypeRoute:
|
|
|
155
172
|
```tsx
|
|
156
173
|
import { route, RouterRoot, Outlet, Link, useParams } from "@typeroute/router";
|
|
157
174
|
|
|
158
|
-
//
|
|
175
|
+
// Routes
|
|
159
176
|
const layout = route("/").component(() => (
|
|
160
177
|
<div>
|
|
161
178
|
<nav>
|
|
162
179
|
<Link to="/">Home</Link>
|
|
163
|
-
<Link to=
|
|
180
|
+
<Link to={user} params={{ id: "42" }}>
|
|
164
181
|
User
|
|
165
182
|
</Link>
|
|
166
183
|
</nav>
|
|
@@ -168,10 +185,9 @@ const layout = route("/").component(() => (
|
|
|
168
185
|
</div>
|
|
169
186
|
));
|
|
170
187
|
|
|
171
|
-
// Pages
|
|
172
188
|
const home = layout.route("/").component(() => <h1>Home</h1>);
|
|
173
189
|
|
|
174
|
-
const user = layout.route("/users/:id").component(
|
|
190
|
+
const user = layout.route("/users/:id").component(() => {
|
|
175
191
|
const { id } = useParams(user); // Fully typed
|
|
176
192
|
return <h1>User {id}</h1>;
|
|
177
193
|
});
|
|
@@ -196,16 +212,6 @@ Everything autocompletes and type-checks automatically. No heavy setup, no magic
|
|
|
196
212
|
|
|
197
213
|
---
|
|
198
214
|
|
|
199
|
-
# Installation
|
|
200
|
-
|
|
201
|
-
```bash
|
|
202
|
-
npm install @typeroute/router
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
TypeRoute requires React 18 or higher.
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
215
|
# Defining routes
|
|
210
216
|
|
|
211
217
|
Routes are created using the `route()` function, following the [builder pattern](https://dev.to/superviz/design-pattern-7-builder-pattern-10j4). You pass it a path and chain methods to configure the route.
|
|
@@ -297,7 +303,7 @@ Beyond paths and components, child routes also inherit search param validators,
|
|
|
297
303
|
|
|
298
304
|
# Setting up the router
|
|
299
305
|
|
|
300
|
-
Before setting up the router, you need to collect your navigable routes into
|
|
306
|
+
Before setting up the router, you need to collect your navigable routes into a collection (either array or record). When building nested route hierarchies, you'll often create intermediate parent routes solely for grouping and shared layouts. These intermediate routes shouldn't be included in your routes collection - only the final, navigable routes should be:
|
|
301
307
|
|
|
302
308
|
```tsx
|
|
303
309
|
// Intermediate route used for hierarchy
|
|
@@ -309,13 +315,16 @@ const about = layout.route("/about").component(About);
|
|
|
309
315
|
|
|
310
316
|
// Collect only the navigable routes
|
|
311
317
|
const routes = [home, about]; // ✅ Don't include `layout`
|
|
318
|
+
|
|
319
|
+
// Or equivalently:
|
|
320
|
+
const routes = { home, about };
|
|
312
321
|
```
|
|
313
322
|
|
|
314
|
-
This makes sure that only actual pages can be matched and appear in autocomplete. The intermediate routes still exist as part of the hierarchy, they just aren't directly navigable. Note that the order of routes in the
|
|
323
|
+
This makes sure that only actual pages can be matched and appear in autocomplete. The intermediate routes still exist as part of the hierarchy, they just aren't directly navigable. Note that the order of routes in the collection doesn't matter - TypeRoute uses a [ranking algorithm](#route-matching-and-ranking) to pick the most specific match.
|
|
315
324
|
|
|
316
325
|
The `RouterRoot` component is the entry point to TypeRoute. It listens to URL changes, matches the current path against your routes, and renders the matching route's component hierarchy.
|
|
317
326
|
|
|
318
|
-
There are two ways to set it up. The simplest is passing your routes
|
|
327
|
+
There are two ways to set it up. The simplest is passing your routes collection directly to `RouterRoot`. This creates a router instance internally (accessible via `useRouter`):
|
|
319
328
|
|
|
320
329
|
```tsx
|
|
321
330
|
import { RouterRoot } from "@typeroute/router";
|
|
@@ -1112,7 +1121,7 @@ For the path `/users/42`:
|
|
|
1112
1121
|
/users/* → [static, wildcard] → weights [2, 0]
|
|
1113
1122
|
```
|
|
1114
1123
|
|
|
1115
|
-
This ranking algorithm means you don't need to order your routes
|
|
1124
|
+
This ranking algorithm means you don't need to order your routes carefully. Define them in any order and TypeRoute figures out the right match regardless:
|
|
1116
1125
|
|
|
1117
1126
|
```tsx
|
|
1118
1127
|
const routes = [
|
|
@@ -1271,9 +1280,7 @@ function handleRequest(req: Request) {
|
|
|
1271
1280
|
if (ssrContext.redirect) {
|
|
1272
1281
|
return Response.redirect(ssrContext.redirect);
|
|
1273
1282
|
}
|
|
1274
|
-
|
|
1275
|
-
headers: { "Content-Type": "text/html" }
|
|
1276
|
-
});
|
|
1283
|
+
// ... Send pre-rendered HTML
|
|
1277
1284
|
}
|
|
1278
1285
|
```
|
|
1279
1286
|
|
|
@@ -1452,6 +1459,63 @@ Add CSS to control the transition:
|
|
|
1452
1459
|
|
|
1453
1460
|
For more advanced techniques, see the [MDN documentation on View Transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API).
|
|
1454
1461
|
|
|
1462
|
+
## Dynamic page titles
|
|
1463
|
+
|
|
1464
|
+
Use [route handles](#route-handles) to define page titles and update the browser's document title dynamically as users navigate. First, attach a `title` handle to each route:
|
|
1465
|
+
|
|
1466
|
+
```tsx
|
|
1467
|
+
const layout = route("/").handle({ title: "App" }).component(Layout);
|
|
1468
|
+
const home = layout.route("/").handle({ title: "Home" }).component(HomePage);
|
|
1469
|
+
const settings = layout
|
|
1470
|
+
.route("/settings")
|
|
1471
|
+
.handle({ title: "Settings" })
|
|
1472
|
+
.component(SettingsPage);
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
Then create a component that reads the handles and updates `document.title`:
|
|
1476
|
+
|
|
1477
|
+
```tsx
|
|
1478
|
+
function DocumentTitle() {
|
|
1479
|
+
const handles = useHandles();
|
|
1480
|
+
|
|
1481
|
+
useEffect(() => {
|
|
1482
|
+
document.title = handles
|
|
1483
|
+
.map(h => h.title)
|
|
1484
|
+
.reverse()
|
|
1485
|
+
.join(" - ");
|
|
1486
|
+
}, [handles]);
|
|
1487
|
+
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
```
|
|
1491
|
+
|
|
1492
|
+
Place this component somewhere in your layout:
|
|
1493
|
+
|
|
1494
|
+
```tsx
|
|
1495
|
+
function Layout() {
|
|
1496
|
+
return (
|
|
1497
|
+
<>
|
|
1498
|
+
<DocumentTitle />
|
|
1499
|
+
<Header />
|
|
1500
|
+
<Outlet />
|
|
1501
|
+
</>
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
```
|
|
1505
|
+
|
|
1506
|
+
When visiting `/settings`, the document title becomes "Settings - App". The `useHandles()` hook returns handles from all routes in the current matching chain (from root to leaf), so reversing the array puts the most specific page first.
|
|
1507
|
+
|
|
1508
|
+
For type safety, register your handle type:
|
|
1509
|
+
|
|
1510
|
+
```tsx
|
|
1511
|
+
declare module "@typeroute/router" {
|
|
1512
|
+
interface Register {
|
|
1513
|
+
routes: typeof routes;
|
|
1514
|
+
handle: { title: string };
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1455
1519
|
---
|
|
1456
1520
|
|
|
1457
1521
|
# API reference
|
|
@@ -1463,7 +1527,7 @@ The `Router` class is the core of TypeRoute. You can create an instance directly
|
|
|
1463
1527
|
**Properties:**
|
|
1464
1528
|
|
|
1465
1529
|
- `router.basePath` - The configured base path
|
|
1466
|
-
- `router.routes` - The array of routes
|
|
1530
|
+
- `router.routes` - The array of navigable routes
|
|
1467
1531
|
- `router.history` - The history instance
|
|
1468
1532
|
- `router.ssrContext` - The SSR context (if provided)
|
|
1469
1533
|
- `router.defaultLinkOptions` - Default link options
|
|
@@ -1859,7 +1923,7 @@ const unsubscribe = history.subscribe(() => {
|
|
|
1859
1923
|
|
|
1860
1924
|
```tsx
|
|
1861
1925
|
interface RouterOptions {
|
|
1862
|
-
routes: Route[]
|
|
1926
|
+
routes: Route[] | Record<string, Route>; // Collection of navigable routes
|
|
1863
1927
|
basePath?: string; // Base path prefix (default: "/")
|
|
1864
1928
|
history?: HistoryLike; // History implementation (default: BrowserHistory)
|
|
1865
1929
|
ssrContext?: SSRContext; // Context for server-side rendering
|
|
@@ -1956,6 +2020,8 @@ interface PreloadContext {
|
|
|
1956
2020
|
|
|
1957
2021
|
- Possibility to pass an arbitrary context to the Router instance for later use in preloads?
|
|
1958
2022
|
- Relative path navigation? Not sure it's worth the extra bundle size given that users can export/import route objects and pass them as navigation option.
|
|
2023
|
+
- Refactor: APIs like useParams, useSearch and useMatch should accept any route object and not just rely on the global routes array.
|
|
2024
|
+
- Refactor: allow `route()` and `.route()` to be called without passing an argument (defaulting to "/")?
|
|
1959
2025
|
- Document usage in test environments
|
|
1960
2026
|
- Navigation blockers (`useBlocker`, etc.)
|
|
1961
2027
|
- Open to suggestions, we can discuss them [here](https://github.com/strblr/typeroute/discussions).
|
package/dist/index.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ type OptionalOnUndefined<T extends object> = Simplify<{ [K in keyof T as undefin
|
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/types.d.ts
|
|
16
16
|
interface Register {}
|
|
17
|
-
type
|
|
18
|
-
routes: infer
|
|
19
|
-
} ?
|
|
17
|
+
type NavigableRoute = Register extends {
|
|
18
|
+
routes: infer Routes;
|
|
19
|
+
} ? Routes extends ReadonlyArray<Route> ? Routes[number] : Routes extends Record<string, Route> ? Routes[keyof Routes] : Route : Route;
|
|
20
20
|
type Handle = Register extends {
|
|
21
21
|
handle: infer Handle;
|
|
22
22
|
} ? Handle : any;
|
|
@@ -38,14 +38,14 @@ interface PreloadContext<Ps extends {} = any, S extends {} = any> {
|
|
|
38
38
|
search: S;
|
|
39
39
|
}
|
|
40
40
|
interface RouterOptions {
|
|
41
|
-
routes:
|
|
41
|
+
routes: ReadonlyArray<NavigableRoute> | Record<string, NavigableRoute>;
|
|
42
42
|
basePath?: string;
|
|
43
43
|
history?: HistoryLike;
|
|
44
44
|
ssrContext?: SSRContext;
|
|
45
45
|
defaultLinkOptions?: LinkOptions;
|
|
46
46
|
}
|
|
47
|
-
type Pattern =
|
|
48
|
-
type GetRoute<P extends Pattern> = Extract<
|
|
47
|
+
type Pattern = NavigableRoute["_"]["pattern"];
|
|
48
|
+
type GetRoute<P extends Pattern> = Extract<NavigableRoute, {
|
|
49
49
|
_: {
|
|
50
50
|
pattern: P;
|
|
51
51
|
};
|
|
@@ -137,7 +137,7 @@ declare class Route<P extends string = string, Ps extends {} = any, S extends {}
|
|
|
137
137
|
//#endregion
|
|
138
138
|
//#region src/router/router.d.ts
|
|
139
139
|
declare class Router {
|
|
140
|
-
readonly routes:
|
|
140
|
+
readonly routes: ReadonlyArray<NavigableRoute>;
|
|
141
141
|
readonly basePath: string;
|
|
142
142
|
readonly history: HistoryLike;
|
|
143
143
|
readonly ssrContext?: SSRContext;
|
|
@@ -210,4 +210,4 @@ declare const LocationContext: react.Context<HistoryLocation | null>;
|
|
|
210
210
|
declare const MatchContext: react.Context<Match | null>;
|
|
211
211
|
declare const OutletContext: react.Context<ReactNode>;
|
|
212
212
|
//#endregion
|
|
213
|
-
export { BrowserHistory, ComponentLoader, GetRoute, Handle, HashHistory, HistoryLike, HistoryLocation, HistoryPushOptions, Link, LinkOptions, LinkProps, LocationContext, Match, MatchContext, MatchOptions, MemoryHistory, Middleware, Navigate, NavigateOptions, NavigateProps, Outlet, OutletContext, Params, Pattern, PreloadContext, Register, Route,
|
|
213
|
+
export { BrowserHistory, ComponentLoader, GetRoute, Handle, HashHistory, HistoryLike, HistoryLocation, HistoryPushOptions, Link, LinkOptions, LinkProps, LocationContext, Match, MatchContext, MatchOptions, MemoryHistory, Middleware, NavigableRoute, Navigate, NavigateOptions, NavigateProps, Outlet, OutletContext, Params, Pattern, PreloadContext, Register, Route, Router, RouterContext, RouterOptions, RouterRoot, RouterRootProps, SSRContext, Search, Updater, Validator, middleware, route, useHandles, useLocation, useMatch, useNavigate, useOutlet, useParams, useRouter, useSearch };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Component as e,Suspense as t,cloneElement as n,createContext as r,isValidElement as i,lazy as a,memo as o,useCallback as s,useContext as c,useEffect as l,useInsertionEffect as u,useLayoutEffect as d,useMemo as f,useRef as p,useState as m,useSyncExternalStore as h}from"react";import{inject as g,parse as _}from"regexparam";import{jsx as v}from"react/jsx-runtime";function y(e){return`/${e}`.replaceAll(/\/+/g,`/`).replace(/(.+)\/$/,`$1`)}function b(e){let{keys:t,pattern:n}=_(e);return{pattern:e,keys:t,regex:n,loose:_(e,!0).pattern,weights:e.split(`/`).slice(1).map(e=>e.includes(`*`)?0:e.includes(`:`)?1:2)}}function x(e){return typeof e==`function`?e:t=>{let n=e[`~standard`].validate(t);if(n instanceof Promise)throw Error(`[TypeRoute] Validation can't be async`);if(n.issues)throw Error(`[TypeRoute] Validation failed`,{cause:n.issues});return n.value}}function S(e){return Object.entries(e).filter(([e,t])=>t!==void 0).map(([e,t])=>`${e}=${encodeURIComponent(w(t))}`).join(`&`)}function C(e){let t=new URLSearchParams(e);return Object.fromEntries([...t.entries()].map(([e,t])=>(t=decodeURIComponent(t),[e,T(t)?JSON.parse(t):t])))}function w(e){return typeof e==`string`&&!T(e)?e:JSON.stringify(e)}function T(e){try{return JSON.parse(e),!0}catch{return!1}}function E(e,t){return y(`${t}/${e}`)}function D(e,t){return(e===t||e.startsWith(`${t}/`))&&(e=e.slice(t.length)||`/`),e}function O(e,t){return[e,S(t)].filter(Boolean).join(`?`)}function k(e){let{pathname:t,search:n}=new URL(e,`http://w`);return{path:t,search:C(n)}}function A({keys:e,regex:t,loose:n},r,i,a){let o=(r?t:n).exec(D(i,a));if(!o)return null;let s={};return e.forEach((e,t)=>{let n=o[t+1];n&&(s[e]=n)}),s}function j(e){return[...e].sort((e,t)=>{let n=e.route._.weights,r=t.route._.weights,i=Math.max(n.length,r.length);for(let e=0;e<i;e++){let t=n[e]??-1,i=r[e]??-1;if(t!==i)return i-t}return 0})}const M=r(null),N=r(null),P=r(null),F=r(null);function I(){let e=c(M);if(e)return e;throw Error(`[TypeRoute] useRouter must be within a router context`)}function L(){let e=c(N);if(e)return e;throw Error(`[TypeRoute] useLocation must be within a router context`)}function R(e){let t=I(),{path:n}=L();return f(()=>t.match(n,e),[t,n,e.from,e.strict,e.params])}function z(){return c(F)}function B(){return I().navigate}function V(){let e=c(P);return f(()=>e?.route._.handles??[],[e])}function H(e){let t=R({from:e});if(t)return t.params;throw Error(`[TypeRoute] Can't read params for non-matching route ${e}`)}function U(e){let t=I(),{search:n,path:r}=L(),i=t.getRoute(e),a=f(()=>i._.validate(n),[i,n]);return[a,Z((e,n)=>{e=typeof e==`function`?e(a):e;let i=O(r,{...a,...e});t.navigate({url:i,replace:n})})]}var W=class{_;_loc=(e,t)=>{let{state:n}=history,[r,i]=this._??[];return i?.path===e&&r===t&&i.state===n?i:(this._=[t,{path:e,search:C(t),state:n}])[1]};constructor(){if(!window[G]){for(let e of[K,q]){let t=history[e];history[e]=function(...n){t.apply(this,n)
|
|
1
|
+
import{Component as e,Suspense as t,cloneElement as n,createContext as r,isValidElement as i,lazy as a,memo as o,useCallback as s,useContext as c,useEffect as l,useInsertionEffect as u,useLayoutEffect as d,useMemo as f,useRef as p,useState as m,useSyncExternalStore as h}from"react";import{inject as g,parse as _}from"regexparam";import{jsx as v}from"react/jsx-runtime";function y(e){return`/${e}`.replaceAll(/\/+/g,`/`).replace(/(.+)\/$/,`$1`)}function b(e){let{keys:t,pattern:n}=_(e);return{pattern:e,keys:t,regex:n,loose:_(e,!0).pattern,weights:e.split(`/`).slice(1).map(e=>e.includes(`*`)?0:e.includes(`:`)?1:2)}}function x(e){return typeof e==`function`?e:t=>{let n=e[`~standard`].validate(t);if(n instanceof Promise)throw Error(`[TypeRoute] Validation can't be async`);if(n.issues)throw Error(`[TypeRoute] Validation failed`,{cause:n.issues});return n.value}}function S(e){return Object.entries(e).filter(([e,t])=>t!==void 0).map(([e,t])=>`${e}=${encodeURIComponent(w(t))}`).join(`&`)}function C(e){let t=new URLSearchParams(e);return Object.fromEntries([...t.entries()].map(([e,t])=>(t=decodeURIComponent(t),[e,T(t)?JSON.parse(t):t])))}function w(e){return typeof e==`string`&&!T(e)?e:JSON.stringify(e)}function T(e){try{return JSON.parse(e),!0}catch{return!1}}function E(e,t){return y(`${t}/${e}`)}function D(e,t){return(e===t||e.startsWith(`${t}/`))&&(e=e.slice(t.length)||`/`),e}function O(e,t){return[e,S(t)].filter(Boolean).join(`?`)}function k(e){let{pathname:t,search:n}=new URL(e,`http://w`);return{path:t,search:C(n)}}function A({keys:e,regex:t,loose:n},r,i,a){let o=(r?t:n).exec(D(i,a));if(!o)return null;let s={};return e.forEach((e,t)=>{let n=o[t+1];n&&(s[e]=n)}),s}function j(e){return[...e].sort((e,t)=>{let n=e.route._.weights,r=t.route._.weights,i=Math.max(n.length,r.length);for(let e=0;e<i;e++){let t=n[e]??-1,i=r[e]??-1;if(t!==i)return i-t}return 0})}const M=r(null),N=r(null),P=r(null),F=r(null);function I(){let e=c(M);if(e)return e;throw Error(`[TypeRoute] useRouter must be within a router context`)}function L(){let e=c(N);if(e)return e;throw Error(`[TypeRoute] useLocation must be within a router context`)}function R(e){let t=I(),{path:n}=L();return f(()=>t.match(n,e),[t,n,e.from,e.strict,e.params])}function z(){return c(F)}function B(){return I().navigate}function V(){let e=c(P);return f(()=>e?.route._.handles??[],[e])}function H(e){let t=R({from:e});if(t)return t.params;throw Error(`[TypeRoute] Can't read params for non-matching route ${e}`)}function U(e){let t=I(),{search:n,path:r}=L(),i=t.getRoute(e),a=f(()=>i._.validate(n),[i,n]);return[a,Z((e,n)=>{e=typeof e==`function`?e(a):e;let i=O(r,{...a,...e});t.navigate({url:i,replace:n})})]}var W=class{_;_loc=(e,t)=>{let{state:n}=history,[r,i]=this._??[];return i?.path===e&&r===t&&i.state===n?i:(this._=[t,{path:e,search:C(t),state:n}])[1]};constructor(){if(!window[G]){for(let e of[K,q]){let t=history[e];history[e]=function(...n){t.apply(this,n),dispatchEvent(new Event(e))}}window[G]=1}}location=()=>this._loc(location.pathname,location.search);go=e=>history.go(e);push=e=>{let{url:t,replace:n,state:r}=e;history[n?q:K](r,``,t)};subscribe=e=>(J.forEach(t=>window.addEventListener(t,e)),()=>{J.forEach(t=>window.removeEventListener(t,e))})};const G=Symbol.for(`wmp01`),K=`pushState`,q=`replaceState`,J=[`popstate`,K,q,`hashchange`];var Y=class{routes;basePath;history;ssrContext;defaultLinkOptions;_;constructor(e){let{routes:t,basePath:n=`/`,history:r,ssrContext:i,defaultLinkOptions:a}=e;this.routes=Object.values(t),this.basePath=y(n),this.history=r??new W,this.ssrContext=i,this.defaultLinkOptions=a,this._={routeMap:new Map(this.routes.map(e=>[e._.pattern,e]))}}getRoute=e=>{if(typeof e!=`string`)return e;let t=this._.routeMap.get(e);if(!t)throw Error(`[TypeRoute] Route not found for ${e}`);return t};match=(e,t)=>{let{from:n,strict:r,params:i}=t,a=this.getRoute(n),o=A(a._,r,e,this.basePath);return o&&(!i||Object.keys(i).every(e=>i[e]===o[e]))?{route:a,params:o}:null};matchAll=e=>j(this.routes.map(t=>this.match(e,{from:t,strict:!0})).filter(e=>!!e))[0]??null;createUrl=e=>{let{to:t,params:n={},search:r={}}=e,{pattern:i}=this.getRoute(t)._;return O(E(g(i,n),this.basePath),r)};preload=async e=>{let{to:t,params:n={},search:r={}}=e,{preloads:i}=this.getRoute(t)._;await Promise.all(i.map(e=>e({params:n,search:r})))};navigate=e=>{if(typeof e==`number`)this.history.go(e);else if(`url`in e)this.history.push(e);else{let{replace:t,state:n}=e;this.history.push({url:this.createUrl(e),replace:t,state:n})}}},X=class{stack=[];index=0;listeners=new Set;constructor(e=`/`){this.stack.push({...k(e),state:void 0})}location=()=>this.stack[this.index];go=e=>{let t=this.index+e;this.stack[t]&&(this.index=t,this.listeners.forEach(e=>e()))};push=e=>{let{url:t,replace:n,state:r}=e,i={...k(t),state:r};this.stack=this.stack.slice(0,this.index+1),n?this.stack[this.index]=i:this.index=this.stack.push(i)-1,this.listeners.forEach(e=>e())};subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})},ee=class extends W{location=()=>{let{pathname:e,search:t}=new URL(location.hash.slice(1),`http://w`);return this._loc(e,t)};push=e=>{let{url:t,replace:n,state:r}=e;history[n?`replaceState`:`pushState`](r,``,`#${t}`)}};function te(e){let[t]=m(()=>`router`in e?e.router:new Y(e)),{subscribe:n,location:r}=t.history,i=h(n,r,r),a=f(()=>t.matchAll(i.path),[t,i.path]);return a||console.error(`[TypeRoute] No matching route for path`,i.path),f(()=>v(M.Provider,{value:t,children:v(N.Provider,{value:i,children:v(P.Provider,{value:a,children:a?.route._.components.reduceRight((e,t)=>v(F.Provider,{value:e,children:v(t,{})}),null)})})}),[t,i,a])}function ne(){return z()}function re(e){let t=I();return d(()=>t.navigate(e),[]),t.ssrContext&&(t.ssrContext.redirect=t.createUrl(e)),null}function ie(e){let t=I(),{to:r,replace:a,state:o,params:c,search:u,strict:d,preload:m,preloadDelay:h=50,style:g,className:_,activeStyle:y,activeClassName:b,asChild:x,children:S,...C}={...t.defaultLinkOptions,...e},w=p(null),T=p(null),E=t.createUrl(e),D=!!R({from:r,strict:d,params:c}),O=Z(()=>t.preload(e)),k=s(()=>{clearTimeout(T.current)},[]),A=s(()=>{k(),T.current=setTimeout(O,h)},[h,k]),j=f(()=>({"data-active":D,style:{...g,...D&&y},className:[_,D&&b].filter(Boolean).join(` `)||void 0}),[D,g,_,y,b]);l(()=>{if(m===`render`)A();else if(m===`viewport`&&w.current){let e=new IntersectionObserver(e=>e.forEach(e=>{e.isIntersecting?A():k()}));return e.observe(w.current),()=>{e.disconnect(),k()}}return k},[m,A,k]);let M=e=>{C.onClick?.(e),!(e.ctrlKey||e.metaKey||e.shiftKey||e.altKey||e.button!==0||e.defaultPrevented)&&(e.preventDefault(),t.navigate({url:E,replace:a,state:o}))},N=(e,t)=>n=>{t?.(n),m===`intent`&&!n.defaultPrevented&&e()},P={...C,...j,ref:ae(w,C.ref),href:E,onClick:M,onFocus:N(A,C.onFocus),onBlur:N(k,C.onBlur),onPointerEnter:N(A,C.onPointerEnter),onPointerLeave:N(k,C.onPointerLeave)};return x&&i(S)?n(S,P):v(`a`,{...P,children:S})}function ae(e,t){return t?n=>{e.current=n;let r=typeof t==`function`?t(n):void(t.current=n);return r&&(()=>{e.current=null,r()})}:e}function Z(e){let t=p(e);return u(()=>{t.current=e},[e]),p(((...e)=>t.current(...e))).current}function oe(e){return()=>v(t,{fallback:v(e,{}),children:z()})}function se(t){class n extends e{constructor(e){super(e),this.state={...e}}static getDerivedStateFromError(e){return{error:[e]}}static getDerivedStateFromProps(e,t){return e.children===t.children?t:{...e,error:void 0}}render(){return this.state.error?v(t,{error:this.state.error[0]}):this.props.children}}return()=>v(n,{children:z()})}function Q(e){return new $({...b(y(e)),validate:e=>e,handles:[],components:[],preloads:[]})}function ce(){return Q(``)}var $=class e{_;_types;constructor(e){this._=e}route=t=>new e({...this._,...b(y(`${this._.pattern}/${t}`)),p:this});use=t=>{let{_:n}=t;return new e({...this._,handles:[...this._.handles,...n.handles],components:[...this._.components,...n.components],preloads:[...this._.preloads,...n.preloads]}).search(n.validate)};search=t=>(t=x(t),new e({...this._,validate:e=>{let n=this._.validate(e);return{...n,...t({...e,...n})}}}));handle=t=>new e({...this._,handles:[...this._.handles,t]});preload=t=>new e({...this._,preloads:[...this._.preloads,e=>t({params:e.params,search:this._.validate(e.search)})]});component=t=>new e({...this._,components:[...this._.components,o(t)]});lazy=e=>{let t=a(async()=>{let t=await e();return`default`in t?t:{default:t}});return this.preload(e).component(t)};suspense=e=>this.component(oe(e));error=e=>this.component(se(e));toString=()=>this._.pattern};export{W as BrowserHistory,ee as HashHistory,ie as Link,N as LocationContext,P as MatchContext,X as MemoryHistory,re as Navigate,ne as Outlet,F as OutletContext,$ as Route,Y as Router,M as RouterContext,te as RouterRoot,ce as middleware,Q as route,V as useHandles,L as useLocation,R as useMatch,B as useNavigate,z as useOutlet,H as useParams,I as useRouter,U as useSearch};
|