@mxweb/router 1.0.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/index.d.ts +371 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +76 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2024-12-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release
|
|
13
|
+
- `Router` component with basename support for adhoc features
|
|
14
|
+
- `Route` component with index and path-based routing
|
|
15
|
+
- `Link` component with `asChild` pattern support
|
|
16
|
+
- `useRouter` hook for accessing router state and navigation
|
|
17
|
+
- Built-in browser History API navigation
|
|
18
|
+
- Next.js App Router adapter support (`next/navigation`)
|
|
19
|
+
- React Router v5 adapter support (`useHistory`)
|
|
20
|
+
- React Router v6 adapter support (`useNavigate`)
|
|
21
|
+
- TypeScript support with full type definitions
|
|
22
|
+
- JSDoc documentation for all public APIs
|
|
23
|
+
|
|
24
|
+
[1.0.0]: https://github.com/mxwebio/mxweb-router/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 MXWeb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @mxweb/router
|
|
2
|
+
|
|
3
|
+
A lightweight router for Next.js App Router adhoc features. Supports both built-in browser navigation and Next.js navigation for seamless routing within embedded applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mxweb/router
|
|
9
|
+
# or
|
|
10
|
+
yarn add @mxweb/router
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @mxweb/router
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { Router, Route, Link, useRouter } from '@mxweb/router';
|
|
19
|
+
import { useRouter as useNextRouter } from 'next/navigation';
|
|
20
|
+
|
|
21
|
+
function MyAdhocFeature() {
|
|
22
|
+
const nextRouter = useNextRouter();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Router basename="/my-feature" adapter={nextRouter}>
|
|
26
|
+
<nav>
|
|
27
|
+
<Link href="/">Home</Link>
|
|
28
|
+
<Link href="/settings">Settings</Link>
|
|
29
|
+
</nav>
|
|
30
|
+
|
|
31
|
+
<Route index element={HomePage} />
|
|
32
|
+
<Route path="settings" element={SettingsPage} />
|
|
33
|
+
</Router>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function HomePage({ navigation }) {
|
|
38
|
+
return <button onClick={() => navigation.push('/settings')}>Go to Settings</button>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function SettingsPage({ navigation }) {
|
|
42
|
+
return <button onClick={() => navigation.pop()}>Go Back</button>;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
47
|
+
|
|
48
|
+
For full documentation, visit **[docs.mxweb.io/router](https://docs.mxweb.io/router)**
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT © MxWeb
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MxWeb Router - A lightweight router for Next.js App Router adhoc features
|
|
3
|
+
*
|
|
4
|
+
* This package provides routing support for embedding features (adhoc apps) into Next.js App Router
|
|
5
|
+
* with a specific basename. It supports both built-in browser navigation and Next.js navigation
|
|
6
|
+
* for seamless routing within the adhoc application.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Using with Next.js App Router
|
|
10
|
+
* import { Router, Route, Link } from '@mxweb/router';
|
|
11
|
+
* import { useRouter } from 'next/navigation';
|
|
12
|
+
*
|
|
13
|
+
* function MyAdhocApp() {
|
|
14
|
+
* const nextRouter = useRouter();
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <Router basename="/my-feature" adapter={nextRouter}>
|
|
18
|
+
* <Route index element={HomePage} />
|
|
19
|
+
* <Route path="settings" element={SettingsPage} />
|
|
20
|
+
* <Link href="/settings">Go to Settings</Link>
|
|
21
|
+
* </Router>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Using with built-in navigation (no adapter)
|
|
27
|
+
* function StandaloneApp() {
|
|
28
|
+
* return (
|
|
29
|
+
* <Router basename="/app">
|
|
30
|
+
* <Route index element={HomePage} />
|
|
31
|
+
* <Route path="about" element={AboutPage} />
|
|
32
|
+
* </Router>
|
|
33
|
+
* );
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* @packageDocumentation
|
|
37
|
+
* @module @mxweb/router
|
|
38
|
+
*/
|
|
39
|
+
import React, { AnchorHTMLAttributes, ComponentType, MouseEventHandler, PropsWithChildren, ReactElement, ReactNode, Ref } from "react";
|
|
40
|
+
import { LiteralObject } from "@mxweb/utils";
|
|
41
|
+
/**
|
|
42
|
+
* Navigation interface for routing operations within the adhoc app.
|
|
43
|
+
* Provides methods to navigate between routes programmatically.
|
|
44
|
+
*/
|
|
45
|
+
export interface RouteNavigation {
|
|
46
|
+
/**
|
|
47
|
+
* Navigate to a new path by pushing it onto the history stack.
|
|
48
|
+
* @param path - The target path relative to the basename
|
|
49
|
+
* @param state - Optional state object to pass to the new route
|
|
50
|
+
*/
|
|
51
|
+
push(path: string, state?: unknown): void;
|
|
52
|
+
/**
|
|
53
|
+
* Replace the current path in the history stack without creating a new entry.
|
|
54
|
+
* @param path - The target path relative to the basename
|
|
55
|
+
* @param state - Optional state object to pass to the new route
|
|
56
|
+
*/
|
|
57
|
+
replace(path: string, state?: unknown): void;
|
|
58
|
+
/**
|
|
59
|
+
* Navigate backward or forward in the history stack.
|
|
60
|
+
* @param delta - Number of steps to go back (negative) or forward (positive). Defaults to -1.
|
|
61
|
+
*/
|
|
62
|
+
pop(delta?: number): void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Utility type that adds navigation prop to component props.
|
|
66
|
+
* Use this type for route element components that need access to navigation.
|
|
67
|
+
*
|
|
68
|
+
* @typeParam Props - The base props type for the component
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```tsx
|
|
72
|
+
* function MyPage({ navigation }: PropsWithNavigation) {
|
|
73
|
+
* return <button onClick={() => navigation.push('/home')}>Go Home</button>;
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export type PropsWithNavigation<Props = {}> = Props & {
|
|
78
|
+
navigation: RouteNavigation;
|
|
79
|
+
};
|
|
80
|
+
interface RouteIndex {
|
|
81
|
+
index: true;
|
|
82
|
+
path?: never;
|
|
83
|
+
element: ComponentType<PropsWithNavigation>;
|
|
84
|
+
}
|
|
85
|
+
interface RoutePath<Pathname extends string = string> {
|
|
86
|
+
index?: never;
|
|
87
|
+
path: Pathname;
|
|
88
|
+
element: ComponentType<PropsWithNavigation>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Props for the Route component. Can be either an index route or a path-based route.
|
|
92
|
+
*
|
|
93
|
+
* @typeParam Pathname - The type of the path string for type-safe routing
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```tsx
|
|
97
|
+
* // Index route (matches the basename exactly)
|
|
98
|
+
* <Route index element={HomePage} />
|
|
99
|
+
*
|
|
100
|
+
* // Path-based route
|
|
101
|
+
* <Route path="settings" element={SettingsPage} />
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export type RouteProps<Pathname extends string = string> = RouteIndex | RoutePath<Pathname>;
|
|
105
|
+
/**
|
|
106
|
+
* Options for Next.js router navigation methods.
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
type NextRouteOptions = {
|
|
110
|
+
scroll?: boolean;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Adapter interface for Next.js App Router navigation.
|
|
114
|
+
* Compatible with the `useRouter()` hook from `next/navigation`.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```tsx
|
|
118
|
+
* import { useRouter } from 'next/navigation';
|
|
119
|
+
*
|
|
120
|
+
* function App() {
|
|
121
|
+
* const nextRouter = useRouter();
|
|
122
|
+
* return <Router adapter={nextRouter}>...</Router>;
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export interface NextRouteAdapter {
|
|
127
|
+
push(href: string, options?: NextRouteOptions): void;
|
|
128
|
+
replace(href: string, options?: NextRouteOptions): void;
|
|
129
|
+
back(): void;
|
|
130
|
+
forward?(): void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Adapter interface for React Router v5 history object.
|
|
134
|
+
* Compatible with the `useHistory()` hook from `react-router-dom` v5.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```tsx
|
|
138
|
+
* import { useHistory } from 'react-router-dom';
|
|
139
|
+
*
|
|
140
|
+
* function App() {
|
|
141
|
+
* const history = useHistory();
|
|
142
|
+
* return <Router adapter={history}>...</Router>;
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export interface ReactRouterV5Adapter {
|
|
147
|
+
push(path: string, state?: unknown): void;
|
|
148
|
+
replace(path: string, state?: unknown): void;
|
|
149
|
+
go(n: number): void;
|
|
150
|
+
goBack?(): void;
|
|
151
|
+
goForward?(): void;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Adapter interface for React Router v6 navigate function.
|
|
155
|
+
* Compatible with the `useNavigate()` hook from `react-router-dom` v6.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```tsx
|
|
159
|
+
* import { useNavigate } from 'react-router-dom';
|
|
160
|
+
*
|
|
161
|
+
* function App() {
|
|
162
|
+
* const navigate = useNavigate();
|
|
163
|
+
* return <Router adapter={navigate}>...</Router>;
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export interface ReactRouterAdapter {
|
|
168
|
+
(to: string | number, options?: {
|
|
169
|
+
replace?: boolean;
|
|
170
|
+
state?: unknown;
|
|
171
|
+
}): void;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Union type for all supported router adapters.
|
|
175
|
+
* The router automatically detects which adapter type is provided and uses the appropriate navigation methods.
|
|
176
|
+
*
|
|
177
|
+
* Supported adapters:
|
|
178
|
+
* - **NextRouteAdapter**: Next.js App Router (`useRouter` from `next/navigation`)
|
|
179
|
+
* - **ReactRouterV5Adapter**: React Router v5 (`useHistory` from `react-router-dom`)
|
|
180
|
+
* - **ReactRouterAdapter**: React Router v6 (`useNavigate` from `react-router-dom`)
|
|
181
|
+
*
|
|
182
|
+
* If no adapter is provided, the router uses the built-in browser History API.
|
|
183
|
+
*/
|
|
184
|
+
export type RouteAdapter = NextRouteAdapter | ReactRouterV5Adapter | ReactRouterAdapter;
|
|
185
|
+
/**
|
|
186
|
+
* State object representing the current router state.
|
|
187
|
+
*
|
|
188
|
+
* @typeParam Basename - The type of the basename string for type-safe routing
|
|
189
|
+
*/
|
|
190
|
+
export interface RouterState<Basename extends string = string> {
|
|
191
|
+
/** The base path prefix for all routes in this router instance */
|
|
192
|
+
basename: Basename;
|
|
193
|
+
/** The current path relative to the basename */
|
|
194
|
+
pathname: string;
|
|
195
|
+
/** The complete URL path including the basename */
|
|
196
|
+
fullpath: string;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Hook to access the router state and navigation methods.
|
|
200
|
+
* Must be used within a `<Router>` component.
|
|
201
|
+
*
|
|
202
|
+
* @returns An object containing:
|
|
203
|
+
* - `state` - The current router state (basename, pathname, fullpath)
|
|
204
|
+
* - `navigation` - The navigation object with push, replace, pop methods
|
|
205
|
+
* - `push` - Shorthand for navigation.push
|
|
206
|
+
* - `replace` - Shorthand for navigation.replace
|
|
207
|
+
* - `pop` - Shorthand for navigation.pop
|
|
208
|
+
* - `isIndexPath` - Function to check if current path is the index route
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```tsx
|
|
212
|
+
* function MyComponent() {
|
|
213
|
+
* const { state, push, isIndexPath } = useRouter();
|
|
214
|
+
*
|
|
215
|
+
* return (
|
|
216
|
+
* <div>
|
|
217
|
+
* <p>Current path: {state.pathname}</p>
|
|
218
|
+
* <button onClick={() => push('/settings')}>Go to Settings</button>
|
|
219
|
+
* {isIndexPath() && <p>You are on the home page</p>}
|
|
220
|
+
* </div>
|
|
221
|
+
* );
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export declare function useRouter(): {
|
|
226
|
+
state: RouterState<string>;
|
|
227
|
+
navigation: RouteNavigation;
|
|
228
|
+
push: <S extends LiteralObject = {}>(path: string, pushState?: S) => void;
|
|
229
|
+
replace: <S extends LiteralObject = {}>(path: string, replaceState?: S) => void;
|
|
230
|
+
pop: (delta?: number) => void;
|
|
231
|
+
isIndexPath: () => boolean;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Props for the Router component.
|
|
235
|
+
*
|
|
236
|
+
* @typeParam Basename - The type of the basename string for type-safe routing
|
|
237
|
+
*/
|
|
238
|
+
export interface RouterProps<Basename extends string = string> {
|
|
239
|
+
/**
|
|
240
|
+
* The base path for all routes in this router.
|
|
241
|
+
* All route paths will be relative to this basename.
|
|
242
|
+
* @default "/"
|
|
243
|
+
*/
|
|
244
|
+
basename?: Basename;
|
|
245
|
+
/**
|
|
246
|
+
* An optional navigation adapter for integrating with external routers.
|
|
247
|
+
* Supports Next.js App Router, React Router v5, and React Router v6.
|
|
248
|
+
* If not provided, uses the built-in browser History API.
|
|
249
|
+
*/
|
|
250
|
+
adapter?: RouteAdapter;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The main Router provider component.
|
|
254
|
+
* Wraps your adhoc application and provides routing context to child components.
|
|
255
|
+
*
|
|
256
|
+
* @typeParam Basename - The type of the basename string for type-safe routing
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```tsx
|
|
260
|
+
* // With Next.js App Router
|
|
261
|
+
* import { useRouter } from 'next/navigation';
|
|
262
|
+
*
|
|
263
|
+
* function MyAdhocFeature() {
|
|
264
|
+
* const nextRouter = useRouter();
|
|
265
|
+
*
|
|
266
|
+
* return (
|
|
267
|
+
* <Router basename="/my-feature" adapter={nextRouter}>
|
|
268
|
+
* <Route index element={HomePage} />
|
|
269
|
+
* <Route path="settings" element={SettingsPage} />
|
|
270
|
+
* </Router>
|
|
271
|
+
* );
|
|
272
|
+
* }
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```tsx
|
|
277
|
+
* // With built-in navigation (standalone mode)
|
|
278
|
+
* function StandaloneApp() {
|
|
279
|
+
* return (
|
|
280
|
+
* <Router basename="/app">
|
|
281
|
+
* <Route index element={HomePage} />
|
|
282
|
+
* <Route path="about" element={AboutPage} />
|
|
283
|
+
* </Router>
|
|
284
|
+
* );
|
|
285
|
+
* }
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
export declare function Router<Basename extends string = string>(props: PropsWithChildren<RouterProps<Basename>>): React.JSX.Element;
|
|
289
|
+
/**
|
|
290
|
+
* A route component that renders its element when the current path matches.
|
|
291
|
+
*
|
|
292
|
+
* @typeParam Pathname - The type of the path string for type-safe routing
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```tsx
|
|
296
|
+
* // Index route - matches when pathname is empty or "/"
|
|
297
|
+
* <Route index element={HomePage} />
|
|
298
|
+
*
|
|
299
|
+
* // Path route - matches when pathname starts with "settings"
|
|
300
|
+
* <Route path="settings" element={SettingsPage} />
|
|
301
|
+
*
|
|
302
|
+
* // Nested route example
|
|
303
|
+
* <Route path="users" element={UsersPage} />
|
|
304
|
+
* <Route path="users/profile" element={UserProfilePage} />
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
export declare function Route<Pathname extends string = string>(props: RouteProps<Pathname>): React.JSX.Element | null;
|
|
308
|
+
/**
|
|
309
|
+
* Base props shared by all Link variants.
|
|
310
|
+
* @internal
|
|
311
|
+
*/
|
|
312
|
+
interface LinkPropsBase {
|
|
313
|
+
/** The target path to navigate to */
|
|
314
|
+
href: string;
|
|
315
|
+
/** If true, replaces the current history entry instead of pushing a new one */
|
|
316
|
+
replace?: boolean;
|
|
317
|
+
/** Optional state object to pass to the target route */
|
|
318
|
+
state?: LiteralObject;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Props for Link when used with asChild pattern.
|
|
322
|
+
* @internal
|
|
323
|
+
*/
|
|
324
|
+
interface LinkPropsAsChild extends LinkPropsBase {
|
|
325
|
+
asChild: true;
|
|
326
|
+
children: ReactElement<{
|
|
327
|
+
href?: string;
|
|
328
|
+
onClick?: MouseEventHandler;
|
|
329
|
+
ref?: Ref<HTMLAnchorElement>;
|
|
330
|
+
}>;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Props for Link when rendering as a standard anchor element.
|
|
334
|
+
* @internal
|
|
335
|
+
*/
|
|
336
|
+
interface LinkPropsDefault extends LinkPropsBase, Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
|
|
337
|
+
asChild?: false;
|
|
338
|
+
children?: ReactNode;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Props for the Link component.
|
|
342
|
+
* Supports two modes:
|
|
343
|
+
* - Default mode: Renders as an `<a>` element
|
|
344
|
+
* - asChild mode: Passes props to a child element (similar to Radix UI pattern)
|
|
345
|
+
*/
|
|
346
|
+
export type LinkProps = LinkPropsAsChild | LinkPropsDefault;
|
|
347
|
+
/**
|
|
348
|
+
* A navigation link component that handles client-side routing.
|
|
349
|
+
* Intercepts clicks and uses the router's navigation methods instead of full page reloads.
|
|
350
|
+
*
|
|
351
|
+
* Supports modifier keys (Ctrl, Cmd, Shift, Alt) for opening in new tabs/windows.
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```tsx
|
|
355
|
+
* // Basic usage
|
|
356
|
+
* <Link href="/settings">Go to Settings</Link>
|
|
357
|
+
*
|
|
358
|
+
* // With replace (no new history entry)
|
|
359
|
+
* <Link href="/settings" replace>Go to Settings</Link>
|
|
360
|
+
*
|
|
361
|
+
* // With state
|
|
362
|
+
* <Link href="/details" state={{ id: 123 }}>View Details</Link>
|
|
363
|
+
*
|
|
364
|
+
* // asChild pattern - pass navigation to a custom component
|
|
365
|
+
* <Link href="/home" asChild>
|
|
366
|
+
* <MyCustomButton>Go Home</MyCustomButton>
|
|
367
|
+
* </Link>
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
export declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
371
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var t=require("react"),e=require("@mxweb/store"),a=require("@mxweb/react-hooks"),s=require("@mxweb/utils");function r(t){return t&&t.__esModule?t:{default:t}}var i=r(t);class n{constructor(t,e=null){this.routerState=t,this.adapter=e}getFullPath(t){return[this.routerState.basename,t].join("/").replace(/\/\/+/g,"/")}isNextAdapter(t){return s.hasOwnProperty(t,"back")&&"function"==typeof t.back}isV5Adapter(t){return s.hasOwnProperty(t,"go")&&"function"==typeof t.go}isV6Adapter(t){return"function"==typeof t}push(t,e={}){if(this.adapter)if(this.isV6Adapter(this.adapter))this.adapter(this.getFullPath(t),{state:e});else{if(this.isNextAdapter(this.adapter)){const a=s.hasOwnProperty(e,"scroll")?{scroll:e.scroll}:void 0;return void this.adapter.push(this.getFullPath(t),a)}this.isV5Adapter(this.adapter)&&this.adapter.push(this.getFullPath(t),e)}else s.isBrowser()&&(window.history.pushState(e,"",this.getFullPath(t)),window.dispatchEvent(new PopStateEvent("popstate")))}replace(t,e={}){if(this.adapter)if(this.isV6Adapter(this.adapter))this.adapter(this.getFullPath(t),{replace:!0,state:e});else{if(this.isNextAdapter(this.adapter)){const a=s.hasOwnProperty(e,"scroll")?{scroll:e.scroll}:void 0;return void this.adapter.replace(this.getFullPath(t),a)}this.isV5Adapter(this.adapter)&&this.adapter.replace(this.getFullPath(t),e)}else s.isBrowser()&&(window.history.replaceState(e,"",this.getFullPath(t)),window.dispatchEvent(new PopStateEvent("popstate")))}pop(t=-1){this.adapter?this.isV6Adapter(this.adapter)?this.adapter(t):this.isNextAdapter(this.adapter)?-1===t?this.adapter.back():1===t&&this.adapter.forward?this.adapter.forward():s.isBrowser()&&window.history.go(t):this.isV5Adapter(this.adapter)&&this.adapter.go(t):s.isBrowser()&&window.history.go(t)}setAdapter(t){return this.adapter=t,this}setState(t){return this.routerState=t,this}}class o extends e.StoreBase{constructor(){super({basename:"",pathname:"",fullpath:""}),this.navigation=new n(this.getState())}setState(t){return super.setState(t),this.navigation.setState(this.getState()),this}setAdapter(t){return this.navigation.setAdapter(t),this}getNavigation(){return{push:this.navigation.push.bind(this.navigation),replace:this.navigation.replace.bind(this.navigation),pop:this.navigation.pop.bind(this.navigation)}}isIndex(t){return s.isNullish(t)||""===t||"/"===t}isIndexPath(){const{basename:t,pathname:e}=this.getState();return!!this.isIndex(e)||(e===t||e===`${t}/`)}isIndexRoute(t){return s.hasOwnProperty(t,"index")&&!0===t.index}isPathRoute(t){return s.hasOwnProperty(t,"path")}isMatch(t,e){return`${e}/`.startsWith(`${t.replace(/^\//,"")}/`)}snapshot(t=""){if(!s.isBrowser())return{basename:t,fullpath:"",pathname:""};const e=window.location.pathname;let a=e;const r=t.startsWith("/")?t:`/${t}`;return r&&"/"!==r&&e.startsWith(r)?a=e.slice(r.length).replace(/^\//,""):e.startsWith("/")&&(a=e.slice(1)),{basename:r,fullpath:e,pathname:a}}}const h=new o,p=t.createContext(null);function l(){const e=t.useContext(p),a=e??h.getState(),s=h.getNavigation();return{state:a,navigation:s,push:(t,e)=>s.push(t,e),replace:(t,e)=>s.replace(t,e),pop:t=>s.pop(t),isIndexPath:()=>h.isIndexPath()}}const u=t.forwardRef((e,s)=>{const{href:r,replace:n=!1,state:o,asChild:h=!1}=e,{push:p,replace:u}=l(),d=t=>{t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||0!==t.button||(t.preventDefault(),n?u(r,o):p(r,o))};if(h){const i=t.Children.only(e.children);if(!t.isValidElement(i))return null;const n=i.props;return t.cloneElement(i,{href:r,ref:a.combineRefs(s,n.ref),onClick:a.combineEvents(n.onClick,d)})}const{onClick:c,children:f,...g}=e;return i.default.createElement("a",{...g,ref:s,href:r,onClick:t=>{c?.(t),d(t)}},f)});u.displayName="Link",exports.Link=u,exports.Route=function(t){const{element:e}=t,{state:{pathname:a},navigation:s,isIndexPath:r}=l();return h.isIndexRoute(t)?r()?i.default.createElement(e,{navigation:s}):null:h.isPathRoute(t)&&h.isMatch(t.path,a)?i.default.createElement(e,{navigation:s}):null},exports.Router=function(e){const{basename:a="/",adapter:r=null,children:n}=e,[o,l]=t.useState(()=>(r&&h.setAdapter(r),h.setState(h.snapshot(a)).getState()));return t.useEffect(()=>{const t=h.onStateChange(()=>{l(t=>{const e=h.getState();return s.isEqualPrimitive(t,e)?t:e})});return h.setState(h.snapshot(a)),t},[a]),t.useEffect(()=>{r&&h.setAdapter(r)},[r]),i.default.createElement(p.Provider,{value:o},n)},exports.useRouter=l;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t,{createContext as e,forwardRef as a,Children as s,isValidElement as i,cloneElement as r,useContext as n,useState as h,useEffect as o}from"react";import{StoreBase as p}from"@mxweb/store";import{combineEvents as l,combineRefs as d}from"@mxweb/react-hooks";import{isNullish as u,hasOwnProperty as c,isBrowser as g,isEqualPrimitive as f}from"@mxweb/utils";class m{constructor(t,e=null){this.routerState=t,this.adapter=e}getFullPath(t){return[this.routerState.basename,t].join("/").replace(/\/\/+/g,"/")}isNextAdapter(t){return c(t,"back")&&"function"==typeof t.back}isV5Adapter(t){return c(t,"go")&&"function"==typeof t.go}isV6Adapter(t){return"function"==typeof t}push(t,e={}){if(this.adapter)if(this.isV6Adapter(this.adapter))this.adapter(this.getFullPath(t),{state:e});else{if(this.isNextAdapter(this.adapter)){const a=c(e,"scroll")?{scroll:e.scroll}:void 0;return void this.adapter.push(this.getFullPath(t),a)}this.isV5Adapter(this.adapter)&&this.adapter.push(this.getFullPath(t),e)}else g()&&(window.history.pushState(e,"",this.getFullPath(t)),window.dispatchEvent(new PopStateEvent("popstate")))}replace(t,e={}){if(this.adapter)if(this.isV6Adapter(this.adapter))this.adapter(this.getFullPath(t),{replace:!0,state:e});else{if(this.isNextAdapter(this.adapter)){const a=c(e,"scroll")?{scroll:e.scroll}:void 0;return void this.adapter.replace(this.getFullPath(t),a)}this.isV5Adapter(this.adapter)&&this.adapter.replace(this.getFullPath(t),e)}else g()&&(window.history.replaceState(e,"",this.getFullPath(t)),window.dispatchEvent(new PopStateEvent("popstate")))}pop(t=-1){this.adapter?this.isV6Adapter(this.adapter)?this.adapter(t):this.isNextAdapter(this.adapter)?-1===t?this.adapter.back():1===t&&this.adapter.forward?this.adapter.forward():g()&&window.history.go(t):this.isV5Adapter(this.adapter)&&this.adapter.go(t):g()&&window.history.go(t)}setAdapter(t){return this.adapter=t,this}setState(t){return this.routerState=t,this}}const v=new class extends p{constructor(){super({basename:"",pathname:"",fullpath:""}),this.navigation=new m(this.getState())}setState(t){return super.setState(t),this.navigation.setState(this.getState()),this}setAdapter(t){return this.navigation.setAdapter(t),this}getNavigation(){return{push:this.navigation.push.bind(this.navigation),replace:this.navigation.replace.bind(this.navigation),pop:this.navigation.pop.bind(this.navigation)}}isIndex(t){return u(t)||""===t||"/"===t}isIndexPath(){const{basename:t,pathname:e}=this.getState();return!!this.isIndex(e)||(e===t||e===`${t}/`)}isIndexRoute(t){return c(t,"index")&&!0===t.index}isPathRoute(t){return c(t,"path")}isMatch(t,e){return`${e}/`.startsWith(`${t.replace(/^\//,"")}/`)}snapshot(t=""){if(!g())return{basename:t,fullpath:"",pathname:""};const e=window.location.pathname;let a=e;const s=t.startsWith("/")?t:`/${t}`;return s&&"/"!==s&&e.startsWith(s)?a=e.slice(s.length).replace(/^\//,""):e.startsWith("/")&&(a=e.slice(1)),{basename:s,fullpath:e,pathname:a}}},w=e(null);function S(){const t=n(w),e=t??v.getState(),a=v.getNavigation();return{state:e,navigation:a,push:(t,e)=>a.push(t,e),replace:(t,e)=>a.replace(t,e),pop:t=>a.pop(t),isIndexPath:()=>v.isIndexPath()}}function x(e){const{basename:a="/",adapter:s=null,children:i}=e,[r,n]=h(()=>(s&&v.setAdapter(s),v.setState(v.snapshot(a)).getState()));return o(()=>{const t=v.onStateChange(()=>{n(t=>{const e=v.getState();return f(t,e)?t:e})});return v.setState(v.snapshot(a)),t},[a]),o(()=>{s&&v.setAdapter(s)},[s]),t.createElement(w.Provider,{value:r},i)}function P(e){const{element:a}=e,{state:{pathname:s},navigation:i,isIndexPath:r}=S();return v.isIndexRoute(e)?r()?t.createElement(a,{navigation:i}):null:v.isPathRoute(e)&&v.isMatch(e.path,s)?t.createElement(a,{navigation:i}):null}const A=a((e,a)=>{const{href:n,replace:h=!1,state:o,asChild:p=!1}=e,{push:u,replace:c}=S(),g=t=>{t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||0!==t.button||(t.preventDefault(),h?c(n,o):u(n,o))};if(p){const t=s.only(e.children);if(!i(t))return null;const h=t.props;return r(t,{href:n,ref:d(a,h.ref),onClick:l(h.onClick,g)})}const{onClick:f,children:m,...v}=e;return t.createElement("a",{...v,ref:a,href:n,onClick:t=>{f?.(t),g(t)}},m)});A.displayName="Link";export{A as Link,P as Route,x as Router,S as useRouter};
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mxweb/router",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight router for Next.js App Router adhoc features with built-in and adapter-based navigation support",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"router",
|
|
8
|
+
"nextjs",
|
|
9
|
+
"app-router",
|
|
10
|
+
"navigation",
|
|
11
|
+
"adhoc",
|
|
12
|
+
"embedded",
|
|
13
|
+
"mxweb",
|
|
14
|
+
"client-side-routing",
|
|
15
|
+
"spa"
|
|
16
|
+
],
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"module": "dist/index.mjs",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "MxWeb Team <mxwebio@gmail.com>",
|
|
22
|
+
"homepage": "https://docs.mxweb.io/router",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md",
|
|
27
|
+
"CHANGELOG.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=14.0.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"clean": "rimraf dist",
|
|
38
|
+
"build": "yarn clean && yarn lint && rollup -c",
|
|
39
|
+
"build:watch": "rollup -c -w",
|
|
40
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
41
|
+
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
|
|
42
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
|
|
43
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"",
|
|
44
|
+
"prepublishOnly": "yarn build"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@mxweb/react-hooks": "^0.0.3",
|
|
48
|
+
"@mxweb/store": "^1.1.0",
|
|
49
|
+
"@mxweb/utils": "^0.0.5",
|
|
50
|
+
"@rollup/plugin-babel": "^6.1.0",
|
|
51
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
52
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
53
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
54
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
55
|
+
"@types/node": "^20",
|
|
56
|
+
"@types/react": "^19",
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
58
|
+
"@typescript-eslint/parser": "^8.50.1",
|
|
59
|
+
"eslint": "^9.39.2",
|
|
60
|
+
"eslint-config-prettier": "^10.1.8",
|
|
61
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
62
|
+
"glob": "^13.0.0",
|
|
63
|
+
"prettier": "^3.7.4",
|
|
64
|
+
"react": "19.2.3",
|
|
65
|
+
"rimraf": "^6.1.2",
|
|
66
|
+
"rollup": "^4.54.0",
|
|
67
|
+
"tslib": "^2.8.1",
|
|
68
|
+
"typescript": "^5"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@mxweb/react-hooks": "^0.0.3",
|
|
72
|
+
"@mxweb/store": "^1.1.0",
|
|
73
|
+
"@mxweb/utils": "^0.0.5",
|
|
74
|
+
"react": "^19.2.3"
|
|
75
|
+
}
|
|
76
|
+
}
|