waymark 0.2.2 → 0.3.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/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,19 @@ 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
+ }
62
+ type SSRContext = {
63
+ redirect?: string;
64
+ statusCode?: number;
65
+ };
42
66
  interface HistoryPushOptions {
43
67
  url: string;
44
68
  replace?: boolean;
@@ -61,99 +85,54 @@ type ComponentLoader = () => Promise<ComponentType | {
61
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, {}>;
62
86
  declare class Route<P extends string = string, Ps extends {} = any, S extends {} = any> {
63
87
  readonly pattern: P;
88
+ readonly _types: {
89
+ params: Ps;
90
+ search: S;
91
+ };
64
92
  readonly _: {
65
- _params?: Ps;
66
- _search?: S;
67
93
  keys: string[];
68
94
  regex: RegExp;
69
95
  looseRegex: RegExp;
70
96
  weights: number[];
71
- mapSearch: (search: Record<string, unknown>) => S;
97
+ validate: (search: Record<string, unknown>) => S;
72
98
  handles: Handle[];
73
99
  components: ComponentType[];
74
- preloaded: boolean;
75
- preloaders: (() => Promise<any>)[];
100
+ preloads: ((context: PreloadContext) => Promise<any>)[];
76
101
  };
77
- constructor(pattern: P, mapSearch: (search: Record<string, unknown>) => S, handles: Handle[], components: ComponentType[], preloaders: (() => Promise<any>)[]);
78
- 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>;
79
- search<S2 extends {}>(mapper: ((search: S & Record<string, unknown>) => S2) | StandardSchemaV1<S & 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>;
80
- handle(handle: Handle): Route<P, Ps, S>;
81
- preloader(preloader: () => Promise<any>): Route<P, Ps, S>;
82
- component(component: ComponentType): Route<P, Ps, S>;
83
- lazy(loader: ComponentLoader): Route<P, Ps, S>;
84
- suspense(fallback: ComponentType): Route<P, Ps, S>;
85
- 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<{
86
111
  error: unknown;
87
- }>): Route<P, Ps, S>;
88
- preload(): Promise<void>;
89
- toString(): P;
112
+ }>) => Route<P, Ps, S>;
113
+ toString: () => P;
90
114
  }
91
115
  //#endregion
92
- //#region src/react/components.d.ts
93
- type RouterRootProps = RouterOptions | {
94
- router: Router;
95
- };
96
- declare function RouterRoot(props: RouterRootProps): ReactNode;
97
- declare function Outlet(): ReactNode;
98
- type NavigateProps<P extends Pattern> = NavigateOptions<P>;
99
- declare function Navigate<P extends Pattern>(props: NavigateProps<P>): null;
100
- type LinkProps<P extends Pattern> = NavigateOptions<P> & LinkOptions & AnchorHTMLAttributes<HTMLAnchorElement> & RefAttributes<HTMLAnchorElement> & {
101
- asChild?: boolean;
102
- };
103
- interface LinkOptions {
104
- strict?: boolean;
105
- preload?: "intent" | "render" | "viewport" | false;
106
- style?: CSSProperties;
107
- className?: string;
108
- activeStyle?: CSSProperties;
109
- activeClassName?: string;
110
- }
111
- declare function Link<P extends Pattern>(props: LinkProps<P>): ReactNode;
112
- //#endregion
113
- //#region src/react/hooks.d.ts
114
- declare function useRouter(): Router;
115
- declare function useHandles(): Handle[];
116
- declare function useOutlet(): react2.ReactNode;
117
- declare function useSubscribe<T>(router: Router, getSnapshot: () => T): T;
118
- declare function useNavigate(): <P extends Pattern>(options: number | HistoryPushOptions | NavigateOptions<P>) => void;
119
- declare function useLocation(): {
120
- path: string;
121
- search: Record<string, unknown>;
122
- state: any;
123
- };
124
- declare function useMatch<P extends Pattern>(options: MatchOptions<P>): Match<P> | null;
125
- declare function useParams<P extends Pattern>(from: P | GetRoute<P>): Params<P>;
126
- declare function useSearch<P extends Pattern>(from: P | GetRoute<P>): readonly [Search<P>, (update: Updater<Search<P>>, replace?: boolean) => void];
127
- //#endregion
128
- //#region src/react/contexts.d.ts
129
- declare const RouterContext: react2.Context<Router | null>;
130
- declare const MatchContext: react2.Context<Match | null>;
131
- declare const OutletContext: react2.Context<ReactNode>;
132
- //#endregion
133
116
  //#region src/router/router.d.ts
134
- interface RouterOptions {
135
- basePath?: string;
136
- routes: RouteList;
137
- history?: HistoryLike;
138
- defaultLinkOptions?: LinkOptions;
139
- }
140
117
  declare class Router {
141
118
  readonly basePath: string;
142
119
  readonly routes: RouteList;
143
120
  readonly history: HistoryLike;
121
+ readonly ssrContext?: SSRContext;
144
122
  readonly defaultLinkOptions?: LinkOptions;
145
123
  private readonly _;
146
124
  constructor(options: RouterOptions);
147
- getRoute<P extends Pattern>(pattern: P | GetRoute<P>): GetRoute<P>;
148
- match<P extends Pattern>(path: string, options: MatchOptions<P>): Match<P> | null;
149
- matchAll(path: string): Match | null;
150
- createUrl<P extends Pattern>(options: NavigateOptions<P>): string;
151
- 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;
152
131
  }
