react-routes-forge 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -9
- package/dist/chunk-3E7SIS7B.js +2 -0
- package/dist/chunk-3E7SIS7B.js.map +1 -0
- package/dist/{chunk-H5UZR7HM.js → chunk-LKDKVG2S.js} +2 -2
- package/dist/hooks/index.cjs +1 -1
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +3 -3
- package/dist/hooks/index.d.ts +3 -3
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/{index-B3PDrNod.d.cts → index-_N1whVbt.d.cts} +4 -1
- package/dist/{index-B3PDrNod.d.ts → index-_N1whVbt.d.ts} +4 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/next/index.cjs +1 -1
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +8 -4
- package/dist/next/index.d.ts +8 -4
- package/dist/next/index.js +1 -1
- package/dist/next/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-NX4N6IWV.js +0 -2
- package/dist/chunk-NX4N6IWV.js.map +0 -1
- /package/dist/{chunk-H5UZR7HM.js.map → chunk-LKDKVG2S.js.map} +0 -0
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- [`defineRoutes(routeMap)`](#defineroutesroutemap)
|
|
24
24
|
- [`build(template, params, query?, options?)`](#buildtemplate-params-query-options)
|
|
25
25
|
- [`buildPath(template, params, query?, options?)`](#buildpathtemplate-params-query-options)
|
|
26
|
+
- [`buildRelative(template, params, query?, options?)`](#buildrelativetemplate-params-query-options)
|
|
26
27
|
- [`isActivePath(currentPath, template, options?)`](#isactivepathcurrentpath-template-options)
|
|
27
28
|
- [`extractParamsFromPath(template, resolvedPath)`](#extractparamsfrompathtemplate-resolvedpath)
|
|
28
29
|
- [`matchPath(template, options?)`](#matchpathtemplate-options)
|
|
@@ -43,6 +44,11 @@
|
|
|
43
44
|
- [`useActivePath(template, options?)`](#useactivepathtemplate-options)
|
|
44
45
|
- [`useTypedSearchParams(options?)`](#usetypedsearchparamsoptions)
|
|
45
46
|
- [Next.js Integration](#nextjs-integration)
|
|
47
|
+
- [`useActivePath` (Next.js)](#nextjs-hooks)
|
|
48
|
+
- [`useNavigateTo` (Next.js)](#nextjs-hooks)
|
|
49
|
+
- [`useRouteParams` (Next.js)](#nextjs-hooks)
|
|
50
|
+
- [`useTypedSearchParams` (Next.js)](#nextjs-hooks)
|
|
51
|
+
- [`NavigateOptions` type](#nextjs-hooks)
|
|
46
52
|
- [Splat (`/*`) segments](#splat--segments)
|
|
47
53
|
- [Route validation](#route-validation)
|
|
48
54
|
- [Query string support](#query-string-support)
|
|
@@ -216,7 +222,7 @@ Quick reference for everything the package exports — grouped by kind. Click th
|
|
|
216
222
|
| [`devWarn(message)`](#devwarnmessage) | Emit a `console.warn` in non-production builds |
|
|
217
223
|
| [`clearPathCache()`](#clearpathcache) | Reset internal regex caches (mainly for tests) |
|
|
218
224
|
|
|
219
|
-
### React hooks
|
|
225
|
+
### React hooks (React Router)
|
|
220
226
|
|
|
221
227
|
| Export | Purpose |
|
|
222
228
|
| ----------------------------------------------------------------------- | ------------------------------------------------------------- |
|
|
@@ -224,10 +230,21 @@ Quick reference for everything the package exports — grouped by kind. Click th
|
|
|
224
230
|
| [`useNavigateTo()`](#usenavigateto) | Typed wrapper around React Router's `useNavigate` |
|
|
225
231
|
| [`useResolvedPath(...)`](#useresolvedpathtemplate-params-query-options) | Resolve a template to a string without navigating |
|
|
226
232
|
| [`useActivePath(template, options?)`](#useactivepathtemplate-options) | Check if the current location matches a route template |
|
|
227
|
-
| [`useTypedSearchParams(options?)`](#usetypedsearchparamsoptions)
|
|
233
|
+
| [`useTypedSearchParams<T>(options?)`](#usetypedsearchparamsoptions) | Typed `useSearchParams` with generic return type and boolean/number coercion |
|
|
234
|
+
|
|
235
|
+
### Next.js hooks (`react-routes-forge/next`)
|
|
236
|
+
|
|
237
|
+
| Export | Purpose |
|
|
238
|
+
| ----------------------------- | ----------------------------------------------------------------------- |
|
|
239
|
+
| `useActivePath(template, options?)` | Matches pathname via `usePathname()` — same API as the React Router variant |
|
|
240
|
+
| `useNavigateTo()` | Navigation via `useRouter()`; returned fn also exposes `.prefetch()` |
|
|
241
|
+
| `useRouteParams<T>()` | Typed params via `useParams()` — same overload API |
|
|
242
|
+
| `useTypedSearchParams<T>(options?)` | Typed query params backed by `useSearchParams()` + `useRouter()` |
|
|
243
|
+
| `NavigateOptions` *(type)* | `{ replace?: boolean; scroll?: boolean }` — nav options type |
|
|
228
244
|
|
|
229
245
|
---
|
|
230
246
|
|
|
247
|
+
|
|
231
248
|
## API reference
|
|
232
249
|
|
|
233
250
|
### `defineRoutes(routeMap)`
|
|
@@ -290,6 +307,10 @@ build("/users/:id", { id: 42 }, { tab: "info" }, { hash: "details" });
|
|
|
290
307
|
// → '/users/42?tab=info#details'
|
|
291
308
|
build("/page", {}, undefined, { hash: "section" });
|
|
292
309
|
// → '/page#section'
|
|
310
|
+
|
|
311
|
+
// Locale prefix — pre-pends the path with a locale segment
|
|
312
|
+
build("/users/:id", { id: 42 }, undefined, { locale: "en-US" });
|
|
313
|
+
// → '/en-US/users/42'
|
|
293
314
|
```
|
|
294
315
|
|
|
295
316
|
**Param values are URL-encoded by default** (`encodeURIComponent`), so characters like `/`, `?`, `#`, or `%` in a value can't break the URL structure:
|
|
@@ -326,6 +347,28 @@ All `build()` examples above apply verbatim to `buildPath`.
|
|
|
326
347
|
|
|
327
348
|
---
|
|
328
349
|
|
|
350
|
+
### `buildRelative(template, params, query?, options?)`
|
|
351
|
+
|
|
352
|
+
Identical to `build()`, but returns a relative path by stripping the leading slash (and returning `"."` if the path resolves to `/` or empty). It is available as a standalone function `buildRelative()` and as `.buildRelative()` on the route values returned by `defineRoutes()`.
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { defineRoutes, buildRelative } from "react-routes-forge";
|
|
356
|
+
|
|
357
|
+
const PATHS = defineRoutes({
|
|
358
|
+
USERS: {
|
|
359
|
+
EDIT: "/users/:id/edit"
|
|
360
|
+
}
|
|
361
|
+
} as const);
|
|
362
|
+
|
|
363
|
+
PATHS.USERS.EDIT.buildRelative({ id: 42 });
|
|
364
|
+
// → 'users/42/edit'
|
|
365
|
+
|
|
366
|
+
buildRelative("/users/:id", { id: 42 });
|
|
367
|
+
// → 'users/42'
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
329
372
|
### `isActivePath(currentPath, template, options?)`
|
|
330
373
|
|
|
331
374
|
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:
|
|
@@ -647,7 +690,7 @@ beforeEach(() => {
|
|
|
647
690
|
|
|
648
691
|
Import these if you're using React Router or Next.js — they live in separate entries (`react-routes-forge/hooks` and `react-routes-forge/next`), so the core package never pulls in router dependencies. The React Router hooks work identically with **`react-router-dom`** (v6/v7) and **`react-router`** (v6/v7).
|
|
649
692
|
|
|
650
|
-
> **Note for Next.js users**: Equivalent hooks for Next.js App Router and Pages Router are available in `react-routes-forge/next`. They share the same API as the React Router hooks documented below.
|
|
693
|
+
> **Note for Next.js users**: Equivalent hooks for Next.js App Router and Pages Router are available in `react-routes-forge/next`. They share the same API as the React Router hooks documented below. See the [Next.js Integration section](#nextjs-integration) for full details, patterns, and troubleshooting.
|
|
651
694
|
|
|
652
695
|
### `useRouteParams<T>()`
|
|
653
696
|
|
|
@@ -686,7 +729,7 @@ function EditUserInferred() {
|
|
|
686
729
|
|
|
687
730
|
### `useNavigateTo()`
|
|
688
731
|
|
|
689
|
-
Thin, typed wrapper around `useNavigate()` (or Next.js's `useRouter()`) that accepts a resolved path (the output of `.build()`) along with the usual navigation options.
|
|
732
|
+
Thin, typed wrapper around `useNavigate()` (or Next.js's `useRouter()`) that accepts a resolved path (the output of `.build()`) along with the usual navigation options. Includes a `.prefetch()` method for Next.js or performance-oriented routers.
|
|
690
733
|
|
|
691
734
|
```tsx
|
|
692
735
|
// For React Router
|
|
@@ -707,6 +750,7 @@ function Component() {
|
|
|
707
750
|
|
|
708
751
|
navigateTo(PATHS.HOME, { replace: true });
|
|
709
752
|
navigateTo(PATHS.USERS.ROOT, { state: { from: "settings" } });
|
|
753
|
+
navigateTo.prefetch(PATHS.USERS.ROOT);
|
|
710
754
|
```
|
|
711
755
|
|
|
712
756
|
---
|
|
@@ -764,23 +808,27 @@ function Nav() {
|
|
|
764
808
|
|
|
765
809
|
---
|
|
766
810
|
|
|
767
|
-
### `useTypedSearchParams(options?)`
|
|
811
|
+
### `useTypedSearchParams<T>(options?)`
|
|
768
812
|
|
|
769
813
|
A typed wrapper around the router's search-params API (React Router's `useSearchParams` or Next.js's `useSearchParams` + `useRouter`). Returns a parsed query params object (using [`extractQueryFromPath()`](#extractqueryfrompathpath-options)) and a setter that updates the query string. The same coercion options are supported: `{ coerceBooleans: true }` and `{ coerceNumbers: true }`.
|
|
770
814
|
|
|
815
|
+
You can pass a generic type parameter `T` to get a strongly-typed `query` object and constrained setter:
|
|
816
|
+
|
|
771
817
|
```tsx
|
|
772
818
|
// For React Router
|
|
773
819
|
import { useTypedSearchParams } from "react-routes-forge/hooks";
|
|
774
820
|
// For Next.js
|
|
775
821
|
// import { useTypedSearchParams } from "react-routes-forge/next";
|
|
776
822
|
|
|
823
|
+
type SearchQuery = { page?: number; sort?: string; active?: boolean };
|
|
824
|
+
|
|
777
825
|
function Filters() {
|
|
778
|
-
const [query, setQuery] = useTypedSearchParams({
|
|
826
|
+
const [query, setQuery] = useTypedSearchParams<SearchQuery>({
|
|
779
827
|
coerceBooleans: true,
|
|
780
828
|
coerceNumbers: true,
|
|
781
829
|
});
|
|
782
830
|
|
|
783
|
-
// query.page is
|
|
831
|
+
// query.page is typed as number | undefined
|
|
784
832
|
const nextPage = (query.page ?? 0) + 1;
|
|
785
833
|
setQuery({ ...query, page: nextPage });
|
|
786
834
|
|
|
@@ -804,19 +852,33 @@ All hooks provided for Next.js are available in `react-routes-forge/next` and re
|
|
|
804
852
|
import { useActivePath, useRouteParams, useNavigateTo, useTypedSearchParams } from "react-routes-forge/next";
|
|
805
853
|
import { PATHS } from "../paths";
|
|
806
854
|
|
|
855
|
+
type SearchQuery = { q?: string; page?: number };
|
|
856
|
+
|
|
807
857
|
export function Sidebar() {
|
|
808
858
|
const isUsersActive = useActivePath(PATHS.USERS.ROOT);
|
|
809
859
|
const { id } = useRouteParams(PATHS.USERS.DETAILS);
|
|
810
860
|
const navigateTo = useNavigateTo();
|
|
861
|
+
const [query, setQuery] = useTypedSearchParams<SearchQuery>({ coerceNumbers: true });
|
|
862
|
+
|
|
863
|
+
// Prefetch a route on hover (Next.js only)
|
|
864
|
+
const handleHover = () => navigateTo.prefetch(PATHS.USERS.ROOT);
|
|
811
865
|
|
|
812
|
-
return <nav>...</nav>;
|
|
866
|
+
return <nav onMouseEnter={handleHover}>...</nav>;
|
|
813
867
|
}
|
|
814
868
|
```
|
|
815
869
|
|
|
870
|
+
**`NavigateOptions` type:**
|
|
871
|
+
The `react-routes-forge/next` entry also exports a `NavigateOptions` type for the navigation options:
|
|
872
|
+
|
|
873
|
+
```ts
|
|
874
|
+
import type { NavigateOptions } from "react-routes-forge/next";
|
|
875
|
+
// { replace?: boolean; scroll?: boolean }
|
|
876
|
+
```
|
|
877
|
+
|
|
816
878
|
**Server Components:**
|
|
817
879
|
You can safely import your `PATHS` definition in Server Components and use `.build()` to generate URLs for `<Link href={...}>`, `redirect()`, or metadata generation without any issues. No `"use client"` is needed for the core API!
|
|
818
880
|
|
|
819
|
-
For a full breakdown of Next.js usage, see the [Next.js documentation](https://mhsmustafa84.github.io/react-routes-forge/nextjs).
|
|
881
|
+
For a full breakdown of Next.js usage, see the [Next.js documentation](https://mhsmustafa84.github.io/react-routes-forge/nextjs/).
|
|
820
882
|
|
|
821
883
|
---
|
|
822
884
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function C(){return globalThis.process?.env?.NODE_ENV==="production"}function $(e){C()||console.warn(e)}var k=/[.*+?^${}()|[\]\\]/g,h=/(^|\/):([A-Za-z0-9_]+)(\?)?/g;function d(e){return e.replace(k,"\\$&")}function b(e){let t=e.endsWith("/*"),s=t?e.slice(0,-2):e,n="",r=0;for(let i of s.matchAll(h)){let o=i.index??0,u=i[0];n+=d(s.slice(r,o));let[,c="",,f]=i;n+=f?`(?:${d(c)}([^/]+))?`:`${d(c)}([^/]+)`,r=o+(u?.length??0)}return n+d(s.slice(r))+(t?"(?:/(.*))?":"")}function E(e,t){return new RegExp(`:${d(t)}\\?(?![A-Za-z0-9_])`).test(e)}var y=new Map,Q=/^/;function S(e){if(e==="/")return Q;let t=y.get(e);if(t!==void 0)return t;let s=new RegExp(`^${b(e)}(?=/|$)`);return y.set(e,s),s}function w(e){try{return decodeURIComponent(e)}catch{return e}}var R=new Map;function x(e,t){let s=t?.end??!0,n=t?.caseSensitive?"":"i",r=`${e}:${s}:${n}`,i=R.get(r);if(i!==void 0)return i;let o=b(e),u=s?`^${o}$`:`^${o}(?=/|$)`,c=new RegExp(u,n);return R.set(r,c),c}function W(){y.clear(),R.clear()}function P(e){let t=[...e.matchAll(h)].map(s=>s[2]);return e.endsWith("/*")&&t.push("*"),t}function L(e){return e.search(h)!==-1||e.endsWith("/*")}function B(e){return P(e)}function D(e,t){let s=t.split("?")[0]??"",n=P(e),r=s.match(x(e));if(!r)return{};let i={};return n.forEach((o,u)=>{let c=r[u+1];c!==void 0&&(i[o]=w(c))}),i}function A(e,t,s){if(t!=null)if(Array.isArray(t))t.forEach(n=>{n!=null&&s.append(e,String(n))});else if(typeof t=="object")for(let[n,r]of Object.entries(t))A(`${e}[${n}]`,r,s);else s.append(e,String(t))}function _(e,t){let s=`${t}[`,n=[];for(let r of e.keys())(r===t||r.startsWith(s))&&n.push(r);for(let r of n)e.delete(r)}function O(e,t,s){let n=e.indexOf("#"),r=n===-1?e:e.slice(0,n),i=n===-1?"":e.slice(n+1),o=r.indexOf("?"),u=o===-1?r:r.slice(0,o),c=o===-1?"":r.slice(o+1),f=new URLSearchParams(c);if(t)for(let[g,p]of Object.entries(t))Object.prototype.hasOwnProperty.call(t,g)&&(_(f,g),p!=null&&A(g,p,f));let a=u,l=f.toString();return l&&(a+="?"+l),s?a+="#"+encodeURIComponent(s):i&&(a+="#"+i),a}function T(e,t,s){let n=t.replace(/\]/g,"").split(/\[/),r=e;for(let i=0;i<n.length-1;i++){let o=n[i];(!r[o]||typeof r[o]!="object"||Array.isArray(r[o]))&&(r[o]={}),r=r[o]}r[n[n.length-1]]=s}function z(e,t){let s=e.indexOf("#"),n=s===-1?e:e.slice(0,s),r=n.indexOf("?");if(r===-1)return{};let i=new URLSearchParams(n.slice(r+1)),o={};for(let u of new Set(i.keys())){let f=i.getAll(u).map(l=>{if(t?.coerceBooleans){let g=String(l).toLowerCase();if(g==="true"||g==="false")return g==="true"}return t?.coerceNumbers&&l.trim()!==""&&!isNaN(Number(l))?Number(l):l}),a=f.length>1?f:f[0]??"";T(o,u,a)}return o}function I(e,t,s,n){let r=P(e),i=r.filter(c=>(t[c]===void 0||t[c]===null)&&!E(e,c)&&c!=="*"),o=r.reduce((c,f)=>{let a=t[f],l=a==null;if(f==="*"){if(l)return c.replace(/\/\*$/,"")||"/";if(typeof a!="string"&&typeof a!="number")throw new TypeError(`Splat parameter must be string or number, got ${typeof a}`);let p=n?.encode===!1?String(a):String(a).split("/").map(m=>encodeURIComponent(m)).join("/");return c.replace(/\/\*$/,`/${p}`)}let g=new RegExp(`(^|/):${d(f)}\\??(?![A-Za-z0-9_])`,"g");return c.replace(g,(p,m)=>{if(l)return p.endsWith("?")?"":`${m}:${f}`;let N=n?.encode===!1?String(a):encodeURIComponent(String(a));return`${m}${N}`})},e);if(i.length>0){if(n?.strict)throw new RangeError(`[route-forge] Missing required param(s) ${i.map(c=>`":${c}"`).join(", ")} in template "${e}".`);$(`[route-forge] Unresolved params in path "${o}". Check that all :param segments have matching keys.`)}let u=n?.locale?j(n.locale,o):o;return O(u,s,n?.hash)}function X(e,t,s,n){return I(e,t,s,n)}function j(...e){return"/"+e.map(n=>n.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function Y(e,t,s={}){let n=s.exact??!0,r=s.caseSensitive??!1,i=(e.split("?")[0]??"").replace(/\/+$/,"")||"/",o=t.replace(/\/+$/,"")||"/",u=r?o:o.toLowerCase(),c=r?i:i.toLowerCase();return(n?x(u,{caseSensitive:r}):S(u)).test(c)}export{C as a,$ as b,S as c,x as d,W as e,P as f,L as g,B as h,D as i,O as j,z as k,I as l,X as m,j as n,Y as o};
|
|
2
|
+
//# sourceMappingURL=chunk-3E7SIS7B.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","/** Global RegExp instances — safe since String.prototype.replace/matchAll don't bleed lastIndex. */\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 path.search(PARAM_SEGMENT_RE) !== -1 || 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, QueryParamValue } from \"../types\";\n\nfunction serializeToParams(keyPrefix: string, value: QueryParamValue, searchParams: URLSearchParams) {\n if (value === undefined || value === null) return;\n \n if (Array.isArray(value)) {\n value.forEach((v) => {\n if (v !== undefined && v !== null) {\n searchParams.append(keyPrefix, String(v));\n }\n });\n } else if (typeof value === 'object') {\n for (const [k, v] of Object.entries(value)) {\n serializeToParams(`${keyPrefix}[${k}]`, v, searchParams);\n }\n } else {\n searchParams.append(keyPrefix, String(value));\n }\n}\n\nfunction deleteMatchingKeys(searchParams: URLSearchParams, rootKey: string) {\n const prefix = `${rootKey}[`;\n const keysToDelete: string[] = [];\n for (const key of searchParams.keys()) {\n if (key === rootKey || key.startsWith(prefix)) {\n keysToDelete.push(key);\n }\n }\n for (const key of keysToDelete) {\n searchParams.delete(key);\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(\n path: string,\n query?: QueryParams,\n hash?: string,\n): string {\n const hashIdx = path.indexOf(\"#\");\n const pathWithoutHash = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const existingHash = hashIdx === -1 ? \"\" : path.slice(hashIdx + 1);\n\n const queryIdx = pathWithoutHash.indexOf(\"?\");\n const base = queryIdx === -1 ? pathWithoutHash : pathWithoutHash.slice(0, queryIdx);\n const existingQueryString = queryIdx === -1 ? \"\" : pathWithoutHash.slice(queryIdx + 1);\n\n const searchParams = new URLSearchParams(existingQueryString);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (!Object.prototype.hasOwnProperty.call(query, key)) continue;\n \n deleteMatchingKeys(searchParams, key);\n \n if (value !== undefined && value !== null) {\n serializeToParams(key, value, searchParams);\n }\n }\n }\n\n let result = base;\n const queryString = searchParams.toString();\n if (queryString) {\n result += \"?\" + queryString;\n }\n\n if (hash) {\n result += \"#\" + encodeURIComponent(hash);\n } else if (existingHash) {\n result += \"#\" + existingHash;\n }\n\n return result;\n}\n\nfunction setDeepProperty(obj: Record<string, unknown>, path: string, value: unknown) {\n const parts = path.replace(/\\]/g, \"\").split(/\\[/);\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i] as string;\n if (!current[part] || typeof current[part] !== 'object' || Array.isArray(current[part])) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n current[parts[parts.length - 1] as string] = value;\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 * Supports deep object nesting via bracket notation (e.g. `user[name]=John`).\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) {\n const lower = String(v).toLowerCase();\n if (lower === \"true\" || lower === \"false\") {\n return lower === \"true\";\n }\n }\n if (options?.coerceNumbers && v.trim() !== \"\" && !isNaN(Number(v))) {\n return Number(v);\n }\n return v;\n });\n const finalValue = parsed.length > 1 ? parsed : (parsed[0] ?? \"\");\n setDeepProperty(result, key, finalValue);\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\n if (typeof value !== 'string' && typeof value !== 'number') {\n throw new TypeError(\n `Splat parameter must be string or number, got ${typeof value}`\n );\n }\n\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 const finalPath = options?.locale\n ? joinPaths(options.locale, resolved)\n : resolved;\n\n return appendQuery(finalPath, 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","serializeToParams","keyPrefix","value","searchParams","v","k","deleteMatchingKeys","rootKey","prefix","keysToDelete","key","appendQuery","path","query","hash","hashIdx","pathWithoutHash","existingHash","queryIdx","base","existingQueryString","result","queryString","setDeepProperty","obj","parts","current","part","extractQueryFromPath","options","noHash","params","parsed","lower","finalValue","buildPath","template","params","query","options","paramNames","extractParamNames","unresolved","name","isOptionalParam","resolved","path","value","missing","encoded","segment","re","escapeRegex","match","boundary","p","devWarn","finalPath","joinPaths","appendQuery","build","segments","isActivePath","currentPath","template","options","exact","caseSensitive","pathname","normalizedTemplate","target","candidate","matchPath","matchPrefix"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as v,b as l,c as k,d as R,f as g,g as p,i as f,l as c,o as b}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
import{a as v,b as l,c as k,d as R,f as g,g as p,i as f,l as c,o as b}from"./chunk-3E7SIS7B.js";function h(t,e="",a=new Set){if(a.has(t))return[];a.add(t);let o=[];for(let r of Object.keys(t)){let n=e?`${e}.${r}`:r,i=t[r];typeof i=="string"?o.push({key:n,path:i}):i instanceof String?o.push({key:n,path:i.valueOf()}):typeof i=="object"&&i!==null&&o.push(...h(i,n,a))}return o}function $(t){let e=t.split("."),a=e[e.length-1]??t;return(a==="ROOT"&&e.length>1?e[e.length-2]:a).replace(/_/g," ").toLowerCase().replace(/^\w/,r=>r.toUpperCase())}function S(t,e,a){let o=Array.isArray(t)?t:h(t),r=e.split("?")[0]??"",n=a?.labelResolver??$,i=a?.labels??{},u=[];for(let s of o){let d=R(s.path),x=r.match(d);if(x){let y=f(s.path,x[0]),P=p(s.path)?c(s.path,y):s.path;u.push({key:s.key,resolvedPath:P,template:s.path,isCurrent:!0});continue}let w=k(s.path),T=r.match(w);if(T){let y=T[0],P=f(s.path,y),B=p(s.path)?c(s.path,P):s.path;u.push({key:s.key,resolvedPath:B,template:s.path,isCurrent:!1})}}let m=s=>s.split("/").filter(Boolean).length;return u.sort((s,d)=>m(s.template)-m(d.template)),u.map(s=>({key:s.key,label:i[s.key]??n(s.key),path:s.resolvedPath,isCurrent:s.isCurrent}))}var j=t=>typeof t=="object"&&t!==null&&!Array.isArray(t)&&Object.getPrototypeOf(t)===Object.prototype;function D(t,e){t.startsWith("/")||l(`[route-forge] Route "${e}" does not start with "/": "${t}".`),t.includes("*")&&!t.endsWith("/*")&&l(`[route-forge] Route "${e}" uses "*" outside a trailing "/*" splat; only a trailing splat is supported: "${t}".`)}function Q(t){let e=new Map,a=new Set,o=h(t);for(let r of o){let n=e.get(r.path);n===void 0?e.set(r.path,r.key):a.has(r.path)||(a.add(r.path),l(`[route-forge] Duplicate route path "${r.path}" for "${n}" and "${r.key}". Only one of them will be reachable.`))}for(let r=0;r<o.length;r++){let n=o[r];if(p(n.path))for(let i=r+1;i<o.length;i++){let u=o[i];if(!p(u.path)&&b(u.path,n.path,{exact:!0})){let m=`${n.key}->${u.key}`;a.has(m)||(a.add(m),l(`[route-forge] Route "${u.key}" ("${u.path}") is shadowed by dynamic route "${n.key}" ("${n.path}"). Place static routes before dynamic parameters in route trees.`))}}}}function C(){Object.prototype.hasOwnProperty.call(String.prototype,"build")||(Object.defineProperty(String.prototype,"build",{value:function(e,a,o){let r=this.valueOf();return p(r)?c(r,e??{},a,o):c(r,{},e,a)},writable:!1,enumerable:!1,configurable:!0}),Object.defineProperty(String.prototype,"buildRelative",{value:function(e,a,o){return this.build(e,a,o).replace(/^\/+/,"")||"."},writable:!1,enumerable:!1,configurable:!0}),Object.defineProperty(String.prototype,"paramNames",{get(){return g(this.valueOf())},enumerable:!1,configurable:!0}))}C();function O(t){let e={};for(let a in t){if(!Object.prototype.hasOwnProperty.call(t,a))continue;let o=t[a];typeof o=="string"?(D(o,a),e[a]=p(o)?o:o):j(o)&&(e[a]=O(o))}return e}function F(t){let e=O(t);return v()||Q(e),e}export{h as a,S as b,F as c};
|
|
2
|
+
//# sourceMappingURL=chunk-LKDKVG2S.js.map
|
package/dist/hooks/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var y=Object.defineProperty;var
|
|
1
|
+
"use strict";var y=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var q=(e,t)=>{for(var n in t)y(e,n,{get:t[n],enumerable:!0})},V=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of z(t))!F.call(e,o)&&o!==n&&y(e,o,{get:()=>t[o],enumerable:!(r=H(t,o))||r.enumerable});return e};var Z=e=>V(y({},"__esModule",{value:!0}),e);var te={};q(te,{useActivePath:()=>U,useNavigateTo:()=>A,useResolvedPath:()=>B,useRouteParams:()=>E,useTypedSearchParams:()=>W});module.exports=Z(te);var $=require("react-router");function E(e){return(0,$.useParams)()}var S=require("react"),w=require("react-router");function A(){let e=(0,w.useNavigate)();return(0,S.useCallback)((t,n)=>{e(String(t),n)},[e])}var G=/[.*+?^${}()|[\]\\]/g,R=/(^|\/):([A-Za-z0-9_]+)(\?)?/g;function g(e){return e.replace(G,"\\$&")}function Q(e){let t=e.endsWith("/*"),n=t?e.slice(0,-2):e,r="",o=0;for(let a of n.matchAll(R)){let s=a.index??0,u=a[0];r+=g(n.slice(o,s));let[,i="",,p]=a;r+=p?`(?:${g(i)}([^/]+))?`:`${g(i)}([^/]+)`,o=s+(u?.length??0)}return r+g(n.slice(o))+(t?"(?:/(.*))?":"")}function T(e,t){return new RegExp(`:${g(t)}\\?(?![A-Za-z0-9_])`).test(e)}var N=new Map,X=/^/;function O(e){if(e==="/")return X;let t=N.get(e);if(t!==void 0)return t;let n=new RegExp(`^${Q(e)}(?=/|$)`);return N.set(e,n),n}var v=new Map;function b(e,t){let n=t?.end??!0,r=t?.caseSensitive?"":"i",o=`${e}:${n}:${r}`,a=v.get(o);if(a!==void 0)return a;let s=Q(e),u=n?`^${s}$`:`^${s}(?=/|$)`,i=new RegExp(u,r);return v.set(o,i),i}function k(e){let t=[...e.matchAll(R)].map(n=>n[2]);return e.endsWith("/*")&&t.push("*"),t}function C(e,t,n){if(t!=null)if(Array.isArray(t))t.forEach(r=>{r!=null&&n.append(e,String(r))});else if(typeof t=="object")for(let[r,o]of Object.entries(t))C(`${e}[${r}]`,o,n);else n.append(e,String(t))}function K(e,t){let n=`${t}[`,r=[];for(let o of e.keys())(o===t||o.startsWith(n))&&r.push(o);for(let o of r)e.delete(o)}function P(e,t,n){let r=e.indexOf("#"),o=r===-1?e:e.slice(0,r),a=r===-1?"":e.slice(r+1),s=o.indexOf("?"),u=s===-1?o:o.slice(0,s),i=s===-1?"":o.slice(s+1),p=new URLSearchParams(i);if(t)for(let[f,m]of Object.entries(t))Object.prototype.hasOwnProperty.call(t,f)&&(K(p,f),m!=null&&C(f,m,p));let c=u,l=p.toString();return l&&(c+="?"+l),n?c+="#"+encodeURIComponent(n):a&&(c+="#"+a),c}function J(e,t,n){let r=t.replace(/\]/g,"").split(/\[/),o=e;for(let a=0;a<r.length-1;a++){let s=r[a];(!o[s]||typeof o[s]!="object"||Array.isArray(o[s]))&&(o[s]={}),o=o[s]}o[r[r.length-1]]=n}function _(e,t){let n=e.indexOf("#"),r=n===-1?e:e.slice(0,n),o=r.indexOf("?");if(o===-1)return{};let a=new URLSearchParams(r.slice(o+1)),s={};for(let u of new Set(a.keys())){let p=a.getAll(u).map(l=>{if(t?.coerceBooleans){let f=String(l).toLowerCase();if(f==="true"||f==="false")return f==="true"}return t?.coerceNumbers&&l.trim()!==""&&!isNaN(Number(l))?Number(l):l}),c=p.length>1?p:p[0]??"";J(s,u,c)}return s}function Y(){return globalThis.process?.env?.NODE_ENV==="production"}function I(e){Y()||console.warn(e)}function j(e,t,n,r){let o=k(e),a=o.filter(i=>(t[i]===void 0||t[i]===null)&&!T(e,i)&&i!=="*"),s=o.reduce((i,p)=>{let c=t[p],l=c==null;if(p==="*"){if(l)return i.replace(/\/\*$/,"")||"/";if(typeof c!="string"&&typeof c!="number")throw new TypeError(`Splat parameter must be string or number, got ${typeof c}`);let m=r?.encode===!1?String(c):String(c).split("/").map(d=>encodeURIComponent(d)).join("/");return i.replace(/\/\*$/,`/${m}`)}let f=new RegExp(`(^|/):${g(p)}\\??(?![A-Za-z0-9_])`,"g");return i.replace(f,(m,d)=>{if(l)return m.endsWith("?")?"":`${d}:${p}`;let D=r?.encode===!1?String(c):encodeURIComponent(String(c));return`${d}${D}`})},e);if(a.length>0){if(r?.strict)throw new RangeError(`[route-forge] Missing required param(s) ${a.map(i=>`":${i}"`).join(", ")} in template "${e}".`);I(`[route-forge] Unresolved params in path "${s}". Check that all :param segments have matching keys.`)}let u=r?.locale?ee(r.locale,s):s;return P(u,n,r?.hash)}function ee(...e){return"/"+e.map(r=>r.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function B(e,t,n,r){return j(e,t,n,r)}var M=require("react-router");function L(e,t,n={}){let r=n.exact??!0,o=n.caseSensitive??!1,a=(e.split("?")[0]??"").replace(/\/+$/,"")||"/",s=t.replace(/\/+$/,"")||"/",u=o?s:s.toLowerCase(),i=o?a:a.toLowerCase();return(r?b(u,{caseSensitive:o}):O(u)).test(i)}function U(e,t={}){let n=(0,M.useLocation)();return L(n.pathname,e,t)}var h=require("react"),x=require("react-router");function W(e){let t=(0,x.useLocation)(),n=(0,x.useNavigate)(),r=(0,h.useMemo)(()=>_(t.search,e),[t.search,e]),o=(0,h.useCallback)((a,s)=>{let u=P("",a);n(`${u}${t.hash}`,s)},[n,t.hash]);return[r,o]}0&&(module.exports={useActivePath,useNavigateTo,useResolvedPath,useRouteParams,useTypedSearchParams});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/hooks/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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","\"use client\";\n\nimport { 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 * @warning React Router can return `undefined` for missing parameters (e.g., if a param is optional or absent).\n * Always check for `undefined` values at runtime, even though the type signature assumes they are present.\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 if (typeof window === 'undefined') {\n throw new Error(\n 'useRouteParams can only be used in browser environment. ' +\n 'Call this hook only in client components or after hydration.'\n );\n }\n return useParams() as Record<string, string>;\n}\n","\"use client\";\n\nimport { 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 if (typeof window === 'undefined') {\n throw new Error(\n 'useNavigateTo can only be used in browser environment. ' +\n 'Call this hook only in client components or after hydration.'\n );\n }\n const navigate = useNavigate();\n\n return useCallback(\n (path: string, options?: NavigateOptions) => {\n navigate(String(path), options);\n },\n [navigate],\n );\n}\n","/** Global RegExp instances — safe since String.prototype.replace/matchAll don't bleed lastIndex. */\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 path.search(PARAM_SEGMENT_RE) !== -1 || 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 (!Object.prototype.hasOwnProperty.call(query, key)) continue;\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 += \"#\" + encodeURIComponent(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) {\n const lower = String(v).toLowerCase();\n if (lower === \"true\" || lower === \"false\") {\n return lower === \"true\";\n }\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\n if (typeof value !== 'string' && typeof value !== 'number') {\n throw new TypeError(\n `Splat parameter must be string or number, got ${typeof value}`\n );\n }\n\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 const finalPath = options?.locale\n ? joinPaths(options.locale, resolved)\n : resolved;\n\n return appendQuery(finalPath, 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","\"use client\";\n\nimport { 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 if (typeof window === \"undefined\") {\n throw new Error(\n \"useResolvedPath can only be used in browser environment. \" +\n \"Call this hook only in client components or after hydration.\",\n );\n }\n return buildPath(template, params, query, options);\n}\n","\"use client\";\n\nimport { 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 if (typeof window === 'undefined') {\n throw new Error(\n 'useActivePath can only be used in browser environment. ' +\n 'Call this hook only in client components or after hydration.'\n );\n }\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","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { 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 if (typeof window === \"undefined\") {\n throw new Error(\n \"useTypedSearchParams can only be used in browser environment. \" +\n \"Call this hook only in client components or after hydration.\",\n );\n }\n const location = useLocation();\n const navigate = useNavigate();\n\n const queryParams = useMemo(\n () => extractQueryFromPath(location.search, options),\n [location.search, options],\n );\n\n const setTypedQuery = useCallback(\n (\n newQuery: QueryParams,\n navigateOptions?: { replace?: boolean; state?: unknown },\n ) => {\n const searchParams = new URLSearchParams();\n Object.entries(newQuery).forEach(([key, value]) => {\n if (!Object.prototype.hasOwnProperty.call(newQuery, key)) return;\n if (value === undefined || value === null) return;\n\n if (Array.isArray(value)) {\n value.forEach((v) => {\n if (v !== undefined && v !== null) {\n searchParams.append(key, String(v));\n }\n });\n } else {\n searchParams.append(key, String(value));\n }\n });\n\n const queryString = searchParams.toString();\n const prefix = queryString ? \"?\" : \"\";\n navigate(`${prefix}${queryString}${location.hash}`, navigateOptions);\n },\n [navigate, location.hash],\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","lower","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","finalPath","joinPaths","appendQuery","joinPaths","segments","segment","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","searchParams","key","value","v","queryString"]}
|
|
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","\"use client\";\n\nimport { 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 * @warning React Router can return `undefined` for missing parameters (e.g., if a param is optional or absent).\n * Always check for `undefined` values at runtime, even though the type signature assumes they are present.\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\n return useParams() as Record<string, string>;\n}\n","\"use client\";\n\nimport { 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\n const navigate = useNavigate();\n\n return useCallback(\n (path: string, options?: NavigateOptions) => {\n navigate(String(path), options);\n },\n [navigate],\n );\n}\n","/** Global RegExp instances — safe since String.prototype.replace/matchAll don't bleed lastIndex. */\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 path.search(PARAM_SEGMENT_RE) !== -1 || 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, QueryParamValue } from \"../types\";\n\nfunction serializeToParams(keyPrefix: string, value: QueryParamValue, searchParams: URLSearchParams) {\n if (value === undefined || value === null) return;\n \n if (Array.isArray(value)) {\n value.forEach((v) => {\n if (v !== undefined && v !== null) {\n searchParams.append(keyPrefix, String(v));\n }\n });\n } else if (typeof value === 'object') {\n for (const [k, v] of Object.entries(value)) {\n serializeToParams(`${keyPrefix}[${k}]`, v, searchParams);\n }\n } else {\n searchParams.append(keyPrefix, String(value));\n }\n}\n\nfunction deleteMatchingKeys(searchParams: URLSearchParams, rootKey: string) {\n const prefix = `${rootKey}[`;\n const keysToDelete: string[] = [];\n for (const key of searchParams.keys()) {\n if (key === rootKey || key.startsWith(prefix)) {\n keysToDelete.push(key);\n }\n }\n for (const key of keysToDelete) {\n searchParams.delete(key);\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(\n path: string,\n query?: QueryParams,\n hash?: string,\n): string {\n const hashIdx = path.indexOf(\"#\");\n const pathWithoutHash = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const existingHash = hashIdx === -1 ? \"\" : path.slice(hashIdx + 1);\n\n const queryIdx = pathWithoutHash.indexOf(\"?\");\n const base = queryIdx === -1 ? pathWithoutHash : pathWithoutHash.slice(0, queryIdx);\n const existingQueryString = queryIdx === -1 ? \"\" : pathWithoutHash.slice(queryIdx + 1);\n\n const searchParams = new URLSearchParams(existingQueryString);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (!Object.prototype.hasOwnProperty.call(query, key)) continue;\n \n deleteMatchingKeys(searchParams, key);\n \n if (value !== undefined && value !== null) {\n serializeToParams(key, value, searchParams);\n }\n }\n }\n\n let result = base;\n const queryString = searchParams.toString();\n if (queryString) {\n result += \"?\" + queryString;\n }\n\n if (hash) {\n result += \"#\" + encodeURIComponent(hash);\n } else if (existingHash) {\n result += \"#\" + existingHash;\n }\n\n return result;\n}\n\nfunction setDeepProperty(obj: Record<string, unknown>, path: string, value: unknown) {\n const parts = path.replace(/\\]/g, \"\").split(/\\[/);\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i] as string;\n if (!current[part] || typeof current[part] !== 'object' || Array.isArray(current[part])) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n current[parts[parts.length - 1] as string] = value;\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 * Supports deep object nesting via bracket notation (e.g. `user[name]=John`).\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) {\n const lower = String(v).toLowerCase();\n if (lower === \"true\" || lower === \"false\") {\n return lower === \"true\";\n }\n }\n if (options?.coerceNumbers && v.trim() !== \"\" && !isNaN(Number(v))) {\n return Number(v);\n }\n return v;\n });\n const finalValue = parsed.length > 1 ? parsed : (parsed[0] ?? \"\");\n setDeepProperty(result, key, finalValue);\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\n if (typeof value !== 'string' && typeof value !== 'number') {\n throw new TypeError(\n `Splat parameter must be string or number, got ${typeof value}`\n );\n }\n\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 const finalPath = options?.locale\n ? joinPaths(options.locale, resolved)\n : resolved;\n\n return appendQuery(finalPath, 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","\"use client\";\n\nimport { 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\n return buildPath(template, params, query, options);\n}\n","\"use client\";\n\nimport { 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\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","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { extractQueryFromPath, appendQuery } 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<T extends QueryParams = QueryParams>(options?: {\n coerceBooleans?: boolean;\n coerceNumbers?: boolean;\n}) {\n\n const location = useLocation();\n const navigate = useNavigate();\n\n const queryParams = useMemo(\n () => extractQueryFromPath(location.search, options) as T,\n [location.search, options],\n );\n\n const setTypedQuery = useCallback(\n (\n newQuery: Partial<T> | QueryParams,\n navigateOptions?: { replace?: boolean; state?: unknown },\n ) => {\n const queryString = appendQuery(\"\", newQuery as QueryParams);\n navigate(`${queryString}${location.hash}`, navigateOptions);\n },\n [navigate, location.hash],\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","serializeToParams","keyPrefix","value","searchParams","v","k","deleteMatchingKeys","rootKey","prefix","keysToDelete","key","appendQuery","path","query","hash","hashIdx","pathWithoutHash","existingHash","queryIdx","base","existingQueryString","result","queryString","setDeepProperty","obj","parts","current","i","part","extractQueryFromPath","options","noHash","params","parsed","lower","finalValue","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","finalPath","joinPaths","appendQuery","joinPaths","segments","segment","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","queryString","appendQuery"]}
|
package/dist/hooks/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-
|
|
1
|
+
import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-_N1whVbt.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A typed wrapper around React Router's `useParams`.
|
|
@@ -91,10 +91,10 @@ declare function useActivePath(template: string, options?: {
|
|
|
91
91
|
* setQuery({ tab: 'details', page: 2 });
|
|
92
92
|
* ```
|
|
93
93
|
*/
|
|
94
|
-
declare function useTypedSearchParams(options?: {
|
|
94
|
+
declare function useTypedSearchParams<T extends QueryParams = QueryParams>(options?: {
|
|
95
95
|
coerceBooleans?: boolean;
|
|
96
96
|
coerceNumbers?: boolean;
|
|
97
|
-
}): readonly [
|
|
97
|
+
}): readonly [T, (newQuery: Partial<T> | QueryParams, navigateOptions?: {
|
|
98
98
|
replace?: boolean;
|
|
99
99
|
state?: unknown;
|
|
100
100
|
}) => void];
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-
|
|
1
|
+
import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-_N1whVbt.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A typed wrapper around React Router's `useParams`.
|
|
@@ -91,10 +91,10 @@ declare function useActivePath(template: string, options?: {
|
|
|
91
91
|
* setQuery({ tab: 'details', page: 2 });
|
|
92
92
|
* ```
|
|
93
93
|
*/
|
|
94
|
-
declare function useTypedSearchParams(options?: {
|
|
94
|
+
declare function useTypedSearchParams<T extends QueryParams = QueryParams>(options?: {
|
|
95
95
|
coerceBooleans?: boolean;
|
|
96
96
|
coerceNumbers?: boolean;
|
|
97
|
-
}): readonly [
|
|
97
|
+
}): readonly [T, (newQuery: Partial<T> | QueryParams, navigateOptions?: {
|
|
98
98
|
replace?: boolean;
|
|
99
99
|
state?: unknown;
|
|
100
100
|
}) => void];
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{k as
|
|
1
|
+
import{j as o,k as s,l as n,o as i}from"../chunk-3E7SIS7B.js";import{useParams as P}from"react-router";function l(e){return P()}import{useCallback as y}from"react";import{useNavigate as g}from"react-router";function f(){let e=g();return y((r,t)=>{e(String(r),t)},[e])}function x(e,r,t,a){return n(e,r,t,a)}import{useLocation as d}from"react-router";function h(e,r={}){let t=d();return i(t.pathname,e,r)}import{useCallback as v,useMemo as R}from"react";import{useLocation as b,useNavigate as N}from"react-router";function T(e){let r=b(),t=N(),a=R(()=>s(r.search,e),[r.search,e]),u=v((m,c)=>{let p=o("",m);t(`${p}${r.hash}`,c)},[t,r.hash]);return[a,u]}export{h as useActivePath,f as useNavigateTo,x as useResolvedPath,l as useRouteParams,T as useTypedSearchParams};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|