intentx-react-router 0.2.0 → 1.0.0-z

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 CHANGED
@@ -39,7 +39,7 @@ navigateIntent("edit-user", 42)
39
39
  # Installation
40
40
 
41
41
  ```bash
42
- npm install intentx-react-router eventbus-z react-router@^7 react-router-dom@^7
42
+ npm install intentx-react-router react-router@^7 react-router-dom@^7
43
43
  ```
44
44
 
45
45
  ## Recommended Versions
@@ -48,7 +48,6 @@ npm install intentx-react-router eventbus-z react-router@^7 react-router-dom@^7
48
48
  - react-dom: >=18
49
49
  - react-router: >=7
50
50
  - react-router-dom: >=7
51
- - eventbus-z: ^2.4.0
52
51
 
53
52
  ---
54
53
 
@@ -81,51 +80,15 @@ export function RouterBinder() {
81
80
  return null
82
81
  }
83
82
  ```
84
-
85
- ---
86
-
87
- ## 3. IntentLink
88
-
89
- ```ts
90
- import React from "react"
91
- import { Link } from "react-router-dom"
92
- import { generatePathFromIntent } from "intentx-react-router"
93
-
94
- export function IntentLink({ intent, params, query, children }) {
95
- let path = generatePathFromIntent(intent, params)
96
- if (query) {
97
- const qs = new URLSearchParams(query).toString()
98
- if (qs) path += "?" + qs
99
- }
100
- return <Link to={path}>{children}</Link>
101
- }
102
- ```
103
-
104
- ---
105
-
106
- ## 4. IntentRoute
107
-
108
- ---
109
-
110
- ```ts
111
- import React from "react"
112
- import { Route } from "react-router-dom"
113
- import { generatePathFromIntent } from "intentx-react-router"
114
-
115
- export function IntentRoute({ intent, component: Component }) {
116
- const path = generatePathFromIntent(intent)
117
- return <Route path={path} render={(props) => <Component {...props} />} />
118
- }
119
- ```
120
-
121
83
  ---
122
84
 
123
- ## 5. Use in App
85
+ ## 3. Use in App
124
86
 
125
87
  ```ts
126
88
  import React from "react"
127
- import { BrowserRouter, Switch } from "react-router-dom"
128
- import { RouterBinder, IntentLink, IntentRoute } from "./router"
89
+ import { BrowserRouter, Routes } from "react-router-dom"
90
+ import { IntentLink, IntentRoute } from "intentx-react-router"
91
+ import { RouterBinder } from "./router"
129
92
 
130
93
  import { UserPage } from "./pages/UserPage"
131
94
  import { EditUserPage } from "./pages/EditUserPage"
@@ -142,39 +105,30 @@ export default function App() {
142
105
  | <IntentLink intent="checkout">Checkout</IntentLink>
143
106
  </nav>
144
107
 
145
- <Switch>
108
+ <Routes>
146
109
  <IntentRoute intent="view-user" component={UserPage} />
147
110
  <IntentRoute intent="edit-user" component={EditUserPage} />
148
111
  <IntentRoute intent="checkout" component={CheckoutPage} />
149
- </Switch>
112
+ </Routes>
150
113
  </BrowserRouter>
151
114
  )
152
115
  }
153
116
  ```
154
117
 
155
- ---
156
-
157
- ## 6. useIntent Hook
118
+ ## 4. IntentRouter
158
119
 
159
120
  ```ts
