@typeroute/router 0.9.0 → 0.10.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 +41 -17
- package/dist/index.d.ts +16 -10
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,7 +144,7 @@ If you believe there's a mistake in the comparison table, please [open an issue]
|
|
|
144
144
|
- [Dynamic page titles](#dynamic-page-titles)
|
|
145
145
|
- [API reference](#api-reference)
|
|
146
146
|
- [Router class](#router-class)
|
|
147
|
-
- [Route
|
|
147
|
+
- [Route builder](#route-builder)
|
|
148
148
|
- [Middleware](#middleware)
|
|
149
149
|
- [Hooks](#hooks)
|
|
150
150
|
- [Components](#components)
|
|
@@ -402,15 +402,20 @@ function About() {
|
|
|
402
402
|
}
|
|
403
403
|
```
|
|
404
404
|
|
|
405
|
-
Then
|
|
405
|
+
Then add a file that re-exports all your routes:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
// pages/routes.ts
|
|
409
|
+
export * from "./home";
|
|
410
|
+
export * from "./about";
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Now in your root app component file, import all the routes as a namespace, register them with module augmentation, and render `RouterRoot`:
|
|
406
414
|
|
|
407
415
|
```tsx
|
|
408
416
|
// app.tsx
|
|
409
417
|
import { RouterRoot } from "@typeroute/router";
|
|
410
|
-
import
|
|
411
|
-
import { about } from "./pages/about";
|
|
412
|
-
|
|
413
|
-
const routes = [home, about];
|
|
418
|
+
import * as routes from "./pages/routes";
|
|
414
419
|
|
|
415
420
|
export function App() {
|
|
416
421
|
return <RouterRoot routes={routes} />;
|
|
@@ -423,6 +428,8 @@ declare module "@typeroute/router" {
|
|
|
423
428
|
}
|
|
424
429
|
```
|
|
425
430
|
|
|
431
|
+
Because `routes` is a namespace object whose values are your route definitions, TypeRoute picks them up automatically - no need to manually maintain a routes collection. When you add a new page, just create the file and re-export it from `routes.ts`.
|
|
432
|
+
|
|
426
433
|
But again, this is just one approach. You could keep all routes in a single file, split them by feature, organize them by route depth, whatever fits your project. TypeRoute doesn't care where the routes come from or how you structure your files.
|
|
427
434
|
|
|
428
435
|
---
|
|
@@ -857,12 +864,12 @@ See [Route preloading](#route-preloading) for ways to load these components befo
|
|
|
857
864
|
|
|
858
865
|
# Data preloading
|
|
859
866
|
|
|
860
|
-
Use `.preload()` to run logic before navigation occurs, typically to prefetch data. Preload functions receive the target route's typed params and
|
|
867
|
+
Use `.preload()` to run logic before navigation occurs, typically to prefetch data. Preload functions receive the target route's typed params, search values, and router context:
|
|
861
868
|
|
|
862
869
|
```tsx
|
|
863
870
|
const userProfile = route("/users/:id")
|
|
864
871
|
.search(z.object({ tab: z.enum(["posts", "comments"]).catch("posts") }))
|
|
865
|
-
.preload(async ({ params, search }) => {
|
|
872
|
+
.preload(async ({ params, search, context }) => {
|
|
866
873
|
await queryClient.prefetchQuery({
|
|
867
874
|
queryKey: ["user", params.id, search.tab],
|
|
868
875
|
queryFn: () => fetchUser(params.id, search.tab)
|
|
@@ -894,6 +901,19 @@ const settings = dashboard.route("/settings").component(Settings);
|
|
|
894
901
|
// Preloading /dashboard/settings runs prefetchDashboardData
|
|
895
902
|
```
|
|
896
903
|
|
|
904
|
+
The `context` parameter provides access to any arbitrary data you passed to the router. This is useful for sharing instances like query clients across your preload functions. To use it, pass the context when creating your router and register its type:
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
<RouterRoot routes={routes} context={{ queryClient }} />;
|
|
908
|
+
|
|
909
|
+
declare module "@typeroute/router" {
|
|
910
|
+
interface Register {
|
|
911
|
+
routes: typeof routes;
|
|
912
|
+
context: { queryClient: QueryClient };
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
```
|
|
916
|
+
|
|
897
917
|
---
|
|
898
918
|
|
|
899
919
|
# Error boundaries
|
|
@@ -1529,6 +1549,7 @@ The `Router` class is the core of TypeRoute. You can create an instance directly
|
|
|
1529
1549
|
- `router.basePath` - The configured base path
|
|
1530
1550
|
- `router.routes` - The array of navigable routes
|
|
1531
1551
|
- `router.history` - The history instance
|
|
1552
|
+
- `router.context` - The router context
|
|
1532
1553
|
- `router.ssrContext` - The SSR context (if provided)
|
|
1533
1554
|
- `router.defaultLinkOptions` - Default link options
|
|
1534
1555
|
|
|
@@ -1541,6 +1562,7 @@ The `Router` class is the core of TypeRoute. You can create an instance directly
|
|
|
1541
1562
|
const router = new Router({ routes });
|
|
1542
1563
|
const router = new Router({ routes, basePath: "/app" });
|
|
1543
1564
|
const router = new Router({ routes, history: new HashHistory() });
|
|
1565
|
+
const router = new Router({ routes, context: { queryClient } });
|
|
1544
1566
|
```
|
|
1545
1567
|
|
|
1546
1568
|
**`router.navigate(options)`** navigates to a new location.
|
|
@@ -1610,7 +1632,7 @@ await router.preload({ to: "/user/:id", params: { id: "42" } });
|
|
|
1610
1632
|
await router.preload({ to: searchPage, search: { q: "test" } });
|
|
1611
1633
|
```
|
|
1612
1634
|
|
|
1613
|
-
## Route
|
|
1635
|
+
## Route builder
|
|
1614
1636
|
|
|
1615
1637
|
Routes are created with the `route()` function and configured by chaining methods.
|
|
1616
1638
|
|
|
@@ -1709,13 +1731,13 @@ const risky = route("/risky").error(ErrorPage).component(RiskyPage);
|
|
|
1709
1731
|
|
|
1710
1732
|
**`.preload(preload)`** registers a preload function for the route.
|
|
1711
1733
|
|
|
1712
|
-
- `preload` - `(
|
|
1734
|
+
- `preload` - `(options: PreloadOptions) => Promise<any>` - An async function receiving typed `params`, `search`, and `context`
|
|
1713
1735
|
- Returns: `Route` - A new route object
|
|
1714
1736
|
|
|
1715
1737
|
```tsx
|
|
1716
1738
|
const user = route("/users/:id")
|
|
1717
1739
|
.search(z.object({ tab: z.string().catch("profile") }))
|
|
1718
|
-
.preload(async ({ params, search }) => {
|
|
1740
|
+
.preload(async ({ params, search, context }) => {
|
|
1719
1741
|
// params.id: string, search.tab: string - fully typed
|
|
1720
1742
|
await prefetchUser(params.id, search.tab);
|
|
1721
1743
|
});
|
|
@@ -1922,13 +1944,14 @@ const unsubscribe = history.subscribe(() => {
|
|
|
1922
1944
|
**`RouterOptions`** are options for creating a `Router` instance or passing to `RouterRoot`.
|
|
1923
1945
|
|
|
1924
1946
|
```tsx
|
|
1925
|
-
|
|
1947
|
+
type RouterOptions = {
|
|
1926
1948
|
routes: Route[] | Record<string, Route>; // Collection of navigable routes
|
|
1927
1949
|
basePath?: string; // Base path prefix (default: "/")
|
|
1928
1950
|
history?: HistoryLike; // History implementation (default: BrowserHistory)
|
|
1951
|
+
context?: Context; // Arbitrary router context
|
|
1929
1952
|
ssrContext?: SSRContext; // Context for server-side rendering
|
|
1930
1953
|
defaultLinkOptions?: LinkOptions; // Default options for all Link components
|
|
1931
|
-
}
|
|
1954
|
+
};
|
|
1932
1955
|
```
|
|
1933
1956
|
|
|
1934
1957
|
**`NavigateOptions`** are options for type-safe navigation.
|
|
@@ -2005,12 +2028,13 @@ type SSRContext = {
|
|
|
2005
2028
|
};
|
|
2006
2029
|
```
|
|
2007
2030
|
|
|
2008
|
-
**`
|
|
2031
|
+
**`PreloadOptions`** is the options object passed to preload functions.
|
|
2009
2032
|
|
|
2010
2033
|
```tsx
|
|
2011
|
-
interface
|
|
2034
|
+
interface PreloadOptions {
|
|
2012
2035
|
params: Params; // Path params for the route
|
|
2013
2036
|
search: Search; // Validated search params
|
|
2037
|
+
context: Context; // Router context
|
|
2014
2038
|
}
|
|
2015
2039
|
```
|
|
2016
2040
|
|
|
@@ -2018,10 +2042,10 @@ interface PreloadContext {
|
|
|
2018
2042
|
|
|
2019
2043
|
# Roadmap
|
|
2020
2044
|
|
|
2021
|
-
- Possibility to pass an arbitrary context to the Router instance for later use in preloads?
|
|
2022
2045
|
- 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
|
|
2046
|
+
- Refactor: APIs like useParams, useSearch and useMatch should accept any route object and not just rely on the global routes collection.
|
|
2024
2047
|
- Refactor: allow `route()` and `.route()` to be called without passing an argument (defaulting to "/")?
|
|
2048
|
+
- A builder method `.index(component)` to simplify patterns like `useOutlet() ?? <div>Index page</div>`, rendering a component only when no child route matched. In practice, this can spare the definition of a child route for `"/"`.
|
|
2025
2049
|
- Document usage in test environments
|
|
2026
2050
|
- Navigation blockers (`useBlocker`, etc.)
|
|
2027
2051
|
- Open to suggestions, we can discuss them [here](https://github.com/strblr/typeroute/discussions).
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ type ParsePattern<P extends string> = Simplify<RouteParams<P>>;
|
|
|
9
9
|
type NormalizePath<P extends string> = RemoveTrailingSlash<DedupSlashes<`/${P}`>>;
|
|
10
10
|
type DedupSlashes<P extends string> = P extends `${infer Prefix}//${infer Rest}` ? `${Prefix}${DedupSlashes<`/${Rest}`>}` : P;
|
|
11
11
|
type RemoveTrailingSlash<P extends string> = P extends `${infer Prefix}/` ? Prefix extends "" ? "/" : Prefix : P;
|
|
12
|
-
type
|
|
12
|
+
type MaybeUndefinedKey<K extends string, T> = undefined extends T ? { [P in K]?: T } : { [P in K]: T };
|
|
13
|
+
type MaybeObjectKey<K extends string, T> = T extends EmptyObject ? { [P in K]?: EmptyObject } : {} extends T ? { [P in K]?: T } : { [P in K]: T };
|
|
13
14
|
type OptionalOnUndefined<T extends object> = Simplify<{ [K in keyof T as undefined extends T[K] ? never : K]: T[K] } & { [K in keyof T as undefined extends T[K] ? K : never]?: T[K] }>;
|
|
14
15
|
//#endregion
|
|
15
16
|
//#region src/types.d.ts
|
|
@@ -19,12 +20,15 @@ type NavigableRoute = Register extends {
|
|
|
19
20
|
} ? Routes extends ReadonlyArray<Route> ? Routes[number] : Routes extends Record<string, Route> ? Routes[keyof Routes] : Route : Route;
|
|
20
21
|
type Handle = Register extends {
|
|
21
22
|
handle: infer Handle;
|
|
22
|
-
} ? Handle :
|
|
23
|
+
} ? Handle : undefined;
|
|
24
|
+
type Context = Register extends {
|
|
25
|
+
context: infer Context;
|
|
26
|
+
} ? Context : undefined;
|
|
23
27
|
interface Middleware<S extends {} = any> {
|
|
24
28
|
use: <S2 extends {}>(middleware: Middleware<S2>) => Middleware<Merge<S, OptionalOnUndefined<S2>>>;
|
|
25
29
|
search: <S2 extends {}>(validate: Validator<S, S2>) => Middleware<Merge<S, OptionalOnUndefined<S2>>>;
|
|
26
30
|
handle: (handle: Handle) => Middleware<S>;
|
|
27
|
-
preload: (preload: (
|
|
31
|
+
preload: (preload: (options: PreloadOptions<{}, S>) => Promise<any>) => Middleware<S>;
|
|
28
32
|
component: (component: ComponentType) => Middleware<S>;
|
|
29
33
|
lazy: (loader: ComponentLoader) => Middleware<S>;
|
|
30
34
|
suspense: (fallback: ComponentType) => Middleware<S>;
|
|
@@ -33,17 +37,18 @@ interface Middleware<S extends {} = any> {
|
|
|
33
37
|
}>) => Middleware<S>;
|
|
34
38
|
}
|
|
35
39
|
type Validator<S extends {}, S2 extends {}> = ((search: S & Record<string, unknown>) => S2) | StandardSchemaV1<Record<string, unknown>, S2>;
|
|
36
|
-
interface
|
|
40
|
+
interface PreloadOptions<Ps extends {} = any, S extends {} = any> {
|
|
37
41
|
params: Ps;
|
|
38
42
|
search: S;
|
|
43
|
+
context: Context;
|
|
39
44
|
}
|
|
40
|
-
|
|
45
|
+
type RouterOptions = {
|
|
41
46
|
routes: ReadonlyArray<NavigableRoute> | Record<string, NavigableRoute>;
|
|
42
47
|
basePath?: string;
|
|
43
48
|
history?: HistoryLike;
|
|
44
49
|
ssrContext?: SSRContext;
|
|
45
50
|
defaultLinkOptions?: LinkOptions;
|
|
46
|
-
}
|
|
51
|
+
} & MaybeUndefinedKey<"context", Context>;
|
|
47
52
|
type Pattern = NavigableRoute["_"]["pattern"];
|
|
48
53
|
type GetRoute<P extends Pattern> = Extract<NavigableRoute, {
|
|
49
54
|
_: {
|
|
@@ -65,7 +70,7 @@ type NavigateOptions<P extends Pattern> = {
|
|
|
65
70
|
to: P | GetRoute<P>;
|
|
66
71
|
replace?: boolean;
|
|
67
72
|
state?: any;
|
|
68
|
-
} &
|
|
73
|
+
} & MaybeObjectKey<"params", Params<P>> & MaybeObjectKey<"search", Search<P>>;
|
|
69
74
|
interface LinkOptions {
|
|
70
75
|
strict?: boolean;
|
|
71
76
|
preload?: "intent" | "render" | "viewport" | false;
|
|
@@ -113,7 +118,7 @@ declare class Route<P extends string = string, Ps extends {} = any, S extends {}
|
|
|
113
118
|
validate: (search: Record<string, unknown>) => S;
|
|
114
119
|
handles: Handle[];
|
|
115
120
|
components: ComponentType[];
|
|
116
|
-
preloads: ((
|
|
121
|
+
preloads: ((options: PreloadOptions) => Promise<any>)[];
|
|
117
122
|
p?: Route;
|
|
118
123
|
};
|
|
119
124
|
readonly _types: {
|
|
@@ -125,7 +130,7 @@ declare class Route<P extends string = string, Ps extends {} = any, S extends {}
|
|
|
125
130
|
use: <S2 extends {}>(middleware: Middleware<S2>) => Route<P, Ps, Merge<S, OptionalOnUndefined<S2>>>;
|
|
126
131
|
search: <S2 extends {}>(validate: Validator<S, S2>) => Route<P, Ps, Merge<S, OptionalOnUndefined<S2>>>;
|
|
127
132
|
handle: (handle: Handle) => Route<P, Ps, S>;
|
|
128
|
-
preload: (preload: (
|
|
133
|
+
preload: (preload: (options: PreloadOptions<Ps, S>) => Promise<any>) => Route<P, Ps, S>;
|
|
129
134
|
component: (component: ComponentType) => Route<P, Ps, S>;
|
|
130
135
|
lazy: (loader: ComponentLoader) => Route<P, Ps, S>;
|
|
131
136
|
suspense: (fallback: ComponentType) => Route<P, Ps, S>;
|
|
@@ -140,6 +145,7 @@ declare class Router {
|
|
|
140
145
|
readonly routes: ReadonlyArray<NavigableRoute>;
|
|
141
146
|
readonly basePath: string;
|
|
142
147
|
readonly history: HistoryLike;
|
|
148
|
+
readonly context: Context;
|
|
143
149
|
readonly ssrContext?: SSRContext;
|
|
144
150
|
readonly defaultLinkOptions?: LinkOptions;
|
|
145
151
|
private readonly _;
|
|
@@ -210,4 +216,4 @@ declare const LocationContext: react.Context<HistoryLocation | null>;
|
|
|
210
216
|
declare const MatchContext: react.Context<Match | null>;
|
|
211
217
|
declare const OutletContext: react.Context<ReactNode>;
|
|
212
218
|
//#endregion
|
|
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,
|
|
219
|
+
export { BrowserHistory, ComponentLoader, Context, GetRoute, Handle, HashHistory, HistoryLike, HistoryLocation, HistoryPushOptions, Link, LinkOptions, LinkProps, LocationContext, Match, MatchContext, MatchOptions, MemoryHistory, Middleware, NavigableRoute, Navigate, NavigateOptions, NavigateProps, Outlet, OutletContext, Params, Pattern, PreloadOptions, 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),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,
|
|
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;context;ssrContext;defaultLinkOptions;_;constructor(e){let{routes:t,basePath:n=`/`,history:r,context:i,ssrContext:a,defaultLinkOptions:o}=e;this.routes=Object.values(t),this.basePath=y(n),this.history=r??new W,this.context=i,this.ssrContext=a,this.defaultLinkOptions=o,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,context:this.context})))};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({...e,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};
|