@farbenmeer/router 0.1.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 +222 -0
- package/dist/context.d.ts +14 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +14 -0
- package/dist/immutable-search-params.d.ts +6 -0
- package/dist/immutable-search-params.d.ts.map +1 -0
- package/dist/immutable-search-params.js +17 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/link.d.ts +8 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +38 -0
- package/dist/route.d.ts +8 -0
- package/dist/route.d.ts.map +1 -0
- package/dist/route.js +39 -0
- package/dist/router.d.ts +16 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +40 -0
- package/dist/use-hash.d.ts +2 -0
- package/dist/use-hash.d.ts.map +1 -0
- package/dist/use-hash.js +5 -0
- package/dist/use-params.d.ts +2 -0
- package/dist/use-params.d.ts.map +1 -0
- package/dist/use-params.js +6 -0
- package/dist/use-pathname.d.ts +2 -0
- package/dist/use-pathname.d.ts.map +1 -0
- package/dist/use-pathname.js +5 -0
- package/dist/use-router.d.ts +5 -0
- package/dist/use-router.d.ts.map +1 -0
- package/dist/use-router.js +5 -0
- package/dist/use-search-params.d.ts +2 -0
- package/dist/use-search-params.d.ts.map +1 -0
- package/dist/use-search-params.js +5 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# @farbenmeer/router Documentation
|
|
2
|
+
|
|
3
|
+
A lightweight, React-based client-side router with support for nested routes, path parameters, and immutable search parameter handling.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@farbenmeer/router` package provides a very lightweight routing solution for React applications with the following key features:
|
|
8
|
+
|
|
9
|
+
- **Declarative routing** with React components
|
|
10
|
+
- **Nested routes** with parameter inheritance
|
|
11
|
+
- **Path parameters** with bracket notation (`[id]`)
|
|
12
|
+
- **Immutable search parameters** for predictable state management
|
|
13
|
+
- **Client-side navigation** with history management
|
|
14
|
+
- **TypeScript support** for better development experience
|
|
15
|
+
- **Testing-friendly** with customizable location and history
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { Router, Route, Link } from "@farbenmeer/router";
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
return (
|
|
24
|
+
<Router>
|
|
25
|
+
<nav>
|
|
26
|
+
<Link href="/">Home</Link>
|
|
27
|
+
<Link href="/users">Users</Link>
|
|
28
|
+
<Link href="/about">About</Link>
|
|
29
|
+
</nav>
|
|
30
|
+
|
|
31
|
+
<main>
|
|
32
|
+
<Route path="/">
|
|
33
|
+
<HomePage />
|
|
34
|
+
</Route>
|
|
35
|
+
|
|
36
|
+
<Route path="/users">
|
|
37
|
+
<UsersLayout />
|
|
38
|
+
<Route exact>
|
|
39
|
+
<UsersList />
|
|
40
|
+
</Route>
|
|
41
|
+
<Route path="[id]">
|
|
42
|
+
<UserProfile />
|
|
43
|
+
</Route>
|
|
44
|
+
</Route>
|
|
45
|
+
|
|
46
|
+
<Route path="/about">
|
|
47
|
+
<AboutPage />
|
|
48
|
+
</Route>
|
|
49
|
+
</main>
|
|
50
|
+
</Router>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Components
|
|
56
|
+
|
|
57
|
+
### [Router](./docs/Router.md)
|
|
58
|
+
The root component that provides routing context to your application.
|
|
59
|
+
|
|
60
|
+
- Manages current location state
|
|
61
|
+
- Provides navigation methods
|
|
62
|
+
- Supports custom location and history for testing
|
|
63
|
+
- Normalizes pathnames (removes trailing slashes)
|
|
64
|
+
|
|
65
|
+
### [Route](./docs/Route.md)
|
|
66
|
+
Conditionally renders content based on the current pathname.
|
|
67
|
+
|
|
68
|
+
- Path matching with parameters (`/users/[id]`)
|
|
69
|
+
- Exact matching option
|
|
70
|
+
- Nested route support
|
|
71
|
+
- Parameter inheritance from parent routes
|
|
72
|
+
|
|
73
|
+
### [Link](./docs/Link.md)
|
|
74
|
+
Declarative navigation component that renders as an anchor element.
|
|
75
|
+
|
|
76
|
+
- Client-side navigation with history management
|
|
77
|
+
- Supports absolute and relative paths
|
|
78
|
+
- Query parameter handling
|
|
79
|
+
|
|
80
|
+
## Hooks
|
|
81
|
+
|
|
82
|
+
### [useRouter](./docs/useRouter.md)
|
|
83
|
+
Provides programmatic navigation methods.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
const router = useRouter();
|
|
87
|
+
router.push("/users/123"); // Navigate with history
|
|
88
|
+
router.replace("/login"); // Replace current entry
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### [usePathname](./docs/usePathname.md)
|
|
92
|
+
Access the current pathname for conditional rendering and active states.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const pathname = usePathname();
|
|
96
|
+
const isActive = pathname === "/users";
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### [useSearchParams](./docs/useSearchParams.md)
|
|
100
|
+
Access and manipulate URL search parameters with immutable methods.
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
const searchParams = useSearchParams();
|
|
104
|
+
const query = searchParams.get("q");
|
|
105
|
+
const newParams = searchParams.set("filter", "active");
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### [useParams](./docs/useParams.md)
|
|
109
|
+
Extract parameters from dynamic route segments.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// Route: /users/[id]/posts/[postId]
|
|
113
|
+
const params = useParams(); // { id: "123", postId: "456" }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### [useHash](./docs/useHash.md)
|
|
117
|
+
Access the current URL hash fragment for tab navigation and anchor linking.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
const hash = useHash();
|
|
121
|
+
const activeTab = hash.slice(1) || "overview";
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Key Features
|
|
125
|
+
|
|
126
|
+
### Nested Routing
|
|
127
|
+
|
|
128
|
+
Create hierarchical route structures with parameter inheritance:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<Route path="/organizations/[orgId]">
|
|
132
|
+
<OrganizationLayout />
|
|
133
|
+
|
|
134
|
+
<Route path="teams/[teamId]">
|
|
135
|
+
<TeamLayout />
|
|
136
|
+
|
|
137
|
+
<Route path="members/[memberId]">
|
|
138
|
+
<MemberProfile />
|
|
139
|
+
</Route>
|
|
140
|
+
</Route>
|
|
141
|
+
</Route>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Path Parameters
|
|
145
|
+
|
|
146
|
+
Define dynamic segments with bracket notation:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<Route path="/users/[id]"> {/* /users/123 */}
|
|
150
|
+
<Route path="/posts/[slug]"> {/* /posts/hello-world */}
|
|
151
|
+
<Route path="/api/[version]"> {/* /api/v1 */}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Immutable Search Parameters
|
|
155
|
+
|
|
156
|
+
Safely update URL search parameters without mutations:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
const searchParams = useSearchParams();
|
|
160
|
+
const withFilter = searchParams.set("category", "electronics");
|
|
161
|
+
const withSort = withFilter.set("sort", "price");
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Testing Support
|
|
165
|
+
|
|
166
|
+
Provide custom location and history for predictable tests:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<Router
|
|
170
|
+
location={{ pathname: "/users/123", search: "?tab=profile", hash: "#bio" }}
|
|
171
|
+
history={mockHistory}
|
|
172
|
+
>
|
|
173
|
+
<App />
|
|
174
|
+
</Router>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Common Patterns
|
|
178
|
+
|
|
179
|
+
### Active Navigation Links
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
function NavLink({ href, children }) {
|
|
183
|
+
const pathname = usePathname();
|
|
184
|
+
const isActive = pathname === href;
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<Link
|
|
188
|
+
href={href}
|
|
189
|
+
className={isActive ? 'nav-link active' : 'nav-link'}
|
|
190
|
+
>
|
|
191
|
+
{children}
|
|
192
|
+
</Link>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Search and Filtering
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
function ProductSearch() {
|
|
201
|
+
const router = useRouter();
|
|
202
|
+
const pathname = usePathname();
|
|
203
|
+
const searchParams = useSearchParams();
|
|
204
|
+
|
|
205
|
+
const updateFilter = (key: string, value: string) => {
|
|
206
|
+
const newParams = searchParams.set(key, value);
|
|
207
|
+
router.push(`${pathname}?${newParams.toString()}`);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<select onChange={(e) => updateFilter("category", e.target.value)}>
|
|
212
|
+
<option value="">All Categories</option>
|
|
213
|
+
<option value="electronics">Electronics</option>
|
|
214
|
+
</select>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
## API Reference
|
|
221
|
+
|
|
222
|
+
For detailed API documentation, see the individual component and hook documentation files linked above.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ImmutableSearchParams } from "./immutable-search-params";
|
|
2
|
+
export declare const PathnameContext: import("react").Context<string>;
|
|
3
|
+
export declare const SearchParamsContext: import("react").Context<ImmutableSearchParams>;
|
|
4
|
+
export declare const HashContext: import("react").Context<string>;
|
|
5
|
+
export declare const RouterContext: import("react").Context<{
|
|
6
|
+
push: (href: string) => void;
|
|
7
|
+
replace: (href: string) => void;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const RouteContext: import("react").Context<{
|
|
10
|
+
path: string;
|
|
11
|
+
params: Record<string, string | string[]>;
|
|
12
|
+
matchedPathname: string;
|
|
13
|
+
}>;
|
|
14
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,eAAO,MAAM,eAAe,iCAA6B,CAAC;AAE1D,eAAO,MAAM,mBAAmB,gDAE/B,CAAC;AAEF,eAAO,MAAM,WAAW,iCAA4B,CAAC;AAErD,eAAO,MAAM,aAAa;UAClB,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;aACnB,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;EAI/B,CAAC;AAEH,eAAO,MAAM,YAAY;UACjB,MAAM;YACJ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;qBACxB,MAAM;EAKvB,CAAC"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import { ImmutableSearchParams } from "./immutable-search-params";
|
|
3
|
+
export const PathnameContext = createContext("/");
|
|
4
|
+
export const SearchParamsContext = createContext(new ImmutableSearchParams());
|
|
5
|
+
export const HashContext = createContext("");
|
|
6
|
+
export const RouterContext = createContext({
|
|
7
|
+
push: () => { },
|
|
8
|
+
replace: () => { },
|
|
9
|
+
});
|
|
10
|
+
export const RouteContext = createContext({
|
|
11
|
+
path: "/",
|
|
12
|
+
params: {},
|
|
13
|
+
matchedPathname: "/",
|
|
14
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class ImmutableSearchParams extends URLSearchParams {
|
|
2
|
+
set(key: string, value: string): ImmutableSearchParams;
|
|
3
|
+
append(name: string, value: string): ImmutableSearchParams;
|
|
4
|
+
delete(key: string): ImmutableSearchParams;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=immutable-search-params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"immutable-search-params.d.ts","sourceRoot":"","sources":["../src/immutable-search-params.ts"],"names":[],"mappings":"AAAA,qBAAa,qBAAsB,SAAQ,eAAe;IAC/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAM9B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAMlC,MAAM,CAAC,GAAG,EAAE,MAAM;CAK5B"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class ImmutableSearchParams extends URLSearchParams {
|
|
2
|
+
set(key, value) {
|
|
3
|
+
const newSearchParams = new URLSearchParams(this);
|
|
4
|
+
newSearchParams.set(key, value);
|
|
5
|
+
return new ImmutableSearchParams(newSearchParams);
|
|
6
|
+
}
|
|
7
|
+
append(name, value) {
|
|
8
|
+
const newSearchParams = new URLSearchParams(this);
|
|
9
|
+
newSearchParams.append(name, value);
|
|
10
|
+
return new ImmutableSearchParams(newSearchParams);
|
|
11
|
+
}
|
|
12
|
+
delete(key) {
|
|
13
|
+
const newSearchParams = new URLSearchParams(this);
|
|
14
|
+
newSearchParams.delete(key);
|
|
15
|
+
return new ImmutableSearchParams(newSearchParams);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Router } from "./router";
|
|
2
|
+
export { Route } from "./route";
|
|
3
|
+
export { Link } from "./link";
|
|
4
|
+
export { useRouter } from "./use-router";
|
|
5
|
+
export { usePathname } from "./use-pathname";
|
|
6
|
+
export { useSearchParams } from "./use-search-params";
|
|
7
|
+
export { useHash } from "./use-hash";
|
|
8
|
+
export { useParams } from "./use-params";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Router } from "./router";
|
|
2
|
+
export { Route } from "./route";
|
|
3
|
+
export { Link } from "./link";
|
|
4
|
+
export { useRouter } from "./use-router";
|
|
5
|
+
export { usePathname } from "./use-pathname";
|
|
6
|
+
export { useSearchParams } from "./use-search-params";
|
|
7
|
+
export { useHash } from "./use-hash";
|
|
8
|
+
export { useParams } from "./use-params";
|
package/dist/link.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type HTMLProps } from "react";
|
|
2
|
+
interface Props extends HTMLProps<HTMLAnchorElement> {
|
|
3
|
+
href: string;
|
|
4
|
+
replace?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function Link({ href, replace, children, onClick, ...rawProps }: Props): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,SAAS,EAAkB,MAAM,OAAO,CAAC;AAQrE,UAAU,KAAM,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,EAAE,KAAK,2CA4C5E"}
|
package/dist/link.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { use, useMemo } from "react";
|
|
3
|
+
import { PathnameContext, RouteContext, RouterContext, SearchParamsContext, } from "./context";
|
|
4
|
+
export function Link({ href, replace, children, onClick, ...rawProps }) {
|
|
5
|
+
const { matchedPathname: parentPathname } = use(RouteContext);
|
|
6
|
+
const router = use(RouterContext);
|
|
7
|
+
const pathname = use(PathnameContext);
|
|
8
|
+
const searchParams = use(SearchParamsContext);
|
|
9
|
+
const target = useMemo(() => {
|
|
10
|
+
if (href.startsWith("/")) {
|
|
11
|
+
return href;
|
|
12
|
+
}
|
|
13
|
+
if (href.startsWith("?")) {
|
|
14
|
+
return pathname + href;
|
|
15
|
+
}
|
|
16
|
+
if (href.startsWith("#")) {
|
|
17
|
+
return (pathname +
|
|
18
|
+
(searchParams.size > 0 ? "?" + searchParams.toString() : "") +
|
|
19
|
+
href);
|
|
20
|
+
}
|
|
21
|
+
if (!href) {
|
|
22
|
+
return parentPathname;
|
|
23
|
+
}
|
|
24
|
+
return parentPathname + "/" + href;
|
|
25
|
+
}, [href, parentPathname, pathname]);
|
|
26
|
+
return (_jsx("a", { href: target, onClick: (event) => {
|
|
27
|
+
onClick?.(event);
|
|
28
|
+
if (event.defaultPrevented)
|
|
29
|
+
return;
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
if (replace) {
|
|
32
|
+
router.replace(target);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
router.push(target);
|
|
36
|
+
}
|
|
37
|
+
}, ...rawProps, children: children }));
|
|
38
|
+
}
|
package/dist/route.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../src/route.tsx"],"names":[],"mappings":"AAGA,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,kDA0BrD"}
|
package/dist/route.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { use, useMemo } from "react";
|
|
3
|
+
import { PathnameContext, RouteContext } from "./context";
|
|
4
|
+
export function Route({ path, exact, children }) {
|
|
5
|
+
const parentRoute = use(RouteContext);
|
|
6
|
+
const pathname = use(PathnameContext);
|
|
7
|
+
const fullPath = useMemo(() => buildFullPath(parentRoute.path, path ?? ""), [parentRoute.path, path]);
|
|
8
|
+
const pathRegex = useMemo(() => exact ? compileExactPathRegex(fullPath) : compilePathRegex(fullPath), [fullPath, exact]);
|
|
9
|
+
const match = useMemo(() => pathname.match(pathRegex), [pathname, pathRegex]);
|
|
10
|
+
const routeContextValue = useMemo(() => ({
|
|
11
|
+
path: fullPath,
|
|
12
|
+
params: match?.groups ?? {},
|
|
13
|
+
matchedPathname: match?.[1] ?? "",
|
|
14
|
+
}), [fullPath, match]);
|
|
15
|
+
if (!match)
|
|
16
|
+
return null;
|
|
17
|
+
return _jsx(RouteContext, { value: routeContextValue, children: children });
|
|
18
|
+
}
|
|
19
|
+
function compilePathRegex(path) {
|
|
20
|
+
if (path === "/") {
|
|
21
|
+
return /^\//;
|
|
22
|
+
}
|
|
23
|
+
return new RegExp(`^(${path.replaceAll(/\[(\w+)\]/g, "(?<$1>[\\w-]+)")})(/.*)?$`);
|
|
24
|
+
}
|
|
25
|
+
function compileExactPathRegex(path) {
|
|
26
|
+
if (path === "/") {
|
|
27
|
+
return /^\/$/;
|
|
28
|
+
}
|
|
29
|
+
return new RegExp(`^(${path.replaceAll(/\[(\w+)\]/g, "(?<$1>[\\w-]+)")})$`);
|
|
30
|
+
}
|
|
31
|
+
function buildFullPath(parentPath, path) {
|
|
32
|
+
if (path?.startsWith("/")) {
|
|
33
|
+
return path;
|
|
34
|
+
}
|
|
35
|
+
if (path) {
|
|
36
|
+
return parentPath + "/" + path;
|
|
37
|
+
}
|
|
38
|
+
return parentPath;
|
|
39
|
+
}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
interface Props {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
location?: {
|
|
5
|
+
pathname: string;
|
|
6
|
+
search: string;
|
|
7
|
+
hash: string;
|
|
8
|
+
};
|
|
9
|
+
history?: {
|
|
10
|
+
pushState: (state: any, title: string, url: string) => void;
|
|
11
|
+
replaceState: (state: any, title: string, url: string) => void;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare function Router(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS3E,UAAU,KAAK;IACb,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,OAAO,CAAC,EAAE;QACR,SAAS,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC5D,YAAY,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;KAChE,CAAC;CACH;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,2CAwDlC"}
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { startTransition, useMemo, useState } from "react";
|
|
3
|
+
import { HashContext, PathnameContext, RouterContext, SearchParamsContext, } from "./context";
|
|
4
|
+
import { ImmutableSearchParams } from "./immutable-search-params";
|
|
5
|
+
export function Router(props) {
|
|
6
|
+
const [pathname, setPathname] = useState(removeTrailingSlash(props.location?.pathname ?? window.location.pathname));
|
|
7
|
+
const [search, setSearch] = useState(props.location?.search ?? window.location.search);
|
|
8
|
+
const [hash, setHash] = useState(props.location?.hash ?? window.location.hash);
|
|
9
|
+
const searchParams = useMemo(() => new ImmutableSearchParams(props.location?.search ?? search), [props.location?.search, search]);
|
|
10
|
+
const routerContextValue = useMemo(() => ({
|
|
11
|
+
push: (url) => {
|
|
12
|
+
(props.history ?? window.history).pushState(null, "", url);
|
|
13
|
+
if (!props.location) {
|
|
14
|
+
startTransition(() => {
|
|
15
|
+
setPathname(removeTrailingSlash(window.location.pathname));
|
|
16
|
+
setSearch(window.location.search);
|
|
17
|
+
setHash(window.location.hash);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
replace: (url) => {
|
|
22
|
+
(props.history ?? window.history).replaceState(null, "", url);
|
|
23
|
+
if (!props.location) {
|
|
24
|
+
startTransition(() => {
|
|
25
|
+
setPathname(removeTrailingSlash(window.location.pathname));
|
|
26
|
+
setSearch(window.location.search);
|
|
27
|
+
setHash(window.location.hash);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
}), []);
|
|
32
|
+
return (_jsx(RouterContext, { value: routerContextValue, children: _jsx(PathnameContext, { value: props.location?.pathname
|
|
33
|
+
? removeTrailingSlash(props.location.pathname)
|
|
34
|
+
: pathname, children: _jsx(SearchParamsContext, { value: searchParams, children: _jsx(HashContext, { value: hash, children: props.children }) }) }) }));
|
|
35
|
+
}
|
|
36
|
+
function removeTrailingSlash(path) {
|
|
37
|
+
if (path === "/")
|
|
38
|
+
return path;
|
|
39
|
+
return path.endsWith("/") ? path.slice(0, -1) : path;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-hash.d.ts","sourceRoot":"","sources":["../src/use-hash.ts"],"names":[],"mappings":"AAGA,wBAAgB,OAAO,WAEtB"}
|
package/dist/use-hash.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-params.d.ts","sourceRoot":"","sources":["../src/use-params.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CACvB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,KAI5C,CAAC,CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-pathname.d.ts","sourceRoot":"","sources":["../src/use-pathname.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,WAE1B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-router.d.ts","sourceRoot":"","sources":["../src/use-router.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS;;;EAExB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-search-params.d.ts","sourceRoot":"","sources":["../src/use-search-params.ts"],"names":[],"mappings":"AAGA,wBAAgB,eAAe,8DAE9B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@farbenmeer/router",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Michel Smola",
|
|
6
|
+
"email": "michel.smola@farbenmeer.de"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"module": "dist/index.js",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"private": false,
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.build.json",
|
|
18
|
+
"release": "bun run build && bun publish"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@happy-dom/global-registrator": "^18.0.1",
|
|
23
|
+
"@testing-library/dom": "^10.4.1",
|
|
24
|
+
"@testing-library/jest-dom": "^6.8.0",
|
|
25
|
+
"@testing-library/react": "^16.3.0",
|
|
26
|
+
"@types/bun": "latest",
|
|
27
|
+
"react": "^19.1.1"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"typescript": "^5",
|
|
31
|
+
"react": "^19.1.1"
|
|
32
|
+
}
|
|
33
|
+
}
|