160
- import { useLocation } from "react-router-dom"
161
- import { resolveIntentFromUrl } from "intentx-react-router"
162
-
163
- export function useIntent() {
164
- const location = useLocation()
165
- const resolved = resolveIntentFromUrl(location.pathname)
166
-
167
- const searchParams = new URLSearchParams(location.search)
168
- const query = {}
169
- searchParams.forEach((value, key) => {
170
- query[key] = value
171
- })
172
-
173
- return {
174
- intent: resolved?.intent ?? null,
175
- params: resolved?.params ?? {},
176
- query
177
- }
121
+ import { IntentRouter } from "intentx-react-router"
122
+ import { Routes } from "react-router-dom"
123
+
124
+ export default function App() {
125
+ return (
126
+ <IntentRouter>
127
+ <Routes>
128
+ <IntentRoute intent="checkout" component={CheckoutPage} />
129
+ </Routes>
130
+ </IntentRouter>
131
+ )
178
132
  }
179
133
  ```
180
134
 
@@ -227,6 +181,82 @@ Result:
227
181
 
228
182
  ---
229
183
 
184
+ ## Query params
185
+
186
+ Intent navigation also supports query parameters.
187
+
188
+ ```ts
189
+ navigateIntent("view-user", {
190
+ userId: 1,
191
+ page: 2
192
+ }, {
193
+ tab: "activity",
194
+ filter: "recent"
195
+ })
196
+ ```
197
+
198
+ ```ts
199
+ /users/1/name/2?tab=activity&filter=recent
200
+ ```
201
+
202
+ ---
203
+
204
+ Query arrays are also supported:
205
+
206
+ ```ts
207
+ navigateIntent("view-user", [1,2], {
208
+ tags: ["admin", "vip"]
209
+ })
210
+ ```
211
+
212
+ Result:
213
+
214
+ ```ts
215
+ /users/1/name/2?tags=admin&tags=vip
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Advanced Navigation
221
+
222
+ options, fallback, microFE
223
+
224
+ ```ts
225
+ import React from "react"
226
+ import { useNavigateIntent, setFallbackPath, setSharedBus } from "intentx-react-router"
227
+ import { createEventBus } from "eventbus-z"
228
+
229
+ // Micro-frontend shared bus
230
+ const externalBus = createEventBus()
231
+ setSharedBus(externalBus)
232
+
233
+ setFallbackPath("/not-found")
234
+
235
+ function Dashboard() {
236
+ const navigateIntent = useNavigateIntent()
237
+
238
+ const handleCheckout = () => {
239
+ navigateIntent("checkout", undefined, { ref: "dashboard" }, { replace: true, scrollTop: true })
240
+ }
241
+
242
+ return (
243
+ <div>
244
+ <h1>Dashboard</h1>
245
+ <button onClick={handleCheckout}>Go to Checkout</button>
246
+ <button
247
+ onClick={() => {
248
+ navigateIntent("unknown-intent")
249
+ }}
250
+ >
251
+ Unknown Intent
252
+ </button>
253
+ </div>
254
+ )
255
+ }
256
+ ```
257
+
258
+ ---
259
+
230
260
  # React Helpers
231
261
 
232
262
  ## useNavigateIntent
@@ -252,7 +282,7 @@ function Button() {
252
282
  ``` tsx
253
283
  import { IntentLink } from "intentx-react-router"
254
284
 
255
- <IntentLink intent="view-user" params={[1,2]}>
285
+ <IntentLink intent="view-user" params={[1,2]} query={{ tab: "profile" }}>
256
286
  Open User
257
287
  </IntentLink>
258
288
  ```
@@ -260,20 +290,20 @@ import { IntentLink } from "intentx-react-router"
260
290
  Equivalent to:
261
291
 
262
292
  ```ts
263
- <Link to="/users/1/name/2" />
293
+ <Link to="/users/1/name/2?tab=profile" />
264
294
  ```
265
295
 
266
296
  ---
267
297
 
268
- ## useIntent
298
+ ## useIntentRouter
269
299
 
270
300
  Reads intent + params from URL.
271
301
 
272
302
  ``` ts
273
- import { useIntent } from "intentx-react-router"
303
+ import { useIntentRouter } from "intentx-react-router"
274
304
 
275
305
  function Page() {
276
- const { intent, params } = useIntent()
306
+ const { intent, params } = useIntentRouter()
277
307
 
278
308
  console.log(intent)
279
309
  console.log(params)
@@ -285,13 +315,13 @@ function Page() {
285
315
  Example result:
286
316
 
287
317
  ```ts
288
- {
289
- intent: "view-user",
290
- params: {
291
- userId: "1",
292
- page: "2"
293
- }
318
+ {
319
+ intent: "view-user",
320
+ params: {
321
+ userId: "1",
322
+ page: "2"
294
323
  }
324
+ }
295
325
  ```
296
326
 
297
327
  ---
@@ -362,34 +392,86 @@ preloadIntent("checkout")
362
392
  Generate path manually.
363
393
 
364
394
  ``` ts
365
- generatePathFromIntent("view-user", [1,2])
395
+ generatePathFromIntent("view-user", [1,2], {
396
+ tab: "activity"
397
+ })
366
398
  ```
367
399
 
368
400
  Result:
369
401
  ```ts
370
- /users/1/name/2
402
+ /users/1/name/2?tab=activity
371
403
  ```
372
404
  ---
373
405
 
374
406
  # Resolve Intent From URL
375
407
 
376
408
  ``` ts
377
- resolveIntentFromUrl("/users/1/name/2")
409
+ resolveIntentFromUrl("/users/1/name/2?tab=activity")
378
410
  ```
379
411
 
380
412
  Result:
381
413
  ```ts
382
- {
383
- intent: "view-user",
384
- params: {
385
- userId: "1",
386
- page: "2"
387
- }
388
- }
414
+ {
415
+ intent: "view-user",
416
+ params: {
417
+ userId: "1",
418
+ page: "2"
419
+ },
420
+ query: {
421
+ tab: "activity"
422
+ }
423
+ }
389
424
  ```
390
425
 
391
426
  ---
392
427
 
428
+ # Event-Driven Navigation
429
+
430
+ Example
431
+
432
+ ```ts
433
+ import { createEventBus } from "eventbus-z"
434
+ import { navigateIntent } from "intentx-react-router"
435
+
436
+ const bus = createEventBus()
437
+
438
+ // Somewhere in your app
439
+ bus.$on("CHECKOUT_REQUESTED", (userId) => {
440
+ navigateIntent("checkout", { userId })
441
+ })
442
+ ```
443
+
444
+ Trigger navigation from anywhere:
445
+ ```ts
446
+ bus.$emit("CHECKOUT_REQUESTED", 42)
447
+ ```
448
+
449
+ Result:
450
+ ```ts
451
+ /checkout?userId=42
452
+ ```
453
+
454
+ Why this is powerful
455
+ - Navigation becomes decoupled from UI components.
456
+
457
+ Instead of:
458
+
459
+ ```ts
460
+ <button onClick={() => navigate("/checkout")} />
461
+ ```
462
+
463
+ You can emit a domain event:
464
+ ```ts
465
+ bus.$emit("CHECKOUT_REQUESTED")
466
+ ```
467
+
468
+ This allows:
469
+ - UI-agnostic navigation
470
+ - better architecture in large apps
471
+ - seamless micro-frontend communication
472
+
473
+ ---
474
+
393
475
  # Comparison
394
476
 
395
477
  | Criteria | intent-router | React Router |
@@ -400,6 +482,7 @@ Result:
400
482
  | Micro-frontend friendly | ✅ | ⚠️ |
401
483
  | Reverse routing | ✅ | ❌ |
402
484
  | Type-safe intent params | ✅ | ⚠️ |
485
+ | Query support | ✅ | ⚠️ |
403
486
 
404
487
  ---
405
488
 
@@ -1,17 +1,27 @@
1
+ import type { GlobalBus } from "eventbus-z";
1
2
  type IntentMap = Record<string, string>;
2
3
  type GuardFn = (intent: string, params: any) => boolean | string | void;
3
- type NavigateFn = (path: string) => void;
4
+ type NavigateFn = (path: string, options?: {
5
+ replace?: boolean;
6
+ scrollTop?: boolean;
7
+ }) => void;
4
8
  type PreloadFn = () => Promise<any> | void;
5
9
  export declare function createIntentRouter(map: IntentMap): void;
6
10
  export declare function bindNavigate(fn: NavigateFn): void;
7
- export declare function navigateIntent(intent: string, params?: Record<string, any> | any[] | string | number): void;
11
+ export declare function setFallbackPath(path: string): void;
12
+ export declare function navigateIntent<P = any>(intent: string, params?: P | any[] | string | number, query?: Record<string, any>, options?: {
13
+ replace?: boolean;
14
+ scrollTop?: boolean;
15
+ }): void;
8
16
  export declare function addIntentGuard(fn: GuardFn): void;
9
17
  export declare function addIntentGuardFor(intent: string, fn: GuardFn): void;
10
18
  export declare function addIntentPreload(intent: string, fn: PreloadFn): void;
11
19
  export declare function preloadIntent(intent: string): void;
12
- export declare function generatePathFromIntent(intent: string, params?: Record<string, any> | any[] | string | number): string;
20
+ export declare function generatePathFromIntent(intent: string, params?: Record<string, any> | any[] | string | number, query?: Record<string, any>): string;
13
21
  export declare function resolveIntentFromUrl(url: string): {
14
22
  intent: string;
15
23
  params: Record<string, any>;
24
+ query: Record<string, any>;
16
25
  } | null;
26
+ export declare function setSharedBus(externalBus: GlobalBus): void;
17
27
  export {};
@@ -1 +1 @@
1
- import{createEventBus as n}from"eventbus-z";import{jsx as t}from"react/jsx-runtime";import{Link as r,useLocation as o,useNavigate as e}from"react-router-dom";import{useEffect as i}from"react";const c=n(),u="INTENT_NAVIGATE";let s={},f=null,a=!1;const l=[],p={},m={};function h(n){s=n}function v(n){f=n,a||(c.$onMultiple(u,E),a=!0)}function d(n,t){c.$emit(u,{intent:n,params:t})}function g(n){l.push(n)}function y(n,t){p[n]||(p[n]=[]),p[n].push(t)}function A(n,t){m[n]||(m[n]=[]),m[n].push(t)}function w(n){z(n)}function E(n){const t=s[n.intent];if(!t)return void console.warn("Unknown intent:",n.intent);const r=$(t,n.params);for(const t of l){const o=t(n.intent,r);if(!1===o)return;if("string"==typeof o)return void(null==f||f(o))}const o=p[n.intent]||[];for(const t of o){const o=t(n.intent,r);if(!1===o)return;if("string"==typeof o)return void(null==f||f(o))}z(n.intent);const e=_(t,r);null==f||f(e)}function z(n){const t=m[n];if(t)for(const n of t)try{const t=n();t instanceof Promise&&t.catch(()=>{})}catch{}}function $(n,t){const r=function(n){const t=[];return n.replace(/:([A-Za-z0-9_]+)/g,(n,r)=>(t.push(r),"")),t}(n);if(!t)return{};if(Array.isArray(t)){const n={};return r.forEach((r,o)=>{void 0===t[o]&&console.warn(`Missing param "${r}"`),n[r]=t[o]}),n}return"object"==typeof t?t:1===r.length?{[r[0]]:t}:{}}function _(n,t){return n.replace(/:([A-Za-z0-9_]+)/g,(n,r)=>{var o;return null!==(o=t[r])&&void 0!==o?o:""})}function x(n,t){const r=s[n];if(!r)return"";return _(r,$(r,t))}function N(n){for(const t in s){const r=s[t],o=[],e=new RegExp("^"+r.replace(/:([A-Za-z0-9_]+)/g,(n,t)=>(o.push(t),"([^/]+)"))+"$"),i=n.match(e);if(!i)continue;const c={};return o.forEach((n,t)=>{c[n]=i[t+1]}),{intent:t,params:c}}return null}function P({intent:n,params:o,query:e,children:i}){let c=x(n,o);if(e){const n=new URLSearchParams(e).toString();n&&(c+="?"+n)}return t(r,{to:c,children:i})}function R(){var n,t;const r=o(),e=N(r.pathname),i=new URLSearchParams(r.search),c={};return i.forEach((n,t)=>{c[t]=n}),{intent:null!==(n=null==e?void 0:e.intent)&&void 0!==n?n:null,params:null!==(t=null==e?void 0:e.params)&&void 0!==t?t:{},query:c}}function S({intent:n,component:r}){const o=R();return o.intent!==n?null:t(r,{...o})}function T({navigate:n}){const t=e();return v(n||t),null}function U(){const n=e();return i(()=>{v(n)},[n]),d}export{P as IntentLink,S as IntentRoute,T as RouterBinder,g as addIntentGuard,y as addIntentGuardFor,A as addIntentPreload,v as bindNavigate,h as createIntentRouter,x as generatePathFromIntent,d as navigateIntent,w as preloadIntent,N as resolveIntentFromUrl,R as useIntent,U as useNavigateIntent};
1
+ import{createEventBus as n}from"eventbus-z";import{jsx as t,Fragment as r,jsxs as o}from"react/jsx-runtime";import{Link as e,useLocation as i,useNavigate as c,BrowserRouter as u}from"react-router-dom";import{useEffect as a}from"react";const s=n();let f=null;let l={},p=null,m=!1;const d=[],h={},y={};let T=null;const A="undefined"!=typeof window&&!0===window.__INTENT_ROUTER_DEBUG__;function E(...n){A&&console.log("[IntentRouter]",...n)}function N(n){if(!n)return"";const t=new URLSearchParams;for(const r in n){const o=n[r];null!=o&&(Array.isArray(o)?o.forEach(n=>t.append(r,String(n))):t.append(r,String(o)))}const r=t.toString();return r?`?${r}`:""}function g(n){l=n,E("intent map loaded",n)}function w(n){p=n,m||(s.$onMultiple("INTENT_NAVIGATE",$),m=!0)}function I(n){T=n}function _(n,t,r,o){E("emit",n,{params:t,query:r});const e={intent:n,params:t,query:r,...o?{options:o}:{}};s.$emit("INTENT_NAVIGATE",e),f&&f.$emit("INTENT_NAVIGATE",e)}function v(n){d.push(n)}function R(n,t){h[n]||(h[n]=[]),h[n].push(t)}function q(n,t){y[n]||(y[n]=[]),y[n].push(t)}function U(n){G(n)}async function $(n){const t=l[n.intent];if(E("receive",n.intent,n),!t)return T&&p?.(T),void console.warn("Unknown intent:",n.intent);const r=S(t,n.params);for(const t of d){const o=await t(n.intent,r);if(!1===o)return;if("string"==typeof o)return void p?.(o,n.options)}const o=h[n.intent]||[];for(const t of o){const o=await t(n.intent,r);if(!1===o)return;if("string"==typeof o)return void p?.(o,n.options)}G(n.intent);const e=V(t,r)+N(n.query);E("navigate",{intent:n.intent,params:r,query:n.query,url:e}),p?.(e,n.options),f&&f.$emit("INTENT_NAVIGATE",n)}function G(n){const t=y[n];if(t)for(const n of t)try{const t=n();t instanceof Promise&&t.catch(()=>{})}catch{}}function S(n,t){const r=function(n){const t=[];return n.replace(/:([A-Za-z0-9_]+)/g,(n,r)=>(t.push(r),"")),t}(n);if(!t)return{};if(Array.isArray(t)){const n={};return r.forEach((r,o)=>{void 0===t[o]&&console.warn(`Missing param "${r}"`),n[r]=t[o]}),n}return"object"==typeof t?t:1===r.length?{[r[0]]:t}:{}}function V(n,t){return n.replace(/:([A-Za-z0-9_]+)/g,(n,r)=>{const o=t[r];return void 0!==o?encodeURIComponent(o):""})}function z(n,t,r){const o=l[n];if(!o)return"";return V(o,S(o,t))+N(r)}function P(n){const[t,r]=n.split("?"),o=function(n){if(!n)return{};const t=new URLSearchParams(n),r={};return t.forEach((n,t)=>{r[t]?Array.isArray(r[t])?r[t].push(n):r[t]=[r[t],n]:r[t]=n}),r}(r);for(const n in l){const r=l[n],e=[],i=new RegExp("^"+r.replace(/:([A-Za-z0-9_]+)/g,(n,t)=>(e.push(t),"([^/]+)"))+"$"),c=t.match(i);if(!c)continue;const u={};return e.forEach((n,t)=>{u[n]=decodeURIComponent(c[t+1])}),{intent:n,params:u,query:o}}return null}function b(n){f=n,f.$onMultiple("INTENT_NAVIGATE",$)}function k({intent:n,params:r,query:o,children:i,replace:c,scrollTop:u}){const a=z(n,r,o);return t(e,{to:a,replace:c,onClick:()=>{u&&window.scrollTo(0,0)},children:i})}function x(){const n=i(),t=P(n.pathname),r=new URLSearchParams(n.search),o={};return r.forEach((n,t)=>{o[t]=n}),{intent:t?.intent??null,params:t?.params??{},query:o}}function C({intent:n,component:o,fallback:e=null,loading:i,guard:c}){const u=x();return u?u.intent!==n?null:c&&!c(u)?t(r,{children:e}):t(o,{...u}):i??null}function L({navigate:n}){const t=c();return w(n||t),null}function M({children:n}){return o(u,{children:[t(L,{}),n]})}function Z(){const n=c();return a(()=>{w(n)},[n]),_}export{k as IntentLink,C as IntentRoute,M as IntentRouter,L as RouterBinder,v as addIntentGuard,R as addIntentGuardFor,q as addIntentPreload,w as bindNavigate,g as createIntentRouter,z as generatePathFromIntent,_ as navigateIntent,U as preloadIntent,P as resolveIntentFromUrl,I as setFallbackPath,b as setSharedBus,x as useIntentRouter,Z as useNavigateIntent};
package/build/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var n=require("eventbus-z"),t=require("react/jsx-runtime"),e=require("react-router-dom"),r=require("react");const o=n.createEventBus(),i="INTENT_NAVIGATE";let u={},s=null,c=!1;const a=[],f={},l={};function p(n){s=n,c||(o.$onMultiple(i,v),c=!0)}function d(n,t){o.$emit(i,{intent:n,params:t})}function v(n){const t=u[n.intent];if(!t)return void console.warn("Unknown intent:",n.intent);const e=m(t,n.params);for(const t of a){const r=t(n.intent,e);if(!1===r)return;if("string"==typeof r)return void(null==s||s(r))}const r=f[n.intent]||[];for(const t of r){const r=t(n.intent,e);if(!1===r)return;if("string"==typeof r)return void(null==s||s(r))}h(n.intent);const o=x(t,e);null==s||s(o)}function h(n){const t=l[n];if(t)for(const n of t)try{const t=n();t instanceof Promise&&t.catch(()=>{})}catch{}}function m(n,t){const e=function(n){const t=[];return n.replace(/:([A-Za-z0-9_]+)/g,(n,e)=>(t.push(e),"")),t}(n);if(!t)return{};if(Array.isArray(t)){const n={};return e.forEach((e,r)=>{void 0===t[r]&&console.warn(`Missing param "${e}"`),n[e]=t[r]}),n}return"object"==typeof t?t:1===e.length?{[e[0]]:t}:{}}function x(n,t){return n.replace(/:([A-Za-z0-9_]+)/g,(n,e)=>{var r;return null!==(r=t[e])&&void 0!==r?r:""})}function g(n,t){const e=u[n];if(!e)return"";return x(e,m(e,t))}function I(n){for(const t in u){const e=u[t],r=[],o=new RegExp("^"+e.replace(/:([A-Za-z0-9_]+)/g,(n,t)=>(r.push(t),"([^/]+)"))+"$"),i=n.match(o);if(!i)continue;const s={};return r.forEach((n,t)=>{s[n]=i[t+1]}),{intent:t,params:s}}return null}function y(){var n,t;const r=e.useLocation(),o=I(r.pathname),i=new URLSearchParams(r.search),u={};return i.forEach((n,t)=>{u[t]=n}),{intent:null!==(n=null==o?void 0:o.intent)&&void 0!==n?n:null,params:null!==(t=null==o?void 0:o.params)&&void 0!==t?t:{},query:u}}exports.IntentLink=function({intent:n,params:r,query:o,children:i}){let u=g(n,r);if(o){const n=new URLSearchParams(o).toString();n&&(u+="?"+n)}return t.jsx(e.Link,{to:u,children:i})},exports.IntentRoute=function({intent:n,component:e}){const r=y();return r.intent!==n?null:t.jsx(e,{...r})},exports.RouterBinder=function({navigate:n}){const t=e.useNavigate();return p(n||t),null},exports.addIntentGuard=function(n){a.push(n)},exports.addIntentGuardFor=function(n,t){f[n]||(f[n]=[]),f[n].push(t)},exports.addIntentPreload=function(n,t){l[n]||(l[n]=[]),l[n].push(t)},exports.bindNavigate=p,exports.createIntentRouter=function(n){u=n},exports.generatePathFromIntent=g,exports.navigateIntent=d,exports.preloadIntent=function(n){h(n)},exports.resolveIntentFromUrl=I,exports.useIntent=y,exports.useNavigateIntent=function(){const n=e.useNavigate();return r.useEffect(()=>{p(n)},[n]),d};
1
+ "use strict";var n=require("eventbus-z"),t=require("react/jsx-runtime"),e=require("react-router-dom"),r=require("react");const o=n.createEventBus();let i=null;let s={},c=null,u=!1;const a=[],p={},f={};let l=null;const d="undefined"!=typeof window&&!0===window.__INTENT_ROUTER_DEBUG__;function h(...n){d&&console.log("[IntentRouter]",...n)}function m(n){if(!n)return"";const t=new URLSearchParams;for(const e in n){const r=n[e];null!=r&&(Array.isArray(r)?r.forEach(n=>t.append(e,String(n))):t.append(e,String(r)))}const e=t.toString();return e?`?${e}`:""}function I(n){c=n,u||(o.$onMultiple("INTENT_NAVIGATE",g),u=!0)}function x(n,t,e,r){h("emit",n,{params:t,query:e});const s={intent:n,params:t,query:e,...r?{options:r}:{}};o.$emit("INTENT_NAVIGATE",s),i&&i.$emit("INTENT_NAVIGATE",s)}async function g(n){const t=s[n.intent];if(h("receive",n.intent,n),!t)return l&&c?.(l),void console.warn("Unknown intent:",n.intent);const e=N(t,n.params);for(const t of a){const r=await t(n.intent,e);if(!1===r)return;if("string"==typeof r)return void c?.(r,n.options)}const r=p[n.intent]||[];for(const t of r){const r=await t(n.intent,e);if(!1===r)return;if("string"==typeof r)return void c?.(r,n.options)}E(n.intent);const o=y(t,e)+m(n.query);h("navigate",{intent:n.intent,params:e,query:n.query,url:o}),c?.(o,n.options),i&&i.$emit("INTENT_NAVIGATE",n)}function E(n){const t=f[n];if(t)for(const n of t)try{const t=n();t instanceof Promise&&t.catch(()=>{})}catch{}}function N(n,t){const e=function(n){const t=[];return n.replace(/:([A-Za-z0-9_]+)/g,(n,e)=>(t.push(e),"")),t}(n);if(!t)return{};if(Array.isArray(t)){const n={};return e.forEach((e,r)=>{void 0===t[r]&&console.warn(`Missing param "${e}"`),n[e]=t[r]}),n}return"object"==typeof t?t:1===e.length?{[e[0]]:t}:{}}function y(n,t){return n.replace(/:([A-Za-z0-9_]+)/g,(n,e)=>{const r=t[e];return void 0!==r?encodeURIComponent(r):""})}function T(n,t,e){const r=s[n];if(!r)return"";return y(r,N(r,t))+m(e)}function A(n){const[t,e]=n.split("?"),r=function(n){if(!n)return{};const t=new URLSearchParams(n),e={};return t.forEach((n,t)=>{e[t]?Array.isArray(e[t])?e[t].push(n):e[t]=[e[t],n]:e[t]=n}),e}(e);for(const n in s){const e=s[n],o=[],i=new RegExp("^"+e.replace(/:([A-Za-z0-9_]+)/g,(n,t)=>(o.push(t),"([^/]+)"))+"$"),c=t.match(i);if(!c)continue;const u={};return o.forEach((n,t)=>{u[n]=decodeURIComponent(c[t+1])}),{intent:n,params:u,query:r}}return null}function v(){const n=e.useLocation(),t=A(n.pathname),r=new URLSearchParams(n.search),o={};return r.forEach((n,t)=>{o[t]=n}),{intent:t?.intent??null,params:t?.params??{},query:o}}function w({navigate:n}){const t=e.useNavigate();return I(n||t),null}exports.IntentLink=function({intent:n,params:r,query:o,children:i,replace:s,scrollTop:c}){const u=T(n,r,o);return t.jsx(e.Link,{to:u,replace:s,onClick:()=>{c&&window.scrollTo(0,0)},children:i})},exports.IntentRoute=function({intent:n,component:e,fallback:r=null,loading:o,guard:i}){const s=v();return s?s.intent!==n?null:i&&!i(s)?t.jsx(t.Fragment,{children:r}):t.jsx(e,{...s}):o??null},exports.IntentRouter=function({children:n}){return t.jsxs(e.BrowserRouter,{children:[t.jsx(w,{}),n]})},exports.RouterBinder=w,exports.addIntentGuard=function(n){a.push(n)},exports.addIntentGuardFor=function(n,t){p[n]||(p[n]=[]),p[n].push(t)},exports.addIntentPreload=function(n,t){f[n]||(f[n]=[]),f[n].push(t)},exports.bindNavigate=I,exports.createIntentRouter=function(n){s=n,h("intent map loaded",n)},exports.generatePathFromIntent=T,exports.navigateIntent=x,exports.preloadIntent=function(n){E(n)},exports.resolveIntentFromUrl=A,exports.setFallbackPath=function(n){l=n},exports.setSharedBus=function(n){i=n,i.$onMultiple("INTENT_NAVIGATE",g)},exports.useIntentRouter=v,exports.useNavigateIntent=function(){const n=e.useNavigate();return r.useEffect(()=>{I(n)},[n]),x};
@@ -1,9 +1,10 @@
1
- import React from "react";
2
1
  type Props = {
3
2
  intent: string;
4
3
  params?: any;
5
4
  query?: Record<string, any>;
6
5
  children: React.ReactNode;
6
+ replace?: boolean;
7
+ scrollTop?: boolean;
7
8
  };
8
- export declare function IntentLink({ intent, params, query, children }: Props): JSX.Element;
9
+ export declare function IntentLink({ intent, params, query, children, replace, scrollTop }: Props): JSX.Element;
9
10
  export {};
@@ -1,7 +1,10 @@
1
- import React from "react";
1
+ import type { IntentData } from "./useIntentRouter";
2
2
  type Props = {
3
3
  intent: string;
4
4
  component: React.ComponentType<any>;
5
+ fallback?: React.ReactNode;
6
+ loading?: React.ReactNode;
7
+ guard?: (data: IntentData) => boolean;
5
8
  };
6
- export declare function IntentRoute({ intent, component: Component }: Props): JSX.Element | null;
9
+ export declare function IntentRoute({ intent, component: Component, fallback, loading, guard, }: Props): React.ReactNode;
7
10
  export {};
@@ -0,0 +1,5 @@
1
+ type Props = {
2
+ children: React.ReactNode;
3
+ };
4
+ export declare function IntentRouter({ children }: Props): JSX.Element;
5
+ export {};
@@ -1,5 +1,6 @@
1
1
  export * from "./IntentLink";
2
2
  export * from "./IntentRoute";
3
+ export * from "./IntentRouter";
3
4
  export * from "./RouterBinder";
4
- export * from "./useIntent";
5
+ export * from "./useIntentRouter";
5
6
  export * from "./useNavigateIntent";
@@ -0,0 +1,6 @@
1
+ export type IntentData<P = any> = {
2
+ intent?: string | null;
3
+ params?: P | any[] | string | number;
4
+ query?: Record<string, any>;
5
+ };
6
+ export declare function useIntentRouter(): IntentData;
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "intentx-react-router",
3
- "version": "0.2.0",
3
+ "version": "1.0.0-z",
4
4
  "description": "Intent-based routing for React. Navigate by intent instead of URLs.",
5
5
  "author": "Delpi.Kye",
6
6
  "license": "MIT",
7
-
8
7
  "type": "module",
9
8
  "main": "./build/index.js",
10
9
  "module": "./build/index.esm.js",
@@ -16,7 +15,6 @@
16
15
  "require": "./build/index.js"
17
16
  }
18
17
  },
19
-
20
18
  "files": [
21
19
  "build"
22
20
  ],
@@ -27,11 +25,9 @@
27
25
  "watch": "rollup -c -w",
28
26
  "typecheck": "tsc --noEmit"
29
27
  },
30
-
31
28
  "engines": {
32
29
  "node": ">=16"
33
30
  },
34
-
35
31
  "repository": {
36
32
  "type": "git",
37
33
  "url": "https://github.com/delpikye-v/intentx-react-router.git"
@@ -52,7 +48,6 @@
52
48
  "spa",
53
49
  "routing"
54
50
  ],
55
-
56
51
  "peerDependencies": {
57
52
  "react": ">=18",
58
53
  "react-dom": ">=18",
@@ -79,7 +74,6 @@
79
74
  "tslib": "^2.6.2",
80
75
  "typescript": "^5.0.0"
81
76
  },
82
-
83
77
  "publishConfig": {
84
78
  "access": "public"
85
79
  }
@@ -1,5 +0,0 @@
1
- export declare function useIntent(): {
2
- intent: string | null;
3
- params: Record<string, any>;
4
- query: Record<string, string>;
5
- };