waymark 0.2.3 → 0.3.1

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/dist/index.d.ts CHANGED
@@ -19,12 +19,23 @@ type RouteList = Register extends {
19
19
  type Handle = Register extends {
20
20
  handle: infer Handle;
21
21
  } ? Handle : any;
22
+ interface PreloadContext<R extends Route = Route> {
23
+ params: R["_types"]["params"];
24
+ search: R["_types"]["search"];
25
+ }
26
+ interface RouterOptions {
27
+ basePath?: string;
28
+ routes: RouteList;
29
+ history?: HistoryLike;
30
+ ssrContext?: SSRContext;
31
+ defaultLinkOptions?: LinkOptions;
32
+ }
22
33
  type Pattern = RouteList[number]["pattern"];
23
34
  type GetRoute<P extends Pattern> = Extract<RouteList[number], {
24
35
  pattern: P;
25
36
  }>;
26
- type Params<P extends Pattern> = NonNullable<GetRoute<P>["_"]["_params"]>;
27
- type Search<P extends Pattern> = NonNullable<GetRoute<P>["_"]["_search"]>;
37
+ type Params<P extends Pattern> = GetRoute<P>["_types"]["params"];
38
+ type Search<P extends Pattern> = GetRoute<P>["_types"]["search"];
28
39
  type MatchOptions<P extends Pattern> = {
29
40
  from: P | GetRoute<P>;
30
41
  strict?: boolean;
@@ -39,6 +50,15 @@ type NavigateOptions<P extends Pattern> = {
39
50
  replace?: boolean;
40
51
  state?: any;
41
52
  } & MaybeKey<"params", Params<P>> & MaybeKey<"search", Search<P>>;
53
+ interface LinkOptions {
54
+ strict?: boolean;
55
+ preload?: "intent" | "render" | "viewport" | false;
56
+ preloadDelay?: number;
57
+ style?: CSSProperties;
58
+ className?: string;
59
+ activeStyle?: CSSProperties;
60
+ activeClassName?: string;
61
+ }
42
62
  type SSRContext = {
43
63
  redirect?: string;
44
64
  statusCode?: number;
@@ -65,83 +85,35 @@ type ComponentLoader = () => Promise<ComponentType | {
65
85
  declare function route<P extends string>(pattern: P): Route<NormalizePath<P>, regexparam0.RouteParams<NormalizePath<P>> extends infer T ? { [KeyType in keyof T]: T[KeyType] } : never, {}>;
66
86
  declare class Route<P extends string = string, Ps extends {} = any, S extends {} = any> {
67
87
  readonly pattern: P;
88
+ readonly _types: {
89
+ params: Ps;
90
+ search: S;
91
+ };
68
92
  readonly _: {
69
- _params?: Ps;
70
- _search?: S;
71
93
  keys: string[];
72
94
  regex: RegExp;
73
95
  looseRegex: RegExp;
74
96
  weights: number[];
75
- mapSearch: (search: Record<string, unknown>) => S;
97
+ validate: (search: Record<string, unknown>) => S;
76
98
  handles: Handle[];
77
99
  components: ComponentType[];
78
- preloaded: boolean;
79
- preloaders: (() => Promise<any>)[];
100
+ preloads: ((context: PreloadContext) => Promise<any>)[];
80
101
  };
81
- constructor(pattern: P, mapSearch: (search: Record<string, unknown>) => S, handles: Handle[], components: ComponentType[], preloaders: (() => Promise<any>)[]);
82
- route<P2 extends string>(subPattern: P2): Route<NormalizePath<`${P}/${P2}`>, regexparam0.RouteParams<NormalizePath<`${P}/${P2}`>> extends infer T ? { [KeyType in keyof T]: T[KeyType] } : never, S>;
83
- search<S2 extends {}>(mapper: ((search: S & Record<string, unknown>) => S2) | StandardSchemaV1<Record<string, unknown>, S2>): Route<P, Ps, (type_fest0.PickIndexSignature<S> extends infer T_1 ? { [Key in keyof T_1 as Key extends keyof type_fest0.PickIndexSignature<{ [K in keyof S2 as undefined extends S2[K] ? never : K]: S2[K] } & { [K_1 in keyof S2 as undefined extends S2[K_1] ? K_1 : never]?: S2[K_1] | undefined } extends infer T_2 ? { [KeyType_1 in keyof T_2]: T_2[KeyType_1] } : never> ? never : Key]: T_1[Key] } : never) & type_fest0.PickIndexSignature<{ [K in keyof S2 as undefined extends S2[K] ? never : K]: S2[K] } & { [K_1 in keyof S2 as undefined extends S2[K_1] ? K_1 : never]?: S2[K_1] | undefined } extends infer T_2 ? { [KeyType_1 in keyof T_2]: T_2[KeyType_1] } : never> & (type_fest0.OmitIndexSignature<S> extends infer T_3 ? { [Key_1 in keyof T_3 as Key_1 extends keyof type_fest0.OmitIndexSignature<{ [K in keyof S2 as undefined extends S2[K] ? never : K]: S2[K] } & { [K_1 in keyof S2 as undefined extends S2[K_1] ? K_1 : never]?: S2[K_1] | undefined } extends infer T_4 ? { [KeyType_1 in keyof T_4]: T_4[KeyType_1] } : never> ? never : Key_1]: T_3[Key_1] } : never) & type_fest0.OmitIndexSignature<{ [K in keyof S2 as undefined extends S2[K] ? never : K]: S2[K] } & { [K_1 in keyof S2 as undefined extends S2[K_1] ? K_1 : never]?: S2[K_1] | undefined } extends infer T_4 ? { [KeyType_1 in keyof T_4]: T_4[KeyType_1] } : never> extends infer T ? { [KeyType in keyof T]: T[KeyType] } : never>;
84
- handle(handle: Handle): Route<P, Ps, S>;
85
- preloader(preloader: () => Promise<any>): Route<P, Ps, S>;
86
- component(component: ComponentType): Route<P, Ps, S>;
87
- lazy(loader: ComponentLoader): Route<P, Ps, S>;
88
- suspense(fallback: ComponentType): Route<P, Ps, S>;
89
- error(fallback: ComponentType<{
102
+ constructor(pattern: P, _: typeof this._);
103
+ route: <P2 extends string>(pattern: P2) => Route<NormalizePath<`${P}/${P2}`>, regexparam0.RouteParams<NormalizePath<`${P}/${P2}`>> extends infer T ? { [KeyType in keyof T]: T[KeyType] } : never, S>;
104
+ search: <S2 extends {}>(validate: ((search: S & Record<string, unknown>) => S2) | StandardSchemaV1<Record<string, unknown>, S2>) => Route<P, Ps, (type_fest0.PickIndexSignature<S> extends infer T_1 ? { [Key in keyof T_1 as Key extends keyof type_fest0.PickIndexSignature<{ [K in keyof S2 as undefined extends S2[K] ? never : K]: S2[K] } & { [K_1 in keyof S2 as undefined extends S2[K_1] ? K_1 : never]?: S2[K_1] | undefined } extends infer T_2 ? { [KeyType_1 in keyof T_2]: T_2[KeyType_1] } : never> ? never : Key]: T_1[Key] } : never) & type_fest0.PickIndexSignature<{ [K in keyof S2 as undefined extends S2[K] ? never : K]: S2[K] } & { [K_1 in keyof S2 as undefined extends S2[K_1] ? K_1 : never]?: S2[K_1] | undefined } extends infer T_2 ? { [KeyType_1 in keyof T_2]: T_2[KeyType_1] } : never> & (type_fest0.OmitIndexSignature<S> extends infer T_3 ? { [Key_1 in keyof T_3 as Key_1 extends keyof type_fest0.OmitIndexSignature<{ [K in keyof S2 as undefined extends S2[K] ? never : K]: S2[K] } & { [K_1 in keyof S2 as undefined extends S2[K_1] ? K_1 : never]?: S2[K_1] | undefined } extends infer T_4 ? { [KeyType_1 in keyof T_4]: T_4[KeyType_1] } : never> ? never : Key_1]: T_3[Key_1] } : never) & type_fest0.OmitIndexSignature<{ [K in keyof S2 as undefined extends S2[K] ? never : K]: S2[K] } & { [K_1 in keyof S2 as undefined extends S2[K_1] ? K_1 : never]?: S2[K_1] | undefined } extends infer T_4 ? { [KeyType_1 in keyof T_4]: T_4[KeyType_1] } : never> extends infer T ? { [KeyType in keyof T]: T[KeyType] } : never>;
105
+ handle: (handle: Handle) => Route<P, Ps, S>;
106
+ preload: (preload: (context: PreloadContext<this>) => Promise<any>) => Route<P, Ps, S>;
107
+ component: (component: ComponentType) => Route<P, Ps, S>;
108
+ lazy: (loader: ComponentLoader) => Route<P, Ps, S>;
109
+ suspense: (fallback: ComponentType) => Route<P, Ps, S>;
110
+ error: (fallback: ComponentType<{
90
111
  error: unknown;
91
- }>): Route<P, Ps, S>;
92
- preload(): Promise<void>;
93
- toString(): P;
112
+ }>) => Route<P, Ps, S>;
113
+ toString: () => P;
94
114
  }
95
115
  //#endregion
96
- //#region src/react/components.d.ts
97
- type RouterRootProps = RouterOptions | {
98
- router: Router;
99
- };
100
- declare function RouterRoot(props: RouterRootProps): ReactNode;
101
- declare function Outlet(): ReactNode;
102
- type NavigateProps<P extends Pattern> = NavigateOptions<P>;
103
- declare function Navigate<P extends Pattern>(props: NavigateProps<P>): null;
104
- type LinkProps<P extends Pattern> = NavigateOptions<P> & LinkOptions & AnchorHTMLAttributes<HTMLAnchorElement> & RefAttributes<HTMLAnchorElement> & {
105
- asChild?: boolean;
106
- };
107
- interface LinkOptions {
108
- strict?: boolean;
109
- preload?: "intent" | "render" | "viewport" | false;
110
- style?: CSSProperties;
111
- className?: string;
112
- activeStyle?: CSSProperties;
113
- activeClassName?: string;
114
- }
115
- declare function Link<P extends Pattern>(props: LinkProps<P>): ReactNode;
116
- //#endregion
117
- //#region src/react/hooks.d.ts
118
- declare function useRouter(): Router;
119
- declare function useHandles(): Handle[];
120
- declare function useOutlet(): react2.ReactNode;
121
- declare function useSubscribe<T>(router: Router, getSnapshot: () => T): T;
122
- declare function useNavigate(): <P extends Pattern>(options: number | HistoryPushOptions | NavigateOptions<P>) => void;
123
- declare function useLocation(): {
124
- path: string;
125
- search: Record<string, unknown>;
126
- state: any;
127
- };
128
- declare function useMatch<P extends Pattern>(options: MatchOptions<P>): Match<P> | null;
129
- declare function useParams<P extends Pattern>(from: P | GetRoute<P>): Params<P>;
130
- declare function useSearch<P extends Pattern>(from: P | GetRoute<P>): readonly [Search<P>, (update: Updater<Search<P>>, replace?: boolean) => void];
131
- //#endregion
132
- //#region src/react/contexts.d.ts
133
- declare const RouterContext: react2.Context<Router | null>;
134
- declare const MatchContext: react2.Context<Match | null>;
135
- declare const OutletContext: react2.Context<ReactNode>;
136
- //#endregion
137
116
  //#region src/router/router.d.ts
138
- interface RouterOptions {
139
- basePath?: string;
140
- routes: RouteList;
141
- history?: HistoryLike;
142
- ssrContext?: SSRContext;
143
- defaultLinkOptions?: LinkOptions;
144
- }
145
117
  declare class Router {
146
118
  readonly basePath: string;
147
119
  readonly routes: RouteList;
@@ -150,16 +122,17 @@ declare class Router {
150
122
  readonly defaultLinkOptions?: LinkOptions;
151
123
  private readonly _;
152
124
  constructor(options: RouterOptions);
153
- getRoute<P extends Pattern>(pattern: P | GetRoute<P>): GetRoute<P>;
154
- match<P extends Pattern>(path: string, options: MatchOptions<P>): Match<P> | null;
155
- matchAll(path: string): Match | null;
156
- createUrl<P extends Pattern>(options: NavigateOptions<P>): string;
157
- navigate<P extends Pattern>(options: NavigateOptions<P> | HistoryPushOptions | number): void;
125
+ getRoute: <P extends Pattern>(pattern: P | GetRoute<P>) => GetRoute<P>;
126
+ match: <P extends Pattern>(path: string, options: MatchOptions<P>) => Match<P> | null;
127
+ matchAll: (path: string) => Match | null;
128
+ createUrl: <P extends Pattern>(options: NavigateOptions<P>) => string;
129
+ preload: <P extends Pattern>(options: NavigateOptions<P>) => Promise<void>;
130
+ navigate: <P extends Pattern>(options: NavigateOptions<P> | HistoryPushOptions | number) => void;
158
131
  }
159
132
  //#endregion
160
133
  //#region src/router/browser-history.d.ts
161
134
  declare class BrowserHistory implements HistoryLike {
162
- private static patchKey;
135
+ private static patch;
163
136
  private memo?;
164
137
  constructor();
165
138
  protected getSearchMemo: (search: string) => Record<string, unknown>;
@@ -172,11 +145,6 @@ declare class BrowserHistory implements HistoryLike {
172
145
  }
173
146
  //#endregion
174
147
  //#region src/router/memory-history.d.ts
175
- interface MemoryLocation {
176
- path: string;
177
- search: Record<string, unknown>;
178
- state: any;
179
- }
180
148
  declare class MemoryHistory implements HistoryLike {
181
149
  private stack;
182
150
  private index;
@@ -199,4 +167,37 @@ declare class HashHistory extends BrowserHistory {
199
167
  push: (options: HistoryPushOptions) => void;
200
168
  }
201
169
  //#endregion
202
- export { BrowserHistory, ComponentLoader, GetRoute, Handle, HashHistory, HistoryLike, HistoryPushOptions, Link, LinkOptions, LinkProps, Match, MatchContext, MatchOptions, MemoryHistory, MemoryLocation, Navigate, NavigateOptions, NavigateProps, Outlet, OutletContext, Params, Pattern, Register, Route, RouteList, Router, RouterContext, RouterOptions, RouterRoot, RouterRootProps, SSRContext, Search, Updater, route, useHandles, useLocation, useMatch, useNavigate, useOutlet, useParams, useRouter, useSearch, useSubscribe };
170
+ //#region src/react/components.d.ts
171
+ type RouterRootProps = RouterOptions | {
172
+ router: Router;
173
+ };
174
+ declare function RouterRoot(props: RouterRootProps): ReactNode;
175
+ declare function Outlet(): ReactNode;
176
+ type NavigateProps<P extends Pattern> = NavigateOptions<P>;
177
+ declare function Navigate<P extends Pattern>(props: NavigateProps<P>): null;
178
+ type LinkProps<P extends Pattern> = NavigateOptions<P> & LinkOptions & AnchorHTMLAttributes<HTMLAnchorElement> & RefAttributes<HTMLAnchorElement> & {
179
+ asChild?: boolean;
180
+ };
181
+ declare function Link<P extends Pattern>(props: LinkProps<P>): ReactNode;
182
+ //#endregion
183
+ //#region src/react/hooks.d.ts
184
+ declare function useRouter(): Router;
185
+ declare function useNavigate(): <P extends Pattern>(options: number | HistoryPushOptions | NavigateOptions<P>) => void;
186
+ declare function useLocation(): {
187
+ path: string;
188
+ search: Record<string, unknown>;
189
+ state: any;
190
+ };
191
+ declare function useOutlet(): react2.ReactNode;
192
+ declare function useParams<P extends Pattern>(from: P | GetRoute<P>): Params<P>;
193
+ declare function useSearch<P extends Pattern>(from: P | GetRoute<P>): readonly [Search<P>, (update: Updater<Search<P>>, replace?: boolean) => void];
194
+ declare function useMatch<P extends Pattern>(options: MatchOptions<P>): Match<P> | null;
195
+ declare function useHandles(): Handle[];
196
+ declare function useSubscribe<T>(router: Router, getSnapshot: () => T): T;
197
+ //#endregion
198
+ //#region src/react/contexts.d.ts
199
+ declare const RouterContext: react2.Context<Router | null>;
200
+ declare const MatchContext: react2.Context<Match | null>;
201
+ declare const OutletContext: react2.Context<ReactNode>;
202
+ //#endregion
203
+ export { BrowserHistory, ComponentLoader, GetRoute, Handle, HashHistory, HistoryLike, HistoryPushOptions, Link, LinkOptions, LinkProps, Match, MatchContext, MatchOptions, MemoryHistory, Navigate, NavigateOptions, NavigateProps, Outlet, OutletContext, Params, Pattern, PreloadContext, Register, Route, RouteList, Router, RouterContext, RouterOptions, RouterRoot, RouterRootProps, SSRContext, Search, Updater, route, useHandles, useLocation, useMatch, useNavigate, useOutlet, useParams, useRouter, useSearch, useSubscribe };
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 ee,useLayoutEffect as l,useMemo as u,useRef as d,useState as f,useSyncExternalStore as p}from"react";import{inject as m,parse as h}from"regexparam";import{jsx as g}from"react/jsx-runtime";function _(e){return`/${e}`.replaceAll(/\/+/g,`/`).replace(/(.+)\/$/,`$1`)}function v(e){return e.split(`/`).slice(1).map(e=>e.includes(`*`)?0:e.includes(`:`)?1:2)}function y(e){return typeof e==`function`?e:t=>{let n=e[`~standard`].validate(t);if(n instanceof Promise)throw Error(`[Waymark] Validation must be synchronous`);if(n.issues)throw Error(`[Waymark] Validation failed`,{cause:n.issues});return n.value}}function b(e){return Object.entries(e).filter(([e,t])=>t!==void 0).map(([e,t])=>`${e}=${encodeURIComponent(S(t))}`).join(`&`)}function x(e){let t=new URLSearchParams(e);return Object.fromEntries([...t.entries()].map(([e,t])=>(t=decodeURIComponent(t),[e,C(t)?JSON.parse(t):t])))}function S(e){return typeof e==`string`&&!C(e)?e:JSON.stringify(e)}function C(e){try{return JSON.parse(e),!0}catch{return!1}}function w(e,t){return _(`${t}/${e}`)}function T(e,t){return(e===t||e.startsWith(`${t}/`))&&(e=e.slice(t.length)||`/`),e}function E(e,t){return[e,b(t)].filter(Boolean).join(`?`)}function D(e){let{pathname:t,search:n}=new URL(e,`http://w`);return{path:t,search:x(n)}}function O(e,t,n,r){let i=e.exec(T(n,r));if(!i)return null;let a={};return t.forEach((e,t)=>{let n=i[t+1];n&&(a[e]=n)}),a}function k(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 A=r(null),j=r(null),M=r(null);function N(){let e=c(A);if(!e)throw Error(`[Waymark] useRouter must be used within a router context`);return e}function P(){let e=c(j);return u(()=>e?.route._.handles??[],[e])}function F(){return c(M)}function I(e,t){return p(e.history.subscribe,t,t)}function L(){let e=N();return u(()=>e.navigate.bind(e),[e])}function R(){let e=N(),t=I(e,e.history.getPath),n=I(e,e.history.getSearch),r=I(e,e.history.getState);return u(()=>({path:t,search:n,state:r}),[t,n,r])}function z(e){let t=N(),n=I(t,t.history.getPath);return u(()=>t.match(n,e),[t,n,e])}function B(e){let t=z({from:e});if(!t)throw Error(`[Waymark] Can't read params for non-matching route: ${e}`);return t.params}function te(e){let t=N(),n=t.getRoute(e),r=I(t,t.history.getSearch);return[u(()=>n._.mapSearch(r),[n,r]),s((e,r)=>{let i=n._.mapSearch(t.history.getSearch());e=typeof e==`function`?e(i):e;let a=E(t.history.getPath(),{...i,...e});t.navigate({url:a,replace:r})},[t,n])]}var V=class e{static patchKey=Symbol.for(`waymark_history_patch_v01`);memo;constructor(){if(typeof history<`u`&&!(e.patchKey in window)){for(let e of[H,U]){let t=history[e];history[e]=function(...n){let r=t.apply(this,n),i=new Event(e);return i.arguments=n,dispatchEvent(i),r}}window[e.patchKey]=!0}}getSearchMemo=e=>this.memo?.search===e?this.memo.parsed:(this.memo={search:e,parsed:x(e)}).parsed;getPath=()=>location.pathname;getSearch=()=>this.getSearchMemo(location.search);getState=()=>history.state;go=e=>history.go(e);push=e=>{let{url:t,replace:n,state:r}=e;history[n?U:H](r,``,t)};subscribe=e=>(W.forEach(t=>window.addEventListener(t,e)),()=>{W.forEach(t=>window.removeEventListener(t,e))})};const H=`pushState`,U=`replaceState`,W=[`popstate`,H,U,`hashchange`];var G=class{basePath;routes;history;ssrContext;defaultLinkOptions;_;constructor(e){let{basePath:t=`/`,routes:n,history:r,ssrContext:i,defaultLinkOptions:a}=e;this.basePath=_(t),this.routes=n,this.history=r??new V,this.ssrContext=i,this.defaultLinkOptions=a,this._={routeMap:new Map(n.map(e=>[e.pattern,e]))}}getRoute(e){if(typeof e!=`string`)return e;let t=this._.routeMap.get(e);if(!t)throw Error(`[Waymark] Route not found for pattern: ${e}`);return t}match(e,t){let{from:n,strict:r,params:i}=t,a=this.getRoute(n),o=O(r?a._.regex:a._.looseRegex,a._.keys,e,this.basePath);return!o||i&&Object.keys(i).some(e=>i[e]!==o[e])?null:{route:a,params:o}}matchAll(e){return k(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 E(w(m(i,n),this.basePath),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})}}},K=class{stack=[];index=0;listeners=new Set;constructor(e=`/`){this.stack.push({...D(e),state:void 0})}getCurrent=()=>this.stack[this.index];getPath=()=>this.getCurrent().path;getSearch=()=>this.getCurrent().search;getState=()=>this.getCurrent().state;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={...D(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)})},q=class extends V{getHashUrl=()=>new URL(location.hash.slice(1),`http://w`);getPath=()=>this.getHashUrl().pathname;getSearch=()=>this.getSearchMemo(this.getHashUrl().search);push=e=>{let{url:t,replace:n,state:r}=e;history[n?`replaceState`:`pushState`](r,``,`#${t}`)}};function J(e){let[t]=f(()=>`router`in e?e.router:new G(e)),n=I(t,t.history.getPath),r=u(()=>t.matchAll(n),[t,n]);return r||console.error(`[Waymark] No matching route found for path:`,n),u(()=>g(A.Provider,{value:t,children:g(j.Provider,{value:r,children:r?.route._.components.reduceRight((e,t)=>g(M.Provider,{value:e,children:g(t,{})}),null)})}),[t,r])}function Y(){return F()}function X(e){let t=N();return l(()=>t.navigate(e),[]),t.ssrContext&&(t.ssrContext.redirect=t.createUrl(e)),null}function Z(e){let t=N(),{to:r,replace:a,state:o,params:s,search:c,strict:l,preload:f,style:p,className:m,activeStyle:h,activeClassName:_,asChild:v,children:y,...b}={...t.defaultLinkOptions,...e},x=d(null),S=t.createUrl(e),C=u(()=>t.getRoute(e.to),[t,e.to]),w=!!z({from:C,strict:l,params:s}),T=u(()=>({"data-active":w,style:{...p,...w&&h},className:[m,w&&_].filter(Boolean).join(` `)||void 0}),[w,p,m,h,_]);ee(()=>{if(f===`render`)C.preload();else if(f===`viewport`&&x.current){let e=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&(C.preload(),e.disconnect())})});return e.observe(x.current),()=>e.disconnect()}},[f,C]);let E=e=>{b.onClick?.(e),!(e.ctrlKey||e.metaKey||e.shiftKey||e.altKey||e.button!==0||e.defaultPrevented)&&(e.preventDefault(),t.navigate({url:S,replace:a,state:o}))},D=e=>{b.onFocus?.(e),f===`intent`&&!e.defaultPrevented&&C.preload()},O=e=>{b.onPointerEnter?.(e),f===`intent`&&!e.defaultPrevented&&C.preload()},k={...b,...T,ref:ne(b.ref,x),href:S,onClick:E,onFocus:D,onPointerEnter:O};return v&&i(y)?n(y,k):g(`a`,{...k,children:y})}function ne(...e){let t=e.filter(e=>!!e);return t.length<=1?t[0]??null:e=>{let n=[];for(let r of t){let t=Q(r,e);n.push(t??(()=>Q(r,null)))}return()=>n.forEach(e=>e())}}function Q(e,t){if(typeof e==`function`)return e(t);e&&(e.current=t)}function re(e){return()=>g(t,{fallback:g(e,{}),children:F()})}function ie(t){class n extends e{constructor(e){super(e),this.state={children:e.children,error:null}}static getDerivedStateFromError(e){return{error:[e]}}static getDerivedStateFromProps(e,t){return e.children===t.children?t:{children:e.children,error:null}}render(){return this.state.error?g(t,{error:this.state.error[0]}):this.props.children}}return()=>g(n,{children:F()})}function ae(e){return new $(_(e),e=>e,[],[],[])}var $=class e{pattern;_;constructor(e,t,n,r,i){let{keys:a,pattern:o}=h(e),s=h(e,!0).pattern,c=v(e);this.pattern=e,this._={keys:a,regex:o,looseRegex:s,weights:c,mapSearch:t,handles:n,components:r,preloaded:!1,preloaders:i}}route(t){let{mapSearch:n,handles:r,components:i,preloaders:a}=this._;return new e(_(`${this.pattern}/${t}`),n,r,i,a)}search(t){let{mapSearch:n,handles:r,components:i,preloaders:a}=this._;return t=y(t),new e(this.pattern,e=>{let r=n(e);return{...r,...t({...e,...r})}},r,i,a)}handle(t){let{mapSearch:n,handles:r,components:i,preloaders:a}=this._;return new e(this.pattern,n,[...r,t],i,a)}preloader(t){let{mapSearch:n,handles:r,components:i,preloaders:a}=this._;return new e(this.pattern,n,r,i,[...a,t])}component(t){let{mapSearch:n,handles:r,components:i,preloaders:a}=this._;return new e(this.pattern,n,r,[...i,o(t)],a)}lazy(e){let t=a(async()=>{let t=await e();return{default:o(`default`in t?t.default:t)}});return this.preloader(e).component(t)}suspense(e){return this.component(re(e))}error(e){return this.component(ie(e))}async preload(){let{preloaded:e,preloaders:t}=this._;e||(this._.preloaded=!0,await Promise.all(t.map(e=>e())))}toString(){return this.pattern}};export{V as BrowserHistory,q as HashHistory,Z as Link,j as MatchContext,K as MemoryHistory,X as Navigate,Y as Outlet,M as OutletContext,$ as Route,G as Router,A as RouterContext,J as RouterRoot,ae as route,P as useHandles,R as useLocation,z as useMatch,L as useNavigate,F as useOutlet,B as useParams,N as useRouter,te as useSearch,I as useSubscribe};
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{keys:t,regex:n,looseRegex:_(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(`[Waymark] Validation must be synchronous`);if(n.issues)throw Error(`[Waymark] 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(e,t,n,r){let i=e.exec(D(n,r));if(!i)return null;let a={};return t.forEach((e,t)=>{let n=i[t+1];n&&(a[e]=n)}),a}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);function F(){let e=c(M);if(!e)throw Error(`[Waymark] useRouter must be used within a router context`);return e}function I(){return F().navigate}function L(){let e=F(),t=U(e,e.history.getPath),n=U(e,e.history.getSearch),r=U(e,e.history.getState);return f(()=>({path:t,search:n,state:r}),[t,n,r])}function R(){return c(P)}function z(e){let t=V({from:e});if(!t)throw Error(`[Waymark] Can't read params for non-matching route: ${e}`);return t.params}function B(e){let t=F(),n=t.getRoute(e),r=U(t,t.history.getSearch),i=f(()=>n._.validate(r),[n,r]);return[i,Q((e,n)=>{e=typeof e==`function`?e(i):e;let r={...i,...e},a=O(t.history.getPath(),r);t.navigate({url:a,replace:n})})]}function V(e){let t=F(),n=U(t,t.history.getPath);return f(()=>t.match(n,e),[t,n,e])}function H(){let e=c(N);return f(()=>e?.route._.handles??[],[e])}function U(e,t){return h(e.history.subscribe,t,t)}var W=class e{static patch=Symbol.for(`wmbhp01`);memo;constructor(){if(typeof history<`u`&&!(e.patch in window)){for(let e of[G,K]){let t=history[e];history[e]=function(...n){let r=t.apply(this,n),i=new Event(e);return i.arguments=n,dispatchEvent(i),r}}window[e.patch]=!0}}getSearchMemo=e=>this.memo?.search===e?this.memo.parsed:(this.memo={search:e,parsed:C(e)}).parsed;getPath=()=>location.pathname;getSearch=()=>this.getSearchMemo(location.search);getState=()=>history.state;go=e=>history.go(e);push=e=>{let{url:t,replace:n,state:r}=e;history[n?K:G](r,``,t)};subscribe=e=>(q.forEach(t=>window.addEventListener(t,e)),()=>{q.forEach(t=>window.removeEventListener(t,e))})};const G=`pushState`,K=`replaceState`,q=[`popstate`,G,K,`hashchange`];var J=class{basePath;routes;history;ssrContext;defaultLinkOptions;_;constructor(e){let{basePath:t=`/`,routes:n,history:r,ssrContext:i,defaultLinkOptions:a}=e;this.basePath=y(t),this.routes=n,this.history=r??new W,this.ssrContext=i,this.defaultLinkOptions=a,this._={routeMap:new Map(n.map(e=>[e.pattern,e]))}}getRoute=e=>{if(typeof e!=`string`)return e;let t=this._.routeMap.get(e);if(!t)throw Error(`[Waymark] Route not found for pattern: ${e}`);return t};match=(e,t)=>{let{from:n,strict:r,params:i}=t,a=this.getRoute(n),o=A(r?a._.regex:a._.looseRegex,a._.keys,e,this.basePath);return!o||i&&Object.keys(i).some(e=>i[e]!==o[e])?null:{route:a,params:o}};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,i=this.getRoute(t);await Promise.all(i._.preloads.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})}}},Y=class{stack=[];index=0;listeners=new Set;constructor(e=`/`){this.stack.push({...k(e),state:void 0})}getCurrent=()=>this.stack[this.index];getPath=()=>this.getCurrent().path;getSearch=()=>this.getCurrent().search;getState=()=>this.getCurrent().state;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)})},X=class extends W{getHashUrl=()=>new URL(location.hash.slice(1),`http://w`);getPath=()=>this.getHashUrl().pathname;getSearch=()=>this.getSearchMemo(this.getHashUrl().search);push=e=>{let{url:t,replace:n,state:r}=e;history[n?`replaceState`:`pushState`](r,``,`#${t}`)}};function Z(e){let[t]=m(()=>`router`in e?e.router:new J(e)),n=U(t,t.history.getPath),r=f(()=>t.matchAll(n),[t,n]);return r||console.error(`[Waymark] No matching route found for path:`,n),f(()=>v(M.Provider,{value:t,children:v(N.Provider,{value:r,children:r?.route._.components.reduceRight((e,t)=>v(P.Provider,{value:e,children:v(t,{})}),null)})}),[t,r])}function ee(){return R()}function te(e){let t=F();return d(()=>t.navigate(e),[]),t.ssrContext&&(t.ssrContext.redirect=t.createUrl(e)),null}function ne(e){let t=F(),{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=!!V({from:e.to,strict:d,params:c}),O=Q(()=>t.preload(e)),k=s(()=>{T.current!==null&&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=>{C.onFocus?.(e),m===`intent`&&!e.defaultPrevented&&A()},P=e=>{C.onBlur?.(e),m===`intent`&&k()},I=e=>{C.onPointerEnter?.(e),m===`intent`&&!e.defaultPrevented&&A()},L=e=>{C.onPointerLeave?.(e),m===`intent`&&k()},R={...C,...j,ref:re(w,C.ref),href:E,onClick:M,onFocus:N,onBlur:P,onPointerEnter:I,onPointerLeave:L};return x&&i(S)?n(S,R):v(`a`,{...R,children:S})}function re(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 Q(e){let t=p(e);return u(()=>{t.current=e},[e]),p(((...e)=>t.current(...e))).current}function ie(e){return()=>v(t,{fallback:v(e,{}),children:R()})}function ae(t){class n extends e{constructor(e){super(e),this.state={children:e.children,error:null}}static getDerivedStateFromError(e){return{error:[e]}}static getDerivedStateFromProps(e,t){return e.children===t.children?t:{children:e.children,error:null}}render(){return this.state.error?v(t,{error:this.state.error[0]}):this.props.children}}return()=>v(n,{children:R()})}function oe(e){let t=y(e);return new $(t,{...b(t),validate:e=>e,handles:[],components:[],preloads:[]})}var $=class e{pattern;_types;_;constructor(e,t){this.pattern=e,this._=t}route=t=>{let n=y(`${this.pattern}/${t}`);return new e(n,{...this._,...b(n)})};search=t=>(t=x(t),new e(this.pattern,{...this._,validate:e=>{let n=this._.validate(e);return{...n,...t({...e,...n})}}}));handle=t=>new e(this.pattern,{...this._,handles:[...this._.handles,t]});preload=t=>new e(this.pattern,{...this._,preloads:[...this._.preloads,e=>t({params:e.params,search:this._.validate(e.search)})]});component=t=>new e(this.pattern,{...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(ie(e));error=e=>this.component(ae(e));toString=()=>this.pattern};export{W as BrowserHistory,X as HashHistory,ne as Link,N as MatchContext,Y as MemoryHistory,te as Navigate,ee as Outlet,P as OutletContext,$ as Route,J as Router,M as RouterContext,Z as RouterRoot,oe as route,H as useHandles,L as useLocation,V as useMatch,I as useNavigate,R as useOutlet,z as useParams,F as useRouter,B as useSearch,U as useSubscribe};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waymark",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "author": "strblr",
6
6
  "description": "Lightweight type-safe router for React",
@@ -39,13 +39,13 @@
39
39
  "minimal"
40
40
  ],
41
41
  "scripts": {
42
- "build": "tsc --noEmit && tsdown --minify --platform browser",
42
+ "build": "tsc --noEmit && tsdown",
43
43
  "prepublishOnly": "bun run build && cp ../../README.md README.md",
44
44
  "postpublish": "rm -f README.md"
45
45
  },
46
46
  "devDependencies": {
47
- "@types/bun": "^1.3.6",
48
- "@types/react": "^19.2.9",
47
+ "@types/bun": "^1.3.7",
48
+ "@types/react": "^19.2.10",
49
49
  "tsdown": "^0.20.1",
50
50
  "typescript": "^5.9.3"
51
51
  },
@@ -55,6 +55,6 @@
55
55
  "dependencies": {
56
56
  "@standard-schema/spec": "^1.1.0",
57
57
  "regexparam": "^3.0.0",
58
- "type-fest": "^5.4.1"
58
+ "type-fest": "5.4.1"
59
59
  }
60
60
  }