waymark 0.2.0 → 0.2.2
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 +7 -9
- package/dist/index.d.ts +8 -8
- package/dist/index.js +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -457,7 +457,7 @@ userProfile.preload();
|
|
|
457
457
|
|
|
458
458
|
### Programmatic navigation
|
|
459
459
|
|
|
460
|
-
For navigation triggered by code rather than user clicks, use the `useNavigate` hook:
|
|
460
|
+
For navigation triggered by code rather than user clicks, use the `useNavigate` hook (or `router.navigate`):
|
|
461
461
|
|
|
462
462
|
```tsx
|
|
463
463
|
import { useNavigate } from "waymark";
|
|
@@ -495,14 +495,12 @@ You can also access the router directly via `useRouter()` (or import the router
|
|
|
495
495
|
For unsafe navigation that bypasses type checking, you can pass `url` instead of `to`, `params` and `search`. This is useful when you don't know the target URL statically (e.g. external redirects):
|
|
496
496
|
|
|
497
497
|
```tsx
|
|
498
|
-
const router = useRouter();
|
|
499
|
-
|
|
500
498
|
// Type-safe navigation
|
|
501
|
-
|
|
499
|
+
navigate({ to: userProfile, params: { id: "42" } });
|
|
502
500
|
|
|
503
501
|
// Unsafe navigation - no type checking
|
|
504
|
-
|
|
505
|
-
|
|
502
|
+
navigate({ url: "/some/unknown/path" });
|
|
503
|
+
navigate({ url: "/callback", replace: true, state: { data: 123 } });
|
|
506
504
|
```
|
|
507
505
|
|
|
508
506
|
### Declarative navigation
|
|
@@ -1177,7 +1175,7 @@ The `History` interface defines how Waymark interacts with navigation. All histo
|
|
|
1177
1175
|
```tsx
|
|
1178
1176
|
interface HistoryLike {
|
|
1179
1177
|
getPath: () => string;
|
|
1180
|
-
getSearch: () => string
|
|
1178
|
+
getSearch: () => Record<string, unknown>;
|
|
1181
1179
|
getState: () => any;
|
|
1182
1180
|
go: (delta: number) => void;
|
|
1183
1181
|
push: (options: HistoryPushOptions) => void;
|
|
@@ -1194,11 +1192,11 @@ const path = history.getPath();
|
|
|
1194
1192
|
// Returns "/users/42"
|
|
1195
1193
|
```
|
|
1196
1194
|
|
|
1197
|
-
`history.getSearch()` returns the current search
|
|
1195
|
+
`history.getSearch()` returns the current search parameters as a parsed object:
|
|
1198
1196
|
|
|
1199
1197
|
```tsx
|
|
1200
1198
|
const search = history.getSearch();
|
|
1201
|
-
// Returns "
|
|
1199
|
+
// Returns { tab: "posts", page: 2 }
|
|
1202
1200
|
```
|
|
1203
1201
|
|
|
1204
1202
|
`history.getState()` returns the current history state:
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ interface HistoryPushOptions {
|
|
|
46
46
|
}
|
|
47
47
|
interface HistoryLike {
|
|
48
48
|
getPath: () => string;
|
|
49
|
-
getSearch: () => string
|
|
49
|
+
getSearch: () => Record<string, unknown>;
|
|
50
50
|
getState: () => any;
|
|
51
51
|
go: (delta: number) => void;
|
|
52
52
|
push: (options: HistoryPushOptions) => void;
|
|
@@ -142,9 +142,7 @@ declare class Router {
|
|
|
142
142
|
readonly routes: RouteList;
|
|
143
143
|
readonly history: HistoryLike;
|
|
144
144
|
readonly defaultLinkOptions?: LinkOptions;
|
|
145
|
-
readonly _
|
|
146
|
-
routeMap: Map<string, Route>;
|
|
147
|
-
};
|
|
145
|
+
private readonly _;
|
|
148
146
|
constructor(options: RouterOptions);
|
|
149
147
|
getRoute<P extends Pattern>(pattern: P | GetRoute<P>): GetRoute<P>;
|
|
150
148
|
match<P extends Pattern>(path: string, options: MatchOptions<P>): Match<P> | null;
|
|
@@ -156,9 +154,11 @@ declare class Router {
|
|
|
156
154
|
//#region src/router/browser-history.d.ts
|
|
157
155
|
declare class BrowserHistory implements HistoryLike {
|
|
158
156
|
private static patchKey;
|
|
157
|
+
private memo?;
|
|
159
158
|
constructor();
|
|
159
|
+
protected getSearchMemo: (search: string) => Record<string, unknown>;
|
|
160
160
|
getPath: () => string;
|
|
161
|
-
getSearch: () => string
|
|
161
|
+
getSearch: () => Record<string, unknown>;
|
|
162
162
|
getState: () => any;
|
|
163
163
|
go: (delta: number) => void;
|
|
164
164
|
push: (options: HistoryPushOptions) => void;
|
|
@@ -168,7 +168,7 @@ declare class BrowserHistory implements HistoryLike {
|
|
|
168
168
|
//#region src/router/memory-history.d.ts
|
|
169
169
|
interface MemoryLocation {
|
|
170
170
|
path: string;
|
|
171
|
-
search: string
|
|
171
|
+
search: Record<string, unknown>;
|
|
172
172
|
state: any;
|
|
173
173
|
}
|
|
174
174
|
declare class MemoryHistory implements HistoryLike {
|
|
@@ -178,7 +178,7 @@ declare class MemoryHistory implements HistoryLike {
|
|
|
178
178
|
constructor(url?: string);
|
|
179
179
|
private getCurrent;
|
|
180
180
|
getPath: () => string;
|
|
181
|
-
getSearch: () => string
|
|
181
|
+
getSearch: () => Record<string, unknown>;
|
|
182
182
|
getState: () => any;
|
|
183
183
|
go: (delta: number) => void;
|
|
184
184
|
push: (options: HistoryPushOptions) => void;
|
|
@@ -189,7 +189,7 @@ declare class MemoryHistory implements HistoryLike {
|
|
|
189
189
|
declare class HashHistory extends BrowserHistory {
|
|
190
190
|
private getHashUrl;
|
|
191
191
|
getPath: () => string;
|
|
192
|
-
getSearch: () => string
|
|
192
|
+
getSearch: () => Record<string, unknown>;
|
|
193
193
|
push: (options: HistoryPushOptions) => void;
|
|
194
194
|
}
|
|
195
195
|
//#endregion
|
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
|
|
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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "waymark",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "strblr",
|
|
6
6
|
"description": "Lightweight type-safe router for React",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/strblr/waymark"
|
|
18
|
+
"url": "git+https://github.com/strblr/waymark.git"
|
|
19
19
|
},
|
|
20
|
-
"homepage": "https://github.
|
|
20
|
+
"homepage": "https://strblr.github.io/waymark",
|
|
21
21
|
"bugs": "https://github.com/strblr/waymark/issues",
|
|
22
22
|
"keywords": [
|
|
23
23
|
"react",
|
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
"minimal"
|
|
40
40
|
],
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "tsdown --minify --platform browser",
|
|
42
|
+
"build": "tsc --noEmit && tsdown --minify --platform browser",
|
|
43
43
|
"copy-readme": "cp ../../README.md README.md",
|
|
44
|
-
"prepublishOnly": "bun run build && bun run copy-readme"
|
|
44
|
+
"prepublishOnly": "bun run build && bun run copy-readme",
|
|
45
|
+
"postpublish": "rm -f README.md"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@types/bun": "^1.3.6",
|