153
132
  //#endregion
154
133
  //#region src/router/browser-history.d.ts
155
134
  declare class BrowserHistory implements HistoryLike {
156
- private static patchKey;
135
+ private static patch;
157
136
  private memo?;
158
137
  constructor();
159
138
  protected getSearchMemo: (search: string) => Record<string, unknown>;
@@ -166,11 +145,6 @@ declare class BrowserHistory implements HistoryLike {
166
145
  }
167
146
  //#endregion
168
147
  //#region src/router/memory-history.d.ts
169
- interface MemoryLocation {
170
- path: string;
171
- search: Record<string, unknown>;
172
- state: any;
173
- }
174
148
  declare class MemoryHistory implements HistoryLike {
175
149
  private stack;
176
150
  private index;
@@ -193,4 +167,37 @@ declare class HashHistory extends BrowserHistory {
193
167
  push: (options: HistoryPushOptions) => void;
194
168
  }
195
169
  //#endregion
196
- 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, 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 l,useLayoutEffect as u,useMemo as d,useRef as f,useState as p,useSyncExternalStore as m}from"react";import{inject as h,parse as g}from"regexparam";import{jsx as _}from"react/jsx-runtime";function v(e){return`/${e}`.replaceAll(/\/+/g,`/`).replace(/(.+)\/$/,`$1`)}function y(e){return e.split(`/`).slice(1).map(e=>e.includes(`*`)?0:e.includes(`:`)?1:2)}function b(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 x(e){return Object.entries(e).filter(([e,t])=>t!==void 0).map(([e,t])=>`${e}=${encodeURIComponent(C(t))}`).join(`&`)}function S(e){let t=new URLSearchParams(e);return Object.fromEntries([...t.entries()].map(([e,t])=>(t=decodeURIComponent(t),[e,w(t)?JSON.parse(t):t])))}function C(e){return typeof e==`string`&&!w(e)?e:JSON.stringify(e)}function w(e){try{return JSON.parse(e),!0}catch{return!1}}function T(e,t){return v(`${t}/${e}`)}function E(e,t){return(e===t||e.startsWith(`${t}/`))&&(e=e.slice(t.length)||`/`),e}function D(e,t){return[e,x(t)].filter(Boolean).join(`?`)}function O(e){let{pathname:t,search:n}=new URL(e,`http://w`);return{path:t,search:S(n)}}function k(e,t,n,r){let i=e.exec(E(n,r));if(!i)return null;let a={};return t.forEach((e,t)=>{let n=i[t+1];n&&(a[e]=n)}),a}function A(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 j=r(null),M=r(null),N=r(null);function P(){let e=c(j);if(!e)throw Error(`[Waymark] useRouter must be used within a router context`);return e}function F(){let e=c(M);return d(()=>e?.route._.handles??[],[e])}function I(){return c(N)}function L(e,t){return m(e.history.subscribe,t,t)}function R(){let e=P();return d(()=>e.navigate.bind(e),[e])}function z(){let e=P(),t=L(e,e.history.getPath),n=L(e,e.history.getSearch),r=L(e,e.history.getState);return d(()=>({path:t,search:n,state:r}),[t,n,r])}function B(e){let t=P(),n=L(t,t.history.getPath);return d(()=>t.match(n,e),[t,n,e])}function V(e){let t=B({from:e});if(!t)throw Error(`[Waymark] Can't read params for non-matching route: ${e}`);return t.params}function H(e){let t=P(),n=t.getRoute(e),r=L(t,t.history.getSearch);return[d(()=>n._.mapSearch(r),[n,r]),s((e,r)=>{let i=n._.mapSearch(t.history.getSearch());e=typeof e==`function`?e(i):e;let a=D(t.history.getPath(),{...i,...e});t.navigate({url:a,replace:r})},[t,n])]}var U=class e{static patchKey=Symbol.for(`waymark_history_patch_v01`);memo;constructor(){if(typeof history<`u`&&!(e.patchKey in window)){for(let e of[W,G]){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:S(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?G:W](r,``,t)};subscribe=e=>(K.forEach(t=>window.addEventListener(t,e)),()=>{K.forEach(t=>window.removeEventListener(t,e))})};const W=`pushState`,G=`replaceState`,K=[`popstate`,W,G,`hashchange`];var q=class{basePath;routes;history;defaultLinkOptions;_;constructor(e){let{basePath:t=`/`,routes:n,history:r,defaultLinkOptions:i}=e;this.basePath=v(t),this.routes=n,this.history=r??new U,this.defaultLinkOptions=i,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=k(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 A(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 D(T(h(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})}}},J=class{stack=[];index=0;listeners=new Set;constructor(e=`/`){this.stack.push({...O(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={...O(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)})},Y=class extends U{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 X(e){let[t]=p(()=>`router`in e?e.router:new q(e)),n=L(t,t.history.getPath),r=d(()=>t.matchAll(n),[t,n]);return r||console.error(`[Waymark] No matching route found for path:`,n),d(()=>_(j.Provider,{value:t,children:_(M.Provider,{value:r,children:r?.route._.components.reduceRight((e,t)=>_(N.Provider,{value:e,children:_(t,{})}),null)})}),[t,r])}function Z(){return I()}function ee(e){let t=R();return u(()=>t(e),[]),null}function te(e){let t=P(),{to:r,replace:a,state:o,params:s,search:c,strict:u,preload:p,style:m,className:h,activeStyle:g,activeClassName:v,asChild:y,children:b,...x}={...t.defaultLinkOptions,...e},S=f(null),C=t.createUrl(e),w=d(()=>t.getRoute(e.to),[t,e.to]),T=!!B({from:w,strict:u,params:s}),E=d(()=>({"data-active":T,style:{...m,...T&&g},className:[h,T&&v].filter(Boolean).join(` `)||void 0}),[T,m,h,g,v]);l(()=>{if(p===`render`)w.preload();else if(p===`viewport`&&S.current){let e=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&(w.preload(),e.disconnect())})});return e.observe(S.current),()=>e.disconnect()}},[p,w]);let D=e=>{x.onClick?.(e),!(e.ctrlKey||e.metaKey||e.shiftKey||e.altKey||e.button!==0||e.defaultPrevented)&&(e.preventDefault(),t.navigate({url:C,replace:a,state:o}))},O=e=>{x.onFocus?.(e),p===`intent`&&!e.defaultPrevented&&w.preload()},k=e=>{x.onPointerEnter?.(e),p===`intent`&&!e.defaultPrevented&&w.preload()},A={...x,...E,ref:ne(x.ref,S),href:C,onClick:D,onFocus:O,onPointerEnter:k};return y&&i(b)?n(b,A):_(`a`,{...A,children:b})}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()=>_(t,{fallback:_(e,{}),children:I()})}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?_(t,{error:this.state.error[0]}):this.props.children}}return()=>_(n,{children:I()})}function ae(e){return new $(v(e),e=>e,[],[],[])}var $=class e{pattern;_;constructor(e,t,n,r,i){let{keys:a,pattern:o}=g(e),s=g(e,!0).pattern,c=y(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(v(`${this.pattern}/${t}`),n,r,i,a)}search(t){let{mapSearch:n,handles:r,components:i,preloaders:a}=this._;return t=b(t),new e(this.pattern,e=>{let r=n(e);return{...r,...t(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{U as BrowserHistory,Y as HashHistory,te as Link,M as MatchContext,J as MemoryHistory,ee as Navigate,Z as Outlet,N as OutletContext,$ as Route,q as Router,j as RouterContext,X as RouterRoot,ae as route,F as useHandles,z as useLocation,B as useMatch,R as useNavigate,I as useOutlet,V as useParams,P as useRouter,H as useSearch,L 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.2",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "author": "strblr",
6
6
  "description": "Lightweight type-safe router for React",
@@ -17,7 +17,7 @@
17
17
  "type": "git",
18
18
  "url": "git+https://github.com/strblr/waymark.git"
19
19
  },
20
- "homepage": "https://strblr.github.io/waymark",
20
+ "homepage": "https://waymark.strblr.workers.dev",
21
21
  "bugs": "https://github.com/strblr/waymark/issues",
22
22
  "keywords": [
23
23
  "react",
@@ -39,14 +39,13 @@
39
39
  "minimal"
40
40
  ],
41
41
  "scripts": {
42
- "build": "tsc --noEmit && tsdown --minify --platform browser",
43
- "copy-readme": "cp ../../README.md README.md",
44
- "prepublishOnly": "bun run build && bun run copy-readme",
42
+ "build": "tsc --noEmit && tsdown",
43
+ "prepublishOnly": "bun run build && cp ../../README.md README.md",
45
44
  "postpublish": "rm -f README.md"
46
45
  },
47
46
  "devDependencies": {
48
- "@types/bun": "^1.3.6",
49
- "@types/react": "^19.2.9",
47
+ "@types/bun": "^1.3.7",
48
+ "@types/react": "^19.2.10",
50
49
  "tsdown": "^0.20.1",
51
50
  "typescript": "^5.9.3"
52
51
  },
@@ -56,6 +55,6 @@
56
55
  "dependencies": {
57
56
  "@standard-schema/spec": "^1.1.0",
58
57
  "regexparam": "^3.0.0",
59
- "type-fest": "^5.4.1"
58
+ "type-fest": "5.4.1"
60
59
  }
61
60
  }
package/tsdown.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from "tsdown";
2
-
3
- export default defineConfig({
4
- entry: ["./src/index.ts"],
5
- report: {
6
- brotli: true
7
- }
8
- });