routexiz 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 delpikye-v
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,566 @@
1
+ # đŸ•šī¸ routexiz
2
+
3
+ [![NPM](https://img.shields.io/npm/v/routexiz.svg)](https://www.npmjs.com/package/routexiz) ![Downloads](https://img.shields.io/npm/dt/routexiz.svg)
4
+
5
+ <a href="https://codesandbox.io/p/sandbox/2lslks" target="_blank">LIVE EXAMPLE</a>
6
+
7
+ A lightweight, powerful, and modern router for React.
8
+
9
+ Built with:
10
+ - ⚡ Suspense-first data loading
11
+ - 🧠 Guards & middlewares
12
+ - 🔗 Nested routing
13
+ - 🚀 Prefetching & caching
14
+ - 🧩 Fully flexible architecture
15
+
16
+ ---
17
+
18
+ # Why routexiz?
19
+
20
+ Traditional routing:
21
+ ```ts
22
+ navigate("/users/1")
23
+ ```
24
+
25
+ routexiz:
26
+ ```ts
27
+ navigate("/users/:id", {
28
+ params: { id: 1 }
29
+ })
30
+ ```
31
+
32
+ - ⚡ Suspense data loading (like React 18 philosophy)
33
+ - 🧠 Guards (before navigation)
34
+ - 🔧 Middleware (side-effects)
35
+ - 🔁 Nested routing builder API
36
+ - 🚀 Prefetch (hover + viewport)
37
+ - 🧩 Loader caching with TTL
38
+ - ❌ Error boundary per route
39
+ - 🎨 Transition support
40
+
41
+ ---
42
+
43
+ # Installation
44
+
45
+ ```bash
46
+ npm install routexiz
47
+ ```
48
+
49
+ ---
50
+
51
+ # Quick Start
52
+
53
+ ```tsx
54
+ import { route, RouterProvider } from "routexiz"
55
+
56
+ route("/", () => <div>Home</div>)
57
+
58
+ export default function App() {
59
+ return <RouterProvider />
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ # Routing API
66
+
67
+ <b>route()</b>
68
+
69
+ ```ts
70
+ route(path, component, config?)
71
+ ```
72
+
73
+ <b>builder API</b>
74
+
75
+ ```ts
76
+ route("/", Layout, root => {
77
+ root.route("/dashboard", Dashboard, dash => {
78
+ dash.guard(fn)
79
+ dash.middleware(fn)
80
+ dash.meta({ title: "Dashboard" })
81
+ })
82
+ })
83
+ ```
84
+
85
+ ---
86
+
87
+ # Navigation
88
+
89
+ ## useNavigate
90
+
91
+ ```ts
92
+ const navigate = useNavigate()
93
+
94
+ navigate("/users/:id", {
95
+ params: { id: 1 },
96
+ query: { tab: "profile" }
97
+ })
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Link
103
+
104
+ ```tsx
105
+ <Link
106
+ to="/users/:id"
107
+ params={{ id: 1 }}
108
+ query={{ tab: "profile" }}
109
+ >
110
+ Open User
111
+ </Link>
112
+ ```
113
+
114
+ ## NavLink
115
+ ```ts
116
+ <NavLink
117
+ to="/users/:id"
118
+ params={{ id: 1 }}
119
+ className={({ isActive }) => isActive ? "active" : ""}
120
+ >
121
+ Open User
122
+ </NavLink>
123
+ ```
124
+
125
+ ---
126
+
127
+ # Data Loading
128
+
129
+ ```ts
130
+ route("/users/:id", User, {
131
+ loader: async ({ params }) => fetchUser(params.id)
132
+ })
133
+
134
+ // Access loader data:
135
+ const data = useRouteData()
136
+ ```
137
+
138
+ ### Access data
139
+
140
+ ```ts
141
+ const data = useRouteData()
142
+ ```
143
+
144
+ ---
145
+
146
+ # Guards
147
+
148
+ ```ts
149
+ dash.guard(({ params }) => {
150
+ if (!isLoggedIn()) return false
151
+ })
152
+ ```
153
+
154
+ ---
155
+
156
+ # Middleware
157
+
158
+ ```ts
159
+ dash.middleware(async ({ params }) => {
160
+ console.log("run side effects")
161
+ })
162
+ ```
163
+
164
+ ---
165
+
166
+ # Prefetch
167
+
168
+ - Hover
169
+ - Viewport (IntersectionObserver)
170
+
171
+ ```tsx
172
+ import { Link } from "routexiz"
173
+
174
+ export default function UsersList() {
175
+ return (
176
+ <div>
177
+ <h2>Users</h2>
178
+ {/* Prefetch on hover */}
179
+ <Link to="/users/1" params={{ id: 1 }} query={{ tab: "profile" }}>
180
+ User 1 (hover to prefetch)
181
+ </Link>
182
+
183
+ {/* Prefetch when enters viewport */}
184
+ <Link to="/users/2" params={{ id: 2 }} query={{ tab: "profile" }}>
185
+ User 2 (prefetch on viewport)
186
+ </Link>
187
+ </div>
188
+ )
189
+ }
190
+ ```
191
+
192
+ ---
193
+
194
+ # Error Handling
195
+
196
+ ```ts
197
+ route("/users/:id", User, {
198
+ errorBoundary: ({ error }) => <div>Error: {String(error)}</div>
199
+ })
200
+ ```
201
+
202
+ ---
203
+
204
+ # Fallback
205
+
206
+ ```ts
207
+ route("/users/:id", User, {
208
+ fallback: <div>Loading...</div>
209
+ })
210
+ ```
211
+
212
+ ---
213
+
214
+ # Lazy
215
+
216
+ ```ts
217
+ import React, { Suspense } from "react"
218
+ import { route, RouterProvider } from "routexiz"
219
+
220
+ // Lazy load page
221
+ const Dashboard = React.lazy(() => import("./pages/Dashboard"))
222
+
223
+ route("/", Dashboard, {
224
+ fallback: <div>Loading Dashboard...</div>,
225
+ })
226
+ ```
227
+
228
+ ---
229
+
230
+ # Cache (TTL)
231
+
232
+ ## Declare route with cache
233
+ ```ts
234
+ route("/users/:id", User, {
235
+ loader: async ({ params }) => {
236
+ const res = await fetch(`https://jsonplaceholder.typicode.com/users/${params.id}`)
237
+ if (!res.ok) throw new Error("User not found")
238
+ return res.json()
239
+ },
240
+ ttl: 5000, // cache expires in 5 seconds
241
+ fallback: <div>Loading user...</div>,
242
+ errorBoundary: ({ error }) => <div>Error: {String(error)}</div>
243
+ })
244
+ ```
245
+
246
+ ## Access cached data inside component
247
+ ```ts
248
+ import { useRouteData, useParams } from "routexiz"
249
+
250
+ function User() {
251
+ const data = useRouteData<any>()
252
+ const params = useParams()
253
+
254
+ return (
255
+ <div>
256
+ User {params.id}
257
+ <pre>{JSON.stringify(data, null, 2)}</pre>
258
+ </div>
259
+ )
260
+ }
261
+
262
+ ```
263
+ ## Prefetch / warm up cache
264
+ ```ts
265
+ import { prefetch } from "routexiz"
266
+
267
+ // Prefetch user 1 in background
268
+ prefetch("/users/1")
269
+ ```
270
+
271
+ ## Cleanup expired cache
272
+ ```ts
273
+ import { cleanupCache } from "routexiz"
274
+
275
+ // Remove all expired resources
276
+ cleanupCache()
277
+
278
+ // Remove only 10 oldest expired items
279
+ cleanupCache(10)
280
+ ```
281
+
282
+ > TTL cache ensures route loaders are cached for fast navigation and automatically cleaned after expiry.
283
+
284
+ ---
285
+
286
+ # Global Fallback & Error
287
+
288
+ `routexiz` allows you to configure global fallback UI and global error boundaries for your entire app.
289
+
290
+ ## Set Global Fallback
291
+ ```ts
292
+ import { setGlobalFallback } from "routexiz"
293
+
294
+ // Enable global fallback with a custom loader
295
+ setGlobalFallback(true, <div>App is loading...</div>)
296
+ ```
297
+
298
+ - If a route does not provide its own fallback, this global fallback will be used.
299
+
300
+ - Works with Suspense loaders.
301
+
302
+ ## Set Global Error
303
+ ```ts
304
+ import { setGlobalError } from "routexiz"
305
+
306
+ // Enable global error handling with a custom render
307
+ setGlobalError(true, (error) => (
308
+ <div style={{ color: "red" }}>Oops! {String(error)}</div>
309
+ ))
310
+ ```
311
+
312
+ - If a route does not provide its own errorBoundary, this global error component will be used.
313
+
314
+ - Receives an error object and must return a valid React element.
315
+
316
+ ## Example with RouterProvider
317
+ ```ts
318
+ import React, { Suspense } from "react"
319
+ import { RouterProvider, setGlobalFallback, setGlobalError } from "routexiz"
320
+
321
+ setGlobalFallback(true, <div>Loading app...</div>)
322
+ setGlobalError(true, (error) => <div>Error occurred: {String(error)}</div>)
323
+
324
+ export default function App() {
325
+ return (
326
+ <Suspense fallback={null}>
327
+ <RouterProvider />
328
+ </Suspense>
329
+ )
330
+ }
331
+ ```
332
+
333
+ - The Suspense wrapper can still override the global fallback if needed.
334
+
335
+ - Per-route fallback and errorBoundary take priority over global settings.
336
+
337
+ ---
338
+
339
+ # Hooks
340
+
341
+ ## useRouteData()
342
+
343
+ Returns the data loaded by the route's loader. Suspense-aware.
344
+
345
+ ```ts
346
+ import { useRouteData } from "routexiz"
347
+
348
+ function User() {
349
+ const data = useRouteData<{ id: string; name: string }>()
350
+ return (
351
+ <div>
352
+ User {data.id}: {data.name}
353
+ </div>
354
+ )
355
+ }
356
+
357
+ ```
358
+
359
+ ## useParams()
360
+
361
+ Returns the dynamic route parameters.
362
+ ```ts
363
+ import { useParams } from "routexiz"
364
+
365
+ function User() {
366
+ const params = useParams()
367
+ return <div>User ID: {params.id}</div>
368
+ }
369
+ ```
370
+
371
+ ## useRouteContext()
372
+
373
+ Returns the current route path.
374
+
375
+ ```ts
376
+ import { useRouteContext } from "routexiz"
377
+
378
+ function CurrentRoute() {
379
+ const path = useRouteContext()
380
+ return <div>Current route: {path}</div>
381
+ }
382
+ ```
383
+
384
+ ## useNavigation()
385
+
386
+ Returns router state: loading, current path, and pending path.
387
+
388
+ ```ts
389
+ import { useNavigation } from "routexiz"
390
+
391
+ function NavStatus() {
392
+ const { loading, path, pendingPath } = useNavigation()
393
+ return (
394
+ <div>
395
+ {loading ? `Navigating to ${pendingPath}...` : `Current path: ${path}`}
396
+ </div>
397
+ )
398
+ }
399
+ ```
400
+
401
+ ## useTransition()
402
+
403
+ Returns transition info (name and stage). Useful for page animations.
404
+
405
+ ```ts
406
+ import { useTransition } from "routexiz"
407
+
408
+ function PageTransition() {
409
+ const { name, stage } = useTransition()
410
+ return <div>Transition {name}: {stage}</div>
411
+ }
412
+ ```
413
+
414
+ ---
415
+
416
+ # Full Example
417
+
418
+ ```ts
419
+ import React from "react"
420
+ import { Link, NavLink, RouterProvider, route, useRouteData, useParams } from "routexiz"
421
+
422
+ /* =========================
423
+ PAGES
424
+ ========================= */
425
+ function Home() {
426
+ return <div>Home Page</div>
427
+ }
428
+
429
+ function About() {
430
+ return <div>About Page</div>
431
+ }
432
+
433
+ function User() {
434
+ const params = useParams()
435
+ const data = useRouteData<any>()
436
+
437
+ return (
438
+ <div>
439
+ User Page {params.id}
440
+ <pre>{JSON.stringify(data, null, 2)}</pre>
441
+ </div>
442
+ )
443
+ }
444
+
445
+ /* =========================
446
+ ROUTES
447
+ ========================= */
448
+ route("/", Home, root => {
449
+ root.route("/about", About)
450
+ root.route("/user/:id", User, {
451
+ loader: async ({ params }) => {
452
+ await new Promise(r => setTimeout(r, 400))
453
+ return { id: params.id, name: "User " + params.id }
454
+ },
455
+ fallback: <div>Loading user...</div>,
456
+ errorBoundary: ({ error }) => <div>Error: {String(error)}</div>
457
+ })
458
+ })
459
+
460
+ /* =========================
461
+ NAVIGATION
462
+ ========================= */
463
+ function Navbar() {
464
+ return (
465
+ <nav style={{ marginBottom: 20 }}>
466
+ {/* Link */}
467
+ <Link
468
+ to="/"
469
+ className="link"
470
+ activeClassName="active-link"
471
+ style={{ marginRight: 10 }}
472
+ >
473
+ Home
474
+ </Link>
475
+
476
+ {/* NavLink */}
477
+ <NavLink
478
+ to="/about"
479
+ className={({ isActive }) => isActive ? "active-link" : "link"}
480
+ style={({ isActive }) => ({
481
+ marginRight: 10,
482
+ color: isActive ? "green" : "blue"
483
+ })}
484
+ >
485
+ About
486
+ </NavLink>
487
+
488
+ <NavLink
489
+ to="/user/1"
490
+ className={({ isActive }) => isActive ? "active-link" : "link"}
491
+ style={({ isActive }) => ({
492
+ marginRight: 10,
493
+ color: isActive ? "green" : "blue"
494
+ })}
495
+ >
496
+ User 1
497
+ </NavLink>
498
+
499
+ <NavLink
500
+ to="/user/2"
501
+ className={({ isActive }) => isActive ? "active-link" : "link"}
502
+ style={({ isActive }) => ({
503
+ color: isActive ? "green" : "blue"
504
+ })}
505
+ >
506
+ User 2
507
+ </NavLink>
508
+ </nav>
509
+ )
510
+ }
511
+
512
+ /* =========================
513
+ APP
514
+ ========================= */
515
+ export default function App() {
516
+ return (
517
+ <div>
518
+ <h1>Link + NavLink Demo</h1>
519
+ <Navbar />
520
+ <RouterProvider />
521
+ </div>
522
+ )
523
+ }
524
+ ```
525
+
526
+ ---
527
+
528
+ # Comparison
529
+
530
+ `routexiz` focuses on **modern React routing** with Suspense-first `data loading`, `nested routes`, `guards`, `middleware`, and `prefetch/caching`.
531
+ It’s lightweight and flexible, designed for client-side SPAs.
532
+
533
+
534
+ | Criteria | routexiz | React Router | TanStack Router | Remix |
535
+ | ------------------------- | -------- | ------------ | --------------- | ----- |
536
+ | Nested routes builder API | ✅ | ✅ | ✅ | ✅ |
537
+ | Suspense-first loaders | ✅ | âš ī¸ | ✅ | ✅ |
538
+ | Guards & middleware | ✅ | âš ī¸ | ✅ | âš ī¸ |
539
+ | Prefetch / caching | ✅ | ❌ | ✅ | âš ī¸ |
540
+ | Error boundary per route | ✅ | âš ī¸ | ✅ | ✅ |
541
+ | Transition support | ✅ | ❌ | âš ī¸ | âš ī¸ |
542
+ | Lightweight & minimal | ✅ | âš ī¸ | âš ī¸ | âš ī¸ |
543
+
544
+ ---
545
+
546
+ # Architecture
547
+
548
+ ```text
549
+ Link / navigate
550
+ ↓
551
+ matchRouteChain
552
+ ↓
553
+ guards → middleware
554
+ ↓
555
+ loader (cached)
556
+ ↓
557
+ Suspense
558
+ ↓
559
+ render tree
560
+ ```
561
+
562
+ ---
563
+
564
+ # License
565
+
566
+ MIT
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
3
+ to: string;
4
+ transition?: string;
5
+ replace?: boolean;
6
+ activeClassName?: string;
7
+ onNavigate?: (to: string) => void;
8
+ partialMatch?: boolean;
9
+ params?: Record<string, any>;
10
+ query?: Record<string, any>;
11
+ hash?: string;
12
+ disablePrefetch?: boolean;
13
+ }
14
+ export declare function Link(props: LinkProps): JSX.Element;
@@ -0,0 +1,23 @@
1
+ type UseLinkCoreOptions = {
2
+ to: string;
3
+ transition?: string;
4
+ replace?: boolean;
5
+ params?: Record<string, any>;
6
+ query?: Record<string, any>;
7
+ hash?: string;
8
+ partialMatch?: boolean;
9
+ disablePrefetch?: boolean;
10
+ };
11
+ export declare function useLinkCore({ to, transition, replace, params, query, hash, partialMatch, disablePrefetch, }: UseLinkCoreOptions): {
12
+ ref: import("react").RefObject<HTMLAnchorElement>;
13
+ fullPath: string;
14
+ isActive: boolean;
15
+ go: () => void;
16
+ };
17
+ export declare const LinkCore: import("react").ForwardRefExoticComponent<import("react").AnchorHTMLAttributes<HTMLAnchorElement> & {
18
+ fullPath: string;
19
+ onNavigate: () => void;
20
+ onPrefetch?: () => void;
21
+ disablePrefetch?: boolean;
22
+ } & import("react").RefAttributes<HTMLAnchorElement>>;
23
+ export {};
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ export interface NavLinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'style'> {
3
+ to: string;
4
+ transition?: string;
5
+ replace?: boolean;
6
+ params?: Record<string, any>;
7
+ query?: Record<string, any>;
8
+ hash?: string;
9
+ partialMatch?: boolean;
10
+ disablePrefetch?: boolean;
11
+ className?: string | ((opts: {
12
+ isActive: boolean;
13
+ }) => string);
14
+ style?: React.CSSProperties | ((opts: {
15
+ isActive: boolean;
16
+ }) => React.CSSProperties);
17
+ }
18
+ export declare function NavLink(props: NavLinkProps): JSX.Element;
@@ -0,0 +1,4 @@
1
+ export declare function RenderTree({ chain, data }: {
2
+ chain: any[];
3
+ data: Record<string, any>;
4
+ }): JSX.Element;
@@ -0,0 +1,3 @@
1
+ export * from "./Link";
2
+ export * from "./NavLink";
3
+ export { useLinkCore } from "./LinkCore";
@@ -0,0 +1,2 @@
1
+ export * from "./router";
2
+ export * from "./components";
@@ -0,0 +1 @@
1
+ import e,{createContext as n,useContext as r,Suspense as t,useState as o,useEffect as a,forwardRef as i,useRef as s}from"react";import{jsx as c,jsxs as h,Fragment as u}from"react/jsx-runtime";import l from"joinclass";const d=n(null),p=n(null),f=n(null),m=n({loading:!1,path:"",pendingPath:""}),y=n({name:"",stage:"idle"});function g(){const e=r(d);return e?.read()}function w(){return r(p)}function v(){return r(f)}function P(){return r(m)}function b(){return r(y)}let q=!1,k=!1,N=c("div",{children:"Loading..."}),E=({error:e})=>h("div",{children:["Global Error: ",String(e)]});function x(e,n){q=e,n&&(N=n)}function S(e,n){k=e,n&&(E=({error:e})=>n(e))}function C(){return q}function F(){return k}function I(){return N}function K(){return E}class M extends e.Component{constructor(e){super(e),this.reset=()=>{this.setState(e=>({hasError:!1,error:null,resetKey:e.resetKey+1}))},this.state={hasError:!1,error:null,resetKey:0}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}render(){if(this.state.hasError){const e=this.props.errorBoundary??(F()?K():void 0),n=this.props.fallback??h("div",{children:["Error: ",String(this.state.error)]});return e?c(e,{error:this.state.error}):n}return c(e.Fragment,{children:this.props.children},this.state.resetKey)}}function R({resource:e,errorBoundary:n,children:r}){try{return e?.read(),c(u,{children:r})}catch(e){if(e instanceof Promise)throw e;const r=n??(F()?K():void 0);if(r)return c(r,{error:e});throw e}}function B({chain:e,data:n}){return c(u,{children:function r(o=0){const a=e[o];if(!a)return null;const i=a.node.component,s=n[a.node.path];if(!i)return r(o+1);let h=a.node.options?.fallback,u=a.node.parent;for(;!h&&u;)h=u.options?.fallback,u=u.parent;return!h&&C()&&(h=I()),c(M,{fallback:h,errorBoundary:a.node.options?.errorBoundary,children:c(t,{fallback:h,children:c(p.Provider,{value:a.node.path,children:c(d.Provider,{value:s,children:c(f.Provider,{value:a.params,children:c(R,{resource:s,errorBoundary:a.node.options?.errorBoundary,children:c(i,{children:r(o+1)})})})})})})})}()})}const D=new Map;function A(e=1/0){const n=Date.now();let r=0;for(const[t,o]of D.entries())if(o.expiry<=n&&(D.delete(t),r++,r>=e))break}function U(e,n,r,t){const o=Date.now(),a=D.get(e);if(a&&!t&&(!r||a.expiry>o))return a.resource;const i=L(n());return D.set(e,{resource:i,expiry:o+(r??0)}),i}function L(e,n){let r=n?"success":"pending",t=n??null;const o=e.then(e=>{r="success",t=e},e=>{r="error",t=e});return{read(){if("pending"===r)throw o;if("error"===r)throw t;return t},update(e){t=e,r="success"},get:()=>t}}function O(e){return e.startsWith("/")||(e="/"+e),"/"!==e&&e.endsWith("/")&&(e=e.slice(0,-1)),e}function W(e){return O(e).split("/").filter(Boolean)}function j(e){const[n,r]=e.split("#"),[t,o]=n.split("?"),a={};return o&&o.split("&").forEach(e=>{const[n,r]=e.split("=");n&&(a[decodeURIComponent(n)]=decodeURIComponent(r??""))}),{path:O(t),query:a,hash:r??""}}function $(e,n){const{path:r,query:t,hash:o}=j(n),a=W(r),i=e.map(e=>({node:e,segIndex:0,params:{},chain:[]}));for(;i.length;){const{node:e,segIndex:n,params:r,chain:s}=i.pop(),c=W(e.path);let h=0;const u={...r};for(;h<c.length;h++){const e=c[h],r=a[n+h];if(!r)break;if(e.startsWith(":"))u[e.slice(1)]=decodeURIComponent(r);else if(e!==r)break}if(h!==c.length)continue;const l=n+h,d=[...s,{node:e,params:u,query:t,hash:o}];if(l===a.length)return d;for(const n of e.children)i.push({node:n,segIndex:l,params:u,chain:d})}return null}function J(e,n,r,t){if(e=O(e),n)for(const r in n)e=e.replace(`:${r}`,encodeURIComponent(n[r]));if(r&&Object.keys(r).length){e+=`?${new URLSearchParams(r).toString()}`}return t&&(e+=`#${t}`),e}let G=[];function T(e,n,r){const t={path:e,component:n,children:[],options:{}};"function"==typeof r?z(t)(r):r&&(t.options=r),G.push(t)}function z(e){const n={guard:r=>(e.guardFns||(e.guardFns=[]),e.guardFns.push(r),n),middleware:r=>(e.middlewares||(e.middlewares=[]),e.middlewares.push(r),n),meta:r=>(e.meta=r,n),route(n,r,t){const o={path:n,component:r,children:[],options:{},guardFns:[],middlewares:[],parent:e};"function"==typeof t?z(o)(t):t&&(o.options=t),e.children.push(o);const a={guard:e=>(o.guardFns?.push(e),a),middleware:e=>(o.middlewares?.push(e),a),meta:e=>(o.meta=e,a),route(e,n,r){const t={path:e,component:n,children:[],options:{},guardFns:[],middlewares:[],parent:o};return"function"==typeof r?z(t)(r):r&&(t.options=r),o.children.push(t),a}};return a}};return e=>{e(n)}}function H(e,n){const r={};return e.forEach(e=>{const t=e.node.options?.loader;if(!t)return;const o=JSON.stringify({path:e.node.path,params:e.params,query:e.query,hash:e.hash}),a=e.node.options?.ttl,i=U(o,()=>t({params:e.params,path:e.node.path,query:e.query,hash:e.hash}),a,n?.forceReload);r[e.node.path]=i}),r}async function Q(e){const n=$(G,e);if(!n)return{};const r={};n.forEach(e=>{const n=e.node.options?.loader;n&&(r[e.node.path]=U(JSON.stringify({path:e.node.path,params:e.params,query:e.query,hash:e.hash}),()=>n({params:e.params,path:e.node.path,query:e.query,hash:e.hash}),e.node.options?.ttl))});for(const e in r)r[e].read();return r}function V(e){const n=$(G,e);n&&H(n,{forceReload:!1})}let X=null,Y=[],Z=[];function _(e){Y.push(e)}function ee(e){Z.push(e)}async function ne(e,n,r=!1){const t={...n,forceReload:n?.forceReload??!0},o=J(e,t.params,t.query,t.hash),a=await async function(e){return Promise.all(Y.map(n=>n(e))).then(e=>e.every(e=>!1!==e))}(o);if(!a)return console.warn("Navigation blocked by beforeNavigation hook",o);r?window.history.replaceState({},"",o):window.history.pushState({},"",o),X?.(o,t),async function(e){for(const n of Z)await n(e)}(o)}function re(e,n){return ne(e,n,!1)}function te(e,n){return re(e,n)}function oe(e){return P().path===e}function ae(e){return()=>V(e)}function ie(){const e=(e,n)=>ne(J(e,n?.params,n?.query,n?.hash),n);return e.replace=(e,n)=>ne(J(e,n?.params,n?.query,n?.hash),n,!0),e}function se(){const[e,n]=o({chain:[],data:{},loading:!1,path:"",pendingPath:"",transition:{name:"",stage:"idle"}});async function r(e,r){let t=$(G,e)??$(G,"*")??[];for(const n of t)for(const r of n.node.guardFns||[]){if(!await r({params:n.params,path:n.node.path,query:n.query,hash:n.hash}))return console.warn("Navigation blocked by guard",e)}for(const e of t)for(const n of e.node.middlewares||[])await n({params:e.params,path:e.node.path,query:e.query,hash:e.hash}).catch(console.error);n(n=>({...n,loading:!0,pendingPath:e,transition:{name:r?.transition||"fade",stage:"exiting"}}));const o=H(t,r);n(n=>({...n,chain:t,data:{...n.data,...o},loading:!1,path:e,pendingPath:"",transition:{name:r?.transition||"fade",stage:"entering"}})),window.scrollTo(0,0)}return a(()=>{X=r,r(window.location.pathname),window.addEventListener("popstate",()=>r(window.location.pathname))},[]),c(m.Provider,{value:{loading:e.loading,path:e.path,pendingPath:e.pendingPath},children:c(y.Provider,{value:e.transition,children:c(B,{chain:e.chain,data:e.data})})})}function ce({to:e,transition:n,replace:r,params:t,query:o,hash:i,partialMatch:c,disablePrefetch:h}){const u=ie(),l=P(),d=s(null),p=J(e,t,o,i),f=c?l.path?.startsWith(p):l.path===p;return a(()=>{if(h)return;const e=d.current;if(!e)return;const n=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&(V(p),n.disconnect())},{rootMargin:"200px"});return n.observe(e),()=>n.disconnect()},[p,h]),{ref:d,fullPath:p,isActive:f,go:()=>{r?u.replace(e,{transition:n,params:t,query:o,hash:i}):u(e,{transition:n,params:t,query:o,hash:i})}}}re.replace=(e,n)=>ne(e,n,!0);const he=i(({fullPath:e,onNavigate:n,onPrefetch:r,onClick:t,onMouseEnter:o,onKeyDown:a,disablePrefetch:i,...s},h)=>c("a",{ref:h,href:e,...s,onClick:e=>{t&&t(e),e.metaKey||e.ctrlKey||e.shiftKey||(e.preventDefault(),n())},onMouseEnter:e=>{o&&o(e),i||r?.()},onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),n()),a&&a(e)}}));function ue(e){const{to:n,transition:r,replace:t,activeClassName:o="active",onNavigate:a,partialMatch:i,params:s,query:h,hash:u,className:d,disablePrefetch:p,...f}=e,{ref:m,fullPath:y,isActive:g,go:w}=ce({to:n,transition:r,replace:t,params:s,query:h,hash:u,partialMatch:i,disablePrefetch:p});return c(he,{ref:m,fullPath:y,onNavigate:()=>{w(),a?.(y)},onPrefetch:()=>V(y),className:l(d,g&&o),disablePrefetch:p,...f})}function le(e){const{to:n,transition:r,replace:t,params:o,query:a,hash:i,partialMatch:s,className:h,style:u,disablePrefetch:l,...d}=e,{ref:p,fullPath:f,isActive:m,go:y}=ce({to:n,transition:r,replace:t,params:o,query:a,hash:i,partialMatch:s,disablePrefetch:l}),g="function"==typeof h?h({isActive:m}):h,w="function"==typeof u?u({isActive:m}):u;return c(he,{ref:p,fullPath:f,onNavigate:y,onPrefetch:()=>V(f),className:g,style:w,disablePrefetch:l,...d})}he.displayName="LinkCore";export{ue as Link,d as LoaderDataContext,le as NavLink,f as ParamsContext,p as RouteContext,se as RouterProvider,m as RouterStateContext,y as TransitionContext,ee as addAfterNavigationHook,_ as addBeforeNavigationHook,J as buildPath,A as cleanupCache,L as createResource,K as getGlobalError,I as getGlobalFallback,U as getResource,F as getUseGlobalError,C as getUseGlobalFallback,Q as loadRouteData,$ as matchRouteChain,re as navigate,j as parseQueryHash,V as prefetch,te as redirect,T as route,S as setGlobalError,x as setGlobalFallback,ce as useLinkCore,ie as useNavigate,P as useNavigation,v as useParams,ae as usePrefetch,w as useRouteContext,g as useRouteData,oe as useRouteMatch,b as useTransition};
package/build/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var e=require("react"),t=require("react/jsx-runtime"),r=require("joinclass");const n=e.createContext(null),o=e.createContext(null),a=e.createContext(null),s=e.createContext({loading:!1,path:"",pendingPath:""}),i=e.createContext({name:"",stage:"idle"});function c(){return e.useContext(s)}let u=!1,h=!1,p=t.jsx("div",{children:"Loading..."}),l=({error:e})=>t.jsxs("div",{children:["Global Error: ",String(e)]});function d(){return u}function f(){return h}function x(){return p}function m(){return l}class g extends e.Component{constructor(e){super(e),this.reset=()=>{this.setState(e=>({hasError:!1,error:null,resetKey:e.resetKey+1}))},this.state={hasError:!1,error:null,resetKey:0}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}render(){if(this.state.hasError){const e=this.props.errorBoundary??(f()?m():void 0),r=this.props.fallback??t.jsxs("div",{children:["Error: ",String(this.state.error)]});return e?t.jsx(e,{error:this.state.error}):r}return t.jsx(e.Fragment,{children:this.props.children},this.state.resetKey)}}function y({resource:e,errorBoundary:r,children:n}){try{return e?.read(),t.jsx(t.Fragment,{children:n})}catch(e){if(e instanceof Promise)throw e;const n=r??(f()?m():void 0);if(n)return t.jsx(n,{error:e});throw e}}function v({chain:r,data:s}){return t.jsx(t.Fragment,{children:function i(c=0){const u=r[c];if(!u)return null;const h=u.node.component,p=s[u.node.path];if(!h)return i(c+1);let l=u.node.options?.fallback,f=u.node.parent;for(;!l&&f;)l=f.options?.fallback,f=f.parent;return!l&&d()&&(l=x()),t.jsx(g,{fallback:l,errorBoundary:u.node.options?.errorBoundary,children:t.jsx(e.Suspense,{fallback:l,children:t.jsx(o.Provider,{value:u.node.path,children:t.jsx(n.Provider,{value:p,children:t.jsx(a.Provider,{value:u.params,children:t.jsx(y,{resource:p,errorBoundary:u.node.options?.errorBoundary,children:t.jsx(h,{children:i(c+1)})})})})})})})}()})}const w=new Map;function P(e,t,r,n){const o=Date.now(),a=w.get(e);if(a&&!n&&(!r||a.expiry>o))return a.resource;const s=b(t());return w.set(e,{resource:s,expiry:o+(r??0)}),s}function b(e,t){let r=t?"success":"pending",n=t??null;const o=e.then(e=>{r="success",n=e},e=>{r="error",n=e});return{read(){if("pending"===r)throw o;if("error"===r)throw n;return n},update(e){n=e,r="success"},get:()=>n}}function q(e){return e.startsWith("/")||(e="/"+e),"/"!==e&&e.endsWith("/")&&(e=e.slice(0,-1)),e}function C(e){return q(e).split("/").filter(Boolean)}function j(e){const[t,r]=e.split("#"),[n,o]=t.split("?"),a={};return o&&o.split("&").forEach(e=>{const[t,r]=e.split("=");t&&(a[decodeURIComponent(t)]=decodeURIComponent(r??""))}),{path:q(n),query:a,hash:r??""}}function k(e,t){const{path:r,query:n,hash:o}=j(t),a=C(r),s=e.map(e=>({node:e,segIndex:0,params:{},chain:[]}));for(;s.length;){const{node:e,segIndex:t,params:r,chain:i}=s.pop(),c=C(e.path);let u=0;const h={...r};for(;u<c.length;u++){const e=c[u],r=a[t+u];if(!r)break;if(e.startsWith(":"))h[e.slice(1)]=decodeURIComponent(r);else if(e!==r)break}if(u!==c.length)continue;const p=t+u,l=[...i,{node:e,params:h,query:n,hash:o}];if(p===a.length)return l;for(const t of e.children)s.push({node:t,segIndex:p,params:h,chain:l})}return null}function R(e,t,r,n){if(e=q(e),t)for(const r in t)e=e.replace(`:${r}`,encodeURIComponent(t[r]));if(r&&Object.keys(r).length){e+=`?${new URLSearchParams(r).toString()}`}return n&&(e+=`#${n}`),e}let N=[];function E(e){const t={guard:r=>(e.guardFns||(e.guardFns=[]),e.guardFns.push(r),t),middleware:r=>(e.middlewares||(e.middlewares=[]),e.middlewares.push(r),t),meta:r=>(e.meta=r,t),route(t,r,n){const o={path:t,component:r,children:[],options:{},guardFns:[],middlewares:[],parent:e};"function"==typeof n?E(o)(n):n&&(o.options=n),e.children.push(o);const a={guard:e=>(o.guardFns?.push(e),a),middleware:e=>(o.middlewares?.push(e),a),meta:e=>(o.meta=e,a),route(e,t,r){const n={path:e,component:t,children:[],options:{},guardFns:[],middlewares:[],parent:o};return"function"==typeof r?E(n)(r):r&&(n.options=r),o.children.push(n),a}};return a}};return e=>{e(t)}}function F(e,t){const r={};return e.forEach(e=>{const n=e.node.options?.loader;if(!n)return;const o=JSON.stringify({path:e.node.path,params:e.params,query:e.query,hash:e.hash}),a=e.node.options?.ttl,s=P(o,()=>n({params:e.params,path:e.node.path,query:e.query,hash:e.hash}),a,t?.forceReload);r[e.node.path]=s}),r}function S(e){const t=k(N,e);t&&F(t,{forceReload:!1})}let D=null,M=[],I=[];async function K(e,t,r=!1){const n={...t,forceReload:t?.forceReload??!0},o=R(e,n.params,n.query,n.hash),a=await async function(e){return Promise.all(M.map(t=>t(e))).then(e=>e.every(e=>!1!==e))}(o);if(!a)return console.warn("Navigation blocked by beforeNavigation hook",o);r?window.history.replaceState({},"",o):window.history.pushState({},"",o),D?.(o,n),async function(e){for(const t of I)await t(e)}(o)}function B(e,t){return K(e,t,!1)}function L(){const e=(e,t)=>K(R(e,t?.params,t?.query,t?.hash),t);return e.replace=(e,t)=>K(R(e,t?.params,t?.query,t?.hash),t,!0),e}function G({to:t,transition:r,replace:n,params:o,query:a,hash:s,partialMatch:i,disablePrefetch:u}){const h=L(),p=c(),l=e.useRef(null),d=R(t,o,a,s),f=i?p.path?.startsWith(d):p.path===d;return e.useEffect(()=>{if(u)return;const e=l.current;if(!e)return;const t=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&(S(d),t.disconnect())},{rootMargin:"200px"});return t.observe(e),()=>t.disconnect()},[d,u]),{ref:l,fullPath:d,isActive:f,go:()=>{n?h.replace(t,{transition:r,params:o,query:a,hash:s}):h(t,{transition:r,params:o,query:a,hash:s})}}}B.replace=(e,t)=>K(e,t,!0);const U=e.forwardRef(({fullPath:e,onNavigate:r,onPrefetch:n,onClick:o,onMouseEnter:a,onKeyDown:s,disablePrefetch:i,...c},u)=>t.jsx("a",{ref:u,href:e,...c,onClick:e=>{o&&o(e),e.metaKey||e.ctrlKey||e.shiftKey||(e.preventDefault(),r())},onMouseEnter:e=>{a&&a(e),i||n?.()},onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),r()),s&&s(e)}}));U.displayName="LinkCore",exports.Link=function(e){const{to:n,transition:o,replace:a,activeClassName:s="active",onNavigate:i,partialMatch:c,params:u,query:h,hash:p,className:l,disablePrefetch:d,...f}=e,{ref:x,fullPath:m,isActive:g,go:y}=G({to:n,transition:o,replace:a,params:u,query:h,hash:p,partialMatch:c,disablePrefetch:d});return t.jsx(U,{ref:x,fullPath:m,onNavigate:()=>{y(),i?.(m)},onPrefetch:()=>S(m),className:r(l,g&&s),disablePrefetch:d,...f})},exports.LoaderDataContext=n,exports.NavLink=function(e){const{to:r,transition:n,replace:o,params:a,query:s,hash:i,partialMatch:c,className:u,style:h,disablePrefetch:p,...l}=e,{ref:d,fullPath:f,isActive:x,go:m}=G({to:r,transition:n,replace:o,params:a,query:s,hash:i,partialMatch:c,disablePrefetch:p}),g="function"==typeof u?u({isActive:x}):u,y="function"==typeof h?h({isActive:x}):h;return t.jsx(U,{ref:d,fullPath:f,onNavigate:m,onPrefetch:()=>S(f),className:g,style:y,disablePrefetch:p,...l})},exports.ParamsContext=a,exports.RouteContext=o,exports.RouterProvider=function(){const[r,n]=e.useState({chain:[],data:{},loading:!1,path:"",pendingPath:"",transition:{name:"",stage:"idle"}});async function o(e,t){let r=k(N,e)??k(N,"*")??[];for(const t of r)for(const r of t.node.guardFns||[]){if(!await r({params:t.params,path:t.node.path,query:t.query,hash:t.hash}))return console.warn("Navigation blocked by guard",e)}for(const e of r)for(const t of e.node.middlewares||[])await t({params:e.params,path:e.node.path,query:e.query,hash:e.hash}).catch(console.error);n(r=>({...r,loading:!0,pendingPath:e,transition:{name:t?.transition||"fade",stage:"exiting"}}));const o=F(r,t);n(n=>({...n,chain:r,data:{...n.data,...o},loading:!1,path:e,pendingPath:"",transition:{name:t?.transition||"fade",stage:"entering"}})),window.scrollTo(0,0)}return e.useEffect(()=>{D=o,o(window.location.pathname),window.addEventListener("popstate",()=>o(window.location.pathname))},[]),t.jsx(s.Provider,{value:{loading:r.loading,path:r.path,pendingPath:r.pendingPath},children:t.jsx(i.Provider,{value:r.transition,children:t.jsx(v,{chain:r.chain,data:r.data})})})},exports.RouterStateContext=s,exports.TransitionContext=i,exports.addAfterNavigationHook=function(e){I.push(e)},exports.addBeforeNavigationHook=function(e){M.push(e)},exports.buildPath=R,exports.cleanupCache=function(e=1/0){const t=Date.now();let r=0;for(const[n,o]of w.entries())if(o.expiry<=t&&(w.delete(n),r++,r>=e))break},exports.createResource=b,exports.getGlobalError=m,exports.getGlobalFallback=x,exports.getResource=P,exports.getUseGlobalError=f,exports.getUseGlobalFallback=d,exports.loadRouteData=async function(e){const t=k(N,e);if(!t)return{};const r={};t.forEach(e=>{const t=e.node.options?.loader;t&&(r[e.node.path]=P(JSON.stringify({path:e.node.path,params:e.params,query:e.query,hash:e.hash}),()=>t({params:e.params,path:e.node.path,query:e.query,hash:e.hash}),e.node.options?.ttl))});for(const e in r)r[e].read();return r},exports.matchRouteChain=k,exports.navigate=B,exports.parseQueryHash=j,exports.prefetch=S,exports.redirect=function(e,t){return B(e,t)},exports.route=function(e,t,r){const n={path:e,component:t,children:[],options:{}};"function"==typeof r?E(n)(r):r&&(n.options=r),N.push(n)},exports.setGlobalError=function(e,t){h=e,t&&(l=({error:e})=>t(e))},exports.setGlobalFallback=function(e,t){u=e,t&&(p=t)},exports.useLinkCore=G,exports.useNavigate=L,exports.useNavigation=c,exports.useParams=function(){return e.useContext(a)},exports.usePrefetch=function(e){return()=>S(e)},exports.useRouteContext=function(){return e.useContext(o)},exports.useRouteData=function(){const t=e.useContext(n);return t?.read()},exports.useRouteMatch=function(e){return c().path===e},exports.useTransition=function(){return e.useContext(i)};
@@ -0,0 +1,25 @@
1
+ export type TransitionStage = "idle" | "entering" | "exiting";
2
+ export declare const LoaderDataContext: import("react").Context<any>;
3
+ export declare const RouteContext: import("react").Context<string | null>;
4
+ export declare const ParamsContext: import("react").Context<any>;
5
+ export declare const RouterStateContext: import("react").Context<{
6
+ loading: boolean;
7
+ path: string;
8
+ pendingPath: string;
9
+ }>;
10
+ export declare const TransitionContext: import("react").Context<{
11
+ name: string;
12
+ stage: TransitionStage;
13
+ }>;
14
+ export declare function useRouteData<T = any>(): T;
15
+ export declare function useRouteContext(): string | null;
16
+ export declare function useParams(): any;
17
+ export declare function useNavigation(): {
18
+ loading: boolean;
19
+ path: string;
20
+ pendingPath: string;
21
+ };
22
+ export declare function useTransition(): {
23
+ name: string;
24
+ stage: TransitionStage;
25
+ };
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import type { ReactNode } from "react";
3
+ import type { BuilderRouteAPI, RouteOptions } from "./types";
4
+ export declare function route<P = any, R = any>(path: string, component?: React.ComponentType<{
5
+ children?: ReactNode;
6
+ }>, options?: ((api: BuilderRouteAPI) => void) | RouteOptions<P, R>): void;
7
+ export declare function loadRouteData(path: string): Promise<Record<string, any>>;
8
+ export declare function prefetch(path: string): void;
9
+ export type NavigateOpts = {
10
+ transition?: string;
11
+ forceReload?: boolean;
12
+ params?: Record<string, string | number>;
13
+ query?: Record<string, string | number>;
14
+ hash?: string;
15
+ };
16
+ export declare function addBeforeNavigationHook(fn: (path: string) => boolean | Promise<boolean>): void;
17
+ export declare function addAfterNavigationHook(fn: (path: string) => void | Promise<void>): void;
18
+ export declare function navigate(path: string, opts?: NavigateOpts): Promise<void>;
19
+ export declare namespace navigate {
20
+ var replace: (path: string, opts?: NavigateOpts) => Promise<void>;
21
+ }
22
+ export declare function redirect(path: string, opts?: NavigateOpts): Promise<void>;
23
+ export declare function useRouteMatch(pathPattern: string): boolean;
24
+ export declare function usePrefetch(path: string): () => void;
25
+ export declare function useNavigate(): {
26
+ (path: string, opts?: NavigateOpts): Promise<void>;
27
+ replace(path: string, opts?: NavigateOpts): Promise<void>;
28
+ };
29
+ export declare function RouterProvider(): JSX.Element;
@@ -0,0 +1,6 @@
1
+ export * from "./contexts";
2
+ export * from "./core";
3
+ export * from "./resource";
4
+ export * from "./setting";
5
+ export * from "./types";
6
+ export * from "./utils";
@@ -0,0 +1,11 @@
1
+ export declare function cleanupCache(limit?: number): void;
2
+ export declare function getResource<T>(key: string, loader: () => Promise<T>, ttl?: number, forceReload?: boolean): {
3
+ read(): unknown;
4
+ update(newData: unknown): void;
5
+ get(): unknown;
6
+ };
7
+ export declare function createResource<T>(promise: Promise<T>, existing?: T): {
8
+ read(): T | null;
9
+ update(newData: T): void;
10
+ get(): T | null;
11
+ };
@@ -0,0 +1,8 @@
1
+ export declare function setGlobalFallback(enable: boolean, fallback?: React.ReactNode): void;
2
+ export declare function setGlobalError(enable: boolean, render?: (error: any) => React.ReactElement | null): void;
3
+ export declare function getUseGlobalFallback(): boolean;
4
+ export declare function getUseGlobalError(): boolean;
5
+ export declare function getGlobalFallback(): import("react").ReactNode;
6
+ export declare function getGlobalError(): import("react").ComponentType<{
7
+ error: any;
8
+ }>;
@@ -0,0 +1,39 @@
1
+ import type { ReactNode } from "react";
2
+ type LoaderContext<P = any> = {
3
+ params: P;
4
+ path: string;
5
+ query?: Record<string, string>;
6
+ hash?: string;
7
+ };
8
+ export type RouteOptions<P = any, R = any> = {
9
+ loader?: (context: LoaderContext<P>) => Promise<R>;
10
+ meta?: Record<string, any>;
11
+ errorBoundary?: React.ComponentType<{
12
+ error: any;
13
+ }>;
14
+ fallback?: React.ReactNode;
15
+ ttl?: number;
16
+ };
17
+ export type GuardFn<P = any> = (context: LoaderContext<P>) => boolean | Promise<boolean>;
18
+ export type MiddlewareFn<P = any> = (context: LoaderContext<P>) => any | Promise<any>;
19
+ export type RouteNode<P = any, R = any> = {
20
+ path: string;
21
+ component?: React.ComponentType<{
22
+ children?: ReactNode;
23
+ }>;
24
+ children: RouteNode[];
25
+ options: RouteOptions<P, R>;
26
+ guardFns?: GuardFn<P>[];
27
+ middlewares?: MiddlewareFn<P>[];
28
+ meta?: Record<string, any>;
29
+ parent?: RouteNode;
30
+ };
31
+ export type BuilderRouteAPI = {
32
+ guard: (fn: GuardFn) => BuilderRouteAPI;
33
+ middleware: (fn: MiddlewareFn) => BuilderRouteAPI;
34
+ route: <P = any, R = any>(path: string, component?: React.ComponentType<{
35
+ children?: ReactNode;
36
+ }>, options?: ((api: BuilderRouteAPI) => void) | RouteOptions<P, R>) => BuilderRouteAPI;
37
+ meta: (obj: Record<string, any>) => BuilderRouteAPI;
38
+ };
39
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { RouteNode } from "./types";
2
+ export declare function parseQueryHash(pathname: string): {
3
+ path: string;
4
+ query: Record<string, string>;
5
+ hash: string;
6
+ };
7
+ export declare function matchRouteChain(routes: RouteNode[], pathname: string): any[] | null;
8
+ export declare function buildPath(path: string, params?: Record<string, any>, query?: Record<string, any>, hash?: string): string;
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "routexiz",
3
+ "version": "0.0.1",
4
+ "description": "Intent-based routing for React. Navigate by intent instead of URLs.",
5
+ "author": "Delpi.Kye",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "type": "module",
9
+
10
+ "main": "./build/index.js",
11
+ "module": "./build/index.esm.js",
12
+ "types": "./build/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./build/index.d.ts",
16
+ "import": "./build/index.esm.js",
17
+ "require": "./build/index.js"
18
+ }
19
+ },
20
+
21
+ "files": [
22
+ "build"
23
+ ],
24
+
25
+ "scripts": {
26
+ "clean": "rimraf build",
27
+ "build": "rimraf build && rollup -c",
28
+ "watch": "rollup -c -w",
29
+ "typecheck": "tsc --noEmit",
30
+ "prepublishOnly": "npm run typecheck && npm run build"
31
+ },
32
+ "engines": {
33
+ "node": ">=16"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/delpikye-v/routexiz.git"
38
+ },
39
+ "homepage": "https://github.com/delpikye-v/routexiz#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/delpikye-v/routexiz/issues"
42
+ },
43
+ "keywords": [
44
+ "react",
45
+ "router",
46
+ "intent-router",
47
+ "navigation",
48
+ "intent-navigation",
49
+ "react-navigation",
50
+ "frontend",
51
+ "spa",
52
+ "routing"
53
+ ],
54
+ "peerDependencies": {
55
+ "react": ">=18",
56
+ "react-dom": ">=18"
57
+ },
58
+ "dependencies": {
59
+ "joinclass": "^1.1.2"
60
+ },
61
+ "devDependencies": {
62
+ "@rollup/plugin-commonjs": "^25.0.7",
63
+ "@rollup/plugin-node-resolve": "^15.2.3",
64
+ "@rollup/plugin-terser": "^0.4.4",
65
+ "@types/react": "^18.0.0",
66
+ "@types/react-dom": "^18.0.0",
67
+ "react": "^18.0.0",
68
+ "react-dom": "^18.0.0",
69
+ "rimraf": "^5.0.5",
70
+ "rollup": "^3.29.4",
71
+ "rollup-plugin-peer-deps-external": "^2.2.4",
72
+ "rollup-plugin-typescript2": "^0.36.0",
73
+ "tslib": "^2.6.2",
74
+ "typescript": "^5.0.0"
75
+ },
76
+ "publishConfig": {
77
+ "access": "public"
78
+ }
79
+ }