@avstantso/react-router-utils 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1] - 2026-02-05
4
+
5
+ ### Fixed
6
+
7
+ - Excluded leaked dependency declarations (`_global/`, `_std-ext/`, `export.d.ts`) from build output
8
+
9
+ ## [0.1.0] - 2026-04-02
10
+
11
+ ### Changed
12
+
13
+ - Initial release
package/README.md ADDED
@@ -0,0 +1,466 @@
1
+ # @avstantso/react-router-utils
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@avstantso/react-router-utils.svg)](https://www.npmjs.com/package/@avstantso/react-router-utils)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Type-safe `react-router-dom` enhancement package that generates pre-bound route components, navigation utilities, and URL matchers from a declarative URL tree.
7
+
8
+ ## Features
9
+
10
+ - **Pre-bound Link trees** - `<Link>` and `<NavLink>` components bound to specific routes with automatic `href` resolution
11
+ - **Navigate trees** - `<Navigate>` components and `useNavigate` hook proxy bound to routes
12
+ - **URL matching** - Type-safe `isUrl` / `isSubUrl` functions for pathname comparison
13
+ - **Route creator** - Factory for building `RouteObject` definitions from the URL tree
14
+ - **Dynamic params** - Full support for parameterized routes (`:userId`) with type-safe `params` props
15
+ - **Preserve option** - Navigate components can preserve `search` and `hash` from the current location
16
+ - **Auto-generated IDs** - Link components receive HTML-safe `id` attributes derived from route paths
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @avstantso/react-router-utils
22
+ ```
23
+
24
+ or
25
+
26
+ ```bash
27
+ yarn add @avstantso/react-router-utils
28
+ ```
29
+
30
+ ## Peer Dependencies
31
+
32
+ - `react` >= 18.0
33
+ - `react-dom` >= 18.0
34
+ - `react-router-dom` >= 7.0
35
+
36
+ ## Quick Start
37
+
38
+ ### 1. Define your URL tree
39
+
40
+ ```typescript
41
+ import { NamesTree } from '@avstantso/utils-names-tree';
42
+
43
+ const UrlsSource = {
44
+ profile: {
45
+ settings: 1,
46
+ ':userId': {
47
+ posts: 1
48
+ }
49
+ },
50
+ about: 1
51
+ } as const;
52
+
53
+ const urlsTree = NamesTree.Urls(UrlsSource, { rootAlias: 'Home' } as const);
54
+ ```
55
+
56
+ ### 2. Create route utilities
57
+
58
+ ```typescript
59
+ import { ReactRouterTree } from '@avstantso/react-router-utils';
60
+
61
+ const { Links, NavLinks, Navigates, isUrl, isSubUrl, RouteCreator } = ReactRouterTree(urlsTree);
62
+ ```
63
+
64
+ ### 3. Use in your app
65
+
66
+ ```tsx
67
+ function App() {
68
+ return (
69
+ <nav>
70
+ <Links.Home>Home</Links.Home>
71
+ <Links.Profile.Settings>Settings</Links.Profile.Settings>
72
+ <Links.Profile.$UserId params={{ userId: '42' }}>User Profile</Links.Profile.$UserId>
73
+ <Links.About>About</Links.About>
74
+ </nav>
75
+ );
76
+ }
77
+ ```
78
+
79
+ ## Table of Contents
80
+
81
+ - [ReactRouterTree](#reactroutertree)
82
+ - [Links & NavLinks](#links--navlinks)
83
+ - [Navigates](#navigates)
84
+ - [Navigate Components](#navigate-components)
85
+ - [useNavigate Hook](#usenavigate-hook)
86
+ - [isUrl & isSubUrl](#isurl--issuburl)
87
+ - [RouteCreator](#routecreator)
88
+
89
+ ---
90
+
91
+ ## ReactRouterTree
92
+
93
+ Main factory function that generates all route utilities from a URL tree.
94
+
95
+ ```typescript
96
+ import { ReactRouterTree } from '@avstantso/react-router-utils';
97
+
98
+ const {
99
+ Links, // Tree of <Link> components
100
+ NavLinks, // Tree of <NavLink> components
101
+ Navigates, // Tree of <Navigate> components + useNavigate hook
102
+ isUrl, // Strict URL matching functions
103
+ isSubUrl, // Prefix URL matching functions
104
+ RouteCreator // Route object factory
105
+ } = ReactRouterTree(urlsTree);
106
+ ```
107
+
108
+ ### Tree Key Naming
109
+
110
+ Route keys are transformed to PascalCase. Dynamic parameters (`:param`) are prefixed with `$` and converted to PascalCase:
111
+
112
+ | URL Tree Key | Generated Key |
113
+ |-------------|---------------|
114
+ | `profile` | `Profile` |
115
+ | `settings` | `Settings` |
116
+ | `:userId` | `$UserId` |
117
+ | `:post-id` | `$PostId` |
118
+ | `rootAlias: 'Home'` | `Home` |
119
+
120
+ ---
121
+
122
+ ## Links & NavLinks
123
+
124
+ Pre-bound `<Link>` and `<NavLink>` components from `react-router-dom`, organized as a tree matching your URL structure. Each link component has its `to` prop pre-set to the corresponding route path.
125
+
126
+ ### Static Routes
127
+
128
+ ```tsx
129
+ import { ReactRouterTree } from '@avstantso/react-router-utils';
130
+
131
+ const { Links, NavLinks } = ReactRouterTree(urlsTree);
132
+
133
+ // Renders <a href="/about">About</a>
134
+ <Links.About>About</Links.About>
135
+
136
+ // Renders <a href="/profile/settings">Settings</a>
137
+ <Links.Profile.Settings>Go to settings</Links.Profile.Settings>
138
+
139
+ // NavLink with active class support
140
+ <NavLinks.About>About</NavLinks.About>
141
+ ```
142
+
143
+ ### Parameterized Routes
144
+
145
+ Pass `params` prop to resolve dynamic segments:
146
+
147
+ ```tsx
148
+ // Renders <a href="/profile/42">User</a>
149
+ <Links.Profile.$UserId params={{ userId: '42' }}>User</Links.Profile.$UserId>
150
+
151
+ // Renders <a href="/profile/99/posts">Posts</a>
152
+ <Links.Profile.$UserId.Posts params={{ userId: '99' }}>Posts</Links.Profile.$UserId.Posts>
153
+
154
+ // Unresolved params are kept as-is
155
+ <Links.Profile.$UserId params={{}}>User</Links.Profile.$UserId>
156
+ // Renders <a href="/profile/:userId">User</a>
157
+ ```
158
+
159
+ ### Additional Props
160
+
161
+ Links accept all standard `Link`/`NavLink` props except `to` (which is pre-bound). You can pass `search` and `hash` via the `to` object form:
162
+
163
+ ```tsx
164
+ <Links.About to={{ search: '?tab=info', hash: '#section' }}>
165
+ About
166
+ </Links.About>
167
+ ```
168
+
169
+ ### HTML ID
170
+
171
+ Each link component has an `id` property derived from the route path, useful for targeting with CSS or scroll anchoring:
172
+
173
+ ```typescript
174
+ console.log(Links.Profile.Settings.id); // HTML-safe id derived from "profile/settings"
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Navigates
180
+
181
+ ### Navigate Components
182
+
183
+ Pre-bound `<Navigate>` components that redirect to specific routes on mount.
184
+
185
+ ```tsx
186
+ const { Navigates } = ReactRouterTree(urlsTree);
187
+
188
+ // Redirects to /about
189
+ <Navigates.About />
190
+
191
+ // Redirects to /profile/settings
192
+ <Navigates.Profile.Settings />
193
+ ```
194
+
195
+ #### With Params
196
+
197
+ ```tsx
198
+ // Redirects to /profile/42
199
+ <Navigates.Profile.$UserId params={{ userId: '42' }} />
200
+
201
+ // Redirects to /profile/99/posts
202
+ <Navigates.Profile.$UserId.Posts params={{ userId: '99' }} />
203
+ ```
204
+
205
+ #### Preserve Option
206
+
207
+ The `preserve` prop saves the current `search` and `hash` when redirecting:
208
+
209
+ ```tsx
210
+ // If current URL is /old-page?tab=1#section
211
+ // Redirects to /about?tab=1#section
212
+ <Navigates.About preserve />
213
+ ```
214
+
215
+ ### useNavigate Hook
216
+
217
+ A proxy-enhanced `useNavigate` hook that provides the same tree-based navigation as the components, but as callable functions.
218
+
219
+ ```tsx
220
+ function MyComponent() {
221
+ const nav = Navigates.useNavigate();
222
+
223
+ return (
224
+ <div>
225
+ {/* Navigate to static routes */}
226
+ <button onClick={() => nav.About()}>Go to About</button>
227
+ <button onClick={() => nav.Profile.Settings()}>Go to Settings</button>
228
+
229
+ {/* Navigate with params */}
230
+ <button onClick={() => nav.Profile.$UserId({ userId: '42' })}>
231
+ Go to User 42
232
+ </button>
233
+
234
+ {/* Navigate to nested parameterized route */}
235
+ <button onClick={() => nav.Profile.$UserId.Posts({ userId: '99' })}>
236
+ Go to User 99 Posts
237
+ </button>
238
+
239
+ {/* Falls through to original navigate for direct paths */}
240
+ <button onClick={() => nav('/about')}>Direct navigate</button>
241
+ </div>
242
+ );
243
+ }
244
+ ```
245
+
246
+ #### Intermediate References
247
+
248
+ The hook proxy is stateless - you can save intermediate references:
249
+
250
+ ```tsx
251
+ function MyComponent() {
252
+ const nav = Navigates.useNavigate();
253
+
254
+ const profileNav = nav.Profile;
255
+ profileNav.Settings(); // navigates to /profile/settings
256
+ profileNav.$UserId({ userId: '1' }); // navigates to /profile/1
257
+ }
258
+ ```
259
+
260
+ ---
261
+
262
+ ## isUrl & isSubUrl
263
+
264
+ Functions for checking if a pathname matches a specific route. Both accept a string pathname or a location object `{ pathname: string }`.
265
+
266
+ ### isUrl (Strict Match)
267
+
268
+ Matches only when the pathname has the exact same segments as the route:
269
+
270
+ ```typescript
271
+ const { isUrl } = ReactRouterTree(urlsTree);
272
+
273
+ isUrl.Home('/'); // true
274
+ isUrl.About('/about'); // true
275
+ isUrl.Profile.Settings('/profile/settings'); // true
276
+
277
+ // Rejects extra or fewer segments
278
+ isUrl.Profile('/profile/settings'); // false
279
+ isUrl.Profile.Settings('/profile'); // false
280
+
281
+ // Rejects non-matching
282
+ isUrl.Profile.Settings('/profile/other'); // false
283
+
284
+ // Rejects without leading slash
285
+ isUrl.About('about'); // false
286
+
287
+ // Accepts location objects
288
+ isUrl.About({ pathname: '/about' }); // true
289
+ ```
290
+
291
+ ### isUrl with Dynamic Params
292
+
293
+ Without `params`, dynamic segments match any value (wildcard). With `params`, they match the specific value:
294
+
295
+ ```typescript
296
+ // Wildcard - matches any userId
297
+ isUrl.Profile.$UserId('/profile/123'); // true
298
+ isUrl.Profile.$UserId('/profile/abc'); // true
299
+
300
+ // With params - matches specific userId
301
+ isUrl.Profile.$UserId('/profile/123', { userId: '123' }); // true
302
+ isUrl.Profile.$UserId('/profile/123', { userId: '456' }); // false
303
+
304
+ // Nested parameterized routes
305
+ isUrl.Profile.$UserId.Posts('/profile/42/posts'); // true
306
+ ```
307
+
308
+ ### isSubUrl (Prefix Match)
309
+
310
+ Matches when the pathname starts with the route:
311
+
312
+ ```typescript
313
+ const { isSubUrl } = ReactRouterTree(urlsTree);
314
+
315
+ // Matches exact and extended paths
316
+ isSubUrl.Profile('/profile'); // true
317
+ isSubUrl.Profile('/profile/settings'); // true
318
+ isSubUrl.Profile('/profile/123/posts'); // true
319
+
320
+ // Rejects completely different paths
321
+ isSubUrl.Profile('/about'); // false
322
+
323
+ // Root matches everything
324
+ isSubUrl.Home('/anything'); // true
325
+ ```
326
+
327
+ ---
328
+
329
+ ## RouteCreator
330
+
331
+ Factory for building `RouteObject` definitions from the URL tree. Useful for defining routes in a type-safe, tree-structured way.
332
+
333
+ ```typescript
334
+ import { ReactRouterTree } from '@avstantso/react-router-utils';
335
+
336
+ const { RouteCreator } = ReactRouterTree(urlsTree);
337
+ const createRoute = RouteCreator(urlsTree);
338
+ ```
339
+
340
+ ### Type: `'url'` (Full Path)
341
+
342
+ Creates routes with full URL paths:
343
+
344
+ ```typescript
345
+ const route = createRoute('url');
346
+
347
+ route.About({ element: <AboutPage /> });
348
+ // { element: <AboutPage />, path: '/about' }
349
+
350
+ route.Profile.Settings({ element: <SettingsPage /> });
351
+ // { element: <SettingsPage />, path: '/profile/settings' }
352
+
353
+ route.Home({ element: <HomePage /> });
354
+ // { element: <HomePage />, path: '/' }
355
+
356
+ route.Profile[':userId'].Posts({ element: <PostsPage /> });
357
+ // { element: <PostsPage />, path: '/profile/:userId/posts' }
358
+ ```
359
+
360
+ ### Type: `'name'` (Segment Only)
361
+
362
+ Creates routes with just the segment name (for nested route definitions):
363
+
364
+ ```typescript
365
+ const route = createRoute('name');
366
+
367
+ route.Profile({ element: <ProfilePage /> });
368
+ // { element: <ProfilePage />, path: 'profile' }
369
+
370
+ route.Profile.Settings({ element: <SettingsPage /> });
371
+ // { element: <SettingsPage />, path: 'settings' }
372
+ ```
373
+
374
+ ### Common Props
375
+
376
+ Pass shared properties to all routes via the second argument:
377
+
378
+ ```typescript
379
+ const route = createRoute('url', { errorElement: <ErrorPage /> });
380
+
381
+ route.About({ element: <AboutPage /> });
382
+ // { errorElement: <ErrorPage />, element: <AboutPage />, path: '/about' }
383
+
384
+ // Route-specific props override common props
385
+ const route2 = createRoute('url', { element: <DefaultPage /> });
386
+ route2.About({ element: <AboutPage /> });
387
+ // { element: <AboutPage />, path: '/about' }
388
+ ```
389
+
390
+ ---
391
+
392
+ ## Type Definitions
393
+
394
+ ### UrlsTree
395
+
396
+ ```typescript
397
+ import type { UrlsTree } from '@avstantso/react-router-utils';
398
+
399
+ // The URL tree type - built from NamesTree.Urls()
400
+ type MyTree = typeof urlsTree;
401
+ ```
402
+
403
+ ### LinksTree
404
+
405
+ ```typescript
406
+ import type { LinksTree, BindedLink, BindedNavLink } from '@avstantso/react-router-utils';
407
+
408
+ // Full Links tree type
409
+ type MyLinks = LinksTree<BindedLink.Props, MyTree>;
410
+
411
+ // Full NavLinks tree type
412
+ type MyNavLinks = LinksTree<BindedNavLink.Props, MyTree>;
413
+ ```
414
+
415
+ ### NavigatesTree
416
+
417
+ ```typescript
418
+ import type { NavigatesTree } from '@avstantso/react-router-utils';
419
+
420
+ // Full Navigates tree type (components + useNavigate)
421
+ type MyNavigates = NavigatesTree<MyTree>;
422
+ ```
423
+
424
+ ### IsUrlTree
425
+
426
+ ```typescript
427
+ import type { IsUrlTree } from '@avstantso/react-router-utils';
428
+
429
+ // Full isUrl/isSubUrl tree type
430
+ type MyIsUrl = IsUrlTree<MyTree>;
431
+ ```
432
+
433
+ ### RouteCreator
434
+
435
+ ```typescript
436
+ import type { RouteCreator } from '@avstantso/react-router-utils';
437
+
438
+ // Route creator function type
439
+ type MyRouteCreator = RouteCreator<MyTree>;
440
+ ```
441
+
442
+ ---
443
+
444
+ ## Requirements
445
+
446
+ - React >= 18.0
447
+ - react-router-dom >= 7.0
448
+ - TypeScript >= 5.0
449
+
450
+ ## Dependencies
451
+
452
+ - [@avstantso/utils-misc](https://www.npmjs.com/package/@avstantso/utils-misc) - Miscellaneous utilities (Cases, HTMLUtils)
453
+ - [@avstantso/utils-names-tree](https://www.npmjs.com/package/@avstantso/utils-names-tree) - Hierarchical name tree builder
454
+ - [react-router-dom](https://www.npmjs.com/package/react-router-dom) - React Router
455
+
456
+ ## License
457
+
458
+ MIT - See [LICENSE](https://gitlab.com/avstantso-js/react-utils/-/blob/main/LICENSE.md) file for details
459
+
460
+ ## Repository
461
+
462
+ [GitLab - avstantso-js/react-utils](https://gitlab.com/avstantso-js/react-utils/-/tree/main/packages/react-router-utils)
463
+
464
+ ## Contributing
465
+
466
+ Contributions are welcome! Please feel free to submit issues or merge requests to the repository.
@@ -0,0 +1,12 @@
1
+ import TS = AVStantso.TS;
2
+ import { Path, To } from 'react-router-dom';
3
+ export type BindedPath = Omit<Path, 'pathname'>;
4
+ export type BindProps<T extends {
5
+ to?: To;
6
+ }> = TS.ReplaceKeyOpt<'to', T, Partial<BindedPath>>;
7
+ export declare function resolveParams(url: string, params?: object): string;
8
+ export declare function BindProps<P extends string, T extends {
9
+ to?: To;
10
+ }>(pathname: P, props: {
11
+ to?: To;
12
+ }, params?: object): T;
@@ -0,0 +1 @@
1
+ export * from './binded-path';