react-routes-forge 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -28
- package/dist/index.d.ts +94 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,20 +17,22 @@ One source of truth for your routes — templates for `<Route path={...} />` and
|
|
|
17
17
|
- [Installation](#installation)
|
|
18
18
|
- [Quick start](#quick-start)
|
|
19
19
|
- [Cheat sheet](#cheat-sheet)
|
|
20
|
-
- [Core concepts](#core-concepts)
|
|
21
20
|
- [API reference](#api-reference)
|
|
22
21
|
- [`defineRoutes(routeMap)`](#defineroutesroutemap)
|
|
23
22
|
- [`build(template, params, query?, options?)`](#buildtemplate-params-query-options)
|
|
24
23
|
- [`isActivePath(currentPath, template, options?)`](#isactivepathcurrentpath-template-options)
|
|
25
24
|
- [`extractParamsFromPath(template, resolvedPath)`](#extractparamsfrompathtemplate-resolvedpath)
|
|
25
|
+
- [`matchPath(template)`](#matchpathtemplate)
|
|
26
26
|
- [`joinPaths(...segments)`](#joinpathssegments)
|
|
27
27
|
- [`getParamNames(template)`](#getparamnamestemplate)
|
|
28
28
|
- [`flattenRoutes(routes)`](#flattenroutesroutes)
|
|
29
|
+
- [`getBreadcrumbs(routes, currentPath, options?)`](#getbreadcrumbsroutes-currentpath-options)
|
|
29
30
|
- [React hooks](#react-hooks)
|
|
30
31
|
- [`useRouteParams<T>()`](#useroutparamst)
|
|
31
32
|
- [`useNavigateTo()`](#usenavigateto)
|
|
32
33
|
- [`useResolvedPath(template, params, query?, options?)`](#useresolvedpathtemplate-params-query-options)
|
|
33
34
|
- [Query string support](#query-string-support)
|
|
35
|
+
- [Hash fragment support](#hash-fragment-support)
|
|
34
36
|
- [Strict mode](#strict-mode)
|
|
35
37
|
- [Migrating from the old pattern](#migrating-from-the-old-pattern)
|
|
36
38
|
- [Known behaviours & gotchas](#known-behaviours--gotchas)
|
|
@@ -133,6 +135,8 @@ export const PATHS = defineRoutes({
|
|
|
133
135
|
} as const);
|
|
134
136
|
```
|
|
135
137
|
|
|
138
|
+
> **Always pass `as const`** — it preserves the literal string types that power `.build()`'s compile-time param checking. Without it, TypeScript widens your path strings to generic `string` and you lose type safety.
|
|
139
|
+
|
|
136
140
|
```tsx
|
|
137
141
|
// App.tsx — static paths and dynamic templates both work directly as strings
|
|
138
142
|
import { Routes, Route } from "react-router-dom";
|
|
@@ -146,46 +150,64 @@ import { PATHS } from "./paths";
|
|
|
146
150
|
</Routes>;
|
|
147
151
|
```
|
|
148
152
|
|
|
149
|
-
```
|
|
153
|
+
```tsx
|
|
150
154
|
// Navigating — call .build() to resolve a dynamic path into a real URL
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
import { useNavigate } from "react-router-dom";
|
|
156
|
+
|
|
157
|
+
function MyComponent() {
|
|
158
|
+
const navigate = useNavigate();
|
|
159
|
+
// ↓ Param type-checked from the template ":id"
|
|
160
|
+
navigate(PATHS.USERS.EDIT.build({ id: 42 })); // → '/users/edit/42'
|
|
161
|
+
navigate(PATHS.ROLES.PERMISSIONS.build({ name: "admin" })); // → '/roles/permissions/admin'
|
|
162
|
+
navigate(PATHS.HOME); // → '/' (static paths work directly)
|
|
163
|
+
}
|
|
154
164
|
```
|
|
155
165
|
|
|
156
166
|
That's the entire API surface you need for most apps. Everything below covers the rest of the toolkit.
|
|
157
167
|
|
|
168
|
+
### Route types
|
|
169
|
+
|
|
170
|
+
| Route type | Example | Behaves as | Gains |
|
|
171
|
+
| ----------- | ----------------------- | --------------------------------------- | ---------------------------------------------------- |
|
|
172
|
+
| **Static** | `HOME: '/'` | Plain string primitive | Nothing extra — use it directly |
|
|
173
|
+
| **Dynamic** | `DETAILS: '/users/:id'` | String-like (coercible to its template) | `.build(params, query?, options?)` and `.paramNames` |
|
|
174
|
+
|
|
175
|
+
`defineRoutes()` walks your route object recursively, leaving static paths untouched and wrapping any path containing a `:param` segment so it can carry a builder alongside its template string.
|
|
176
|
+
|
|
158
177
|
---
|
|
159
178
|
|
|
160
179
|
## Cheat sheet
|
|
161
180
|
|
|
162
|
-
Quick reference for everything the package exports. Click through to the full section for details and examples.
|
|
163
|
-
|
|
164
|
-
| Export | Kind | Purpose |
|
|
165
|
-
| ---------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------- |
|
|
166
|
-
| [`defineRoutes(routeMap)`](#defineroutesroutemap) | function | Builds the typed `PATHS` object from a nested route map |
|
|
167
|
-
| [`build(template, params, query?, options?)`](#buildtemplate-params-query-options) | function | Resolve a template into a URL without `defineRoutes` |
|
|
168
|
-
| [`isActivePath(currentPath, template, options?)`](#isactivepathcurrentpath-template-options) | function | Check if a path matches a template (nav-highlighting) |
|
|
169
|
-
| [`extractParamsFromPath(template, resolvedPath)`](#extractparamsfrompathtemplate-resolvedpath) | function | Pull param values back out of a resolved URL |
|
|
170
|
-
| [`joinPaths(...segments)`](#joinpathssegments) | function | Join and normalize path segments |
|
|
171
|
-
| [`getParamNames(template)`](#getparamnamestemplate) | function | List the `:param` names in a template |
|
|
172
|
-
| [`flattenRoutes(routes)`](#flattenroutesroutes) | function | Flatten a `PATHS` tree for sitemaps / duplicate detection |
|
|
173
|
-
| [`useRouteParams<T>()`](#useroutparamst) | hook | Typed wrapper around React Router's `useParams` |
|
|
174
|
-
| [`useNavigateTo()`](#usenavigateto) | hook | Typed wrapper around React Router's `useNavigate` |
|
|
175
|
-
| [`useResolvedPath(...)`](#useresolvedpathtemplate-params-query-options) | hook | Resolve a template to a string without navigating |
|
|
176
|
-
| `.build(params, query?, options?)` | method | On every dynamic route — resolves to a concrete URL |
|
|
177
|
-
| `.paramNames` | property | On every dynamic route — the param names it expects |
|
|
181
|
+
Quick reference for everything the package exports — grouped by kind. Click through to the full section for details and examples.
|
|
178
182
|
|
|
179
|
-
|
|
183
|
+
### Route definition
|
|
180
184
|
|
|
181
|
-
|
|
185
|
+
| Export | Purpose |
|
|
186
|
+
| ---------------------------------------------------- | ------------------------------------------------------ |
|
|
187
|
+
| [`defineRoutes(routeMap)`](#defineroutesroutemap) | Builds the typed `PATHS` object from a nested route map |
|
|
188
|
+
| `.build(params, query?, options?)` | On every dynamic route — resolves to a concrete URL |
|
|
189
|
+
| `.paramNames` | On every dynamic route — the param names it expects |
|
|
182
190
|
|
|
183
|
-
|
|
184
|
-
| ----------- | ----------------------- | --------------------------------------- | ---------------------------------------------------- |
|
|
185
|
-
| **Static** | `HOME: '/'` | Plain string primitive | Nothing extra — use it directly |
|
|
186
|
-
| **Dynamic** | `DETAILS: '/users/:id'` | String-like (coercible to its template) | `.build(params, query?, options?)` and `.paramNames` |
|
|
191
|
+
### Utilities
|
|
187
192
|
|
|
188
|
-
|
|
193
|
+
| Export | Purpose |
|
|
194
|
+
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
|
|
195
|
+
| [`build(template, params, query?, options?)`](#buildtemplate-params-query-options) | Resolve a template into a URL without `defineRoutes` |
|
|
196
|
+
| [`isActivePath(currentPath, template, options?)`](#isactivepathcurrentpath-template-options)| Check if a path matches a template (nav-highlighting) |
|
|
197
|
+
| [`extractParamsFromPath(template, resolvedPath)`](#extractparamsfrompathtemplate-resolvedpath)| Pull param values back out of a resolved URL |
|
|
198
|
+
| [`matchPath(template)`](#matchpathtemplate) | Convert a route template into an anchored `RegExp` |
|
|
199
|
+
| [`joinPaths(...segments)`](#joinpathssegments) | Join and normalize path segments |
|
|
200
|
+
| [`getParamNames(template)`](#getparamnamestemplate) | List the `:param` names in a template |
|
|
201
|
+
| [`flattenRoutes(routes)`](#flattenroutesroutes) | Flatten a `PATHS` tree for sitemaps / duplicate detection |
|
|
202
|
+
| [`getBreadcrumbs(routes, currentPath, options?)`](#getbreadcrumbsroutes-currentpath-options)| Build a breadcrumb trail from a route tree and current URL |
|
|
203
|
+
|
|
204
|
+
### React hooks
|
|
205
|
+
|
|
206
|
+
| Export | Purpose |
|
|
207
|
+
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
|
|
208
|
+
| [`useRouteParams<T>()`](#useroutparamst) | Typed wrapper around React Router's `useParams` |
|
|
209
|
+
| [`useNavigateTo()`](#usenavigateto) | Typed wrapper around React Router's `useNavigate` |
|
|
210
|
+
| [`useResolvedPath(...)`](#useresolvedpathtemplate-params-query-options) | Resolve a template to a string without navigating |
|
|
189
211
|
|
|
190
212
|
---
|
|
191
213
|
|
|
@@ -215,6 +237,7 @@ const PATHS = defineRoutes({
|
|
|
215
237
|
|
|
216
238
|
PATHS.SERVICES.ROOT; // '/services'
|
|
217
239
|
PATHS.SERVICES.BENEFICIARY_CARE_CENTER.EDIT.build({ id: 7 }); // '/services/beneficiary-care-center/edit/7'
|
|
240
|
+
PATHS.SERVICES.BENEFICIARY_CARE_CENTER.EDIT.build({ id: 7 }, { tab: "info" }, { hash: "details" }); // → '/services/beneficiary-care-center/edit/7?tab=info#details'
|
|
218
241
|
PATHS.SERVICES.BENEFICIARY_CARE_CENTER.EDIT.paramNames; // ['id']
|
|
219
242
|
```
|
|
220
243
|
|
|
@@ -239,6 +262,12 @@ build("/users", {}, { sort: "asc" });
|
|
|
239
262
|
// Strict mode — throw instead of warn when a param is missing
|
|
240
263
|
build("/users/:id", {}, undefined, { strict: true });
|
|
241
264
|
// ✗ throws RangeError: [route-forge] Missing required param(s) ":id" in template "/users/:id".
|
|
265
|
+
|
|
266
|
+
// Hash fragment — appended after the query string
|
|
267
|
+
build("/users/:id", { id: 42 }, { tab: "info" }, { hash: "details" });
|
|
268
|
+
// → '/users/42?tab=info#details'
|
|
269
|
+
build("/page", {}, undefined, { hash: "section" });
|
|
270
|
+
// → '/page#section'
|
|
242
271
|
```
|
|
243
272
|
|
|
244
273
|
---
|
|
@@ -289,6 +318,23 @@ extractParamsFromPath("/a/:x/b/:y", "/a/foo/b/bar");
|
|
|
289
318
|
|
|
290
319
|
---
|
|
291
320
|
|
|
321
|
+
### `matchPath(template)`
|
|
322
|
+
|
|
323
|
+
Converts a route template string into an anchored `RegExp` — useful when you need custom matching logic beyond [`isActivePath`](#isactivepathcurrentpath-template-options) or [`extractParamsFromPath`](#extractparamsfrompathtemplate-resolvedpath). Query strings are **not** stripped; split on `"?"` first if needed.
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { matchPath } from "react-routes-forge";
|
|
327
|
+
|
|
328
|
+
const re = matchPath("/users/:id");
|
|
329
|
+
re.test("/users/42"); // true
|
|
330
|
+
re.exec("/users/42"); // ['/users/42', '42']
|
|
331
|
+
re.test("/users/42/posts"); // false (exact match only)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
This is the building block used internally by `isActivePath` and `extractParamsFromPath`.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
292
338
|
### `joinPaths(...segments)`
|
|
293
339
|
|
|
294
340
|
Safely joins path segments, normalizing duplicate/missing slashes.
|
|
@@ -362,6 +408,59 @@ it("has no duplicate route paths", () => {
|
|
|
362
408
|
|
|
363
409
|
---
|
|
364
410
|
|
|
411
|
+
### `getBreadcrumbs(routes, currentPath, options?)`
|
|
412
|
+
|
|
413
|
+
Walks a route tree (or a pre-flattened array from `flattenRoutes()`) and returns every route that is an ancestor of (or an exact match to) the current URL. Dynamic params in ancestor paths are automatically resolved from the matched portion of the URL. Query strings on `currentPath` are ignored.
|
|
414
|
+
|
|
415
|
+
Each breadcrumb entry contains:
|
|
416
|
+
- **`key`** — the dot-joined key from the route tree (e.g. `"USERS.EDIT"`)
|
|
417
|
+
- **`label`** — a human-readable label derived from the key (e.g. `"USERS.ROOT"` → `"Users"`, `"USERS.EDIT"` → `"Edit"`)
|
|
418
|
+
- **`path`** — the resolved breadcrumb path with params filled in (e.g. `"/users/edit/42"`)
|
|
419
|
+
- **`isCurrent`** — `true` only for the deepest (exact) match
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
import { defineRoutes, getBreadcrumbs } from "react-routes-forge";
|
|
423
|
+
|
|
424
|
+
const PATHS = defineRoutes({
|
|
425
|
+
HOME: "/",
|
|
426
|
+
USERS: {
|
|
427
|
+
ROOT: "/users",
|
|
428
|
+
EDIT: "/users/edit/:id",
|
|
429
|
+
},
|
|
430
|
+
SERVICES: {
|
|
431
|
+
BCC: {
|
|
432
|
+
EDIT: "/services/bcc/edit/:id",
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
} as const);
|
|
436
|
+
|
|
437
|
+
getBreadcrumbs(PATHS, "/users/edit/42");
|
|
438
|
+
// →
|
|
439
|
+
// [
|
|
440
|
+
// { key: "HOME", label: "Home", path: "/", isCurrent: false },
|
|
441
|
+
// { key: "USERS.ROOT", label: "Users", path: "/users", isCurrent: false },
|
|
442
|
+
// { key: "USERS.EDIT", label: "Edit", path: "/users/edit/42", isCurrent: true },
|
|
443
|
+
// ]
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Custom label resolver** — override the default key-to-label conversion:
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
getBreadcrumbs(PATHS, "/users/edit/42", {
|
|
450
|
+
labelResolver: (key) => key.split(".").pop()!.replace(/_/g, " ").toUpperCase(),
|
|
451
|
+
});
|
|
452
|
+
// → [{ label: "HOME" }, { label: "ROOT" }, { label: "EDIT" }]
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Pre-flattened input** — pass a cached `flattenRoutes()` result instead of the tree:
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
const flat = flattenRoutes(PATHS);
|
|
459
|
+
getBreadcrumbs(flat, "/users/edit/42"); // same result as passing the tree
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
365
464
|
## React hooks
|
|
366
465
|
|
|
367
466
|
Import these only if you're using React Router — they're tree-shakeable and won't be bundled unless imported.
|
|
@@ -429,6 +528,10 @@ const path = useResolvedPath("/users/:id", { id: 42 }, { tab: "info" });
|
|
|
429
528
|
|
|
430
529
|
// Strict mode — throws RangeError instead of warning on missing params
|
|
431
530
|
const path = useResolvedPath("/users/:id", {}, undefined, { strict: true });
|
|
531
|
+
|
|
532
|
+
// With hash fragment
|
|
533
|
+
const path = useResolvedPath("/page", {}, undefined, { hash: "section" });
|
|
534
|
+
// → '/page#section'
|
|
432
535
|
```
|
|
433
536
|
|
|
434
537
|
---
|
|
@@ -469,6 +572,32 @@ build(PATHS.USERS.ROOT, {}, { sort: "asc", page: 2 });
|
|
|
469
572
|
|
|
470
573
|
---
|
|
471
574
|
|
|
575
|
+
## Hash fragment support
|
|
576
|
+
|
|
577
|
+
URL hash fragments (`#section`) are supported in every path-resolving function — `.build()`, `build()`, and `useResolvedPath()` — via the `hash` option. The hash is appended after the query string, if any.
|
|
578
|
+
|
|
579
|
+
```ts
|
|
580
|
+
// Via fluent .build() on a dynamic route
|
|
581
|
+
PATHS.USERS.DETAILS.build({ id: 42 }, undefined, { hash: "profile" });
|
|
582
|
+
// → '/users/42#profile'
|
|
583
|
+
|
|
584
|
+
// With query + hash
|
|
585
|
+
PATHS.USERS.DETAILS.build({ id: 42 }, { tab: "info" }, { hash: "details" });
|
|
586
|
+
// → '/users/42?tab=info#details'
|
|
587
|
+
|
|
588
|
+
// Via standalone build()
|
|
589
|
+
build("/page", {}, undefined, { hash: "section" });
|
|
590
|
+
// → '/page#section'
|
|
591
|
+
|
|
592
|
+
// Via useResolvedPath
|
|
593
|
+
useResolvedPath("/users/:id", { id: 5 }, { tab: "billing" }, { hash: "invoice" });
|
|
594
|
+
// → '/users/5?tab=billing#invoice'
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
The leading `#` is added automatically — pass just the fragment name (e.g. `"details"`, not `"#details"`).
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
472
601
|
## Strict mode
|
|
473
602
|
|
|
474
603
|
By default, a missing required param leaves the `:param` placeholder in the resolved string and logs a `console.warn` — useful for catching bugs during development without crashing the app.
|
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,11 @@ type BuildPathOptions = {
|
|
|
53
53
|
* Useful in dev/test environments to catch missing params early.
|
|
54
54
|
*/
|
|
55
55
|
strict?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* URL hash fragment to append after the query string (e.g. `"section"` → `#section`).
|
|
58
|
+
* The leading `#` is added automatically.
|
|
59
|
+
*/
|
|
60
|
+
hash?: string;
|
|
56
61
|
};
|
|
57
62
|
/**
|
|
58
63
|
* A single entry produced by `flattenRoutes()`.
|
|
@@ -63,6 +68,38 @@ type FlatRoute = {
|
|
|
63
68
|
/** The raw path template string, e.g. `"/services/bcc/edit/:id"`. */
|
|
64
69
|
path: string;
|
|
65
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* A single breadcrumb entry produced by `getBreadcrumbs()`.
|
|
73
|
+
*
|
|
74
|
+
* @see {@link getBreadcrumbs}
|
|
75
|
+
*/
|
|
76
|
+
type BreadcrumbItem = {
|
|
77
|
+
/** Dot-joined key from the route tree, e.g. `"USERS.EDIT"`. */
|
|
78
|
+
key: string;
|
|
79
|
+
/** Human-readable label derived from the key (or from `labelResolver`). */
|
|
80
|
+
label: string;
|
|
81
|
+
/** The resolved path (params filled in), e.g. `"/users/edit/42"`. */
|
|
82
|
+
path: string;
|
|
83
|
+
/** `true` if this is the current page (exact match). */
|
|
84
|
+
isCurrent: boolean;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Options for `getBreadcrumbs()`.
|
|
88
|
+
*
|
|
89
|
+
* @see {@link getBreadcrumbs}
|
|
90
|
+
*/
|
|
91
|
+
type BreadcrumbOptions = {
|
|
92
|
+
/**
|
|
93
|
+
* Custom label resolver. Receives the dot-joined key
|
|
94
|
+
* (e.g. `"USERS.BCC.EDIT"`) and returns the display label.
|
|
95
|
+
*
|
|
96
|
+
* @default
|
|
97
|
+
* The default implementation takes the last key segment,
|
|
98
|
+
* replaces underscores with spaces, and capitalises the
|
|
99
|
+
* first letter (e.g. `"PRODUCT_DETAILS"` → `"Product Details"`).
|
|
100
|
+
*/
|
|
101
|
+
labelResolver?: (key: string) => string;
|
|
102
|
+
};
|
|
66
103
|
|
|
67
104
|
declare function buildPath(template: string, params: RouteParams, query?: QueryParams, options?: BuildPathOptions): string;
|
|
68
105
|
declare function extractParamNames(template: string): string[];
|
|
@@ -74,6 +111,29 @@ declare function extractParamsFromPath(template: string, resolvedPath: string):
|
|
|
74
111
|
declare function joinPaths(...segments: string[]): string;
|
|
75
112
|
declare function build(template: string, params: RouteParams, query?: QueryParams, options?: BuildPathOptions): string;
|
|
76
113
|
declare function getParamNames(template: string): string[];
|
|
114
|
+
/**
|
|
115
|
+
* Convert a route template string into an anchored `RegExp` for matching paths.
|
|
116
|
+
*
|
|
117
|
+
* Each `:param` segment becomes a capturing group so the returned regex
|
|
118
|
+
* can be used with `.test()` or `.exec()`.
|
|
119
|
+
*
|
|
120
|
+
* Query strings are **not** stripped — callers should split on `"?"` first
|
|
121
|
+
* (see {@link isActivePath} or {@link extractParamsFromPath} for higher-level
|
|
122
|
+
* helpers that handle this automatically).
|
|
123
|
+
*
|
|
124
|
+
* @param template - A route template, e.g. `"/users/:id"` or `"/users/:id/posts/:postId"`.
|
|
125
|
+
* @returns A `RegExp` anchored with `^` and `$` that captures param values.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const re = matchPath("/users/:id");
|
|
130
|
+
* re.test("/users/42"); // true
|
|
131
|
+
* re.exec("/users/42"); // ["/users/42", "42"]
|
|
132
|
+
* re.test("/users/42/posts"); // false (exact match)
|
|
133
|
+
* re.test("/users/42?page=1"); // true (query is part of captured value)
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
declare function matchPath(template: string): RegExp;
|
|
77
137
|
/**
|
|
78
138
|
* Walk a `defineRoutes` output tree and return a flat array of
|
|
79
139
|
* `{ key, path }` entries where `key` is the dot-joined key path from
|
|
@@ -91,6 +151,39 @@ declare function getParamNames(template: string): string[];
|
|
|
91
151
|
* if (dupes.length) console.warn('Duplicate paths:', dupes);
|
|
92
152
|
*/
|
|
93
153
|
declare function flattenRoutes(routes: Record<string, unknown>, prefix?: string): FlatRoute[];
|
|
154
|
+
/**
|
|
155
|
+
* Build a breadcrumb trail from a route tree or a flat route list.
|
|
156
|
+
*
|
|
157
|
+
* For a given `currentPath`, it walks the route tree and returns every route
|
|
158
|
+
* that is an ancestor of (or an exact match to) the current page. Ancestors
|
|
159
|
+
* are matched by prefix (e.g. `/users` matches `/users/edit/42/posts`).
|
|
160
|
+
*
|
|
161
|
+
* Dynamic params in ancestor paths are automatically resolved from the
|
|
162
|
+
* matched portion of the URL.
|
|
163
|
+
*
|
|
164
|
+
* @param routes - A route tree (output of `defineRoutes`) or a pre-flattened
|
|
165
|
+
* array from `flattenRoutes()`.
|
|
166
|
+
* @param currentPath - The current URL (with or without query string).
|
|
167
|
+
* @param options - Optional label resolver.
|
|
168
|
+
* @returns An array of {@link BreadcrumbItem} ordered by depth
|
|
169
|
+
* (most general first), where the last item is the current page.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* const PATHS = defineRoutes({
|
|
174
|
+
* HOME: "/",
|
|
175
|
+
* USERS: { ROOT: "/users", EDIT: "/users/edit/:id" },
|
|
176
|
+
* } as const);
|
|
177
|
+
*
|
|
178
|
+
* getBreadcrumbs(PATHS, "/users/edit/42");
|
|
179
|
+
* // → [
|
|
180
|
+
* // { key: "HOME", label: "Home", path: "/", isCurrent: false },
|
|
181
|
+
* // { key: "USERS.ROOT", label: "Users", path: "/users", isCurrent: false },
|
|
182
|
+
* // { key: "USERS.EDIT", label: "Edit", path: "/users/edit/42", isCurrent: true },
|
|
183
|
+
* // ]
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
declare function getBreadcrumbs(routes: Record<string, unknown> | FlatRoute[], currentPath: string, options?: BreadcrumbOptions): BreadcrumbItem[];
|
|
94
187
|
|
|
95
188
|
type RouteInput = {
|
|
96
189
|
[key: string]: string | RouteInput;
|
|
@@ -154,4 +247,4 @@ declare function useNavigateTo(): (path: string, options?: NavigateOptions) => v
|
|
|
154
247
|
*/
|
|
155
248
|
declare function useResolvedPath(template: string, params: RouteParams, query?: QueryParams, options?: BuildPathOptions): string;
|
|
156
249
|
|
|
157
|
-
export { type BuildPathOptions, type ExtractParams, type FlatRoute, type PathParams, type QueryParams, type RouteBuilder, type RouteLeaf, type RouteMap, type RouteParam, type RouteParams, type RoutePath, build, buildPath, defineRoutes, extractParamNames, extractParamsFromPath, flattenRoutes, getParamNames, isActivePath, isDynamic, joinPaths, useNavigateTo, useResolvedPath, useRouteParams };
|
|
250
|
+
export { type BreadcrumbItem, type BreadcrumbOptions, type BuildPathOptions, type ExtractParams, type FlatRoute, type PathParams, type QueryParams, type RouteBuilder, type RouteLeaf, type RouteMap, type RouteParam, type RouteParams, type RoutePath, build, buildPath, defineRoutes, extractParamNames, extractParamsFromPath, flattenRoutes, getBreadcrumbs, getParamNames, isActivePath, isDynamic, joinPaths, matchPath, useNavigateTo, useResolvedPath, useRouteParams };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var R=()=>/:([^/]+)/g,w=()=>/[.*+?^${}()|[\]\\]/g;function v(t){return t.replace(w(),"\\$&")}function y(t){return v(t).replace(R(),"([^/]+)")}function x(t,e,a){let n=t,s=new URLSearchParams;if(e)for(let[u,r]of Object.entries(e))r!=null&&(Array.isArray(r)?r.forEach(i=>{i!=null&&s.append(u,String(i))}):s.append(u,String(r)));let o=s.toString();return o&&(n+=(n.includes("?")?"&":"?")+o),a&&(n+="#"+a),n}function c(t,e,a,n){let s=p(t),o=s.filter(r=>e[r]===void 0||e[r]===null),u=s.reduce((r,i)=>{let l=e[i],g=l==null?`:${i}`:String(l);return r.replace(new RegExp(`:${v(i)}\\??(?=/|$)`,"g"),g)},t);if(o.length>0){if(n?.strict)throw new RangeError(`[route-forge] Missing required param(s) ${o.map(i=>`":${i}"`).join(", ")} in template "${t}".`);globalThis.process?.env?.NODE_ENV!=="production"&&console.warn(`[route-forge] Unresolved params in path "${u}". Check that all :param segments have matching keys.`)}return x(u,a,n?.hash)}function p(t){return[...t.matchAll(R())].map(e=>e[1].replace(/\?$/,""))}function m(t){return R().test(t)}function E(t,e,a={exact:!0}){let n=t.split("?")[0]??"";return(a.exact?h(e):new RegExp(`^${y(e)}`)).test(n)}function f(t,e){let a=e.split("?")[0]??"",n=p(t),s=a.match(h(t));return s?Object.fromEntries(n.map((o,u)=>[o,s[u+1]??""])):{}}function N(...t){return"/"+t.map(n=>n.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function B(t,e,a,n){return c(t,e,a,n)}function $(t){return p(t)}function h(t){return new RegExp(`^${y(t)}$`)}function T(t,e=""){let a=[];for(let n of Object.keys(t)){let s=e?`${e}.${n}`:n,o=t[n];typeof o=="string"?a.push({key:s,path:o}):o instanceof String?a.push({key:s,path:o.valueOf()}):typeof o=="object"&&o!==null&&a.push(...T(o,s))}return a}function A(t){let e=t.split("."),a=e[e.length-1]??t;return(a==="ROOT"&&e.length>1?e[e.length-2]:a).replace(/_/g," ").toLowerCase().replace(/^\w/,s=>s.toUpperCase())}function Q(t,e,a){let n=Array.isArray(t)?t:T(t),s=e.split("?")[0]??"",o=a?.labelResolver??A,u=[];for(let r of n){let i=h(r.path),l=s.match(i);if(l){let P=f(r.path,l[0]),d=m(r.path)?c(r.path,P):r.path;u.push({key:r.key,resolvedPath:d,template:r.path,isCurrent:!0});continue}let g=new RegExp(`^${y(r.path)}`),b=s.match(g);if(b){let P=b[0],d=f(r.path,P),O=m(r.path)?c(r.path,d):r.path;u.push({key:r.key,resolvedPath:O,template:r.path,isCurrent:!1})}}return u.sort((r,i)=>r.template.length-i.template.length),u.map(r=>({key:r.key,label:o(r.key),path:r.resolvedPath,isCurrent:r.isCurrent}))}var j=t=>typeof t=="object"&&t!==null;function S(t){let e=p(t),a=new String(t);return a.build=(n,s,o)=>c(t,n,s,o),a.paramNames=e,a}function k(t){let e={};for(let a in t){if(!Object.prototype.hasOwnProperty.call(t,a))continue;let n=t[a];typeof n=="string"?e[a]=m(n)?S(n):n:j(n)&&(e[a]=k(n))}return e}function D(t){return k(t)}import{useParams as I,useNavigate as C,generatePath as F}from"react-router-dom";function K(){return I()}function M(){let t=C();return(e,a)=>{t(e,a)}}function _(t,e,a,n){if(!p(t).every(r=>e[r]!==void 0&&e[r]!==null))return c(t,e,a,n);let u=F(t,Object.fromEntries(Object.entries(e).map(([r,i])=>[r,String(i)])));return x(u,a,n?.hash)}export{B as build,c as buildPath,D as defineRoutes,p as extractParamNames,f as extractParamsFromPath,T as flattenRoutes,Q as getBreadcrumbs,$ as getParamNames,E as isActivePath,m as isDynamic,N as joinPaths,h as matchPath,M as useNavigateTo,_ as useResolvedPath,K as useRouteParams};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/utils.ts","../src/core/defineRoutes.ts","../src/hooks/index.ts"],"sourcesContent":["import type { QueryParams, RouteParam, RouteParams, BuildPathOptions, FlatRoute } from \"../types\";\n\n/** Returns a fresh RegExp each call — avoids shared `lastIndex` state on /g patterns. */\nconst PATH_PARAM_RE = () => /:([^/]+)/g;\nconst ESCAPE_RE = () => /[.*+?^${}()|[\\]\\\\]/g;\n\nfunction escapeRegex(value: string): string {\n return value.replace(ESCAPE_RE(), \"\\\\$&\");\n}\n\nfunction createTemplatePattern(template: string): string {\n return escapeRegex(template).replace(PATH_PARAM_RE(), \"([^/]+)\");\n}\n\nexport function appendQuery(path: string, query?: QueryParams): string {\n if (!query) return path;\n\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n value.forEach((v) => {\n if (v !== undefined && v !== null) searchParams.append(key, String(v));\n });\n } else {\n searchParams.append(key, String(value));\n }\n }\n\n const queryString = searchParams.toString();\n if (!queryString) return path;\n\n return path + (path.includes(\"?\") ? \"&\" : \"?\") + queryString;\n}\n\nexport function buildPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n const paramNames = extractParamNames(template);\n const unresolved = paramNames.filter(\n (name) => params[name] === undefined || params[name] === null,\n );\n\n const resolved = paramNames.reduce((path, name) => {\n const value = params[name];\n const replacement = value === undefined || value === null ? `:${name}` : String(value);\n return path.replace(\n new RegExp(`:${escapeRegex(name)}\\\\??(?=/|$)`, \"g\"),\n replacement,\n );\n }, template);\n\n if (unresolved.length > 0) {\n if (options?.strict) {\n throw new RangeError(\n `[route-forge] Missing required param(s) ${unresolved.map((p) => `\":${p}\"`).join(\", \")} in template \"${template}\".`,\n );\n }\n\n const runtimeProcess = (\n globalThis as typeof globalThis & {\n process?: {\n env?: Record<string, string | undefined>;\n };\n }\n ).process;\n\n if (runtimeProcess?.env?.NODE_ENV !== \"production\") {\n console.warn(\n `[route-forge] Unresolved params in path \"${resolved}\". ` +\n `Check that all :param segments have matching keys.`,\n );\n }\n }\n\n return appendQuery(resolved, query);\n}\n\nexport function extractParamNames(template: string): string[] {\n return [...template.matchAll(PATH_PARAM_RE())].map(\n (match) => (match[1] as string).replace(/\\?$/, \"\"),\n );\n}\n\nexport function isDynamic(path: string): boolean {\n return PATH_PARAM_RE().test(path);\n}\n\nexport function isActivePath(\n currentPath: string,\n template: string,\n options: { exact?: boolean } = { exact: true },\n): boolean {\n const pathWithoutSearch = currentPath.split(\"?\")[0] ?? \"\";\n const pattern = createTemplatePattern(template);\n const regex = options.exact\n ? new RegExp(`^${pattern}$`)\n : new RegExp(`^${pattern}`);\n\n return regex.test(pathWithoutSearch);\n}\n\nexport function extractParamsFromPath(\n template: string,\n resolvedPath: string,\n): Record<string, string> {\n const pathWithoutSearch = resolvedPath.split(\"?\")[0] ?? \"\";\n const paramNames = extractParamNames(template);\n const regex = new RegExp(`^${createTemplatePattern(template)}$`);\n const match = pathWithoutSearch.match(regex);\n\n if (!match) return {};\n\n return Object.fromEntries(\n paramNames.map((name, index) => [name, match[index + 1] ?? \"\"]),\n );\n}\n\nexport function joinPaths(...segments: string[]): string {\n return (\n \"/\" +\n segments\n .map((segment) => segment.replace(/^\\/+|\\/+$/g, \"\"))\n .filter(Boolean)\n .join(\"/\")\n );\n}\n\nexport function build(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n\nexport function getParamNames(template: string): string[] {\n return extractParamNames(template);\n}\n\n/**\n * Walk a `defineRoutes` output tree and return a flat array of\n * `{ key, path }` entries where `key` is the dot-joined key path from\n * the root (e.g. `\"SERVICES.BCC.EDIT\"`) and `path` is the raw template\n * string (e.g. `\"/services/bcc/edit/:id\"`).\n *\n * Useful for:\n * - Generating sitemaps from a single source of truth.\n * - Detecting duplicate path strings across branches at startup:\n *\n * @example\n * const flat = flattenRoutes(PATHS);\n * const paths = flat.map((r) => r.path);\n * const dupes = paths.filter((p, i) => paths.indexOf(p) !== i);\n * if (dupes.length) console.warn('Duplicate paths:', dupes);\n */\nexport function flattenRoutes(\n routes: Record<string, unknown>,\n prefix = \"\",\n): FlatRoute[] {\n const entries: FlatRoute[] = [];\n\n for (const key of Object.keys(routes)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n const value = routes[key];\n\n if (typeof value === \"string\") {\n // Plain static string leaf.\n entries.push({ key: fullKey, path: value });\n } else if (value instanceof String) {\n // String-object leaf (wrapped dynamic path from defineRoutes).\n entries.push({ key: fullKey, path: value.valueOf() });\n } else if (typeof value === \"object\" && value !== null) {\n // Nested route group — recurse.\n entries.push(\n ...flattenRoutes(value as Record<string, unknown>, fullKey),\n );\n }\n // Anything else (functions, numbers, …) is silently skipped.\n }\n\n return entries;\n}\n\n","import { buildPath, extractParamNames, isDynamic } from \"./utils\";\nimport type {\n BuildPathOptions,\n ExtractParams,\n PathParams,\n QueryParams,\n RoutePath,\n} from \"../types\";\n\n// Re-export so consumers that import from 'core/defineRoutes' get the full surface\nexport { buildPath, extractParamNames, isDynamic } from \"./utils\";\n\ntype RouteInput = {\n [key: string]: string | RouteInput;\n};\n\ntype DynamicRoute<T extends string> = T extends `${string}:${string}`\n ? T & {\n build(params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions): RoutePath;\n paramNames: Array<ExtractParams<T>>;\n }\n : T;\n\ntype ResolvedRoutes<T extends RouteInput> = {\n [K in keyof T]: T[K] extends RouteInput\n ? ResolvedRoutes<T[K]>\n : T[K] extends string\n ? DynamicRoute<T[K]>\n : never;\n};\n\nconst isRouteGroup = (value: unknown): value is RouteInput =>\n typeof value === \"object\" && value !== null;\n\nfunction wrapDynamicPath<T extends string>(template: T): DynamicRoute<T> {\n const paramNames = extractParamNames(template);\n const wrapped = new String(template) as unknown as DynamicRoute<T> & {\n build: (params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions) => RoutePath;\n paramNames: Array<ExtractParams<T>>;\n };\n\n wrapped.build = (params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions) =>\n buildPath(template, params, query, options) as RoutePath;\n wrapped.paramNames = paramNames as Array<ExtractParams<T>>;\n\n return wrapped as unknown as DynamicRoute<T>;\n}\n\nfunction processRouteMap<T extends RouteInput>(routes: T): ResolvedRoutes<T> {\n const result = {} as ResolvedRoutes<T>;\n\n for (const key in routes) {\n if (!Object.prototype.hasOwnProperty.call(routes, key)) continue;\n\n const value = routes[key];\n\n if (typeof value === \"string\") {\n result[key] = (\n isDynamic(value) ? wrapDynamicPath(value) : value\n ) as ResolvedRoutes<T>[typeof key];\n } else if (isRouteGroup(value)) {\n result[key] = processRouteMap(\n value as RouteInput,\n ) as unknown as ResolvedRoutes<T>[typeof key];\n }\n }\n\n return result;\n}\n\nexport function defineRoutes<T extends RouteInput>(\n routes: T,\n): ResolvedRoutes<T> {\n return processRouteMap(routes);\n}\n","/**\n * React integration hooks for route-forge.\n * These are thin wrappers — import only if you're using React Router.\n */\n\nimport { useParams, useNavigate, generatePath } from \"react-router-dom\";\nimport type {\n ExtractParams,\n QueryParams,\n RouteParams,\n BuildPathOptions,\n} from \"../types\";\nimport { appendQuery, extractParamNames, buildPath } from \"../core/utils\";\n\n// ─── useRouteParams ──────────────────────────────────────────────────────────\n\n/**\n * A typed wrapper around React Router's `useParams`.\n *\n * @example\n * ```tsx\n * // Route: '/a/:x/b/:y/c/:z'\n * const { x, y, z } = useRouteParams<'/a/:x/b/:y/c/:z'>();\n * ```\n */\nexport function useRouteParams<T extends string>(): Record<\n ExtractParams<T>,\n string\n> {\n return useParams() as Record<ExtractParams<T>, string>;\n}\n\n// ─── useNavigateTo ──────────────────────────────────────────────────────────\n\ntype NavigateOptions = {\n replace?: boolean;\n state?: unknown;\n};\n\n/**\n * A typed `navigate` helper that accepts a resolved path (output of `.build()`)\n * or a plain static path, with optional navigation options.\n *\n * @example\n * ```tsx\n * const navigateTo = useNavigateTo();\n * navigateTo(PATHS.USERS.EDIT.build({ id: 42 }));\n * navigateTo(PATHS.HOME, { replace: true });\n * ```\n */\nexport function useNavigateTo() {\n const navigate = useNavigate();\n\n return (path: string, options?: NavigateOptions) => {\n navigate(path, options);\n };\n}\n\n// ─── useResolvedPath ─────────────────────────────────────────────────────────\n\n/**\n * Resolves a dynamic path template against params using React Router's\n * `generatePath`, with proper typing.\n *\n * Accepts the same `options` bag as `build()` / `buildPath()`:\n * - (default) soft-fail: `console.warn` and return the partial path with unresolved `:param` placeholders.\n * - `{ strict: true }`: throw a `RangeError` on missing params — matching `.build()`'s strict behaviour.\n *\n * When all params are present, resolution is delegated to React Router's `generatePath`,\n * which correctly handles splat (`*`) and optional (`:param?`) segments.\n *\n * @example\n * ```tsx\n * const path = useResolvedPath('/users/:id', { id: 42 }); // → '/users/42'\n * const path = useResolvedPath('/users/:id', {}, undefined, { strict: true }); // throws RangeError\n * ```\n */\nexport function useResolvedPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n const paramNames = extractParamNames(template);\n const hasAllParams = paramNames.every(\n (name) => params[name] !== undefined && params[name] !== null,\n );\n\n if (!hasAllParams) {\n // Let buildPath own all missing-param behaviour (warn/throw) —\n // avoids re-implementing (and duplicating) the same check here.\n return buildPath(template, params, query, options);\n }\n\n // All params present — use generatePath for correct splat / optional-segment handling.\n const path = generatePath(\n template,\n Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])),\n );\n\n return appendQuery(path, query);\n}\n"],"mappings":"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","names":["PATH_PARAM_RE","ESCAPE_RE","escapeRegex","value","createTemplatePattern","template","appendQuery","path","query","searchParams","key","v","queryString","buildPath","params","options","paramNames","extractParamNames","unresolved","name","resolved","replacement","p","match","isDynamic","isActivePath","currentPath","pathWithoutSearch","pattern","extractParamsFromPath","resolvedPath","regex","index","joinPaths","segments","segment","build","getParamNames","flattenRoutes","routes","prefix","entries","fullKey","isRouteGroup","value","wrapDynamicPath","template","paramNames","extractParamNames","wrapped","params","query","options","buildPath","processRouteMap","routes","result","key","isDynamic","defineRoutes","useParams","useNavigate","generatePath","useRouteParams","useParams","useNavigateTo","navigate","useNavigate","path","options","useResolvedPath","template","params","query","extractParamNames","name","buildPath","generatePath","k","v","appendQuery"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/utils.ts","../src/core/defineRoutes.ts","../src/hooks/index.ts"],"sourcesContent":["import type { BreadcrumbItem, BreadcrumbOptions, QueryParams, RouteParam, RouteParams, BuildPathOptions, FlatRoute } from \"../types\";\n\n/** Returns a fresh RegExp each call — avoids shared `lastIndex` state on /g patterns. */\nconst PATH_PARAM_RE = () => /:([^/]+)/g;\nconst ESCAPE_RE = () => /[.*+?^${}()|[\\]\\\\]/g;\n\nfunction escapeRegex(value: string): string {\n return value.replace(ESCAPE_RE(), \"\\\\$&\");\n}\n\nfunction createTemplatePattern(template: string): string {\n return escapeRegex(template).replace(PATH_PARAM_RE(), \"([^/]+)\");\n}\n\nexport function appendQuery(path: string, query?: QueryParams, hash?: string): string {\n let result = path;\n\n const searchParams = new URLSearchParams();\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n value.forEach((v) => {\n if (v !== undefined && v !== null) searchParams.append(key, String(v));\n });\n } else {\n searchParams.append(key, String(value));\n }\n }\n }\n\n const queryString = searchParams.toString();\n if (queryString) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + queryString;\n }\n\n if (hash) {\n result += \"#\" + hash;\n }\n\n return result;\n}\n\nexport function buildPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n const paramNames = extractParamNames(template);\n const unresolved = paramNames.filter(\n (name) => params[name] === undefined || params[name] === null,\n );\n\n const resolved = paramNames.reduce((path, name) => {\n const value = params[name];\n const replacement = value === undefined || value === null ? `:${name}` : String(value);\n return path.replace(\n new RegExp(`:${escapeRegex(name)}\\\\??(?=/|$)`, \"g\"),\n replacement,\n );\n }, template);\n\n if (unresolved.length > 0) {\n if (options?.strict) {\n throw new RangeError(\n `[route-forge] Missing required param(s) ${unresolved.map((p) => `\":${p}\"`).join(\", \")} in template \"${template}\".`,\n );\n }\n\n const runtimeProcess = (\n globalThis as typeof globalThis & {\n process?: {\n env?: Record<string, string | undefined>;\n };\n }\n ).process;\n\n if (runtimeProcess?.env?.NODE_ENV !== \"production\") {\n console.warn(\n `[route-forge] Unresolved params in path \"${resolved}\". ` +\n `Check that all :param segments have matching keys.`,\n );\n }\n }\n\n return appendQuery(resolved, query, options?.hash);\n}\n\nexport function extractParamNames(template: string): string[] {\n return [...template.matchAll(PATH_PARAM_RE())].map(\n (match) => (match[1] as string).replace(/\\?$/, \"\"),\n );\n}\n\nexport function isDynamic(path: string): boolean {\n return PATH_PARAM_RE().test(path);\n}\n\nexport function isActivePath(\n currentPath: string,\n template: string,\n options: { exact?: boolean } = { exact: true },\n): boolean {\n const pathWithoutSearch = currentPath.split(\"?\")[0] ?? \"\";\n const regex = options.exact\n ? matchPath(template)\n : new RegExp(`^${createTemplatePattern(template)}`);\n\n return regex.test(pathWithoutSearch);\n}\n\nexport function extractParamsFromPath(\n template: string,\n resolvedPath: string,\n): Record<string, string> {\n const pathWithoutSearch = resolvedPath.split(\"?\")[0] ?? \"\";\n const paramNames = extractParamNames(template);\n const match = pathWithoutSearch.match(matchPath(template));\n\n if (!match) return {};\n\n return Object.fromEntries(\n paramNames.map((name, index) => [name, match[index + 1] ?? \"\"]),\n );\n}\n\nexport function joinPaths(...segments: string[]): string {\n const processed = segments.map((segment) =>\n segment.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\"),\n );\n const filtered = processed.filter(Boolean);\n return \"/\" + filtered.join(\"/\");\n}\n\nexport function build(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n\nexport function getParamNames(template: string): string[] {\n return extractParamNames(template);\n}\n\n/**\n * Convert a route template string into an anchored `RegExp` for matching paths.\n *\n * Each `:param` segment becomes a capturing group so the returned regex\n * can be used with `.test()` or `.exec()`.\n *\n * Query strings are **not** stripped — callers should split on `\"?\"` first\n * (see {@link isActivePath} or {@link extractParamsFromPath} for higher-level\n * helpers that handle this automatically).\n *\n * @param template - A route template, e.g. `\"/users/:id\"` or `\"/users/:id/posts/:postId\"`.\n * @returns A `RegExp` anchored with `^` and `$` that captures param values.\n *\n * @example\n * ```ts\n * const re = matchPath(\"/users/:id\");\n * re.test(\"/users/42\"); // true\n * re.exec(\"/users/42\"); // [\"/users/42\", \"42\"]\n * re.test(\"/users/42/posts\"); // false (exact match)\n * re.test(\"/users/42?page=1\"); // true (query is part of captured value)\n * ```\n */\nexport function matchPath(template: string): RegExp {\n return new RegExp(`^${createTemplatePattern(template)}$`);\n}\n\n/**\n * Walk a `defineRoutes` output tree and return a flat array of\n * `{ key, path }` entries where `key` is the dot-joined key path from\n * the root (e.g. `\"SERVICES.BCC.EDIT\"`) and `path` is the raw template\n * string (e.g. `\"/services/bcc/edit/:id\"`).\n *\n * Useful for:\n * - Generating sitemaps from a single source of truth.\n * - Detecting duplicate path strings across branches at startup:\n *\n * @example\n * const flat = flattenRoutes(PATHS);\n * const paths = flat.map((r) => r.path);\n * const dupes = paths.filter((p, i) => paths.indexOf(p) !== i);\n * if (dupes.length) console.warn('Duplicate paths:', dupes);\n */\nexport function flattenRoutes(\n routes: Record<string, unknown>,\n prefix = \"\",\n): FlatRoute[] {\n const entries: FlatRoute[] = [];\n\n for (const key of Object.keys(routes)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n const value = routes[key];\n\n if (typeof value === \"string\") {\n // Plain static string leaf.\n entries.push({ key: fullKey, path: value });\n } else if (value instanceof String) {\n // String-object leaf (wrapped dynamic path from defineRoutes).\n entries.push({ key: fullKey, path: value.valueOf() });\n } else if (typeof value === \"object\" && value !== null) {\n // Nested route group — recurse.\n entries.push(\n ...flattenRoutes(value as Record<string, unknown>, fullKey),\n );\n }\n // Anything else (functions, numbers, …) is silently skipped.\n }\n\n return entries;\n}\n\nfunction deriveBreadcrumbLabel(key: string): string {\n const parts = key.split(\".\");\n const last = parts[parts.length - 1] ?? key;\n // Use the parent segment when the leaf is the conventional \"ROOT\" key,\n // so USERS.ROOT → \"Users\" rather than \"Root\".\n const raw = last === \"ROOT\" && parts.length > 1 ? parts[parts.length - 2]! : last;\n return raw\n .replace(/_/g, \" \")\n .toLowerCase()\n .replace(/^\\w/, (c) => c.toUpperCase());\n}\n\n/**\n * Build a breadcrumb trail from a route tree or a flat route list.\n *\n * For a given `currentPath`, it walks the route tree and returns every route\n * that is an ancestor of (or an exact match to) the current page. Ancestors\n * are matched by prefix (e.g. `/users` matches `/users/edit/42/posts`).\n *\n * Dynamic params in ancestor paths are automatically resolved from the\n * matched portion of the URL.\n *\n * @param routes - A route tree (output of `defineRoutes`) or a pre-flattened\n * array from `flattenRoutes()`.\n * @param currentPath - The current URL (with or without query string).\n * @param options - Optional label resolver.\n * @returns An array of {@link BreadcrumbItem} ordered by depth\n * (most general first), where the last item is the current page.\n *\n * @example\n * ```ts\n * const PATHS = defineRoutes({\n * HOME: \"/\",\n * USERS: { ROOT: \"/users\", EDIT: \"/users/edit/:id\" },\n * } as const);\n *\n * getBreadcrumbs(PATHS, \"/users/edit/42\");\n * // → [\n * // { key: \"HOME\", label: \"Home\", path: \"/\", isCurrent: false },\n * // { key: \"USERS.ROOT\", label: \"Users\", path: \"/users\", isCurrent: false },\n * // { key: \"USERS.EDIT\", label: \"Edit\", path: \"/users/edit/42\", isCurrent: true },\n * // ]\n * ```\n */\nexport function getBreadcrumbs(\n routes: Record<string, unknown> | FlatRoute[],\n currentPath: string,\n options?: BreadcrumbOptions,\n): BreadcrumbItem[] {\n const flat = Array.isArray(routes)\n ? routes\n : flattenRoutes(routes);\n const pathname = currentPath.split(\"?\")[0] ?? \"\";\n const labelFn = options?.labelResolver ?? deriveBreadcrumbLabel;\n\n const items: Array<{\n key: string;\n resolvedPath: string;\n template: string;\n isCurrent: boolean;\n }> = [];\n\n for (const route of flat) {\n const exactRe = matchPath(route.path);\n const exactMatch = pathname.match(exactRe);\n\n if (exactMatch) {\n const params = extractParamsFromPath(route.path, exactMatch[0]);\n const resolved = isDynamic(route.path)\n ? buildPath(route.path, params)\n : route.path;\n items.push({\n key: route.key,\n resolvedPath: resolved,\n template: route.path,\n isCurrent: true,\n });\n continue;\n }\n\n const prefixRe = new RegExp(`^${createTemplatePattern(route.path)}`);\n const prefixMatch = pathname.match(prefixRe);\n\n if (prefixMatch) {\n const matchedPortion = prefixMatch[0];\n const params = extractParamsFromPath(route.path, matchedPortion);\n const resolved = isDynamic(route.path)\n ? buildPath(route.path, params)\n : route.path;\n items.push({\n key: route.key,\n resolvedPath: resolved,\n template: route.path,\n isCurrent: false,\n });\n }\n }\n\n items.sort((a, b) => a.template.length - b.template.length);\n\n return items.map((item) => ({\n key: item.key,\n label: labelFn(item.key),\n path: item.resolvedPath,\n isCurrent: item.isCurrent,\n }));\n}\n\n","import { buildPath, extractParamNames, isDynamic } from \"./utils\";\nimport type {\n BuildPathOptions,\n ExtractParams,\n PathParams,\n QueryParams,\n RoutePath,\n} from \"../types\";\n\n// Re-export so consumers that import from 'core/defineRoutes' get the full surface\nexport { buildPath, extractParamNames, isDynamic } from \"./utils\";\n\ntype RouteInput = {\n [key: string]: string | RouteInput;\n};\n\ntype DynamicRoute<T extends string> = T extends `${string}:${string}`\n ? T & {\n build(params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions): RoutePath;\n paramNames: Array<ExtractParams<T>>;\n }\n : T;\n\ntype ResolvedRoutes<T extends RouteInput> = {\n [K in keyof T]: T[K] extends RouteInput\n ? ResolvedRoutes<T[K]>\n : T[K] extends string\n ? DynamicRoute<T[K]>\n : never;\n};\n\nconst isRouteGroup = (value: unknown): value is RouteInput =>\n typeof value === \"object\" && value !== null;\n\nfunction wrapDynamicPath<T extends string>(template: T): DynamicRoute<T> {\n const paramNames = extractParamNames(template);\n const wrapped = new String(template) as unknown as DynamicRoute<T> & {\n build: (params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions) => RoutePath;\n paramNames: Array<ExtractParams<T>>;\n };\n\n wrapped.build = (params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions) =>\n buildPath(template, params, query, options) as RoutePath;\n wrapped.paramNames = paramNames as Array<ExtractParams<T>>;\n\n return wrapped as unknown as DynamicRoute<T>;\n}\n\nfunction processRouteMap<T extends RouteInput>(routes: T): ResolvedRoutes<T> {\n const result = {} as ResolvedRoutes<T>;\n\n for (const key in routes) {\n if (!Object.prototype.hasOwnProperty.call(routes, key)) continue;\n\n const value = routes[key];\n\n if (typeof value === \"string\") {\n result[key] = (\n isDynamic(value) ? wrapDynamicPath(value) : value\n ) as ResolvedRoutes<T>[typeof key];\n } else if (isRouteGroup(value)) {\n result[key] = processRouteMap(\n value as RouteInput,\n ) as unknown as ResolvedRoutes<T>[typeof key];\n }\n }\n\n return result;\n}\n\nexport function defineRoutes<T extends RouteInput>(\n routes: T,\n): ResolvedRoutes<T> {\n return processRouteMap(routes);\n}\n","/**\n * React integration hooks for route-forge.\n * These are thin wrappers — import only if you're using React Router.\n */\n\nimport { useParams, useNavigate, generatePath } from \"react-router-dom\";\nimport type {\n ExtractParams,\n QueryParams,\n RouteParams,\n BuildPathOptions,\n} from \"../types\";\nimport { appendQuery, extractParamNames, buildPath } from \"../core/utils\";\n\n// ─── useRouteParams ──────────────────────────────────────────────────────────\n\n/**\n * A typed wrapper around React Router's `useParams`.\n *\n * @example\n * ```tsx\n * // Route: '/a/:x/b/:y/c/:z'\n * const { x, y, z } = useRouteParams<'/a/:x/b/:y/c/:z'>();\n * ```\n */\nexport function useRouteParams<T extends string>(): Record<\n ExtractParams<T>,\n string\n> {\n return useParams() as Record<ExtractParams<T>, string>;\n}\n\n// ─── useNavigateTo ──────────────────────────────────────────────────────────\n\ntype NavigateOptions = {\n replace?: boolean;\n state?: unknown;\n};\n\n/**\n * A typed `navigate` helper that accepts a resolved path (output of `.build()`)\n * or a plain static path, with optional navigation options.\n *\n * @example\n * ```tsx\n * const navigateTo = useNavigateTo();\n * navigateTo(PATHS.USERS.EDIT.build({ id: 42 }));\n * navigateTo(PATHS.HOME, { replace: true });\n * ```\n */\nexport function useNavigateTo() {\n const navigate = useNavigate();\n\n return (path: string, options?: NavigateOptions) => {\n navigate(path, options);\n };\n}\n\n// ─── useResolvedPath ─────────────────────────────────────────────────────────\n\n/**\n * Resolves a dynamic path template against params using React Router's\n * `generatePath`, with proper typing.\n *\n * Accepts the same `options` bag as `build()` / `buildPath()`:\n * - (default) soft-fail: `console.warn` and return the partial path with unresolved `:param` placeholders.\n * - `{ strict: true }`: throw a `RangeError` on missing params — matching `.build()`'s strict behaviour.\n *\n * When all params are present, resolution is delegated to React Router's `generatePath`,\n * which correctly handles splat (`*`) and optional (`:param?`) segments.\n *\n * @example\n * ```tsx\n * const path = useResolvedPath('/users/:id', { id: 42 }); // → '/users/42'\n * const path = useResolvedPath('/users/:id', {}, undefined, { strict: true }); // throws RangeError\n * ```\n */\nexport function useResolvedPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n const paramNames = extractParamNames(template);\n const hasAllParams = paramNames.every(\n (name) => params[name] !== undefined && params[name] !== null,\n );\n\n if (!hasAllParams) {\n // Let buildPath own all missing-param behaviour (warn/throw) —\n // avoids re-implementing (and duplicating) the same check here.\n return buildPath(template, params, query, options);\n }\n\n // All params present — use generatePath for correct splat / optional-segment handling.\n const path = generatePath(\n template,\n Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])),\n );\n\n return appendQuery(path, query, options?.hash);\n}\n"],"mappings":"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","names":["PATH_PARAM_RE","ESCAPE_RE","escapeRegex","value","createTemplatePattern","template","appendQuery","path","query","hash","result","searchParams","key","v","queryString","buildPath","params","options","paramNames","extractParamNames","unresolved","name","resolved","replacement","p","match","isDynamic","isActivePath","currentPath","pathWithoutSearch","matchPath","extractParamsFromPath","resolvedPath","index","joinPaths","segments","segment","build","getParamNames","flattenRoutes","routes","prefix","entries","fullKey","deriveBreadcrumbLabel","parts","last","c","getBreadcrumbs","flat","pathname","labelFn","items","route","exactRe","exactMatch","prefixRe","prefixMatch","matchedPortion","a","b","item","isRouteGroup","value","wrapDynamicPath","template","paramNames","extractParamNames","wrapped","params","query","options","buildPath","processRouteMap","routes","result","key","isDynamic","defineRoutes","useParams","useNavigate","generatePath","useRouteParams","useParams","useNavigateTo","navigate","useNavigate","path","options","useResolvedPath","template","params","query","extractParamNames","name","buildPath","generatePath","k","v","appendQuery"]}
|