react-routes-forge 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,11 +22,14 @@
22
22
  - [API reference](#api-reference)
23
23
  - [`defineRoutes(routeMap)`](#defineroutesroutemap)
24
24
  - [`build(template, params, query?, options?)`](#buildtemplate-params-query-options)
25
+ - [`buildPath(template, params, query?, options?)`](#buildpathtemplate-params-query-options)
25
26
  - [`isActivePath(currentPath, template, options?)`](#isactivepathcurrentpath-template-options)
26
27
  - [`extractParamsFromPath(template, resolvedPath)`](#extractparamsfrompathtemplate-resolvedpath)
27
28
  - [`matchPath(template, options?)`](#matchpathtemplate-options)
28
29
  - [`joinPaths(...segments)`](#joinpathssegments)
29
30
  - [`getParamNames(template)`](#getparamnamestemplate)
31
+ - [`extractParamNames(template)`](#extractparamnamestemplate)
32
+ - [`isDynamic(template)`](#isdynamictemplate)
30
33
  - [`flattenRoutes(routes)`](#flattenroutesroutes)
31
34
  - [`getBreadcrumbs(routes, currentPath, options?)`](#getbreadcrumbsroutes-currentpath-options)
32
35
  - [`appendQuery(path, query?, hash?)`](#appendquerypath-query-hash)
@@ -165,13 +168,13 @@ That's the entire API surface you need for most apps. Everything below covers th
165
168
 
166
169
  ### Route types
167
170
 
168
- | Route type | Example | Behaves as | Gains |
169
- | ----------- | ----------------------- | --------------------------------------- | --------------------------------------------------------------------- |
170
- | **Static** | `HOME: '/'` | String-like (coercible to its template) | `.build(query?, options?)` — attach query/hash, no params to fill |
171
- | **Dynamic** | `DETAILS: '/users/:id'` | String-like (coercible to its template) | `.build(params, query?, options?)` and `.paramNames` |
172
- | **Splat** | `FILES: '/files/*'` | String-like (coercible to its template) | `.build(params, query?, options?)` and `.paramNames` |
171
+ | Route type | Example | Behaves as | Gains |
172
+ | ----------- | ----------------------- | ------------------------------------- | --------------------------------------------------------------------- |
173
+ | **Static** | `HOME: '/'` | A primitive string (its template) | `.build(query?, options?)` — attach query/hash, no params to fill |
174
+ | **Dynamic** | `DETAILS: '/users/:id'` | A primitive string (its template) | `.build(params, query?, options?)` and `.paramNames` |
175
+ | **Splat** | `FILES: '/files/*'` | A primitive string (its template) | `.build(params, query?, options?)` and `.paramNames` |
173
176
 
174
- `defineRoutes()` walks your route object recursively, wrapping every path in a string-coercible object and attaching a `.build()` helper so both static and dynamic routes can carry a query string or hash. Dynamic paths (containing a `:param` segment or a trailing `/*` splat) additionally gain `.paramNames`.
177
+ `defineRoutes()` walks your route object recursively, returning every path as a genuine primitive string. `.build()` (and `.paramNames` on dynamic paths) are attached to `String.prototype` once, so both static and dynamic routes can carry a query string or hash. Dynamic paths (containing a `:param` segment or a trailing `/*` splat) additionally gain `.paramNames`.
175
178
 
176
179
  Param names are `[A-Za-z0-9_]` only (matching React Router), so a static suffix after a param stays literal — `/files/:name.json` builds `{ name: "report" }` → `/files/report.json`, and `:name.json` is **not** treated as a single param name.
177
180
 
@@ -197,11 +200,14 @@ Quick reference for everything the package exports — grouped by kind. Click th
197
200
  | Export | Purpose |
198
201
  | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
199
202
  | [`build(template, params, query?, options?)`](#buildtemplate-params-query-options) | Resolve a template into a URL without `defineRoutes` |
203
+ | [`buildPath(template, params, query?, options?)`](#buildpathtemplate-params-query-options) | Same as `build()` — the underlying resolver `build` aliases |
200
204
  | [`isActivePath(currentPath, template, options?)`](#isactivepathcurrentpath-template-options) | Check if a path matches a template (nav-highlighting) |
201
205
  | [`extractParamsFromPath(template, resolvedPath)`](#extractparamsfrompathtemplate-resolvedpath) | Pull param values back out of a resolved URL |
202
206
  | [`matchPath(template, options?)`](#matchpathtemplate-options) | Convert a route template into an anchored `RegExp` |
203
207
  | [`joinPaths(...segments)`](#joinpathssegments) | Join and normalize path segments |
204
208
  | [`getParamNames(template)`](#getparamnamestemplate) | List the `:param` names in a template |
209
+ | [`extractParamNames(template)`](#extractparamnamestemplate) | Same as `getParamNames()` — the canonical implementation |
210
+ | [`isDynamic(template)`](#isdynamictemplate) | `true` if a template contains a `:param` or trailing `/*` |
205
211
  | [`flattenRoutes(routes)`](#flattenroutesroutes) | Flatten a `PATHS` tree for sitemaps / duplicate detection |
206
212
  | [`getBreadcrumbs(routes, currentPath, options?)`](#getbreadcrumbsroutes-currentpath-options) | Build a breadcrumb trail from a route tree and current URL |
207
213
  | [`appendQuery(path, query?, hash?)`](#appendquerypath-query-hash) | Append query params / hash to an existing path |
@@ -227,7 +233,7 @@ Quick reference for everything the package exports — grouped by kind. Click th
227
233
 
228
234
  Creates a fully typed route object from a nested plain object.
229
235
 
230
- - Every path is string-coercible — use it directly anywhere a string is expected (e.g. `<Route path={...} />`).
236
+ - Every path is a genuine primitive string — use it directly anywhere a string is expected (e.g. `<Route path={...} />`).
231
237
  - Static paths gain **`.build(query?, options?)`** — attach a query string and/or hash fragment without params.
232
238
  - Dynamic paths (containing `:param`) and splat paths (trailing `/*`) gain:
233
239
  - **`.build(params, query?, options?)`** — resolves the template into a concrete URL
@@ -304,6 +310,21 @@ See [Splat (`/*`) segments](#splat--segments) for details.
304
310
 
305
311
  ---
306
312
 
313
+ ### `buildPath(template, params, query?, options?)`
314
+
315
+ The canonical path resolver that [`build()`](#buildtemplate-params-query-options) is an alias of — identical signature and behaviour. It is exported under both names; reach for `buildPath` when you want the name to match the internals (e.g. when reading the [`useResolvedPath()`](#useresolvedpathtemplate-params-query-options) wrapper), and `build` for a shorter call site.
316
+
317
+ ```ts
318
+ import { buildPath } from "react-routes-forge";
319
+
320
+ buildPath("/users/:id", { id: 42 }); // → '/users/42'
321
+ buildPath("/users", {}, { sort: "asc" }); // → '/users?sort=asc'
322
+ ```
323
+
324
+ All `build()` examples above apply verbatim to `buildPath`.
325
+
326
+ ---
327
+
307
328
  ### `isActivePath(currentPath, template, options?)`
308
329
 
309
330
  Checks whether a resolved path matches a route template — the building block for nav-highlighting ("is this link active?"). Query strings on `currentPath` are ignored automatically. It mirrors React Router's `NavLink` matching semantics:
@@ -411,6 +432,36 @@ getParamNames("/users"); // → []
411
432
 
412
433
  ---
413
434
 
435
+ ### `extractParamNames(template)`
436
+
437
+ The canonical implementation behind [`getParamNames()`](#getparamnamestemplate) — same signature and results, kept under both names for compatibility. Param names are `[A-Za-z0-9_]` only, recognized at the start of a segment; a trailing `/*` splat is reported as `['*']`.
438
+
439
+ ```ts
440
+ import { extractParamNames } from "react-routes-forge";
441
+
442
+ extractParamNames("/users/:id/posts/:postId"); // → ['id', 'postId']
443
+ extractParamNames("/files/*"); // → ['*']
444
+ extractParamNames("/users"); // → []
445
+ ```
446
+
447
+ ---
448
+
449
+ ### `isDynamic(template)`
450
+
451
+ Returns `true` when a template contains a `:param` segment or a trailing `/*` splat, and `false` otherwise. This is exactly the test `defineRoutes()` uses to decide whether a path gains `.paramNames`:
452
+
453
+ ```ts
454
+ import { isDynamic } from "react-routes-forge";
455
+
456
+ isDynamic("/users/:id"); // true
457
+ isDynamic("/users/:id?"); // true (optional params count)
458
+ isDynamic("/files/*"); // true (splat)
459
+ isDynamic("/users"); // false
460
+ isDynamic("/users/foo:bar"); // false (literal colon inside a segment)
461
+ ```
462
+
463
+ ---
464
+
414
465
  ### `flattenRoutes(routes)`
415
466
 
416
467
  Walks a `defineRoutes()` tree and returns a flat array of `{ key, path }` entries, where `key` is the dot-joined path from the root (e.g. `"SERVICES.BENEFICIARY_CARE_CENTER.EDIT"`) and `path` is the raw template string.
@@ -930,22 +981,23 @@ Everywhere the template string itself was used (e.g. `<Route path={PATHS.SERVICE
930
981
 
931
982
  ## Known behaviours & gotchas
932
983
 
933
- ### Routes are `String` objects, not primitives
984
+ ### Routes are genuine primitive strings
934
985
 
935
- `defineRoutes` wraps **every** path static, dynamic, and splat in [`String` objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/String) so that `.build()` (and `.paramNames` on dynamic routes) can be attached as properties. This means:
986
+ `defineRoutes()` returns **plain primitive strings** — `typeof` a route value is `"string"` and strict equality against the template works. `.build()` (and `.paramNames` on dynamic routes) are not own properties of the route value; they are attached to `String.prototype` once, so every route value can still call them lazily from its own text:
936
987
 
937
988
  ```ts
938
989
  // ✓ These all work as expected
990
+ PATHS.HOME === "/"; // true (strict equality works)
991
+ typeof PATHS.HOME; // 'string'
939
992
  String(PATHS.HOME); // '/'
940
993
  `${PATHS.USERS.EDIT}`; // '/users/edit/:id'
941
- PATHS.USERS.EDIT == "/users/edit/:id"; // true (loose equality)
942
-
943
- // ✗ Watch out for these
944
- typeof PATHS.HOME; // 'object' ← not 'string'
945
- PATHS.HOME === "/"; // false ← strict equality fails
994
+ PATHS.USERS.EDIT.build({ id: 42 }); // '/users/42' (via String.prototype)
995
+ PATHS.USERS.EDIT.paramNames; // ['id']
946
996
  ```
947
997
 
948
- Prefer template literals or explicit `String()` coercion when comparing route values, and avoid using them as plain object/`Map` keys.
998
+ Because route values are primitives, they work anywhere a plain string does — as object/`Map` keys, and directly with React Router's `<Link to={...}>` or `navigate()` (which branch on `typeof to === "string"`), no `.build()` call required for static paths.
999
+
1000
+ The flip side: since `.build` and `.paramNames` live on `String.prototype`, **every** string in your app has them, not just routes. `"/foo".build({})` → `'/foo'`, and `"/a/:b".paramNames` → `['b']` — `.paramNames` is a lazy getter that parses the string's own text, so it's always correct. Just be aware the helpers exist globally when you're using the library.
949
1001
 
950
1002
  ### `useResolvedPath` vs. the library's own `buildPath`
951
1003
 
@@ -0,0 +1,2 @@
1
+ function N(){return globalThis.process?.env?.NODE_ENV==="production"}function $(e){N()||console.warn(e)}var _=()=>/[.*+?^${}()|[\]\\]/g,p=()=>/(^|\/):([A-Za-z0-9_]+)(\?)?/g;function l(e){return e.replace(_(),"\\$&")}function E(e){let t=e.endsWith("/*"),r=t?e.slice(0,-2):e,s="",o=0;for(let i of r.matchAll(p())){let c=i.index??0,n=i[0];s+=l(r.slice(o,c));let[,a="",,u]=i;s+=u?`(?:${l(a)}([^/]+))?`:`${l(a)}([^/]+)`,o=c+(n?.length??0)}return s+l(r.slice(o))+(t?"(?:/(.*))?":"")}function y(e,t){return new RegExp(`:${l(t)}\\?(?![A-Za-z0-9_])`).test(e)}var P=new Map,C=/^/;function b(e){if(e==="/")return C;let t=P.get(e);if(t!==void 0)return t;let r=new RegExp(`^${E(e)}(?=/|$)`);return P.set(e,r),r}function S(e){try{return decodeURIComponent(e)}catch{return e}}var R=new Map;function m(e,t){let r=t?.end??!0,s=t?.caseSensitive?"":"i",o=`${e}:${r}:${s}`,i=R.get(o);if(i!==void 0)return i;let c=E(e),n=r?`^${c}$`:`^${c}(?=/|$)`,a=new RegExp(n,s);return R.set(o,a),a}function Q(){P.clear(),R.clear()}function h(e){let t=[...e.matchAll(p())].map(r=>r[2]);return e.endsWith("/*")&&t.push("*"),t}function M(e){return p().test(e)||e.endsWith("/*")}function W(e){return h(e)}function k(e,t){let r=t.split("?")[0]??"",s=h(e),o=r.match(m(e));if(!o)return{};let i={};return s.forEach((c,n)=>{let a=o[n+1];a!==void 0&&(i[c]=S(a))}),i}function A(e,t,r){let s=e.indexOf("#"),o=s===-1?e:e.slice(0,s),i=s===-1?"":e.slice(s+1),c=new URLSearchParams;if(t)for(let[u,f]of Object.entries(t))f!=null&&(Array.isArray(f)?f.forEach(g=>{g!=null&&c.append(u,String(g))}):c.append(u,String(f)));let n=o,a=c.toString();return a&&(n+=(n.includes("?")?"&":"?")+a),r?n+="#"+r:i&&(n+="#"+i),n}function U(e,t){let r=e.indexOf("#"),s=r===-1?e:e.slice(0,r),o=s.indexOf("?");if(o===-1)return{};let i=new URLSearchParams(s.slice(o+1)),c={};for(let n of new Set(i.keys())){let u=i.getAll(n).map(f=>t?.coerceBooleans&&(f==="true"||f==="false")?f==="true":t?.coerceNumbers&&f.trim()!==""&&!isNaN(Number(f))?Number(f):f);c[n]=u.length>1?u:u[0]??""}return c}function O(e,t,r,s){let o=h(e),i=o.filter(n=>(t[n]===void 0||t[n]===null)&&!y(e,n)&&n!=="*"),c=o.reduce((n,a)=>{let u=t[a],f=u==null;if(a==="*"){if(f)return n.replace(/\/\*$/,"")||"/";let x=s?.encode===!1?String(u):String(u).split("/").map(d=>encodeURIComponent(d)).join("/");return n.replace(/\/\*$/,`/${x}`)}let g=new RegExp(`(^|/):${l(a)}\\??(?![A-Za-z0-9_])`,"g");return n.replace(g,(x,d)=>{if(f)return x.endsWith("?")?"":`${d}:${a}`;let w=s?.encode===!1?String(u):encodeURIComponent(String(u));return`${d}${w}`})},e);if(i.length>0){if(s?.strict)throw new RangeError(`[route-forge] Missing required param(s) ${i.map(n=>`":${n}"`).join(", ")} in template "${e}".`);$(`[route-forge] Unresolved params in path "${c}". Check that all :param segments have matching keys.`)}return A(c,r,s?.hash)}function L(e,t,r,s){return O(e,t,r,s)}function Z(...e){return"/"+e.map(s=>s.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function X(e,t,r={}){let s=r.exact??!0,o=r.caseSensitive??!1,i=(e.split("?")[0]??"").replace(/\/+$/,"")||"/",c=t.replace(/\/+$/,"")||"/",n=o?c:c.toLowerCase(),a=o?i:i.toLowerCase();return(s?m(n,{caseSensitive:o}):b(n)).test(a)}export{N as a,$ as b,b as c,m as d,Q as e,h as f,M as g,W as h,k as i,A as j,U as k,O as l,L as m,Z as n,X as o};
2
+ //# sourceMappingURL=chunk-IP4ZU25B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/environment.ts","../src/core/pattern.ts","../src/core/params.ts","../src/core/query.ts","../src/core/build.ts","../src/core/match.ts"],"sourcesContent":["/**\n * Returns `true` when running in a production bundle (suppresses dev warnings).\n */\nexport function isProduction(): boolean {\n const runtimeProcess = (\n globalThis as typeof globalThis & {\n process?: { env?: Record<string, string | undefined> };\n }\n ).process;\n return runtimeProcess?.env?.NODE_ENV === \"production\";\n}\n\n/**\n * Emits a `console.warn` in non-production environments.\n * Shared by the core utilities and `defineRoutes()` so the production check\n * lives in one place.\n */\nexport function devWarn(message: string): void {\n if (!isProduction()) {\n console.warn(message);\n }\n}\n","/** Returns a fresh RegExp each call — avoids shared `lastIndex` state on /g patterns. */\nconst ESCAPE_RE = () => /[.*+?^${}()|[\\]\\\\]/g;\n\n/**\n * Matches a `:param` token that starts a URL segment.\n *\n * A param is only recognized when its `:` sits at the very start of the\n * template or is immediately preceded by `/`, so a literal colon inside a\n * segment (e.g. `/users/foo:bar`) is not treated as a param. Param names are\n * restricted to `[A-Za-z0-9_]` (matching React Router), so any static suffix\n * (e.g. `.json` in `/files/:name.json`) stays a literal. The optional `?`\n * marker (`:param?`) is captured separately so it can be handled as a\n * whole-segment modifier.\n */\nexport const PARAM_SEGMENT_RE = () => /(^|\\/):([A-Za-z0-9_]+)(\\?)?/g;\n\nexport function escapeRegex(value: string): string {\n return value.replace(ESCAPE_RE(), \"\\\\$&\");\n}\n\n/**\n * Convert a route template into an unanchored `RegExp` source string.\n *\n * - Required `:name` segments become a capturing group `([^/]+)`.\n * - Optional `:name?` segments become `(?:([^/]+))?` — the whole segment is\n * optional, matching React Router semantics.\n * - A trailing splat (`/*`) becomes `(?:/(.*))?`, capturing the remainder of\n * the path (including slashes) or nothing at all.\n * - Everything else is regex-escaped literally.\n */\nfunction createTemplatePattern(template: string): string {\n const splat = template.endsWith(\"/*\");\n const base = splat ? template.slice(0, -2) : template;\n let pattern = \"\";\n let cursor = 0;\n\n for (const match of base.matchAll(PARAM_SEGMENT_RE())) {\n const start = match.index ?? 0;\n const token = match[0];\n\n pattern += escapeRegex(base.slice(cursor, start));\n\n const [, boundary = \"\", , optional] = match;\n pattern += optional\n ? `(?:${escapeRegex(boundary)}([^/]+))?`\n : `${escapeRegex(boundary)}([^/]+)`;\n\n cursor = start + (token?.length ?? 0);\n }\n\n return (\n pattern + escapeRegex(base.slice(cursor)) + (splat ? \"(?:/(.*))?\" : \"\")\n );\n}\n\n/** Returns `true` when `name` is an optional (`:name?`) param in `template`. */\nexport function isOptionalParam(template: string, name: string): boolean {\n return new RegExp(`:${escapeRegex(name)}\\\\?(?![A-Za-z0-9_])`).test(template);\n}\n\n/**\n * Returns a `RegExp` that matches `template` as a path prefix, honoring\n * segment boundaries (`/users` matches `/users/42` but not `/usersettings`).\n *\n * The root template `/` is a prefix of every path.\n *\n * Compiled patterns are cached per template: the resulting `RegExp` has no\n * `/g` flag, so repeated `.test()`/`.exec()` calls are side-effect free and\n * sharing instances across callers is safe.\n */\nconst PREFIX_CACHE = new Map<string, RegExp>();\nconst ROOT_PREFIX_RE = /^/;\n\nexport function matchPrefix(template: string): RegExp {\n if (template === \"/\") return ROOT_PREFIX_RE;\n const cached = PREFIX_CACHE.get(template);\n if (cached !== undefined) return cached;\n const re = new RegExp(`^${createTemplatePattern(template)}(?=/|$)`);\n PREFIX_CACHE.set(template, re);\n return re;\n}\n\n/** Decode a URL-encoded param value, falling back to the raw value on error. */\nexport function safeDecode(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\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 *\n * Compiled patterns are cached per template (the regex has no `/g` flag, so\n * sharing instances across callers is safe).\n */\nconst PATH_CACHE = new Map<string, RegExp>();\n\nexport function matchPath(\n template: string,\n options?: { end?: boolean; caseSensitive?: boolean },\n): RegExp {\n const end = options?.end ?? true;\n const flags = options?.caseSensitive ? \"\" : \"i\";\n const cacheKey = `${template}:${end}:${flags}`;\n\n const cached = PATH_CACHE.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const basePattern = createTemplatePattern(template);\n const pattern = end ? `^${basePattern}$` : `^${basePattern}(?=/|$)`;\n const re = new RegExp(pattern, flags);\n PATH_CACHE.set(cacheKey, re);\n return re;\n}\n\n/**\n * Clears internal regex cache maps (PREFIX_CACHE and PATH_CACHE).\n * Useful in test suites to prevent cached patterns from leaking across test cases.\n */\nexport function clearPathCache(): void {\n PREFIX_CACHE.clear();\n PATH_CACHE.clear();\n}\n","import { PARAM_SEGMENT_RE, matchPath, safeDecode } from \"./pattern\";\n\n/**\n * Extract the `:param` names from a route template.\n *\n * A param is only recognized at the start of a segment (see\n * {@link PARAM_SEGMENT_RE}), so literal colons (`/users/foo:bar`) and static\n * suffixes after a param (`/files/:name.json`) are not treated as params.\n * A trailing splat (`/*`) is reported as the `\"*\"` name.\n */\nexport function extractParamNames(template: string): string[] {\n const names = [...template.matchAll(PARAM_SEGMENT_RE())].map(\n (match) => match[2] as string,\n );\n if (template.endsWith(\"/*\")) names.push(\"*\");\n return names;\n}\n\n/** Returns `true` when the template contains a `:param` or trailing `/*` splat. */\nexport function isDynamic(path: string): boolean {\n return PARAM_SEGMENT_RE().test(path) || path.endsWith(\"/*\");\n}\n\n/**\n * Alias of {@link extractParamNames} — kept for backwards compatibility.\n */\nexport function getParamNames(template: string): string[] {\n return extractParamNames(template);\n}\n\n/**\n * Extract the param values matched by `template` from a resolved path.\n *\n * The query string is stripped before matching, so `/users/42?tab=profile`\n * still yields `{ id: \"42\" }`. Values are URL-decoded back to their original\n * form; a non-matching path yields an empty object.\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 const result: Record<string, string> = {};\n paramNames.forEach((name, index) => {\n const raw = match[index + 1];\n if (raw !== undefined) result[name] = safeDecode(raw);\n });\n return result;\n}\n","import type { QueryParams } from \"../types\";\n\n/**\n * Append a query string and/or hash fragment to a path that may already\n * contain a query or hash.\n *\n * - Existing query pairs are preserved; new ones are joined with `&`.\n * - The query string is always inserted before any hash fragment, so an\n * existing `#section` on `path` is kept unless a new `hash` is given.\n *\n * @example\n * ```ts\n * appendQuery(\"/users?tab=list\", { page: 2 }); // → \"/users?tab=list&page=2\"\n * appendQuery(\"/users#top\", { tab: \"list\" }); // → \"/users?tab=list#top\"\n * ```\n */\nexport function appendQuery(\n path: string,\n query?: QueryParams,\n hash?: string,\n): string {\n const hashIdx = path.indexOf(\"#\");\n const base = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const existingHash = hashIdx === -1 ? \"\" : path.slice(hashIdx + 1);\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)\n searchParams.append(key, String(v));\n });\n } else {\n searchParams.append(key, String(value));\n }\n }\n }\n\n let result = base;\n const queryString = searchParams.toString();\n if (queryString) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + queryString;\n }\n\n if (hash) {\n result += \"#\" + hash;\n } else if (existingHash) {\n result += \"#\" + existingHash;\n }\n\n return result;\n}\n\n/**\n * Parse the query string out of a path (or bare query string) into a plain\n * object. Repeated keys become arrays; a single key is a scalar string.\n *\n * With `{ coerceBooleans: true }`, the strings `\"true\"`/`\"false\"` are\n * converted to actual booleans.\n *\n * @example\n * ```ts\n * extractQueryFromPath(\"/users/42?tab=profile&tag=a&tag=b\");\n * // → { tab: \"profile\", tag: [\"a\", \"b\"] }\n * extractQueryFromPath(\"/search?active=true\", { coerceBooleans: true });\n * // → { active: true }\n * ```\n */\nexport function extractQueryFromPath(\n path: string,\n options?: { coerceBooleans?: boolean; coerceNumbers?: boolean },\n): QueryParams {\n const hashIdx = path.indexOf(\"#\");\n const noHash = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const queryIdx = noHash.indexOf(\"?\");\n if (queryIdx === -1) return {};\n\n const params = new URLSearchParams(noHash.slice(queryIdx + 1));\n const result: QueryParams = {};\n\n for (const key of new Set(params.keys())) {\n const values = params.getAll(key);\n const parsed = values.map((v) => {\n if (options?.coerceBooleans && (v === \"true\" || v === \"false\")) {\n return v === \"true\";\n }\n if (options?.coerceNumbers && v.trim() !== \"\" && !isNaN(Number(v))) {\n return Number(v);\n }\n return v;\n });\n result[key] = parsed.length > 1 ? parsed : (parsed[0] ?? \"\");\n }\n\n return result;\n}\n","import { escapeRegex, isOptionalParam } from \"./pattern\";\nimport { extractParamNames } from \"./params\";\nimport { appendQuery } from \"./query\";\nimport { devWarn } from \"./environment\";\nimport type { BuildPathOptions, QueryParams, RouteParams } from \"../types\";\n\n/**\n * Resolve a route template against a params object, appending an optional\n * query string and hash fragment.\n *\n * Missing required params are left as their `:name` placeholder and a\n * `console.warn` is emitted — pass `{ strict: true }` to throw a `RangeError`\n * instead. Optional (`:param?`) segments are dropped entirely when missing;\n * a missing splat just drops the trailing `/*`. Values are URL-encoded by\n * default (`{ encode: false }` opts out).\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) =>\n (params[name] === undefined || params[name] === null) &&\n !isOptionalParam(template, name) &&\n // A missing splat simply drops the `/*` suffix (matching React Router,\n // where `/files/*` also matches `/files`).\n name !== \"*\",\n );\n\n const resolved = paramNames.reduce((path, name) => {\n const value = params[name];\n const missing = value === undefined || value === null;\n\n if (name === \"*\") {\n if (missing) return path.replace(/\\/\\*$/, \"\") || \"/\";\n // Splat values are path-like: preserve `/` separators but still\n // encode characters that could break the URL (`?`, `#`, spaces, …).\n const encoded =\n options?.encode === false\n ? String(value)\n : String(value)\n .split(\"/\")\n .map((segment) => encodeURIComponent(segment))\n .join(\"/\");\n return path.replace(/\\/\\*$/, `/${encoded}`);\n }\n\n const re = new RegExp(\n `(^|/):${escapeRegex(name)}\\\\??(?![A-Za-z0-9_])`,\n \"g\",\n );\n\n return path.replace(re, (match, boundary) => {\n if (missing) {\n // Optional segment: drop the whole `/segment`. Required: keep the\n // `:name` placeholder so strict mode can report it.\n return match.endsWith(\"?\") ? \"\" : `${boundary}:${name}`;\n }\n\n const encoded =\n options?.encode === false\n ? String(value)\n : encodeURIComponent(String(value));\n return `${boundary}${encoded}`;\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 devWarn(\n `[route-forge] Unresolved params in path \"${resolved}\". ` +\n `Check that all :param segments have matching keys.`,\n );\n }\n\n return appendQuery(resolved, query, options?.hash);\n}\n\n/**\n * Standalone alias of {@link buildPath} — resolves a template against params.\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\n/**\n * Join URL path segments into a single slash-prefixed path, normalising\n * duplicate slashes.\n *\n * @example\n * joinPaths(\"/api/\", \"/v1/\", \"users\"); // → \"/api/v1/users\"\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","import { matchPath, matchPrefix } from \"./pattern\";\n\n/**\n * Test whether `currentPath` matches `template`, mirroring React Router's\n * `NavLink` matching semantics:\n *\n * - Case-insensitive by default (pass `caseSensitive: true` to opt out).\n * - Trailing slashes are tolerated (`/users/` matches `/users`).\n * - `exact: true` (the default) requires a full match; `exact: false`\n * matches any path that starts with the template.\n */\nexport function isActivePath(\n currentPath: string,\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {},\n): boolean {\n // NOTE: default each property individually rather than relying on a\n // default *object* for `options` — JS default parameters only apply when\n // the whole argument is omitted, so `isActivePath(a, b, { caseSensitive: true })`\n // would otherwise silently lose the documented `exact: true` default.\n const exact = options.exact ?? true;\n const caseSensitive = options.caseSensitive ?? false;\n\n const pathname = (currentPath.split(\"?\")[0] ?? \"\").replace(/\\/+$/, \"\") || \"/\";\n const normalizedTemplate = template.replace(/\\/+$/, \"\") || \"/\";\n const target = caseSensitive\n ? normalizedTemplate\n : normalizedTemplate.toLowerCase();\n const candidate = caseSensitive ? pathname : pathname.toLowerCase();\n const regex = exact\n ? matchPath(target, { caseSensitive })\n : matchPrefix(target);\n\n return regex.test(candidate);\n}\n"],"mappings":"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","names":["isProduction","devWarn","message","ESCAPE_RE","PARAM_SEGMENT_RE","escapeRegex","value","createTemplatePattern","template","splat","base","pattern","cursor","match","start","token","boundary","optional","isOptionalParam","name","PREFIX_CACHE","ROOT_PREFIX_RE","matchPrefix","cached","re","safeDecode","PATH_CACHE","matchPath","options","end","flags","cacheKey","basePattern","clearPathCache","extractParamNames","template","names","PARAM_SEGMENT_RE","match","isDynamic","path","getParamNames","extractParamsFromPath","resolvedPath","pathWithoutSearch","paramNames","matchPath","result","name","index","raw","safeDecode","appendQuery","path","query","hash","hashIdx","base","existingHash","searchParams","key","value","v","result","queryString","extractQueryFromPath","options","noHash","queryIdx","params","parsed","buildPath","template","params","query","options","paramNames","extractParamNames","unresolved","name","isOptionalParam","resolved","path","value","missing","encoded","segment","re","escapeRegex","match","boundary","p","devWarn","appendQuery","build","joinPaths","segments","isActivePath","currentPath","template","options","exact","caseSensitive","pathname","normalizedTemplate","target","candidate","matchPath","matchPrefix"]}
@@ -1,2 +1,2 @@
1
- "use strict";var m=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var O=Object.prototype.hasOwnProperty;var C=(e,t)=>{for(var n in t)m(e,n,{get:t[n],enumerable:!0})},N=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of A(t))!O.call(e,a)&&a!==n&&m(e,a,{get:()=>t[a],enumerable:!(r=k(t,a))||r.enumerable});return e};var B=e=>N(m({},"__esModule",{value:!0}),e);var D={};C(D,{useActivePath:()=>z,useNavigateTo:()=>W,useResolvedPath:()=>H,useRouteParams:()=>M,useTypedSearchParams:()=>q});module.exports=B(D);var x=require("react"),p=require("react-router");var Q=()=>/[.*+?^${}()|[\]\\]/g,b=()=>/(^|\/):([A-Za-z0-9_]+)(\?)?/g;function T(){return globalThis.process?.env?.NODE_ENV==="production"}function _(e){T()||console.warn(e)}function g(e){return e.replace(Q(),"\\$&")}function v(e){let t=e.endsWith("/*"),n=t?e.slice(0,-2):e,r="",a=0;for(let o of n.matchAll(b())){let c=o.index??0,s=o[0];r+=g(n.slice(a,c));let[,i="",,l]=o;r+=l?`(?:${g(i)}([^/]+))?`:`${g(i)}([^/]+)`,a=c+(s?.length??0)}return r+g(n.slice(a))+(t?"(?:/(.*))?":"")}function F(e,t){return new RegExp(`:${g(t)}\\?(?![A-Za-z0-9_])`).test(e)}var y=new Map,I=/^/;function L(e){if(e==="/")return I;let t=y.get(e);if(t!==void 0)return t;let n=new RegExp(`^${v(e)}(?=/|$)`);return y.set(e,n),n}function P(e,t,n){let r=e.indexOf("#"),a=r===-1?e:e.slice(0,r),o=r===-1?"":e.slice(r+1),c=new URLSearchParams;if(t)for(let[l,u]of Object.entries(t))u!=null&&(Array.isArray(u)?u.forEach(d=>{d!=null&&c.append(l,String(d))}):c.append(l,String(u)));let s=a,i=c.toString();return i&&(s+=(s.includes("?")?"&":"?")+i),n?s+="#"+n:o&&(s+="#"+o),s}function $(e,t){let n=e.indexOf("#"),r=n===-1?e:e.slice(0,n),a=r.indexOf("?");if(a===-1)return{};let o=new URLSearchParams(r.slice(a+1)),c={};for(let s of new Set(o.keys())){let l=o.getAll(s).map(u=>t?.coerceBooleans&&(u==="true"||u==="false")?u==="true":t?.coerceNumbers&&u.trim()!==""&&!isNaN(Number(u))?Number(u):u);c[s]=l.length>1?l:l[0]??""}return c}function S(e,t,n,r){let a=U(e),o=a.filter(s=>(t[s]===void 0||t[s]===null)&&!F(e,s)&&s!=="*"),c=a.reduce((s,i)=>{let l=t[i],u=l==null;if(i==="*"){if(u)return s.replace(/\/\*$/,"")||"/";let h=r?.encode===!1?String(l):String(l).split("/").map(f=>encodeURIComponent(f)).join("/");return s.replace(/\/\*$/,`/${h}`)}let d=new RegExp(`(^|/):${g(i)}\\??(?![A-Za-z0-9_])`,"g");return s.replace(d,(h,f)=>{if(u)return h.endsWith("?")?"":`${f}:${i}`;let w=r?.encode===!1?String(l):encodeURIComponent(String(l));return`${f}${w}`})},e);if(o.length>0){if(r?.strict)throw new RangeError(`[route-forge] Missing required param(s) ${o.map(s=>`":${s}"`).join(", ")} in template "${e}".`);_(`[route-forge] Unresolved params in path "${c}". Check that all :param segments have matching keys.`)}return P(c,n,r?.hash)}function U(e){let t=[...e.matchAll(b())].map(n=>n[2]);return e.endsWith("/*")&&t.push("*"),t}function E(e,t,n={exact:!0,caseSensitive:!1}){let r=(e.split("?")[0]??"").replace(/\/+$/,"")||"/",a=t.replace(/\/+$/,"")||"/",o=n.caseSensitive?a:a.toLowerCase(),c=n.caseSensitive?r:r.toLowerCase();return(n.exact?j(o):L(o)).test(c)}var R=new Map;function j(e,t){let n=t?.end??!0,r=t?.caseSensitive?"":"i",a=`${e}:${n}:${r}`,o=R.get(a);if(o!==void 0)return o;let c=v(e),s=n?`^${c}$`:`^${c}(?=/|$)`,i=new RegExp(s,r);return R.set(a,i),i}function M(e){return(0,p.useParams)()}function W(){let e=(0,p.useNavigate)();return(0,x.useCallback)((t,n)=>{e(String(t),n)},[e])}function H(e,t,n,r){return S(e,t,n,r)}function z(e,t={exact:!0,caseSensitive:!1}){let n=(0,p.useLocation)();return E(n.pathname,e,t)}function q(e){let t=(0,p.useLocation)(),n=(0,p.useNavigate)(),r=$(t.search,e),a=(0,x.useCallback)((o,c)=>{let s=P("",o),i=new URLSearchParams(s.startsWith("?")?s.slice(1):s);n(`?${i.toString()}`,c)},[n]);return[r,a]}0&&(module.exports={useActivePath,useNavigateTo,useResolvedPath,useRouteParams,useTypedSearchParams});
1
+ "use strict";var x=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var z=(e,t)=>{for(var r in t)x(e,r,{get:t[r],enumerable:!0})},D=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of F(t))!H.call(e,s)&&s!==r&&x(e,s,{get:()=>t[s],enumerable:!(o=j(t,s))||o.enumerable});return e};var q=e=>D(x({},"__esModule",{value:!0}),e);var K={};z(K,{useActivePath:()=>W,useNavigateTo:()=>v,useResolvedPath:()=>k,useRouteParams:()=>b,useTypedSearchParams:()=>M});module.exports=q(K);var R=require("react-router");function b(e){return(0,R.useParams)()}var $=require("react"),E=require("react-router");function v(){let e=(0,E.useNavigate)();return(0,$.useCallback)((t,r)=>{e(String(t),r)},[e])}var Z=()=>/[.*+?^${}()|[\]\\]/g,h=()=>/(^|\/):([A-Za-z0-9_]+)(\?)?/g;function m(e){return e.replace(Z(),"\\$&")}function A(e){let t=e.endsWith("/*"),r=t?e.slice(0,-2):e,o="",s=0;for(let a of r.matchAll(h())){let i=a.index??0,n=a[0];o+=m(r.slice(s,i));let[,c="",,u]=a;o+=u?`(?:${m(c)}([^/]+))?`:`${m(c)}([^/]+)`,s=i+(n?.length??0)}return o+m(r.slice(s))+(t?"(?:/(.*))?":"")}function w(e,t){return new RegExp(`:${m(t)}\\?(?![A-Za-z0-9_])`).test(e)}var S=new Map,G=/^/;function O(e){if(e==="/")return G;let t=S.get(e);if(t!==void 0)return t;let r=new RegExp(`^${A(e)}(?=/|$)`);return S.set(e,r),r}var N=new Map;function y(e,t){let r=t?.end??!0,o=t?.caseSensitive?"":"i",s=`${e}:${r}:${o}`,a=N.get(s);if(a!==void 0)return a;let i=A(e),n=r?`^${i}$`:`^${i}(?=/|$)`,c=new RegExp(n,o);return N.set(s,c),c}function Q(e){let t=[...e.matchAll(h())].map(r=>r[2]);return e.endsWith("/*")&&t.push("*"),t}function g(e,t,r){let o=e.indexOf("#"),s=o===-1?e:e.slice(0,o),a=o===-1?"":e.slice(o+1),i=new URLSearchParams;if(t)for(let[u,p]of Object.entries(t))p!=null&&(Array.isArray(p)?p.forEach(l=>{l!=null&&i.append(u,String(l))}):i.append(u,String(p)));let n=s,c=i.toString();return c&&(n+=(n.includes("?")?"&":"?")+c),r?n+="#"+r:a&&(n+="#"+a),n}function T(e,t){let r=e.indexOf("#"),o=r===-1?e:e.slice(0,r),s=o.indexOf("?");if(s===-1)return{};let a=new URLSearchParams(o.slice(s+1)),i={};for(let n of new Set(a.keys())){let u=a.getAll(n).map(p=>t?.coerceBooleans&&(p==="true"||p==="false")?p==="true":t?.coerceNumbers&&p.trim()!==""&&!isNaN(Number(p))?Number(p):p);i[n]=u.length>1?u:u[0]??""}return i}function X(){return globalThis.process?.env?.NODE_ENV==="production"}function C(e){X()||console.warn(e)}function _(e,t,r,o){let s=Q(e),a=s.filter(n=>(t[n]===void 0||t[n]===null)&&!w(e,n)&&n!=="*"),i=s.reduce((n,c)=>{let u=t[c],p=u==null;if(c==="*"){if(p)return n.replace(/\/\*$/,"")||"/";let P=o?.encode===!1?String(u):String(u).split("/").map(f=>encodeURIComponent(f)).join("/");return n.replace(/\/\*$/,`/${P}`)}let l=new RegExp(`(^|/):${m(c)}\\??(?![A-Za-z0-9_])`,"g");return n.replace(l,(P,f)=>{if(p)return P.endsWith("?")?"":`${f}:${c}`;let U=o?.encode===!1?String(u):encodeURIComponent(String(u));return`${f}${U}`})},e);if(a.length>0){if(o?.strict)throw new RangeError(`[route-forge] Missing required param(s) ${a.map(n=>`":${n}"`).join(", ")} in template "${e}".`);C(`[route-forge] Unresolved params in path "${i}". Check that all :param segments have matching keys.`)}return g(i,r,o?.hash)}function k(e,t,r,o){return _(e,t,r,o)}var I=require("react-router");function B(e,t,r={}){let o=r.exact??!0,s=r.caseSensitive??!1,a=(e.split("?")[0]??"").replace(/\/+$/,"")||"/",i=t.replace(/\/+$/,"")||"/",n=s?i:i.toLowerCase(),c=s?a:a.toLowerCase();return(o?y(n,{caseSensitive:s}):O(n)).test(c)}function W(e,t={}){let r=(0,I.useLocation)();return B(r.pathname,e,t)}var L=require("react"),d=require("react-router");function M(e){let t=(0,d.useLocation)(),r=(0,d.useNavigate)(),o=T(t.search,e),s=(0,L.useCallback)((a,i)=>{let n=g("",a),c=new URLSearchParams(n.startsWith("?")?n.slice(1):n);r(`?${c.toString()}`,i)},[r]);return[o,s]}0&&(module.exports={useActivePath,useNavigateTo,useResolvedPath,useRouteParams,useTypedSearchParams});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/index.ts","../../src/core/utils.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useParams, useNavigate, useLocation } from \"react-router\";\nimport type {\n ExtractParams,\n QueryParams,\n RouteParams,\n BuildPathOptions,\n} from \"../types\";\nimport { buildPath, isActivePath, extractQueryFromPath, appendQuery } from \"../core/utils\";\n\n// ─── useRouteParams ──────────────────────────────────────────────────────────\n\n/**\n * A typed wrapper around React Router's `useParams`.\n *\n * Pass a route template as a type parameter, or a dynamic route value from a\n * `defineRoutes()` tree for automatic type inference:\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 * // Or pass a route from your PATHS tree — params are inferred from it:\n * const PATHS = defineRoutes({ USERS: { EDIT: '/users/edit/:id' } } as const);\n * const { id } = useRouteParams(PATHS.USERS.EDIT);\n * ```\n */\n// Overload 1: no-arg generic — caller provides the template literal as T\nexport function useRouteParams<T extends string = string>(): Record<\n ExtractParams<T>,\n string\n>;\n// Overload 2: pass a route from defineRoutes() — P is inferred from paramNames array element type\nexport function useRouteParams<P extends string>(\n route: { readonly paramNames: ReadonlyArray<P> | Array<P> },\n): Record<P, string>;\n// Implementation\nexport function useRouteParams<P extends string>(\n _route?: { readonly paramNames: ReadonlyArray<P> | Array<P> },\n): Record<string, string> {\n return useParams() as Record<string, string>;\n}\n\n// ─── useNavigateTo ──────────────────────────────────────────────────────────\n\nexport type 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 route value straight from `defineRoutes()` (e.g. a `String` object or a\n * primitive string), with optional navigation options.\n *\n * `String` objects (used by route values so they can carry `.build()`) are\n * coerced to primitives, since React Router's `navigate()` ignores them.\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 useCallback(\n (path: string, options?: NavigateOptions) => {\n navigate(String(path), options);\n },\n [navigate],\n );\n}\n\n// ─── useResolvedPath ─────────────────────────────────────────────────────────\n\n/**\n * Resolves a dynamic path template against params, mirroring `buildPath()`.\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 * @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 * const path = useResolvedPath('/files/*', { \"*\": \"a/b\" }); // → '/files/a/b'\n * ```\n */\nexport function useResolvedPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n\n// ─── useActivePath ───────────────────────────────────────────────────────────\n\n/**\n * A hook that checks whether the current location matches a route template or path.\n * Thin wrapper around `isActivePath(useLocation().pathname, template, options)`.\n *\n * @example\n * ```tsx\n * const isActive = useActivePath(PATHS.USERS.ROOT, { exact: false });\n * ```\n */\nexport function useActivePath(\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {\n exact: true,\n caseSensitive: false,\n },\n): boolean {\n const location = useLocation();\n return isActivePath(location.pathname, template, options);\n}\n\n// ─── useTypedSearchParams ────────────────────────────────────────────────────\n\n/**\n * A typed wrapper around React Router's `useSearchParams`.\n * Returns parsed query params object and a setter that updates query params.\n *\n * Implemented on top of `useLocation` + `useNavigate` (both exported by the\n * `react-router` core in v6 and v7) rather than `useSearchParams`, because in\n * React Router v6 `useSearchParams` only exists in the DOM wrapper package\n * (`react-router-dom`), while v7 moved it into `react-router`. Reimplementing\n * it lets the whole hooks entry work identically against both packages.\n *\n * @example\n * ```tsx\n * const [query, setQuery] = useTypedSearchParams({ coerceBooleans: true, coerceNumbers: true });\n * setQuery({ tab: 'details', page: 2 });\n * ```\n */\nexport function useTypedSearchParams(options?: {\n coerceBooleans?: boolean;\n coerceNumbers?: boolean;\n}) {\n const location = useLocation();\n const navigate = useNavigate();\n\n const queryParams = extractQueryFromPath(location.search, options);\n\n const setTypedQuery = useCallback(\n (\n newQuery: QueryParams,\n navigateOptions?: { replace?: boolean; state?: unknown },\n ) => {\n const updatedPath = appendQuery(\"\", newQuery);\n const updatedSearchParams = new URLSearchParams(\n updatedPath.startsWith(\"?\") ? updatedPath.slice(1) : updatedPath,\n );\n navigate(`?${updatedSearchParams.toString()}`, navigateOptions);\n },\n [navigate],\n );\n\n return [queryParams, setTypedQuery] as const;\n}\n\n","import type {\n BreadcrumbItem,\n BreadcrumbOptions,\n BuildPathOptions,\n FlatRoute,\n QueryParams,\n RouteParams,\n} from \"../types\";\n\n/** Returns a fresh RegExp each call — avoids shared `lastIndex` state on /g patterns. */\nconst ESCAPE_RE = () => /[.*+?^${}()|[\\]\\\\]/g;\n\n/**\n * Matches a `:param` token that starts a URL segment.\n *\n * A param is only recognized when its `:` sits at the very start of the\n * template or is immediately preceded by `/`, so a literal colon inside a\n * segment (e.g. `/users/foo:bar`) is not treated as a param. Param names are\n * restricted to `[A-Za-z0-9_]` (matching React Router), so any static suffix\n * (e.g. `.json` in `/files/:name.json`) stays a literal. The optional `?`\n * marker (`:param?`) is captured separately so it can be handled as a\n * whole-segment modifier.\n */\nconst PARAM_SEGMENT_RE = () => /(^|\\/):([A-Za-z0-9_]+)(\\?)?/g;\n\n/** Returns `true` when running in a production bundle (suppresses dev warnings). */\nfunction isProduction(): boolean {\n const runtimeProcess = (\n globalThis as typeof globalThis & {\n process?: { env?: Record<string, string | undefined> };\n }\n ).process;\n return runtimeProcess?.env?.NODE_ENV === \"production\";\n}\n\n/**\n * Emits a `console.warn` in non-production environments.\n * Shared by the core utilities and `defineRoutes()` so the production check\n * lives in one place.\n */\nexport function devWarn(message: string): void {\n if (!isProduction()) {\n console.warn(message);\n }\n}\n\n/**\n * Clears internal regex cache maps (PREFIX_CACHE and PATH_CACHE).\n * Useful in test suites to prevent cached patterns from leaking across test cases.\n */\nexport function clearPathCache(): void {\n PREFIX_CACHE.clear();\n PATH_CACHE.clear();\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(ESCAPE_RE(), \"\\\\$&\");\n}\n\n/**\n * Convert a route template into an unanchored `RegExp` source string.\n *\n * - Required `:name` segments become a capturing group `([^/]+)`.\n * - Optional `:name?` segments become `(?:([^/]+))?` — the whole segment is\n * optional, matching React Router semantics.\n * - A trailing splat (`/*`) becomes `(?:/(.*))?`, capturing the remainder of\n * the path (including slashes) or nothing at all.\n * - Everything else is regex-escaped literally.\n */\nfunction createTemplatePattern(template: string): string {\n const splat = template.endsWith(\"/*\");\n const base = splat ? template.slice(0, -2) : template;\n let pattern = \"\";\n let cursor = 0;\n\n for (const match of base.matchAll(PARAM_SEGMENT_RE())) {\n const start = match.index ?? 0;\n const token = match[0];\n\n pattern += escapeRegex(base.slice(cursor, start));\n\n const [, boundary = \"\", , optional] = match;\n pattern += optional\n ? `(?:${escapeRegex(boundary)}([^/]+))?`\n : `${escapeRegex(boundary)}([^/]+)`;\n\n cursor = start + (token?.length ?? 0);\n }\n\n return pattern + escapeRegex(base.slice(cursor)) + (splat ? \"(?:/(.*))?\" : \"\");\n}\n\n/** Returns `true` when `name` is an optional (`:name?`) param in `template`. */\nfunction isOptionalParam(template: string, name: string): boolean {\n return new RegExp(`:${escapeRegex(name)}\\\\?(?![A-Za-z0-9_])`).test(template);\n}\n\n/**\n * Returns a `RegExp` that matches `template` as a path prefix, honoring\n * segment boundaries (`/users` matches `/users/42` but not `/usersettings`).\n *\n * The root template `/` is a prefix of every path.\n *\n * Compiled patterns are cached per template: the resulting `RegExp` has no\n * `/g` flag, so repeated `.test()`/`.exec()` calls are side-effect free and\n * sharing instances across callers is safe.\n */\nconst PREFIX_CACHE = new Map<string, RegExp>();\nconst ROOT_PREFIX_RE = /^/;\n\nfunction matchPrefix(template: string): RegExp {\n if (template === \"/\") return ROOT_PREFIX_RE;\n const cached = PREFIX_CACHE.get(template);\n if (cached !== undefined) return cached;\n const re = new RegExp(`^${createTemplatePattern(template)}(?=/|$)`);\n PREFIX_CACHE.set(template, re);\n return re;\n}\n\n/** Decode a URL-encoded param value, falling back to the raw value on error. */\nfunction safeDecode(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n/**\n * Append a query string and/or hash fragment to a path that may already\n * contain a query or hash.\n *\n * - Existing query pairs are preserved; new ones are joined with `&`.\n * - The query string is always inserted before any hash fragment, so an\n * existing `#section` on `path` is kept unless a new `hash` is given.\n *\n * @example\n * ```ts\n * appendQuery(\"/users?tab=list\", { page: 2 }); // → \"/users?tab=list&page=2\"\n * appendQuery(\"/users#top\", { tab: \"list\" }); // → \"/users?tab=list#top\"\n * ```\n */\nexport function appendQuery(path: string, query?: QueryParams, hash?: string): string {\n const hashIdx = path.indexOf(\"#\");\n const base = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const existingHash = hashIdx === -1 ? \"\" : path.slice(hashIdx + 1);\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 let result = base;\n const queryString = searchParams.toString();\n if (queryString) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + queryString;\n }\n\n if (hash) {\n result += \"#\" + hash;\n } else if (existingHash) {\n result += \"#\" + existingHash;\n }\n\n return result;\n}\n\n/**\n * Parse the query string out of a path (or bare query string) into a plain\n * object. Repeated keys become arrays; a single key is a scalar string.\n *\n * With `{ coerceBooleans: true }`, the strings `\"true\"`/`\"false\"` are\n * converted to actual booleans.\n *\n * @example\n * ```ts\n * extractQueryFromPath(\"/users/42?tab=profile&tag=a&tag=b\");\n * // → { tab: \"profile\", tag: [\"a\", \"b\"] }\n * extractQueryFromPath(\"/search?active=true\", { coerceBooleans: true });\n * // → { active: true }\n * ```\n */\nexport function extractQueryFromPath(\n path: string,\n options?: { coerceBooleans?: boolean; coerceNumbers?: boolean },\n): QueryParams {\n const hashIdx = path.indexOf(\"#\");\n const noHash = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const queryIdx = noHash.indexOf(\"?\");\n if (queryIdx === -1) return {};\n\n const params = new URLSearchParams(noHash.slice(queryIdx + 1));\n const result: QueryParams = {};\n\n for (const key of new Set(params.keys())) {\n const values = params.getAll(key);\n const parsed = values.map((v) => {\n if (options?.coerceBooleans && (v === \"true\" || v === \"false\")) {\n return v === \"true\";\n }\n if (options?.coerceNumbers && v.trim() !== \"\" && !isNaN(Number(v))) {\n return Number(v);\n }\n return v;\n });\n result[key] = parsed.length > 1 ? parsed : (parsed[0] ?? \"\");\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) =>\n (params[name] === undefined || params[name] === null) &&\n !isOptionalParam(template, name) &&\n // A missing splat simply drops the `/*` suffix (matching React Router,\n // where `/files/*` also matches `/files`).\n name !== \"*\",\n );\n\n const resolved = paramNames.reduce((path, name) => {\n const value = params[name];\n const missing = value === undefined || value === null;\n\n if (name === \"*\") {\n if (missing) return path.replace(/\\/\\*$/, \"\") || \"/\";\n // Splat values are path-like: preserve `/` separators but still\n // encode characters that could break the URL (`?`, `#`, spaces, …).\n const encoded =\n options?.encode === false\n ? String(value)\n : String(value)\n .split(\"/\")\n .map((segment) => encodeURIComponent(segment))\n .join(\"/\");\n return path.replace(/\\/\\*$/, `/${encoded}`);\n }\n\n const re = new RegExp(\n `(^|/):${escapeRegex(name)}\\\\??(?![A-Za-z0-9_])`,\n \"g\",\n );\n\n return path.replace(re, (match, boundary) => {\n if (missing) {\n // Optional segment: drop the whole `/segment`. Required: keep the\n // `:name` placeholder so strict mode can report it.\n return match.endsWith(\"?\") ? \"\" : `${boundary}:${name}`;\n }\n\n const encoded =\n options?.encode === false\n ? String(value)\n : encodeURIComponent(String(value));\n return `${boundary}${encoded}`;\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 devWarn(\n `[route-forge] Unresolved params in path \"${resolved}\". ` +\n `Check that all :param segments have matching keys.`,\n );\n }\n\n return appendQuery(resolved, query, options?.hash);\n}\n\nexport function extractParamNames(template: string): string[] {\n const names = [...template.matchAll(PARAM_SEGMENT_RE())].map(\n (match) => match[2] as string,\n );\n if (template.endsWith(\"/*\")) names.push(\"*\");\n return names;\n}\n\nexport function isDynamic(path: string): boolean {\n return PARAM_SEGMENT_RE().test(path) || path.endsWith(\"/*\");\n}\n\n/**\n * Test whether `currentPath` matches `template`, mirroring React Router's\n * `NavLink` matching semantics:\n *\n * - Case-insensitive by default (pass `caseSensitive: true` to opt out).\n * - Trailing slashes are tolerated (`/users/` matches `/users`).\n * - `exact: true` (the default) requires a full match; `exact: false`\n * matches any path that starts with the template.\n */\nexport function isActivePath(\n currentPath: string,\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {\n exact: true,\n caseSensitive: false,\n },\n): boolean {\n const pathname = (currentPath.split(\"?\")[0] ?? \"\").replace(/\\/+$/, \"\") || \"/\";\n const normalizedTemplate = template.replace(/\\/+$/, \"\") || \"/\";\n const target = options.caseSensitive\n ? normalizedTemplate\n : normalizedTemplate.toLowerCase();\n const candidate = options.caseSensitive ? pathname : pathname.toLowerCase();\n const regex = options.exact ? matchPath(target) : matchPrefix(target);\n\n return regex.test(candidate);\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 const result: Record<string, string> = {};\n paramNames.forEach((name, index) => {\n const raw = match[index + 1];\n if (raw !== undefined) result[name] = safeDecode(raw);\n });\n return result;\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 *\n * Compiled patterns are cached per template (the regex has no `/g` flag, so\n * sharing instances across callers is safe).\n */\nconst PATH_CACHE = new Map<string, RegExp>();\n\nexport function matchPath(\n template: string,\n options?: { end?: boolean; caseSensitive?: boolean },\n): RegExp {\n const end = options?.end ?? true;\n const flags = options?.caseSensitive ? \"\" : \"i\";\n const cacheKey = `${template}:${end}:${flags}`;\n\n const cached = PATH_CACHE.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const basePattern = createTemplatePattern(template);\n const pattern = end ? `^${basePattern}$` : `^${basePattern}(?=/|$)`;\n const re = new RegExp(pattern, flags);\n PATH_CACHE.set(cacheKey, re);\n return re;\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 const labels = options?.labels ?? {};\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 = matchPrefix(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: labels[item.key] ?? labelFn(item.key),\n path: item.resolvedPath,\n isCurrent: item.isCurrent,\n }));\n}\n\n"],"mappings":"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","names":["hooks_exports","__export","useActivePath","useNavigateTo","useResolvedPath","useRouteParams","useTypedSearchParams","__toCommonJS","import_react","import_react_router","ESCAPE_RE","PARAM_SEGMENT_RE","isProduction","devWarn","message","escapeRegex","value","ESCAPE_RE","createTemplatePattern","template","splat","base","pattern","cursor","match","PARAM_SEGMENT_RE","start","token","boundary","optional","isOptionalParam","name","PREFIX_CACHE","ROOT_PREFIX_RE","matchPrefix","cached","re","appendQuery","path","query","hash","hashIdx","base","existingHash","searchParams","key","value","v","result","queryString","extractQueryFromPath","options","noHash","queryIdx","params","parsed","buildPath","template","paramNames","extractParamNames","unresolved","name","isOptionalParam","resolved","missing","encoded","segment","re","escapeRegex","match","boundary","p","devWarn","names","PARAM_SEGMENT_RE","isActivePath","currentPath","template","options","pathname","normalizedTemplate","target","candidate","matchPath","matchPrefix","PATH_CACHE","matchPath","template","options","end","flags","cacheKey","cached","basePattern","createTemplatePattern","pattern","re","useRouteParams","_route","useNavigateTo","navigate","path","options","useResolvedPath","template","params","query","buildPath","useActivePath","location","isActivePath","useTypedSearchParams","queryParams","extractQueryFromPath","setTypedQuery","newQuery","navigateOptions","updatedPath","appendQuery","updatedSearchParams"]}
1
+ {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/useRouteParams.ts","../../src/hooks/useNavigateTo.ts","../../src/core/pattern.ts","../../src/core/params.ts","../../src/core/query.ts","../../src/core/environment.ts","../../src/core/build.ts","../../src/hooks/useResolvedPath.ts","../../src/hooks/useActivePath.ts","../../src/core/match.ts","../../src/hooks/useTypedSearchParams.ts"],"sourcesContent":["export { useRouteParams } from \"./useRouteParams\";\nexport { useNavigateTo } from \"./useNavigateTo\";\nexport type { NavigateOptions } from \"./useNavigateTo\";\nexport { useResolvedPath } from \"./useResolvedPath\";\nexport { useActivePath } from \"./useActivePath\";\nexport { useTypedSearchParams } from \"./useTypedSearchParams\";\n","import { useParams } from \"react-router\";\nimport type { ExtractParams } from \"../types\";\n\n/**\n * A typed wrapper around React Router's `useParams`.\n *\n * Pass a route template as a type parameter, or a dynamic route value from a\n * `defineRoutes()` tree for automatic type inference:\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 * // Or pass a route from your PATHS tree — params are inferred from it:\n * const PATHS = defineRoutes({ USERS: { EDIT: '/users/edit/:id' } } as const);\n * const { id } = useRouteParams(PATHS.USERS.EDIT);\n * ```\n */\n// Overload 1: no-arg generic — caller provides the template literal as T\nexport function useRouteParams<T extends string = string>(): Record<\n ExtractParams<T>,\n string\n>;\n// Overload 2: pass a route from defineRoutes() — P is inferred from paramNames array element type\nexport function useRouteParams<P extends string>(route: {\n readonly paramNames: ReadonlyArray<P> | Array<P>;\n}): Record<P, string>;\n// Implementation\nexport function useRouteParams<P extends string>(_route?: {\n readonly paramNames: ReadonlyArray<P> | Array<P>;\n}): Record<string, string> {\n return useParams() as Record<string, string>;\n}\n","import { useCallback } from \"react\";\nimport { useNavigate } from \"react-router\";\n\nexport type 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 route value straight from `defineRoutes()` (e.g. a `String` object or a\n * primitive string), with optional navigation options.\n *\n * `String` objects (used by route values so they can carry `.build()`) are\n * coerced to primitives, since React Router's `navigate()` ignores them.\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 useCallback(\n (path: string, options?: NavigateOptions) => {\n navigate(String(path), options);\n },\n [navigate],\n );\n}\n","/** Returns a fresh RegExp each call — avoids shared `lastIndex` state on /g patterns. */\nconst ESCAPE_RE = () => /[.*+?^${}()|[\\]\\\\]/g;\n\n/**\n * Matches a `:param` token that starts a URL segment.\n *\n * A param is only recognized when its `:` sits at the very start of the\n * template or is immediately preceded by `/`, so a literal colon inside a\n * segment (e.g. `/users/foo:bar`) is not treated as a param. Param names are\n * restricted to `[A-Za-z0-9_]` (matching React Router), so any static suffix\n * (e.g. `.json` in `/files/:name.json`) stays a literal. The optional `?`\n * marker (`:param?`) is captured separately so it can be handled as a\n * whole-segment modifier.\n */\nexport const PARAM_SEGMENT_RE = () => /(^|\\/):([A-Za-z0-9_]+)(\\?)?/g;\n\nexport function escapeRegex(value: string): string {\n return value.replace(ESCAPE_RE(), \"\\\\$&\");\n}\n\n/**\n * Convert a route template into an unanchored `RegExp` source string.\n *\n * - Required `:name` segments become a capturing group `([^/]+)`.\n * - Optional `:name?` segments become `(?:([^/]+))?` — the whole segment is\n * optional, matching React Router semantics.\n * - A trailing splat (`/*`) becomes `(?:/(.*))?`, capturing the remainder of\n * the path (including slashes) or nothing at all.\n * - Everything else is regex-escaped literally.\n */\nfunction createTemplatePattern(template: string): string {\n const splat = template.endsWith(\"/*\");\n const base = splat ? template.slice(0, -2) : template;\n let pattern = \"\";\n let cursor = 0;\n\n for (const match of base.matchAll(PARAM_SEGMENT_RE())) {\n const start = match.index ?? 0;\n const token = match[0];\n\n pattern += escapeRegex(base.slice(cursor, start));\n\n const [, boundary = \"\", , optional] = match;\n pattern += optional\n ? `(?:${escapeRegex(boundary)}([^/]+))?`\n : `${escapeRegex(boundary)}([^/]+)`;\n\n cursor = start + (token?.length ?? 0);\n }\n\n return (\n pattern + escapeRegex(base.slice(cursor)) + (splat ? \"(?:/(.*))?\" : \"\")\n );\n}\n\n/** Returns `true` when `name` is an optional (`:name?`) param in `template`. */\nexport function isOptionalParam(template: string, name: string): boolean {\n return new RegExp(`:${escapeRegex(name)}\\\\?(?![A-Za-z0-9_])`).test(template);\n}\n\n/**\n * Returns a `RegExp` that matches `template` as a path prefix, honoring\n * segment boundaries (`/users` matches `/users/42` but not `/usersettings`).\n *\n * The root template `/` is a prefix of every path.\n *\n * Compiled patterns are cached per template: the resulting `RegExp` has no\n * `/g` flag, so repeated `.test()`/`.exec()` calls are side-effect free and\n * sharing instances across callers is safe.\n */\nconst PREFIX_CACHE = new Map<string, RegExp>();\nconst ROOT_PREFIX_RE = /^/;\n\nexport function matchPrefix(template: string): RegExp {\n if (template === \"/\") return ROOT_PREFIX_RE;\n const cached = PREFIX_CACHE.get(template);\n if (cached !== undefined) return cached;\n const re = new RegExp(`^${createTemplatePattern(template)}(?=/|$)`);\n PREFIX_CACHE.set(template, re);\n return re;\n}\n\n/** Decode a URL-encoded param value, falling back to the raw value on error. */\nexport function safeDecode(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\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 *\n * Compiled patterns are cached per template (the regex has no `/g` flag, so\n * sharing instances across callers is safe).\n */\nconst PATH_CACHE = new Map<string, RegExp>();\n\nexport function matchPath(\n template: string,\n options?: { end?: boolean; caseSensitive?: boolean },\n): RegExp {\n const end = options?.end ?? true;\n const flags = options?.caseSensitive ? \"\" : \"i\";\n const cacheKey = `${template}:${end}:${flags}`;\n\n const cached = PATH_CACHE.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const basePattern = createTemplatePattern(template);\n const pattern = end ? `^${basePattern}$` : `^${basePattern}(?=/|$)`;\n const re = new RegExp(pattern, flags);\n PATH_CACHE.set(cacheKey, re);\n return re;\n}\n\n/**\n * Clears internal regex cache maps (PREFIX_CACHE and PATH_CACHE).\n * Useful in test suites to prevent cached patterns from leaking across test cases.\n */\nexport function clearPathCache(): void {\n PREFIX_CACHE.clear();\n PATH_CACHE.clear();\n}\n","import { PARAM_SEGMENT_RE, matchPath, safeDecode } from \"./pattern\";\n\n/**\n * Extract the `:param` names from a route template.\n *\n * A param is only recognized at the start of a segment (see\n * {@link PARAM_SEGMENT_RE}), so literal colons (`/users/foo:bar`) and static\n * suffixes after a param (`/files/:name.json`) are not treated as params.\n * A trailing splat (`/*`) is reported as the `\"*\"` name.\n */\nexport function extractParamNames(template: string): string[] {\n const names = [...template.matchAll(PARAM_SEGMENT_RE())].map(\n (match) => match[2] as string,\n );\n if (template.endsWith(\"/*\")) names.push(\"*\");\n return names;\n}\n\n/** Returns `true` when the template contains a `:param` or trailing `/*` splat. */\nexport function isDynamic(path: string): boolean {\n return PARAM_SEGMENT_RE().test(path) || path.endsWith(\"/*\");\n}\n\n/**\n * Alias of {@link extractParamNames} — kept for backwards compatibility.\n */\nexport function getParamNames(template: string): string[] {\n return extractParamNames(template);\n}\n\n/**\n * Extract the param values matched by `template` from a resolved path.\n *\n * The query string is stripped before matching, so `/users/42?tab=profile`\n * still yields `{ id: \"42\" }`. Values are URL-decoded back to their original\n * form; a non-matching path yields an empty object.\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 const result: Record<string, string> = {};\n paramNames.forEach((name, index) => {\n const raw = match[index + 1];\n if (raw !== undefined) result[name] = safeDecode(raw);\n });\n return result;\n}\n","import type { QueryParams } from \"../types\";\n\n/**\n * Append a query string and/or hash fragment to a path that may already\n * contain a query or hash.\n *\n * - Existing query pairs are preserved; new ones are joined with `&`.\n * - The query string is always inserted before any hash fragment, so an\n * existing `#section` on `path` is kept unless a new `hash` is given.\n *\n * @example\n * ```ts\n * appendQuery(\"/users?tab=list\", { page: 2 }); // → \"/users?tab=list&page=2\"\n * appendQuery(\"/users#top\", { tab: \"list\" }); // → \"/users?tab=list#top\"\n * ```\n */\nexport function appendQuery(\n path: string,\n query?: QueryParams,\n hash?: string,\n): string {\n const hashIdx = path.indexOf(\"#\");\n const base = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const existingHash = hashIdx === -1 ? \"\" : path.slice(hashIdx + 1);\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)\n searchParams.append(key, String(v));\n });\n } else {\n searchParams.append(key, String(value));\n }\n }\n }\n\n let result = base;\n const queryString = searchParams.toString();\n if (queryString) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + queryString;\n }\n\n if (hash) {\n result += \"#\" + hash;\n } else if (existingHash) {\n result += \"#\" + existingHash;\n }\n\n return result;\n}\n\n/**\n * Parse the query string out of a path (or bare query string) into a plain\n * object. Repeated keys become arrays; a single key is a scalar string.\n *\n * With `{ coerceBooleans: true }`, the strings `\"true\"`/`\"false\"` are\n * converted to actual booleans.\n *\n * @example\n * ```ts\n * extractQueryFromPath(\"/users/42?tab=profile&tag=a&tag=b\");\n * // → { tab: \"profile\", tag: [\"a\", \"b\"] }\n * extractQueryFromPath(\"/search?active=true\", { coerceBooleans: true });\n * // → { active: true }\n * ```\n */\nexport function extractQueryFromPath(\n path: string,\n options?: { coerceBooleans?: boolean; coerceNumbers?: boolean },\n): QueryParams {\n const hashIdx = path.indexOf(\"#\");\n const noHash = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const queryIdx = noHash.indexOf(\"?\");\n if (queryIdx === -1) return {};\n\n const params = new URLSearchParams(noHash.slice(queryIdx + 1));\n const result: QueryParams = {};\n\n for (const key of new Set(params.keys())) {\n const values = params.getAll(key);\n const parsed = values.map((v) => {\n if (options?.coerceBooleans && (v === \"true\" || v === \"false\")) {\n return v === \"true\";\n }\n if (options?.coerceNumbers && v.trim() !== \"\" && !isNaN(Number(v))) {\n return Number(v);\n }\n return v;\n });\n result[key] = parsed.length > 1 ? parsed : (parsed[0] ?? \"\");\n }\n\n return result;\n}\n","/**\n * Returns `true` when running in a production bundle (suppresses dev warnings).\n */\nexport function isProduction(): boolean {\n const runtimeProcess = (\n globalThis as typeof globalThis & {\n process?: { env?: Record<string, string | undefined> };\n }\n ).process;\n return runtimeProcess?.env?.NODE_ENV === \"production\";\n}\n\n/**\n * Emits a `console.warn` in non-production environments.\n * Shared by the core utilities and `defineRoutes()` so the production check\n * lives in one place.\n */\nexport function devWarn(message: string): void {\n if (!isProduction()) {\n console.warn(message);\n }\n}\n","import { escapeRegex, isOptionalParam } from \"./pattern\";\nimport { extractParamNames } from \"./params\";\nimport { appendQuery } from \"./query\";\nimport { devWarn } from \"./environment\";\nimport type { BuildPathOptions, QueryParams, RouteParams } from \"../types\";\n\n/**\n * Resolve a route template against a params object, appending an optional\n * query string and hash fragment.\n *\n * Missing required params are left as their `:name` placeholder and a\n * `console.warn` is emitted — pass `{ strict: true }` to throw a `RangeError`\n * instead. Optional (`:param?`) segments are dropped entirely when missing;\n * a missing splat just drops the trailing `/*`. Values are URL-encoded by\n * default (`{ encode: false }` opts out).\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) =>\n (params[name] === undefined || params[name] === null) &&\n !isOptionalParam(template, name) &&\n // A missing splat simply drops the `/*` suffix (matching React Router,\n // where `/files/*` also matches `/files`).\n name !== \"*\",\n );\n\n const resolved = paramNames.reduce((path, name) => {\n const value = params[name];\n const missing = value === undefined || value === null;\n\n if (name === \"*\") {\n if (missing) return path.replace(/\\/\\*$/, \"\") || \"/\";\n // Splat values are path-like: preserve `/` separators but still\n // encode characters that could break the URL (`?`, `#`, spaces, …).\n const encoded =\n options?.encode === false\n ? String(value)\n : String(value)\n .split(\"/\")\n .map((segment) => encodeURIComponent(segment))\n .join(\"/\");\n return path.replace(/\\/\\*$/, `/${encoded}`);\n }\n\n const re = new RegExp(\n `(^|/):${escapeRegex(name)}\\\\??(?![A-Za-z0-9_])`,\n \"g\",\n );\n\n return path.replace(re, (match, boundary) => {\n if (missing) {\n // Optional segment: drop the whole `/segment`. Required: keep the\n // `:name` placeholder so strict mode can report it.\n return match.endsWith(\"?\") ? \"\" : `${boundary}:${name}`;\n }\n\n const encoded =\n options?.encode === false\n ? String(value)\n : encodeURIComponent(String(value));\n return `${boundary}${encoded}`;\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 devWarn(\n `[route-forge] Unresolved params in path \"${resolved}\". ` +\n `Check that all :param segments have matching keys.`,\n );\n }\n\n return appendQuery(resolved, query, options?.hash);\n}\n\n/**\n * Standalone alias of {@link buildPath} — resolves a template against params.\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\n/**\n * Join URL path segments into a single slash-prefixed path, normalising\n * duplicate slashes.\n *\n * @example\n * joinPaths(\"/api/\", \"/v1/\", \"users\"); // → \"/api/v1/users\"\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","import { buildPath } from \"../core/build\";\nimport type { BuildPathOptions, QueryParams, RouteParams } from \"../types\";\n\n/**\n * Resolves a dynamic path template against params, mirroring `buildPath()`.\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 * @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 * const path = useResolvedPath('/files/*', { \"*\": \"a/b\" }); // → '/files/a/b'\n * ```\n */\nexport function useResolvedPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n","import { useLocation } from \"react-router\";\nimport { isActivePath } from \"../core/match\";\n\n/**\n * A hook that checks whether the current location matches a route template or path.\n * Thin wrapper around `isActivePath(useLocation().pathname, template, options)`.\n *\n * @example\n * ```tsx\n * const isActive = useActivePath(PATHS.USERS.ROOT, { exact: false });\n * ```\n */\nexport function useActivePath(\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {},\n): boolean {\n // Forwarded as-is — isActivePath defaults `exact`/`caseSensitive`\n // individually, so a partial options object here still behaves correctly.\n const location = useLocation();\n return isActivePath(location.pathname, template, options);\n}\n","import { matchPath, matchPrefix } from \"./pattern\";\n\n/**\n * Test whether `currentPath` matches `template`, mirroring React Router's\n * `NavLink` matching semantics:\n *\n * - Case-insensitive by default (pass `caseSensitive: true` to opt out).\n * - Trailing slashes are tolerated (`/users/` matches `/users`).\n * - `exact: true` (the default) requires a full match; `exact: false`\n * matches any path that starts with the template.\n */\nexport function isActivePath(\n currentPath: string,\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {},\n): boolean {\n // NOTE: default each property individually rather than relying on a\n // default *object* for `options` — JS default parameters only apply when\n // the whole argument is omitted, so `isActivePath(a, b, { caseSensitive: true })`\n // would otherwise silently lose the documented `exact: true` default.\n const exact = options.exact ?? true;\n const caseSensitive = options.caseSensitive ?? false;\n\n const pathname = (currentPath.split(\"?\")[0] ?? \"\").replace(/\\/+$/, \"\") || \"/\";\n const normalizedTemplate = template.replace(/\\/+$/, \"\") || \"/\";\n const target = caseSensitive\n ? normalizedTemplate\n : normalizedTemplate.toLowerCase();\n const candidate = caseSensitive ? pathname : pathname.toLowerCase();\n const regex = exact\n ? matchPath(target, { caseSensitive })\n : matchPrefix(target);\n\n return regex.test(candidate);\n}\n","import { useCallback } from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { appendQuery, extractQueryFromPath } from \"../core/query\";\nimport type { QueryParams } from \"../types\";\n\n/**\n * A typed wrapper around React Router's `useSearchParams`.\n * Returns parsed query params object and a setter that updates query params.\n *\n * Implemented on top of `useLocation` + `useNavigate` (both exported by the\n * `react-router` core in v6 and v7) rather than `useSearchParams`, because in\n * React Router v6 `useSearchParams` only exists in the DOM wrapper package\n * (`react-router-dom`), while v7 moved it into `react-router`. Reimplementing\n * it lets the whole hooks entry work identically against both packages.\n *\n * @example\n * ```tsx\n * const [query, setQuery] = useTypedSearchParams({ coerceBooleans: true, coerceNumbers: true });\n * setQuery({ tab: 'details', page: 2 });\n * ```\n */\nexport function useTypedSearchParams(options?: {\n coerceBooleans?: boolean;\n coerceNumbers?: boolean;\n}) {\n const location = useLocation();\n const navigate = useNavigate();\n\n const queryParams = extractQueryFromPath(location.search, options);\n\n const setTypedQuery = useCallback(\n (\n newQuery: QueryParams,\n navigateOptions?: { replace?: boolean; state?: unknown },\n ) => {\n const updatedPath = appendQuery(\"\", newQuery);\n const updatedSearchParams = new URLSearchParams(\n updatedPath.startsWith(\"?\") ? updatedPath.slice(1) : updatedPath,\n );\n navigate(`?${updatedSearchParams.toString()}`, navigateOptions);\n },\n [navigate],\n );\n\n return [queryParams, setTypedQuery] as const;\n}\n"],"mappings":"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","names":["hooks_exports","__export","useActivePath","useNavigateTo","useResolvedPath","useRouteParams","useTypedSearchParams","__toCommonJS","import_react_router","useRouteParams","_route","import_react","import_react_router","useNavigateTo","navigate","path","options","ESCAPE_RE","PARAM_SEGMENT_RE","escapeRegex","value","createTemplatePattern","template","splat","base","pattern","cursor","match","start","token","boundary","optional","isOptionalParam","name","PREFIX_CACHE","ROOT_PREFIX_RE","matchPrefix","cached","re","PATH_CACHE","matchPath","template","options","end","flags","cacheKey","cached","basePattern","createTemplatePattern","pattern","re","extractParamNames","template","names","PARAM_SEGMENT_RE","match","appendQuery","path","query","hash","hashIdx","base","existingHash","searchParams","key","value","v","result","queryString","extractQueryFromPath","options","noHash","queryIdx","params","parsed","isProduction","devWarn","message","buildPath","template","params","query","options","paramNames","extractParamNames","unresolved","name","isOptionalParam","resolved","path","value","missing","encoded","segment","re","escapeRegex","match","boundary","p","devWarn","appendQuery","useResolvedPath","template","params","query","options","buildPath","import_react_router","isActivePath","currentPath","template","options","exact","caseSensitive","pathname","normalizedTemplate","target","candidate","matchPath","matchPrefix","useActivePath","template","options","location","isActivePath","import_react","import_react_router","useTypedSearchParams","options","location","navigate","queryParams","extractQueryFromPath","setTypedQuery","newQuery","navigateOptions","updatedPath","appendQuery","updatedSearchParams"]}
@@ -1,4 +1,4 @@
1
- import { R as RouteParams, Q as QueryParams, B as BuildPathOptions, E as ExtractParams } from '../index-Beg7xp8k.cjs';
1
+ import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-CRAdjXCI.cjs';
2
2
 
3
3
  /**
4
4
  * A typed wrapper around React Router's `useParams`.
@@ -20,6 +20,7 @@ declare function useRouteParams<T extends string = string>(): Record<ExtractPara
20
20
  declare function useRouteParams<P extends string>(route: {
21
21
  readonly paramNames: ReadonlyArray<P> | Array<P>;
22
22
  }): Record<P, string>;
23
+
23
24
  type NavigateOptions = {
24
25
  replace?: boolean;
25
26
  state?: unknown;
@@ -40,6 +41,7 @@ type NavigateOptions = {
40
41
  * ```
41
42
  */
42
43
  declare function useNavigateTo(): (path: string, options?: NavigateOptions) => void;
44
+
43
45
  /**
44
46
  * Resolves a dynamic path template against params, mirroring `buildPath()`.
45
47
  *
@@ -55,6 +57,7 @@ declare function useNavigateTo(): (path: string, options?: NavigateOptions) => v
55
57
  * ```
56
58
  */
57
59
  declare function useResolvedPath(template: string, params: RouteParams, query?: QueryParams, options?: BuildPathOptions): string;
60
+
58
61
  /**
59
62
  * A hook that checks whether the current location matches a route template or path.
60
63
  * Thin wrapper around `isActivePath(useLocation().pathname, template, options)`.
@@ -68,6 +71,7 @@ declare function useActivePath(template: string, options?: {
68
71
  exact?: boolean;
69
72
  caseSensitive?: boolean;
70
73
  }): boolean;
74
+
71
75
  /**
72
76
  * A typed wrapper around React Router's `useSearchParams`.
73
77
  * Returns parsed query params object and a setter that updates query params.
@@ -1,4 +1,4 @@
1
- import { R as RouteParams, Q as QueryParams, B as BuildPathOptions, E as ExtractParams } from '../index-Beg7xp8k.js';
1
+ import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-CRAdjXCI.js';
2
2
 
3
3
  /**
4
4
  * A typed wrapper around React Router's `useParams`.
@@ -20,6 +20,7 @@ declare function useRouteParams<T extends string = string>(): Record<ExtractPara
20
20
  declare function useRouteParams<P extends string>(route: {
21
21
  readonly paramNames: ReadonlyArray<P> | Array<P>;
22
22
  }): Record<P, string>;
23
+
23
24
  type NavigateOptions = {
24
25
  replace?: boolean;
25
26
  state?: unknown;
@@ -40,6 +41,7 @@ type NavigateOptions = {
40
41
  * ```
41
42
  */
42
43
  declare function useNavigateTo(): (path: string, options?: NavigateOptions) => void;
44
+
43
45
  /**
44
46
  * Resolves a dynamic path template against params, mirroring `buildPath()`.
45
47
  *
@@ -55,6 +57,7 @@ declare function useNavigateTo(): (path: string, options?: NavigateOptions) => v
55
57
  * ```
56
58
  */
57
59
  declare function useResolvedPath(template: string, params: RouteParams, query?: QueryParams, options?: BuildPathOptions): string;
60
+
58
61
  /**
59
62
  * A hook that checks whether the current location matches a route template or path.
60
63
  * Thin wrapper around `isActivePath(useLocation().pathname, template, options)`.
@@ -68,6 +71,7 @@ declare function useActivePath(template: string, options?: {
68
71
  exact?: boolean;
69
72
  caseSensitive?: boolean;
70
73
  }): boolean;
74
+
71
75
  /**
72
76
  * A typed wrapper around React Router's `useSearchParams`.
73
77
  * Returns parsed query params object and a setter that updates query params.
@@ -1,2 +1,2 @@
1
- import{c as n,d as s,e as i,h as u}from"../chunk-EFHUAXKE.js";import{useCallback as c}from"react";import{useParams as y,useNavigate as m,useLocation as p}from"react-router";function f(e){return y()}function v(){let e=m();return c((a,t)=>{e(String(a),t)},[e])}function b(e,a,t,r){return i(e,a,t,r)}function N(e,a={exact:!0,caseSensitive:!1}){let t=p();return u(t.pathname,e,a)}function S(e){let a=p(),t=m(),r=s(a.search,e),P=c((l,d)=>{let o=n("",l),g=new URLSearchParams(o.startsWith("?")?o.slice(1):o);t(`?${g.toString()}`,d)},[t]);return[r,P]}export{N as useActivePath,v as useNavigateTo,b as useResolvedPath,f as useRouteParams,S as useTypedSearchParams};
1
+ import{j as s,k as n,l as i,o as u}from"../chunk-IP4ZU25B.js";import{useParams as y}from"react-router";function g(e){return y()}import{useCallback as l}from"react";import{useNavigate as f}from"react-router";function d(){let e=f();return l((r,t)=>{e(String(r),t)},[e])}function x(e,r,t,a){return i(e,r,t,a)}import{useLocation as h}from"react-router";function v(e,r={}){let t=h();return u(t.pathname,e,r)}import{useCallback as R}from"react";import{useLocation as b,useNavigate as N}from"react-router";function T(e){let r=b(),t=N(),a=n(r.search,e),m=R((p,c)=>{let o=s("",p),P=new URLSearchParams(o.startsWith("?")?o.slice(1):o);t(`?${P.toString()}`,c)},[t]);return[a,m]}export{v as useActivePath,d as useNavigateTo,x as useResolvedPath,g as useRouteParams,T as useTypedSearchParams};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useParams, useNavigate, useLocation } from \"react-router\";\nimport type {\n ExtractParams,\n QueryParams,\n RouteParams,\n BuildPathOptions,\n} from \"../types\";\nimport { buildPath, isActivePath, extractQueryFromPath, appendQuery } from \"../core/utils\";\n\n// ─── useRouteParams ──────────────────────────────────────────────────────────\n\n/**\n * A typed wrapper around React Router's `useParams`.\n *\n * Pass a route template as a type parameter, or a dynamic route value from a\n * `defineRoutes()` tree for automatic type inference:\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 * // Or pass a route from your PATHS tree — params are inferred from it:\n * const PATHS = defineRoutes({ USERS: { EDIT: '/users/edit/:id' } } as const);\n * const { id } = useRouteParams(PATHS.USERS.EDIT);\n * ```\n */\n// Overload 1: no-arg generic — caller provides the template literal as T\nexport function useRouteParams<T extends string = string>(): Record<\n ExtractParams<T>,\n string\n>;\n// Overload 2: pass a route from defineRoutes() — P is inferred from paramNames array element type\nexport function useRouteParams<P extends string>(\n route: { readonly paramNames: ReadonlyArray<P> | Array<P> },\n): Record<P, string>;\n// Implementation\nexport function useRouteParams<P extends string>(\n _route?: { readonly paramNames: ReadonlyArray<P> | Array<P> },\n): Record<string, string> {\n return useParams() as Record<string, string>;\n}\n\n// ─── useNavigateTo ──────────────────────────────────────────────────────────\n\nexport type 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 route value straight from `defineRoutes()` (e.g. a `String` object or a\n * primitive string), with optional navigation options.\n *\n * `String` objects (used by route values so they can carry `.build()`) are\n * coerced to primitives, since React Router's `navigate()` ignores them.\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 useCallback(\n (path: string, options?: NavigateOptions) => {\n navigate(String(path), options);\n },\n [navigate],\n );\n}\n\n// ─── useResolvedPath ─────────────────────────────────────────────────────────\n\n/**\n * Resolves a dynamic path template against params, mirroring `buildPath()`.\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 * @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 * const path = useResolvedPath('/files/*', { \"*\": \"a/b\" }); // → '/files/a/b'\n * ```\n */\nexport function useResolvedPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n\n// ─── useActivePath ───────────────────────────────────────────────────────────\n\n/**\n * A hook that checks whether the current location matches a route template or path.\n * Thin wrapper around `isActivePath(useLocation().pathname, template, options)`.\n *\n * @example\n * ```tsx\n * const isActive = useActivePath(PATHS.USERS.ROOT, { exact: false });\n * ```\n */\nexport function useActivePath(\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {\n exact: true,\n caseSensitive: false,\n },\n): boolean {\n const location = useLocation();\n return isActivePath(location.pathname, template, options);\n}\n\n// ─── useTypedSearchParams ────────────────────────────────────────────────────\n\n/**\n * A typed wrapper around React Router's `useSearchParams`.\n * Returns parsed query params object and a setter that updates query params.\n *\n * Implemented on top of `useLocation` + `useNavigate` (both exported by the\n * `react-router` core in v6 and v7) rather than `useSearchParams`, because in\n * React Router v6 `useSearchParams` only exists in the DOM wrapper package\n * (`react-router-dom`), while v7 moved it into `react-router`. Reimplementing\n * it lets the whole hooks entry work identically against both packages.\n *\n * @example\n * ```tsx\n * const [query, setQuery] = useTypedSearchParams({ coerceBooleans: true, coerceNumbers: true });\n * setQuery({ tab: 'details', page: 2 });\n * ```\n */\nexport function useTypedSearchParams(options?: {\n coerceBooleans?: boolean;\n coerceNumbers?: boolean;\n}) {\n const location = useLocation();\n const navigate = useNavigate();\n\n const queryParams = extractQueryFromPath(location.search, options);\n\n const setTypedQuery = useCallback(\n (\n newQuery: QueryParams,\n navigateOptions?: { replace?: boolean; state?: unknown },\n ) => {\n const updatedPath = appendQuery(\"\", newQuery);\n const updatedSearchParams = new URLSearchParams(\n updatedPath.startsWith(\"?\") ? updatedPath.slice(1) : updatedPath,\n );\n navigate(`?${updatedSearchParams.toString()}`, navigateOptions);\n },\n [navigate],\n );\n\n return [queryParams, setTypedQuery] as const;\n}\n\n"],"mappings":"8DAAA,OAAS,eAAAA,MAAmB,QAC5B,OAAS,aAAAC,EAAW,eAAAC,EAAa,eAAAC,MAAmB,eAqC7C,SAASC,EACdC,EACwB,CACxB,OAAOC,EAAU,CACnB,CAwBO,SAASC,GAAgB,CAC9B,IAAMC,EAAWC,EAAY,EAE7B,OAAOC,EACL,CAACC,EAAcC,IAA8B,CAC3CJ,EAAS,OAAOG,CAAI,EAAGC,CAAO,CAChC,EACA,CAACJ,CAAQ,CACX,CACF,CAkBO,SAASK,EACdC,EACAC,EACAC,EACAJ,EACQ,CACR,OAAOK,EAAUH,EAAUC,EAAQC,EAAOJ,CAAO,CACnD,CAaO,SAASM,EACdJ,EACAF,EAAwD,CACtD,MAAO,GACP,cAAe,EACjB,EACS,CACT,IAAMO,EAAWC,EAAY,EAC7B,OAAOC,EAAaF,EAAS,SAAUL,EAAUF,CAAO,CAC1D,CAoBO,SAASU,EAAqBV,EAGlC,CACD,IAAMO,EAAWC,EAAY,EACvBZ,EAAWC,EAAY,EAEvBc,EAAcC,EAAqBL,EAAS,OAAQP,CAAO,EAE3Da,EAAgBf,EACpB,CACEgB,EACAC,IACG,CACH,IAAMC,EAAcC,EAAY,GAAIH,CAAQ,EACtCI,EAAsB,IAAI,gBAC9BF,EAAY,WAAW,GAAG,EAAIA,EAAY,MAAM,CAAC,EAAIA,CACvD,EACApB,EAAS,IAAIsB,EAAoB,SAAS,CAAC,GAAIH,CAAe,CAChE,EACA,CAACnB,CAAQ,CACX,EAEA,MAAO,CAACe,EAAaE,CAAa,CACpC","names":["useCallback","useParams","useNavigate","useLocation","useRouteParams","_route","useParams","useNavigateTo","navigate","useNavigate","useCallback","path","options","useResolvedPath","template","params","query","buildPath","useActivePath","location","useLocation","isActivePath","useTypedSearchParams","queryParams","extractQueryFromPath","setTypedQuery","newQuery","navigateOptions","updatedPath","appendQuery","updatedSearchParams"]}
1
+ {"version":3,"sources":["../../src/hooks/useRouteParams.ts","../../src/hooks/useNavigateTo.ts","../../src/hooks/useResolvedPath.ts","../../src/hooks/useActivePath.ts","../../src/hooks/useTypedSearchParams.ts"],"sourcesContent":["import { useParams } from \"react-router\";\nimport type { ExtractParams } from \"../types\";\n\n/**\n * A typed wrapper around React Router's `useParams`.\n *\n * Pass a route template as a type parameter, or a dynamic route value from a\n * `defineRoutes()` tree for automatic type inference:\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 * // Or pass a route from your PATHS tree — params are inferred from it:\n * const PATHS = defineRoutes({ USERS: { EDIT: '/users/edit/:id' } } as const);\n * const { id } = useRouteParams(PATHS.USERS.EDIT);\n * ```\n */\n// Overload 1: no-arg generic — caller provides the template literal as T\nexport function useRouteParams<T extends string = string>(): Record<\n ExtractParams<T>,\n string\n>;\n// Overload 2: pass a route from defineRoutes() — P is inferred from paramNames array element type\nexport function useRouteParams<P extends string>(route: {\n readonly paramNames: ReadonlyArray<P> | Array<P>;\n}): Record<P, string>;\n// Implementation\nexport function useRouteParams<P extends string>(_route?: {\n readonly paramNames: ReadonlyArray<P> | Array<P>;\n}): Record<string, string> {\n return useParams() as Record<string, string>;\n}\n","import { useCallback } from \"react\";\nimport { useNavigate } from \"react-router\";\n\nexport type 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 route value straight from `defineRoutes()` (e.g. a `String` object or a\n * primitive string), with optional navigation options.\n *\n * `String` objects (used by route values so they can carry `.build()`) are\n * coerced to primitives, since React Router's `navigate()` ignores them.\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 useCallback(\n (path: string, options?: NavigateOptions) => {\n navigate(String(path), options);\n },\n [navigate],\n );\n}\n","import { buildPath } from \"../core/build\";\nimport type { BuildPathOptions, QueryParams, RouteParams } from \"../types\";\n\n/**\n * Resolves a dynamic path template against params, mirroring `buildPath()`.\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 * @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 * const path = useResolvedPath('/files/*', { \"*\": \"a/b\" }); // → '/files/a/b'\n * ```\n */\nexport function useResolvedPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n","import { useLocation } from \"react-router\";\nimport { isActivePath } from \"../core/match\";\n\n/**\n * A hook that checks whether the current location matches a route template or path.\n * Thin wrapper around `isActivePath(useLocation().pathname, template, options)`.\n *\n * @example\n * ```tsx\n * const isActive = useActivePath(PATHS.USERS.ROOT, { exact: false });\n * ```\n */\nexport function useActivePath(\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {},\n): boolean {\n // Forwarded as-is — isActivePath defaults `exact`/`caseSensitive`\n // individually, so a partial options object here still behaves correctly.\n const location = useLocation();\n return isActivePath(location.pathname, template, options);\n}\n","import { useCallback } from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { appendQuery, extractQueryFromPath } from \"../core/query\";\nimport type { QueryParams } from \"../types\";\n\n/**\n * A typed wrapper around React Router's `useSearchParams`.\n * Returns parsed query params object and a setter that updates query params.\n *\n * Implemented on top of `useLocation` + `useNavigate` (both exported by the\n * `react-router` core in v6 and v7) rather than `useSearchParams`, because in\n * React Router v6 `useSearchParams` only exists in the DOM wrapper package\n * (`react-router-dom`), while v7 moved it into `react-router`. Reimplementing\n * it lets the whole hooks entry work identically against both packages.\n *\n * @example\n * ```tsx\n * const [query, setQuery] = useTypedSearchParams({ coerceBooleans: true, coerceNumbers: true });\n * setQuery({ tab: 'details', page: 2 });\n * ```\n */\nexport function useTypedSearchParams(options?: {\n coerceBooleans?: boolean;\n coerceNumbers?: boolean;\n}) {\n const location = useLocation();\n const navigate = useNavigate();\n\n const queryParams = extractQueryFromPath(location.search, options);\n\n const setTypedQuery = useCallback(\n (\n newQuery: QueryParams,\n navigateOptions?: { replace?: boolean; state?: unknown },\n ) => {\n const updatedPath = appendQuery(\"\", newQuery);\n const updatedSearchParams = new URLSearchParams(\n updatedPath.startsWith(\"?\") ? updatedPath.slice(1) : updatedPath,\n );\n navigate(`?${updatedSearchParams.toString()}`, navigateOptions);\n },\n [navigate],\n );\n\n return [queryParams, setTypedQuery] as const;\n}\n"],"mappings":"8DAAA,OAAS,aAAAA,MAAiB,eA6BnB,SAASC,EAAiCC,EAEtB,CACzB,OAAOF,EAAU,CACnB,CCjCA,OAAS,eAAAG,MAAmB,QAC5B,OAAS,eAAAC,MAAmB,eAsBrB,SAASC,GAAgB,CAC9B,IAAMC,EAAWF,EAAY,EAE7B,OAAOD,EACL,CAACI,EAAcC,IAA8B,CAC3CF,EAAS,OAAOC,CAAI,EAAGC,CAAO,CAChC,EACA,CAACF,CAAQ,CACX,CACF,CCfO,SAASG,EACdC,EACAC,EACAC,EACAC,EACQ,CACR,OAAOC,EAAUJ,EAAUC,EAAQC,EAAOC,CAAO,CACnD,CCxBA,OAAS,eAAAE,MAAmB,eAYrB,SAASC,EACdC,EACAC,EAAwD,CAAC,EAChD,CAGT,IAAMC,EAAWC,EAAY,EAC7B,OAAOC,EAAaF,EAAS,SAAUF,EAAUC,CAAO,CAC1D,CCpBA,OAAS,eAAAI,MAAmB,QAC5B,OAAS,eAAAC,EAAa,eAAAC,MAAmB,eAoBlC,SAASC,EAAqBC,EAGlC,CACD,IAAMC,EAAWC,EAAY,EACvBC,EAAWC,EAAY,EAEvBC,EAAcC,EAAqBL,EAAS,OAAQD,CAAO,EAE3DO,EAAgBC,EACpB,CACEC,EACAC,IACG,CACH,IAAMC,EAAcC,EAAY,GAAIH,CAAQ,EACtCI,EAAsB,IAAI,gBAC9BF,EAAY,WAAW,GAAG,EAAIA,EAAY,MAAM,CAAC,EAAIA,CACvD,EACAR,EAAS,IAAIU,EAAoB,SAAS,CAAC,GAAIH,CAAe,CAChE,EACA,CAACP,CAAQ,CACX,EAEA,MAAO,CAACE,EAAaE,CAAa,CACpC","names":["useParams","useRouteParams","_route","useCallback","useNavigate","useNavigateTo","navigate","path","options","useResolvedPath","template","params","query","options","buildPath","useLocation","useActivePath","template","options","location","useLocation","isActivePath","useCallback","useLocation","useNavigate","useTypedSearchParams","options","location","useLocation","navigate","useNavigate","queryParams","extractQueryFromPath","setTypedQuery","useCallback","newQuery","navigateOptions","updatedPath","appendQuery","updatedSearchParams"]}
@@ -145,4 +145,4 @@ type MatchPathOptions = {
145
145
  caseSensitive?: boolean;
146
146
  };
147
147
 
148
- export type { BuildPathOptions as B, ExtractParams as E, FlatRoute as F, MatchPathOptions as M, PathParams as P, QueryParams as Q, RouteParams as R, BreadcrumbOptions as a, BreadcrumbItem as b, RoutePath as c, RouteTree as d, RouteBuilder as e, RouteLeaf as f, RouteMap as g, RouteParam as h };
148
+ export type { BuildPathOptions as B, ExtractParams as E, FlatRoute as F, MatchPathOptions as M, PathParams as P, QueryParams as Q, RouteParams as R, RoutePath as a, RouteTree as b, BreadcrumbOptions as c, BreadcrumbItem as d, RouteBuilder as e, RouteLeaf as f, RouteMap as g, RouteParam as h };
@@ -145,4 +145,4 @@ type MatchPathOptions = {
145
145
  caseSensitive?: boolean;
146
146
  };
147
147
 
148
- export type { BuildPathOptions as B, ExtractParams as E, FlatRoute as F, MatchPathOptions as M, PathParams as P, QueryParams as Q, RouteParams as R, BreadcrumbOptions as a, BreadcrumbItem as b, RoutePath as c, RouteTree as d, RouteBuilder as e, RouteLeaf as f, RouteMap as g, RouteParam as h };
148
+ export type { BuildPathOptions as B, ExtractParams as E, FlatRoute as F, MatchPathOptions as M, PathParams as P, QueryParams as Q, RouteParams as R, RoutePath as a, RouteTree as b, BreadcrumbOptions as c, BreadcrumbItem as d, RouteBuilder as e, RouteLeaf as f, RouteMap as g, RouteParam as h };