@real-router/react 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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Oleg Ivanov
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.
22
+
package/README.md ADDED
@@ -0,0 +1,442 @@
1
+ # @real-router/react
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![React](https://img.shields.io/badge/React-18+-61DAFB.svg)](https://reactjs.org/)
5
+
6
+ React integration for [@real-router/core](https://github.com/greydragon888/real-router) - hooks, components, and context providers.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @real-router/react @real-router/core @real-router/browser-plugin
12
+ # or
13
+ pnpm add @real-router/react @real-router/core @real-router/browser-plugin
14
+ # or
15
+ yarn add @real-router/react @real-router/core @real-router/browser-plugin
16
+ # or
17
+ bun add @real-router/react @real-router/core @real-router/browser-plugin
18
+ ```
19
+
20
+ ### Peer Dependencies
21
+
22
+ - `react` >= 18.0.0
23
+ - `@real-router/core` (core router)
24
+ - `@real-router/browser-plugin` (for browser history integration)
25
+
26
+ ## Quick Start
27
+
28
+ ```tsx
29
+ import { createRouter } from "@real-router/core";
30
+ import { browserPluginFactory } from "@real-router/browser-plugin";
31
+ import { RouterProvider, useRoute, Link } from "@real-router/react";
32
+ import { createRoot } from "react-dom/client";
33
+
34
+ // Define routes
35
+ const routes = [
36
+ { name: "home", path: "/" },
37
+ { name: "users", path: "/users" },
38
+ { name: "users.profile", path: "/:id" },
39
+ ];
40
+
41
+ // Create and configure router
42
+ const router = createRouter(routes);
43
+ router.usePlugin(browserPluginFactory());
44
+ router.start();
45
+
46
+ // App component
47
+ function App() {
48
+ const { route } = useRoute();
49
+
50
+ return (
51
+ <div>
52
+ <nav>
53
+ <Link routeName="home">Home</Link>
54
+ <Link routeName="users">Users</Link>
55
+ </nav>
56
+ <main>
57
+ <h1>Current route: {route?.name}</h1>
58
+ </main>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ // Render with provider
64
+ createRoot(document.getElementById("root")!).render(
65
+ <RouterProvider router={router}>
66
+ <App />
67
+ </RouterProvider>,
68
+ );
69
+ ```
70
+
71
+ ## API Reference
72
+
73
+ ### Provider
74
+
75
+ #### RouterProvider
76
+
77
+ Provides router instance and state to all child components via React Context.
78
+
79
+ ```tsx
80
+ import { RouterProvider } from "@real-router/react";
81
+
82
+ <RouterProvider router={router}>
83
+ <App />
84
+ </RouterProvider>;
85
+ ```
86
+
87
+ **Props:**
88
+
89
+ - `router` - Router instance created with `createRouter()`
90
+ - `children` - React children
91
+
92
+ ### Hooks
93
+
94
+ #### useRouter
95
+
96
+ Returns the router instance from context.
97
+
98
+ ```tsx
99
+ import { useRouter } from "@real-router/react";
100
+
101
+ function NavigateButton() {
102
+ const router = useRouter();
103
+
104
+ return <button onClick={() => router.navigate("home")}>Go Home</button>;
105
+ }
106
+ ```
107
+
108
+ #### useRoute
109
+
110
+ Returns the current route state, previous route, and router instance. Re-renders on every route change.
111
+
112
+ ```tsx
113
+ import { useRoute } from "@real-router/react";
114
+
115
+ function CurrentRoute() {
116
+ const { router, route, previousRoute } = useRoute();
117
+
118
+ return (
119
+ <div>
120
+ <p>Current: {route?.name}</p>
121
+ <p>Previous: {previousRoute?.name}</p>
122
+ <p>Params: {JSON.stringify(route?.params)}</p>
123
+ <button onClick={() => router.navigate("home")}>Go Home</button>
124
+ </div>
125
+ );
126
+ }
127
+ ```
128
+
129
+ **Returns:**
130
+
131
+ - `router` - Router instance
132
+ - `route` - Current route state (`State | undefined`)
133
+ - `previousRoute` - Previous route state (`State | undefined`)
134
+
135
+ #### useRouteNode
136
+
137
+ Optimized hook that only re-renders when the specified route node changes. Ideal for nested route structures.
138
+
139
+ ```tsx
140
+ import { useRouteNode } from "@real-router/react";
141
+
142
+ function UsersSection() {
143
+ // Only re-renders when routes starting with "users" change
144
+ const { router, route, previousRoute } = useRouteNode("users");
145
+
146
+ // route is undefined when current route is NOT under "users" node
147
+ if (!route) {
148
+ return null;
149
+ }
150
+
151
+ if (route.name === "users") {
152
+ return <UsersList />;
153
+ }
154
+
155
+ if (route.name === "users.profile") {
156
+ return <UserProfile id={route.params.id} />;
157
+ }
158
+
159
+ return null;
160
+ }
161
+ ```
162
+
163
+ **Parameters:**
164
+
165
+ - `nodeName` - Route segment to observe (e.g., `"users"`, `"users.profile"`)
166
+
167
+ **Returns:**
168
+
169
+ - `router` - Router instance
170
+ - `route` - Current route state (`State | undefined`) - `undefined` when current route is not under the specified node
171
+ - `previousRoute` - Previous route state (`State | undefined`)
172
+
173
+ ### Components
174
+
175
+ #### Link
176
+
177
+ Navigation link component with automatic active state detection.
178
+
179
+ ```tsx
180
+ import { Link } from "@real-router/react";
181
+
182
+ <Link
183
+ routeName="users.profile"
184
+ routeParams={{ id: "123" }}
185
+ activeClassName="active"
186
+ activeStrict={false}
187
+ >
188
+ View Profile
189
+ </Link>;
190
+ ```
191
+
192
+ **Props:**
193
+
194
+ | Prop | Type | Default | Description |
195
+ | ------------------- | ----------------------- | ----------- | ----------------------------------- |
196
+ | `routeName` | `string` | (required) | Target route name |
197
+ | `routeParams` | `Params` | `{}` | Route parameters |
198
+ | `routeOptions` | `{ reload?, replace? }` | `{}` | Navigation options |
199
+ | `activeClassName` | `string` | `"active"` | Class applied when route is active |
200
+ | `activeStrict` | `boolean` | `false` | Strict matching (exact route only) |
201
+ | `ignoreQueryParams` | `boolean` | `true` | Ignore query params in active check |
202
+ | `className` | `string` | `undefined` | Base CSS class |
203
+ | `onClick` | `(event) => void` | `undefined` | Click handler |
204
+ | `successCallback` | `(state) => void` | `undefined` | Called on successful navigation |
205
+ | `errorCallback` | `(error) => void` | `undefined` | Called on navigation error |
206
+ | `target` | `string` | `undefined` | Link target (e.g., `"_blank"`) |
207
+
208
+ **Note:** The rendered `<a>` element also includes `data-route` and `data-active` attributes for CSS styling and event delegation.
209
+
210
+ #### ConnectedLink
211
+
212
+ Same as `Link`, but re-renders on every route change. Use when you need the link to update based on current route state.
213
+
214
+ ```tsx
215
+ import { ConnectedLink } from "@real-router/react";
216
+
217
+ <ConnectedLink routeName="dashboard" activeClassName="nav-active">
218
+ Dashboard
219
+ </ConnectedLink>;
220
+ ```
221
+
222
+ #### BaseLink
223
+
224
+ Low-level link component that requires router instance as prop. Useful for custom implementations.
225
+
226
+ ```tsx
227
+ import { BaseLink, useRouter } from "@real-router/react";
228
+
229
+ function CustomLink({ to, children }) {
230
+ const router = useRouter();
231
+
232
+ return (
233
+ <BaseLink router={router} routeName={to}>
234
+ {children}
235
+ </BaseLink>
236
+ );
237
+ }
238
+ ```
239
+
240
+ ### Context
241
+
242
+ For advanced use cases, you can access contexts directly:
243
+
244
+ ```tsx
245
+ import { RouterContext, RouteContext } from "@real-router/react";
246
+ import { useContext } from "react";
247
+
248
+ function CustomComponent() {
249
+ // RouterContext provides: Router | null
250
+ const router = useContext(RouterContext);
251
+
252
+ // RouteContext provides: { router, route, previousRoute } | null
253
+ const routeContext = useContext(RouteContext);
254
+
255
+ if (!router || !routeContext) {
256
+ throw new Error("Must be used within RouterProvider");
257
+ }
258
+
259
+ // Access route state
260
+ const { route, previousRoute } = routeContext;
261
+
262
+ // ...
263
+ }
264
+ ```
265
+
266
+ **Note:** Prefer using hooks (`useRouter`, `useRoute`, `useRouteNode`) over direct context access, as hooks provide better error handling and TypeScript support.
267
+
268
+ ## TypeScript
269
+
270
+ Full TypeScript support with exported types:
271
+
272
+ ```tsx
273
+ import {
274
+ RouterProvider,
275
+ Link,
276
+ ConnectedLink,
277
+ BaseLink,
278
+ useRouter,
279
+ useRoute,
280
+ useRouteNode,
281
+ RouterContext,
282
+ RouteContext,
283
+ type BaseLinkProps,
284
+ } from "@real-router/react";
285
+ ```
286
+
287
+ ### Typed Route Parameters
288
+
289
+ You can type route parameters by using type assertions:
290
+
291
+ ```tsx
292
+ import type { Params, State } from "@real-router/core";
293
+
294
+ interface UserParams extends Params {
295
+ id: string;
296
+ }
297
+
298
+ function UserProfile() {
299
+ const { route } = useRoute();
300
+
301
+ // Type assertion for params
302
+ const params = route?.params as UserParams | undefined;
303
+
304
+ return <h1>User: {params?.id}</h1>;
305
+ }
306
+ ```
307
+
308
+ ## Related Packages
309
+
310
+ - [@real-router/core](https://www.npmjs.com/package/@real-router/core) — Core router
311
+ - [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) — Browser history integration
312
+
313
+ ## Migration from react-router5
314
+
315
+ ### Import Changes
316
+
317
+ ```diff
318
+ - import { RouterProvider, Link, useRoute, useRouter, useRouteNode } from 'react-router5';
319
+ + import { RouterProvider, Link, useRoute, useRouter, useRouteNode } from '@real-router/react';
320
+ ```
321
+
322
+ ### Removed: Higher-Order Components (HOCs)
323
+
324
+ HOCs have been removed in favor of hooks:
325
+
326
+ ```diff
327
+ - import { withRouter, withRoute, routeNode } from 'react-router5';
328
+ + import { useRouter, useRoute, useRouteNode } from '@real-router/react';
329
+
330
+ - const MyComponent = withRouter(({ router }) => {
331
+ - return <button onClick={() => router.navigate('home')}>Home</button>;
332
+ - });
333
+ + function MyComponent() {
334
+ + const router = useRouter();
335
+ + return <button onClick={() => router.navigate('home')}>Home</button>;
336
+ + }
337
+
338
+ - const MyRoute = withRoute(({ route }) => {
339
+ - return <div>Current: {route.name}</div>;
340
+ - });
341
+ + function MyRoute() {
342
+ + const { route } = useRoute();
343
+ + return <div>Current: {route?.name}</div>;
344
+ + }
345
+
346
+ - const UsersNode = routeNode('users')(({ route }) => {
347
+ - return <div>{route.name}</div>;
348
+ - });
349
+ + function UsersNode() {
350
+ + const { route } = useRouteNode('users');
351
+ + return <div>{route?.name}</div>;
352
+ + }
353
+ ```
354
+
355
+ ### Removed: Render Props
356
+
357
+ Render prop components have been removed in favor of hooks:
358
+
359
+ ```diff
360
+ - import { Router, Route, RouteNode } from 'react-router5';
361
+ + import { useRouter, useRoute, useRouteNode } from '@real-router/react';
362
+
363
+ - <Router>
364
+ - {({ router }) => <button onClick={() => router.navigate('home')}>Home</button>}
365
+ - </Router>
366
+ + function MyComponent() {
367
+ + const router = useRouter();
368
+ + return <button onClick={() => router.navigate('home')}>Home</button>;
369
+ + }
370
+
371
+ - <Route>
372
+ - {({ route }) => <div>Current: {route.name}</div>}
373
+ - </Route>
374
+ + function MyRoute() {
375
+ + const { route } = useRoute();
376
+ + return <div>Current: {route?.name}</div>;
377
+ + }
378
+
379
+ - <RouteNode nodeName="users">
380
+ - {({ route }) => <div>{route.name}</div>}
381
+ - </RouteNode>
382
+ + function UsersNode() {
383
+ + const { route } = useRouteNode('users');
384
+ + return <div>{route?.name}</div>;
385
+ + }
386
+ ```
387
+
388
+ ### Available APIs
389
+
390
+ | API | react-router5 | @real-router/react |
391
+ |-----|---------------|---------------|
392
+ | `RouterProvider` | ✓ | ✓ |
393
+ | `Link` | ✓ | ✓ |
394
+ | `ConnectedLink` | ✓ | ✓ |
395
+ | `BaseLink` | ✓ | ✓ |
396
+ | `useRouter` | ✓ | ✓ |
397
+ | `useRoute` | ✓ | ✓ |
398
+ | `useRouteNode` | ✓ | ✓ |
399
+ | `withRouter` | ✓ | ❌ removed |
400
+ | `withRoute` | ✓ | ❌ removed |
401
+ | `routeNode` | ✓ | ❌ removed |
402
+ | `Router` (render prop) | ✓ | ❌ removed |
403
+ | `Route` (render prop) | ✓ | ❌ removed |
404
+ | `RouteNode` (render prop) | ✓ | ❌ removed |
405
+
406
+ ### Full Migration Example
407
+
408
+ ```diff
409
+ - import { createRouter } from 'router5';
410
+ - import browserPlugin from 'router5-plugin-browser';
411
+ - import { RouterProvider, withRoute, Link } from 'react-router5';
412
+ + import { createRouter } from '@real-router/core';
413
+ + import { browserPluginFactory } from '@real-router/browser-plugin';
414
+ + import { RouterProvider, useRoute, Link } from '@real-router/react';
415
+
416
+ const router = createRouter(routes);
417
+ - router.usePlugin(browserPlugin());
418
+ + router.usePlugin(browserPluginFactory());
419
+
420
+ - const CurrentRoute = withRoute(({ route }) => (
421
+ - <span>{route.name}</span>
422
+ - ));
423
+ + function CurrentRoute() {
424
+ + const { route } = useRoute();
425
+ + return <span>{route?.name}</span>;
426
+ + }
427
+
428
+ function App() {
429
+ return (
430
+ <RouterProvider router={router}>
431
+ <nav>
432
+ <Link routeName="home">Home</Link>
433
+ </nav>
434
+ <CurrentRoute />
435
+ </RouterProvider>
436
+ );
437
+ }
438
+ ```
439
+
440
+ ## License
441
+
442
+ MIT © [Oleg Ivanov](https://github.com/greydragon888)
@@ -0,0 +1,80 @@
1
+ import { Router, Params, State, RouterError, NavigationOptions } from '@real-router/core';
2
+ import * as react from 'react';
3
+ import { MouseEvent, ReactNode, FC, HTMLAttributes, MouseEventHandler } from 'react';
4
+
5
+ interface RouteState<P extends Params = Params, MP extends Params = Params> {
6
+ route: State<P, MP> | undefined;
7
+ previousRoute?: State | undefined;
8
+ }
9
+ type RouteContext$1 = {
10
+ router: Router;
11
+ } & RouteState;
12
+ interface BaseLinkProps$1 {
13
+ [key: string]: unknown;
14
+ router: Router;
15
+ routeName: string;
16
+ routeParams?: Params;
17
+ routeOptions?: {
18
+ [key: string]: unknown;
19
+ reload?: boolean;
20
+ replace?: boolean;
21
+ };
22
+ className?: string;
23
+ activeClassName?: string;
24
+ activeStrict?: boolean;
25
+ ignoreQueryParams?: boolean;
26
+ onClick?: (evt: MouseEvent<HTMLAnchorElement>) => void;
27
+ successCallback?: (state?: State) => void;
28
+ errorCallback?: (err: RouterError) => void;
29
+ target?: string;
30
+ children?: ReactNode;
31
+ previousRoute?: State;
32
+ }
33
+
34
+ /**
35
+ * Optimized BaseLink component with memoization and performance improvements
36
+ */
37
+ declare const BaseLink: FC<BaseLinkProps$1>;
38
+
39
+ interface BaseLinkProps<P extends Params = Params, MP extends Params = Params> extends HTMLAttributes<HTMLAnchorElement> {
40
+ router: Router;
41
+ routeName: string;
42
+ route?: State<P, MP> | undefined;
43
+ previousRoute?: State | undefined;
44
+ routeParams?: P;
45
+ routeOptions?: NavigationOptions;
46
+ className?: string;
47
+ activeClassName?: string;
48
+ activeStrict?: boolean;
49
+ ignoreQueryParams?: boolean;
50
+ target?: string;
51
+ onClick?: MouseEventHandler<HTMLAnchorElement>;
52
+ onMouseOver?: MouseEventHandler<HTMLAnchorElement>;
53
+ successCallback?: (state?: State<P, MP>) => void;
54
+ errorCallback?: (error?: RouterError) => void;
55
+ }
56
+
57
+ declare const Link: FC<Omit<BaseLinkProps, "router">>;
58
+
59
+ declare const ConnectedLink: FC<Omit<BaseLinkProps, "router" | "route" | "previousRoute">>;
60
+
61
+ /**
62
+ * Hook that subscribes to a specific route node with optimizations.
63
+ * Provides the current and previous route when the node is affected.
64
+ */
65
+ declare function useRouteNode(nodeName: string): RouteContext$1;
66
+
67
+ declare const useRoute: () => RouteContext$1;
68
+
69
+ interface RouteProviderProps {
70
+ router: Router;
71
+ children: ReactNode;
72
+ }
73
+ declare const RouterProvider: FC<RouteProviderProps>;
74
+
75
+ declare const RouteContext: react.Context<RouteContext$1 | null>;
76
+ declare const RouterContext: react.Context<Router<object> | null>;
77
+
78
+ declare const useRouter: () => Router;
79
+
80
+ export { BaseLink, type BaseLinkProps$1 as BaseLinkProps, ConnectedLink, Link, RouteContext, RouterContext, RouterProvider, useRoute, useRouteNode, useRouter };
@@ -0,0 +1 @@
1
+ var e=require("react"),r=require("@real-router/helpers"),t=require("react/jsx-runtime"),u=Object.freeze({}),o=Object.freeze({});function s(r,t,u){const o=e.useRef(void 0),s=e.useRef(t),n=e.useRef(u);if(s.current=t,n.current=u,void 0===o.current){const e=r.getState(),t=!n.current||e&&n.current(e);o.current=s.current(t&&e?{route:e,previousRoute:void 0}:void 0)}const a=e.useCallback(()=>o.current,[]),i=e.useCallback(e=>r.subscribe(r=>{let t=!0;if(n.current&&(t=n.current(r.route,r.previousRoute)),!t)return;const u=s.current(r);Object.is(o.current,u)||(o.current=u,e())}),[r]);return e.useSyncExternalStore(i,a,a)}function n(r){const t=JSON.stringify(r);return e.useMemo(()=>r,[t])}var a=new WeakMap;var i=e.memo(({routeName:a,routeParams:i=u,routeOptions:c=o,className:l,activeClassName:v="active",activeStrict:d=!1,ignoreQueryParams:m=!0,onClick:p,successCallback:R,errorCallback:f,target:b,router:C,children:g,...x})=>{const k=n(i),N=n(c),S=function(t,o,a=u,i=!1,c=!0){const l=n(a),v=e.useMemo(()=>function(e,r,t,u){return JSON.stringify({routeName:e,routeParams:r,activeStrict:t,ignoreQueryParams:u})}(o,l,i,c),[o,l,i,c]),d=e.useRef(void 0),m=e.useRef(void 0);m.current!==v&&(d.current=void 0,m.current=v);const p=e.useCallback((e,t)=>{const u=r.areRoutesRelated(o,e.name),s=t&&r.areRoutesRelated(o,t.name);return!(!u&&!s)},[o]),R=e.useCallback(e=>{const u=e?.route??t.getState();if(!u)return d.current=!1,!1;if(!r.areRoutesRelated(o,u.name))return d.current=!1,!1;const s=t.isActiveRoute(o,l,i,c);return d.current=s,s},[t,o,l,i,c]);return s(t,R,p)}(C,a,k,d,m),h=e.useMemo(()=>"buildUrl"in C&&"function"==typeof C.buildUrl?C.buildUrl(a,k):C.buildPath(a,k),[C,a,k]),y=e.useCallback(e=>{if(p&&(p(e),e.defaultPrevented))return;if(!function(e){return!(0!==e.button||e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}(e)||"_blank"===b)return;e.preventDefault();const r=R||f?(e,r)=>{e?f?.(e):R?.(r)}:void 0;r?C.navigate(a,k,N,r):C.navigate(a,k,N)},[p,b,C,a,k,N,R,f]),P=e.useMemo(()=>S&&v?l?`${l} ${v}`.trim():v:l??void 0,[S,l,v]),{previousRoute:O,...j}=x;return t.jsx("a",{...j,href:h,className:P,onClick:y,"data-route":a,"data-active":S,children:g})},(e,r)=>e.router===r.router&&e.routeName===r.routeName&&JSON.stringify(e.routeParams)===JSON.stringify(r.routeParams)&&JSON.stringify(e.routeOptions)===JSON.stringify(r.routeOptions)&&e.className===r.className&&e.activeClassName===r.activeClassName&&e.activeStrict===r.activeStrict&&e.ignoreQueryParams===r.ignoreQueryParams&&e.onClick===r.onClick&&e.successCallback===r.successCallback&&e.errorCallback===r.errorCallback&&e.target===r.target&&e.children===r.children);i.displayName="BaseLink";var c=e.createContext(null),l=e.createContext(null),v=()=>{const r=e.useContext(c);if(!r)throw new Error("useRoute must be used within a RouteProvider");return r},d=()=>{const r=e.useContext(l);if(!r)throw new Error("useRouter must be used within a RouterProvider");return r};exports.BaseLink=i,exports.ConnectedLink=e=>{const{router:r,route:u}=v(),{routeOptions:o,...s}=e;return t.jsx(i,{router:r,route:u,...s})},exports.Link=e=>{const r=d(),{route:u,previousRoute:o,routeOptions:s,...n}=e;return t.jsx(i,{router:r,...n})},exports.RouteContext=c,exports.RouterContext=l,exports.RouterProvider=({router:r,children:u})=>{const o=e.useMemo(()=>{let e={route:r.getState(),previousRoute:void 0};return{getSnapshot:()=>e,subscribe:t=>r.subscribe(({route:r,previousRoute:u})=>{e={route:r,previousRoute:u},t()})}},[r]),s=e.useSyncExternalStore(o.subscribe,o.getSnapshot);return t.jsx(l.Provider,{value:r,children:t.jsx(c.Provider,{value:{router:r,...s},children:u})})},exports.useRoute=v,exports.useRouteNode=function(r){const t=d(),u=e.useMemo(()=>function(e,r){let t=a.get(e);t||(t=new Map,a.set(e,t));let u=t.get(r);if(!u){u=e.shouldUpdateNode(r);const o=u;u=(e,r)=>o(e,r),t.set(r,u)}return u}(t,r),[t,r]),o=e.useCallback(e=>{const u=e?.route??t.getState();return u&&""!==r&&u.name!==r&&!u.name.startsWith(`${r}.`)?{route:void 0,previousRoute:e?.previousRoute}:{route:u,previousRoute:e?.previousRoute}},[t,r]),n=s(t,o,u);return e.useMemo(()=>({router:t,route:n.route,previousRoute:n.previousRoute}),[t,n.route,n.previousRoute])},exports.useRouter=d;//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/hooks/useRouterSubscription.tsx","../../src/hooks/useStableValue.tsx","../../src/utils.ts","../../src/hooks/useIsActiveRoute.tsx","../../src/components/BaseLink.tsx","../../src/components/Link.tsx","../../src/components/ConnectedLink.tsx","../../src/hooks/useRouteNode.tsx","../../src/context.ts","../../src/hooks/useRoute.tsx","../../src/RouterProvider.tsx","../../src/hooks/useRouter.tsx"],"names":["useRef","useCallback","useSyncExternalStore","useMemo","areRoutesRelated","memo","jsx","createContext","useContext"],"mappings":"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","file":"index.js","sourcesContent":["// packages/react/modules/constants.ts\n\n/**\n * Stable empty object for default params\n */\nexport const EMPTY_PARAMS = Object.freeze({});\n\n/**\n * Stable empty options object\n */\nexport const EMPTY_OPTIONS = Object.freeze({});\n","// packages/react/modules/hooks/useRouterSubscription.tsx\n\nimport { useCallback, useRef, useSyncExternalStore } from \"react\";\n\nimport type { Router, State, SubscribeState } from \"@real-router/core\";\n\n/**\n * Generic hook for subscribing to router changes with optimization.\n *\n * @param router - Real Router instance\n * @param selector - Function to derive state from router subscription\n * @param shouldUpdate - Optional predicate to filter updates\n */\nexport function useRouterSubscription<T>(\n router: Router,\n selector: (sub?: SubscribeState) => T,\n shouldUpdate?: (newRoute: State, prevRoute?: State) => boolean,\n): T {\n // Store current value\n const stateRef = useRef<T | undefined>(undefined);\n const selectorRef = useRef(selector);\n const shouldUpdateRef = useRef(shouldUpdate);\n\n // Update refs to avoid stale closures\n selectorRef.current = selector;\n shouldUpdateRef.current = shouldUpdate;\n\n // Lazy initialization\n if (stateRef.current === undefined) {\n // Get initial state from router\n const currentState = router.getState();\n\n // Check if initial state is relevant for this subscription\n const shouldInitialize =\n !shouldUpdateRef.current ||\n (currentState && shouldUpdateRef.current(currentState));\n\n stateRef.current = selectorRef.current(\n shouldInitialize && currentState\n ? { route: currentState, previousRoute: undefined }\n : undefined,\n );\n }\n\n // Stable snapshot getter\n const getSnapshot = useCallback(() => stateRef.current as T, []);\n\n // Subscribe function with optimization\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return router.subscribe((next) => {\n // Check if we should process this update\n let shouldProcess = true;\n\n if (shouldUpdateRef.current) {\n shouldProcess = shouldUpdateRef.current(\n next.route,\n next.previousRoute,\n );\n }\n\n if (!shouldProcess) {\n return;\n }\n\n // Calculate new value\n const newValue = selectorRef.current(next);\n\n // Only trigger update if value actually changed\n if (!Object.is(stateRef.current, newValue)) {\n stateRef.current = newValue;\n\n onStoreChange();\n }\n });\n },\n [router],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n","// packages/react/modules/hooks/useStableValue.tsx\n\nimport { useMemo } from \"react\";\n\n/**\n * Stabilizes a value reference based on deep equality (via JSON serialization).\n * Returns the same reference until the serialized value changes.\n *\n * Useful for object/array dependencies in hooks like useMemo, useCallback, useEffect\n * to prevent unnecessary re-renders when the value is structurally the same.\n *\n * @example\n * ```tsx\n * const stableParams = useStableValue(routeParams);\n * const href = useMemo(() => {\n * return router.buildUrl(routeName, stableParams);\n * }, [router, routeName, stableParams]);\n * ```\n *\n * @param value - The value to stabilize\n * @returns A stable reference to the value\n */\nexport function useStableValue<T>(value: T): T {\n const serialized = JSON.stringify(value);\n\n // We intentionally use serialized in deps to detect deep changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return useMemo(() => value, [serialized]);\n}\n","// packages/react/modules/utils.ts\n\nimport type { Params, Router, State } from \"@real-router/core\";\nimport type { MouseEvent } from \"react\";\n\n/**\n * Cache for shouldUpdateNode functions to avoid recreating them\n */\nexport const shouldUpdateCache = new WeakMap<\n Router,\n Map<string, (toState: State, fromState?: State) => boolean>\n>();\n\n/**\n * Get cached shouldUpdateNode function for a router and nodeName\n */\nexport function getCachedShouldUpdate(\n router: Router,\n nodeName: string,\n): (toState: State, fromState?: State) => boolean {\n let cache = shouldUpdateCache.get(router);\n\n if (!cache) {\n cache = new Map();\n shouldUpdateCache.set(router, cache);\n }\n\n let fn = cache.get(nodeName);\n\n if (!fn) {\n fn = router.shouldUpdateNode(nodeName);\n\n const originalFn = fn;\n\n fn = (toState: State, fromState?: State) => originalFn(toState, fromState);\n\n cache.set(nodeName, fn);\n }\n\n return fn;\n}\n\n/**\n * Check if navigation should be handled by router\n */\nexport function shouldNavigate(evt: MouseEvent): boolean {\n return (\n evt.button === 0 && // left click\n !evt.metaKey &&\n !evt.altKey &&\n !evt.ctrlKey &&\n !evt.shiftKey\n );\n}\n\n/**\n * Create cache key for route active check\n */\nexport function createActiveCheckKey(\n routeName: string,\n routeParams: Params,\n activeStrict: boolean,\n ignoreQueryParams: boolean,\n): string {\n return JSON.stringify({\n routeName,\n routeParams,\n activeStrict,\n ignoreQueryParams,\n });\n}\n","// packages/react/modules/hooks/useIsActiveRoute.tsx\n\nimport { areRoutesRelated } from \"@real-router/helpers\";\nimport { useCallback, useMemo, useRef } from \"react\";\n\nimport { useRouterSubscription } from \"./useRouterSubscription\";\nimport { useStableValue } from \"./useStableValue\";\nimport { EMPTY_PARAMS } from \"../constants\";\nimport { createActiveCheckKey } from \"../utils\";\n\nimport type { Params, Router, State, SubscribeState } from \"@real-router/core\";\n\n/**\n * Optimized hook to check if a route is active.\n * Minimizes unnecessary recalculations and re-renders.\n */\nexport function useIsActiveRoute(\n router: Router,\n routeName: string,\n routeParams: Params = EMPTY_PARAMS,\n activeStrict = false,\n ignoreQueryParams = true,\n): boolean {\n // Stabilize params reference to prevent unnecessary recalculations\n const stableParams = useStableValue(routeParams);\n\n // Create stable cache key\n const cacheKey = useMemo(\n () =>\n createActiveCheckKey(\n routeName,\n stableParams,\n activeStrict,\n ignoreQueryParams,\n ),\n [routeName, stableParams, activeStrict, ignoreQueryParams],\n );\n\n // Cache the active state\n const isActiveRef = useRef<boolean | undefined>(undefined);\n const lastCacheKey = useRef<string | undefined>(undefined);\n\n if (lastCacheKey.current !== cacheKey) {\n isActiveRef.current = undefined;\n lastCacheKey.current = cacheKey;\n }\n\n // Optimize shouldUpdate to skip unrelated routes\n const shouldUpdate = useCallback(\n (newRoute: State, prevRoute?: State) => {\n const isNewRelated = areRoutesRelated(routeName, newRoute.name);\n const isPrevRelated =\n prevRoute && areRoutesRelated(routeName, prevRoute.name);\n\n return !!(isNewRelated || isPrevRelated);\n },\n [routeName],\n );\n\n // Selector that performs active check\n const selector = useCallback(\n (sub?: SubscribeState): boolean => {\n const currentRoute = sub?.route ?? router.getState();\n\n // Fast path: if no current route, not active\n if (!currentRoute) {\n isActiveRef.current = false;\n\n return false;\n }\n\n // Fast path: skip unrelated routes\n if (!areRoutesRelated(routeName, currentRoute.name)) {\n isActiveRef.current = false;\n\n return false;\n }\n\n // Full check for related routes\n const isActive = router.isActiveRoute(\n routeName,\n stableParams,\n activeStrict,\n ignoreQueryParams,\n );\n\n isActiveRef.current = isActive;\n\n return isActive;\n },\n [router, routeName, stableParams, activeStrict, ignoreQueryParams],\n );\n\n return useRouterSubscription(router, selector, shouldUpdate);\n}\n","// packages/react/modules/components/BaseLink.tsx\n\nimport { memo, useCallback, useMemo } from \"react\";\n\nimport { EMPTY_PARAMS, EMPTY_OPTIONS } from \"../constants\";\nimport { useIsActiveRoute } from \"../hooks/useIsActiveRoute\";\nimport { useStableValue } from \"../hooks/useStableValue\";\nimport { shouldNavigate } from \"../utils\";\n\nimport type { BaseLinkProps } from \"../types\";\nimport type { RouterError, State } from \"@real-router/core\";\nimport type { FC, MouseEvent } from \"react\";\n\n/**\n * Optimized BaseLink component with memoization and performance improvements\n */\nexport const BaseLink: FC<BaseLinkProps> = memo(\n ({\n routeName,\n routeParams = EMPTY_PARAMS,\n routeOptions = EMPTY_OPTIONS,\n className,\n activeClassName = \"active\",\n activeStrict = false,\n ignoreQueryParams = true,\n onClick,\n successCallback,\n errorCallback,\n target,\n router,\n children,\n ...props\n }) => {\n // Stabilize object references to prevent unnecessary re-renders\n const stableParams = useStableValue(routeParams);\n const stableOptions = useStableValue(routeOptions);\n\n // Use optimized hook for active state checking\n const isActive = useIsActiveRoute(\n router,\n routeName,\n stableParams,\n activeStrict,\n ignoreQueryParams,\n );\n\n // Build URL with memoization\n const href = useMemo(() => {\n // Check if router has buildUrl method (from browser plugin)\n if (\"buildUrl\" in router && typeof router.buildUrl === \"function\") {\n return router.buildUrl(routeName, stableParams);\n }\n\n // Fallback to buildPath\n return router.buildPath(routeName, stableParams);\n }, [router, routeName, stableParams]);\n\n // Optimized click handler\n const handleClick = useCallback(\n (evt: MouseEvent<HTMLAnchorElement>) => {\n // Call custom onClick if provided\n if (onClick) {\n onClick(evt);\n // Respect preventDefault from custom handler\n if (evt.defaultPrevented) {\n return;\n }\n }\n\n // Check if we should handle navigation\n if (!shouldNavigate(evt) || target === \"_blank\") {\n return;\n }\n\n // Prevent default link behavior\n evt.preventDefault();\n\n // Create navigation callback if needed\n const done =\n successCallback || errorCallback\n ? (err?: RouterError, state?: State) => {\n if (err) {\n errorCallback?.(err);\n } else {\n successCallback?.(state);\n }\n }\n : undefined;\n\n // Perform navigation\n if (done) {\n router.navigate(routeName, stableParams, stableOptions, done);\n } else {\n router.navigate(routeName, stableParams, stableOptions);\n }\n },\n [\n onClick,\n target,\n router,\n routeName,\n stableParams,\n stableOptions,\n successCallback,\n errorCallback,\n ],\n );\n\n // Build className efficiently\n const finalClassName = useMemo(() => {\n if (isActive && activeClassName) {\n return className\n ? `${className} ${activeClassName}`.trim()\n : activeClassName;\n }\n\n return className ?? undefined;\n }, [isActive, className, activeClassName]);\n\n // Filter out previousRoute from props\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { previousRoute, ...restProps } = props;\n\n return (\n <a\n {...restProps}\n href={href}\n className={finalClassName}\n onClick={handleClick}\n data-route={routeName} // For event delegation if needed\n data-active={isActive} // For CSS selectors if needed\n >\n {children}\n </a>\n );\n },\n (prevProps, nextProps) => {\n // Custom comparison for better memoization\n // Check if props that affect rendering have changed\n return (\n prevProps.router === nextProps.router &&\n prevProps.routeName === nextProps.routeName &&\n JSON.stringify(prevProps.routeParams) ===\n JSON.stringify(nextProps.routeParams) &&\n JSON.stringify(prevProps.routeOptions) ===\n JSON.stringify(nextProps.routeOptions) &&\n prevProps.className === nextProps.className &&\n prevProps.activeClassName === nextProps.activeClassName &&\n prevProps.activeStrict === nextProps.activeStrict &&\n prevProps.ignoreQueryParams === nextProps.ignoreQueryParams &&\n prevProps.onClick === nextProps.onClick &&\n prevProps.successCallback === nextProps.successCallback &&\n prevProps.errorCallback === nextProps.errorCallback &&\n prevProps.target === nextProps.target &&\n prevProps.children === nextProps.children\n );\n },\n);\n\nBaseLink.displayName = \"BaseLink\";\n","// packages/react/modules/components/Link.tsx\n\nimport { useRouter } from \"@real-router/react\";\n\nimport { BaseLink } from \"./BaseLink\";\n\nimport type { BaseLinkProps } from \"./interfaces\";\nimport type { FC } from \"react\";\n\nexport const Link: FC<Omit<BaseLinkProps, \"router\">> = (props) => {\n const router = useRouter();\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { route, previousRoute, routeOptions, ...linkProps } = props;\n\n return <BaseLink router={router} {...linkProps} />;\n};\n","// packages/react/modules/components/ConnectedLink.tsx\n\nimport { useRoute } from \"@real-router/react\";\n\nimport { BaseLink } from \"./BaseLink\";\n\nimport type { BaseLinkProps } from \"./interfaces\";\nimport type { FC } from \"react\";\n\nexport const ConnectedLink: FC<\n Omit<BaseLinkProps, \"router\" | \"route\" | \"previousRoute\">\n> = (props) => {\n const { router, route } = useRoute();\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { routeOptions, ...linkProps } = props;\n\n return <BaseLink router={router} route={route} {...linkProps} />;\n};\n","// packages/react/modules/hooks/useRouteNode.tsx\n\nimport { useCallback, useMemo } from \"react\";\n\nimport { useRouter } from \"@real-router/react\";\n\nimport { useRouterSubscription } from \"./useRouterSubscription\";\nimport { getCachedShouldUpdate } from \"../utils\";\n\nimport type { RouteContext, RouteState } from \"../types\";\nimport type { State, SubscribeState } from \"@real-router/core\";\n\n/**\n * Hook that subscribes to a specific route node with optimizations.\n * Provides the current and previous route when the node is affected.\n */\nexport function useRouteNode(nodeName: string): RouteContext {\n // Get router from context with error handling\n const router = useRouter();\n\n // Get cached shouldUpdate function to avoid recreation\n const shouldUpdate = useMemo(\n () => getCachedShouldUpdate(router, nodeName),\n [router, nodeName],\n );\n\n // Stable state factory\n // useRouteNode.tsx\n const stateFactory = useCallback(\n (sub?: SubscribeState): RouteState => {\n const currentRoute = sub?.route ?? router.getState();\n\n // Проверяем, активен ли узел\n if (currentRoute && nodeName !== \"\") {\n // Корневой узел всегда активен\n const isNodeActive =\n currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`);\n\n if (!isNodeActive) {\n return {\n route: undefined,\n previousRoute: sub?.previousRoute,\n };\n }\n }\n\n return {\n route: currentRoute,\n previousRoute: sub?.previousRoute,\n };\n },\n [router, nodeName],\n );\n\n // Subscribe to router with optimization\n const state = useRouterSubscription<RouteState>(\n router,\n stateFactory,\n shouldUpdate as (newRoute: State, prevRoute?: State) => boolean,\n );\n\n // Return memoized context - useMemo ensures stable reference when deps unchanged\n return useMemo(\n (): RouteContext => ({\n router,\n route: state.route,\n previousRoute: state.previousRoute,\n }),\n [router, state.route, state.previousRoute],\n );\n}\n","// packages/react/modules/context.ts\n\nimport { createContext } from \"react\";\n\nimport type { RouteContext as RouteContextType } from \"./types\";\nimport type { Router } from \"@real-router/core\";\n\nexport const RouteContext = createContext<RouteContextType | null>(null);\n\nexport const RouterContext = createContext<Router | null>(null);\n","// packages/react/modules/hooks/useRoute.tsx\n\nimport { useContext } from \"react\";\n\nimport { RouteContext } from \"../context\";\n\nimport type { RouteContext as RouteContextType } from \"../types\";\n\nexport const useRoute = (): RouteContextType => {\n const routeContext = useContext(RouteContext);\n\n if (!routeContext) {\n throw new Error(\"useRoute must be used within a RouteProvider\");\n }\n\n return routeContext;\n};\n","// packages/react/modules/RouterProvider.tsx\n\nimport { useMemo, useSyncExternalStore } from \"react\";\n\nimport { RouteContext, RouterContext } from \"./context\";\n\nimport type { RouteState } from \"./types\";\nimport type { Router } from \"@real-router/core\";\nimport type { FC, ReactNode } from \"react\";\n\nexport interface RouteProviderProps {\n router: Router;\n children: ReactNode;\n}\n\nexport const RouterProvider: FC<RouteProviderProps> = ({\n router,\n children,\n}) => {\n // Local store state to hold route information\n const store = useMemo(() => {\n let currentState: RouteState = {\n route: router.getState(),\n previousRoute: undefined,\n };\n\n // This will be called to return the current state snapshot\n const getSnapshot = () => currentState;\n\n // Subscribe to router updates and notify React when state changes\n const subscribe = (callback: () => void) => {\n const unsubscribe = router.subscribe(({ route, previousRoute }) => {\n currentState = { route, previousRoute };\n callback(); // Notify React to trigger re-render\n });\n\n // Note: router.subscribe() always returns a function, no need to check\n return unsubscribe;\n };\n\n return { getSnapshot, subscribe };\n }, [router]);\n\n // Using useSyncExternalStore to manage subscription and state updates\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot);\n\n return (\n <RouterContext.Provider value={router}>\n <RouteContext.Provider value={{ router, ...state }}>\n {children}\n </RouteContext.Provider>\n </RouterContext.Provider>\n );\n};\n","// packages/react/modules/hooks/useRouter.tsx\n\nimport { useContext } from \"react\";\n\nimport { RouterContext } from \"../context\";\n\nimport type { Router } from \"@real-router/core\";\n\nexport const useRouter = (): Router => {\n const router = useContext(RouterContext);\n\n if (!router) {\n throw new Error(\"useRouter must be used within a RouterProvider\");\n }\n\n return router;\n};\n"]}
@@ -0,0 +1 @@
1
+ {"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js":{"bytes":569,"imports":[],"format":"esm"},"src/constants.ts":{"bytes":225,"imports":[{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useRouterSubscription.tsx":{"bytes":2370,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useStableValue.tsx":{"bytes":976,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/utils.ts":{"bytes":1483,"imports":[{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useIsActiveRoute.tsx":{"bytes":2679,"imports":[{"path":"@real-router/helpers","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"src/hooks/useRouterSubscription.tsx","kind":"import-statement","original":"./useRouterSubscription"},{"path":"src/hooks/useStableValue.tsx","kind":"import-statement","original":"./useStableValue"},{"path":"src/constants.ts","kind":"import-statement","original":"../constants"},{"path":"src/utils.ts","kind":"import-statement","original":"../utils"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/components/BaseLink.tsx":{"bytes":4826,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/constants.ts","kind":"import-statement","original":"../constants"},{"path":"src/hooks/useIsActiveRoute.tsx","kind":"import-statement","original":"../hooks/useIsActiveRoute"},{"path":"src/hooks/useStableValue.tsx","kind":"import-statement","original":"../hooks/useStableValue"},{"path":"src/utils.ts","kind":"import-statement","original":"../utils"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/components/Link.tsx":{"bytes":512,"imports":[{"path":"src/index.ts","kind":"import-statement","original":"@real-router/react"},{"path":"src/components/BaseLink.tsx","kind":"import-statement","original":"./BaseLink"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/components/ConnectedLink.tsx":{"bytes":563,"imports":[{"path":"src/index.ts","kind":"import-statement","original":"@real-router/react"},{"path":"src/components/BaseLink.tsx","kind":"import-statement","original":"./BaseLink"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useRouteNode.tsx":{"bytes":2096,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/index.ts","kind":"import-statement","original":"@real-router/react"},{"path":"src/hooks/useRouterSubscription.tsx","kind":"import-statement","original":"./useRouterSubscription"},{"path":"src/utils.ts","kind":"import-statement","original":"../utils"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/context.ts":{"bytes":333,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useRoute.tsx":{"bytes":417,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/context.ts","kind":"import-statement","original":"../context"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/RouterProvider.tsx":{"bytes":1596,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/context.ts","kind":"import-statement","original":"./context"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useRouter.tsx":{"bytes":378,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/context.ts","kind":"import-statement","original":"../context"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":529,"imports":[{"path":"src/components/BaseLink.tsx","kind":"import-statement","original":"./components/BaseLink"},{"path":"src/components/Link.tsx","kind":"import-statement","original":"./components/Link"},{"path":"src/components/ConnectedLink.tsx","kind":"import-statement","original":"./components/ConnectedLink"},{"path":"src/hooks/useRouteNode.tsx","kind":"import-statement","original":"./hooks/useRouteNode"},{"path":"src/hooks/useRoute.tsx","kind":"import-statement","original":"./hooks/useRoute"},{"path":"src/RouterProvider.tsx","kind":"import-statement","original":"./RouterProvider"},{"path":"src/context.ts","kind":"import-statement","original":"./context"},{"path":"src/hooks/useRouter.tsx","kind":"import-statement","original":"./hooks/useRouter"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/cjs/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":26879},"dist/cjs/index.js":{"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"@real-router/helpers","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true}],"exports":["BaseLink","ConnectedLink","Link","RouteContext","RouterContext","RouterProvider","useRoute","useRouteNode","useRouter"],"entryPoint":"src/index.ts","inputs":{"src/components/BaseLink.tsx":{"bytesInOutput":3235},"src/constants.ts":{"bytesInOutput":77},"src/hooks/useIsActiveRoute.tsx":{"bytesInOutput":1723},"src/hooks/useRouterSubscription.tsx":{"bytesInOutput":1386},"src/hooks/useStableValue.tsx":{"bytesInOutput":157},"src/utils.ts":{"bytesInOutput":816},"src/index.ts":{"bytesInOutput":0},"src/components/Link.tsx":{"bytesInOutput":243},"src/components/ConnectedLink.tsx":{"bytesInOutput":247},"src/hooks/useRouteNode.tsx":{"bytesInOutput":1064},"src/hooks/useRoute.tsx":{"bytesInOutput":230},"src/context.ts":{"bytesInOutput":120},"src/RouterProvider.tsx":{"bytesInOutput":893},"src/hooks/useRouter.tsx":{"bytesInOutput":232}},"bytes":11031}}}
@@ -0,0 +1,80 @@
1
+ import { Router, Params, State, RouterError, NavigationOptions } from '@real-router/core';
2
+ import * as react from 'react';
3
+ import { MouseEvent, ReactNode, FC, HTMLAttributes, MouseEventHandler } from 'react';
4
+
5
+ interface RouteState<P extends Params = Params, MP extends Params = Params> {
6
+ route: State<P, MP> | undefined;
7
+ previousRoute?: State | undefined;
8
+ }
9
+ type RouteContext$1 = {
10
+ router: Router;
11
+ } & RouteState;
12
+ interface BaseLinkProps$1 {
13
+ [key: string]: unknown;
14
+ router: Router;
15
+ routeName: string;
16
+ routeParams?: Params;
17
+ routeOptions?: {
18
+ [key: string]: unknown;
19
+ reload?: boolean;
20
+ replace?: boolean;
21
+ };
22
+ className?: string;
23
+ activeClassName?: string;
24
+ activeStrict?: boolean;
25
+ ignoreQueryParams?: boolean;
26
+ onClick?: (evt: MouseEvent<HTMLAnchorElement>) => void;
27
+ successCallback?: (state?: State) => void;
28
+ errorCallback?: (err: RouterError) => void;
29
+ target?: string;
30
+ children?: ReactNode;
31
+ previousRoute?: State;
32
+ }
33
+
34
+ /**
35
+ * Optimized BaseLink component with memoization and performance improvements
36
+ */
37
+ declare const BaseLink: FC<BaseLinkProps$1>;
38
+
39
+ interface BaseLinkProps<P extends Params = Params, MP extends Params = Params> extends HTMLAttributes<HTMLAnchorElement> {
40
+ router: Router;
41
+ routeName: string;
42
+ route?: State<P, MP> | undefined;
43
+ previousRoute?: State | undefined;
44
+ routeParams?: P;
45
+ routeOptions?: NavigationOptions;
46
+ className?: string;
47
+ activeClassName?: string;
48
+ activeStrict?: boolean;
49
+ ignoreQueryParams?: boolean;
50
+ target?: string;
51
+ onClick?: MouseEventHandler<HTMLAnchorElement>;
52
+ onMouseOver?: MouseEventHandler<HTMLAnchorElement>;
53
+ successCallback?: (state?: State<P, MP>) => void;
54
+ errorCallback?: (error?: RouterError) => void;
55
+ }
56
+
57
+ declare const Link: FC<Omit<BaseLinkProps, "router">>;
58
+
59
+ declare const ConnectedLink: FC<Omit<BaseLinkProps, "router" | "route" | "previousRoute">>;
60
+
61
+ /**
62
+ * Hook that subscribes to a specific route node with optimizations.
63
+ * Provides the current and previous route when the node is affected.
64
+ */
65
+ declare function useRouteNode(nodeName: string): RouteContext$1;
66
+
67
+ declare const useRoute: () => RouteContext$1;
68
+
69
+ interface RouteProviderProps {
70
+ router: Router;
71
+ children: ReactNode;
72
+ }
73
+ declare const RouterProvider: FC<RouteProviderProps>;
74
+
75
+ declare const RouteContext: react.Context<RouteContext$1 | null>;
76
+ declare const RouterContext: react.Context<Router<object> | null>;
77
+
78
+ declare const useRouter: () => Router;
79
+
80
+ export { BaseLink, type BaseLinkProps$1 as BaseLinkProps, ConnectedLink, Link, RouteContext, RouterContext, RouterProvider, useRoute, useRouteNode, useRouter };
@@ -0,0 +1 @@
1
+ import{memo as r,useMemo as e,useCallback as t,createContext as u,useRef as o,useSyncExternalStore as n,useContext as i}from"react";import{areRoutesRelated as a}from"@real-router/helpers";import{jsx as s}from"react/jsx-runtime";var c=Object.freeze({}),l=Object.freeze({});function v(r,e,u){const i=o(void 0),a=o(e),s=o(u);if(a.current=e,s.current=u,void 0===i.current){const e=r.getState(),t=!s.current||e&&s.current(e);i.current=a.current(t&&e?{route:e,previousRoute:void 0}:void 0)}const c=t(()=>i.current,[]),l=t(e=>r.subscribe(r=>{let t=!0;if(s.current&&(t=s.current(r.route,r.previousRoute)),!t)return;const u=a.current(r);Object.is(i.current,u)||(i.current=u,e())}),[r]);return n(l,c,c)}function d(r){const t=JSON.stringify(r);return e(()=>r,[t])}var m=new WeakMap;var f=r(({routeName:r,routeParams:u=c,routeOptions:n=l,className:i,activeClassName:m="active",activeStrict:f=!1,ignoreQueryParams:p=!0,onClick:b,successCallback:g,errorCallback:h,target:N,router:R,children:y,...S})=>{const O=d(u),P=d(n),k=function(r,u,n=c,i=!1,s=!0){const l=d(n),m=e(()=>function(r,e,t,u){return JSON.stringify({routeName:r,routeParams:e,activeStrict:t,ignoreQueryParams:u})}(u,l,i,s),[u,l,i,s]),f=o(void 0),p=o(void 0);p.current!==m&&(f.current=void 0,p.current=m);const b=t((r,e)=>{const t=a(u,r.name),o=e&&a(u,e.name);return!(!t&&!o)},[u]),g=t(e=>{const t=e?.route??r.getState();if(!t)return f.current=!1,!1;if(!a(u,t.name))return f.current=!1,!1;const o=r.isActiveRoute(u,l,i,s);return f.current=o,o},[r,u,l,i,s]);return v(r,g,b)}(R,r,O,f,p),C=e(()=>"buildUrl"in R&&"function"==typeof R.buildUrl?R.buildUrl(r,O):R.buildPath(r,O),[R,r,O]),w=t(e=>{if(b&&(b(e),e.defaultPrevented))return;if(!function(r){return!(0!==r.button||r.metaKey||r.altKey||r.ctrlKey||r.shiftKey)}(e)||"_blank"===N)return;e.preventDefault();const t=g||h?(r,e)=>{r?h?.(r):g?.(e)}:void 0;t?R.navigate(r,O,P,t):R.navigate(r,O,P)},[b,N,R,r,O,P,g,h]),J=e(()=>k&&m?i?`${i} ${m}`.trim():m:i??void 0,[k,i,m]),{previousRoute:j,...K}=S;return s("a",{...K,href:C,className:J,onClick:w,"data-route":r,"data-active":k,children:y})},(r,e)=>r.router===e.router&&r.routeName===e.routeName&&JSON.stringify(r.routeParams)===JSON.stringify(e.routeParams)&&JSON.stringify(r.routeOptions)===JSON.stringify(e.routeOptions)&&r.className===e.className&&r.activeClassName===e.activeClassName&&r.activeStrict===e.activeStrict&&r.ignoreQueryParams===e.ignoreQueryParams&&r.onClick===e.onClick&&r.successCallback===e.successCallback&&r.errorCallback===e.errorCallback&&r.target===e.target&&r.children===e.children);f.displayName="BaseLink";var p=r=>{const e=S(),{route:t,previousRoute:u,routeOptions:o,...n}=r;return s(f,{router:e,...n})},b=r=>{const{router:e,route:t}=R(),{routeOptions:u,...o}=r;return s(f,{router:e,route:t,...o})};function g(r){const u=S(),o=e(()=>function(r,e){let t=m.get(r);t||(t=new Map,m.set(r,t));let u=t.get(e);if(!u){u=r.shouldUpdateNode(e);const o=u;u=(r,e)=>o(r,e),t.set(e,u)}return u}(u,r),[u,r]),n=t(e=>{const t=e?.route??u.getState();return t&&""!==r&&t.name!==r&&!t.name.startsWith(`${r}.`)?{route:void 0,previousRoute:e?.previousRoute}:{route:t,previousRoute:e?.previousRoute}},[u,r]),i=v(u,n,o);return e(()=>({router:u,route:i.route,previousRoute:i.previousRoute}),[u,i.route,i.previousRoute])}var h=u(null),N=u(null),R=()=>{const r=i(h);if(!r)throw new Error("useRoute must be used within a RouteProvider");return r},y=({router:r,children:t})=>{const u=e(()=>{let e={route:r.getState(),previousRoute:void 0};return{getSnapshot:()=>e,subscribe:t=>r.subscribe(({route:r,previousRoute:u})=>{e={route:r,previousRoute:u},t()})}},[r]),o=n(u.subscribe,u.getSnapshot);return s(N.Provider,{value:r,children:s(h.Provider,{value:{router:r,...o},children:t})})},S=()=>{const r=i(N);if(!r)throw new Error("useRouter must be used within a RouterProvider");return r};export{f as BaseLink,b as ConnectedLink,p as Link,h as RouteContext,N as RouterContext,y as RouterProvider,R as useRoute,g as useRouteNode,S as useRouter};//# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/hooks/useRouterSubscription.tsx","../../src/hooks/useStableValue.tsx","../../src/utils.ts","../../src/hooks/useIsActiveRoute.tsx","../../src/components/BaseLink.tsx","../../src/components/Link.tsx","../../src/components/ConnectedLink.tsx","../../src/hooks/useRouteNode.tsx","../../src/context.ts","../../src/hooks/useRoute.tsx","../../src/RouterProvider.tsx","../../src/hooks/useRouter.tsx"],"names":["useMemo","useRef","useCallback","jsx","useSyncExternalStore","useContext"],"mappings":"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","file":"index.mjs","sourcesContent":["// packages/react/modules/constants.ts\n\n/**\n * Stable empty object for default params\n */\nexport const EMPTY_PARAMS = Object.freeze({});\n\n/**\n * Stable empty options object\n */\nexport const EMPTY_OPTIONS = Object.freeze({});\n","// packages/react/modules/hooks/useRouterSubscription.tsx\n\nimport { useCallback, useRef, useSyncExternalStore } from \"react\";\n\nimport type { Router, State, SubscribeState } from \"@real-router/core\";\n\n/**\n * Generic hook for subscribing to router changes with optimization.\n *\n * @param router - Real Router instance\n * @param selector - Function to derive state from router subscription\n * @param shouldUpdate - Optional predicate to filter updates\n */\nexport function useRouterSubscription<T>(\n router: Router,\n selector: (sub?: SubscribeState) => T,\n shouldUpdate?: (newRoute: State, prevRoute?: State) => boolean,\n): T {\n // Store current value\n const stateRef = useRef<T | undefined>(undefined);\n const selectorRef = useRef(selector);\n const shouldUpdateRef = useRef(shouldUpdate);\n\n // Update refs to avoid stale closures\n selectorRef.current = selector;\n shouldUpdateRef.current = shouldUpdate;\n\n // Lazy initialization\n if (stateRef.current === undefined) {\n // Get initial state from router\n const currentState = router.getState();\n\n // Check if initial state is relevant for this subscription\n const shouldInitialize =\n !shouldUpdateRef.current ||\n (currentState && shouldUpdateRef.current(currentState));\n\n stateRef.current = selectorRef.current(\n shouldInitialize && currentState\n ? { route: currentState, previousRoute: undefined }\n : undefined,\n );\n }\n\n // Stable snapshot getter\n const getSnapshot = useCallback(() => stateRef.current as T, []);\n\n // Subscribe function with optimization\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return router.subscribe((next) => {\n // Check if we should process this update\n let shouldProcess = true;\n\n if (shouldUpdateRef.current) {\n shouldProcess = shouldUpdateRef.current(\n next.route,\n next.previousRoute,\n );\n }\n\n if (!shouldProcess) {\n return;\n }\n\n // Calculate new value\n const newValue = selectorRef.current(next);\n\n // Only trigger update if value actually changed\n if (!Object.is(stateRef.current, newValue)) {\n stateRef.current = newValue;\n\n onStoreChange();\n }\n });\n },\n [router],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n","// packages/react/modules/hooks/useStableValue.tsx\n\nimport { useMemo } from \"react\";\n\n/**\n * Stabilizes a value reference based on deep equality (via JSON serialization).\n * Returns the same reference until the serialized value changes.\n *\n * Useful for object/array dependencies in hooks like useMemo, useCallback, useEffect\n * to prevent unnecessary re-renders when the value is structurally the same.\n *\n * @example\n * ```tsx\n * const stableParams = useStableValue(routeParams);\n * const href = useMemo(() => {\n * return router.buildUrl(routeName, stableParams);\n * }, [router, routeName, stableParams]);\n * ```\n *\n * @param value - The value to stabilize\n * @returns A stable reference to the value\n */\nexport function useStableValue<T>(value: T): T {\n const serialized = JSON.stringify(value);\n\n // We intentionally use serialized in deps to detect deep changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return useMemo(() => value, [serialized]);\n}\n","// packages/react/modules/utils.ts\n\nimport type { Params, Router, State } from \"@real-router/core\";\nimport type { MouseEvent } from \"react\";\n\n/**\n * Cache for shouldUpdateNode functions to avoid recreating them\n */\nexport const shouldUpdateCache = new WeakMap<\n Router,\n Map<string, (toState: State, fromState?: State) => boolean>\n>();\n\n/**\n * Get cached shouldUpdateNode function for a router and nodeName\n */\nexport function getCachedShouldUpdate(\n router: Router,\n nodeName: string,\n): (toState: State, fromState?: State) => boolean {\n let cache = shouldUpdateCache.get(router);\n\n if (!cache) {\n cache = new Map();\n shouldUpdateCache.set(router, cache);\n }\n\n let fn = cache.get(nodeName);\n\n if (!fn) {\n fn = router.shouldUpdateNode(nodeName);\n\n const originalFn = fn;\n\n fn = (toState: State, fromState?: State) => originalFn(toState, fromState);\n\n cache.set(nodeName, fn);\n }\n\n return fn;\n}\n\n/**\n * Check if navigation should be handled by router\n */\nexport function shouldNavigate(evt: MouseEvent): boolean {\n return (\n evt.button === 0 && // left click\n !evt.metaKey &&\n !evt.altKey &&\n !evt.ctrlKey &&\n !evt.shiftKey\n );\n}\n\n/**\n * Create cache key for route active check\n */\nexport function createActiveCheckKey(\n routeName: string,\n routeParams: Params,\n activeStrict: boolean,\n ignoreQueryParams: boolean,\n): string {\n return JSON.stringify({\n routeName,\n routeParams,\n activeStrict,\n ignoreQueryParams,\n });\n}\n","// packages/react/modules/hooks/useIsActiveRoute.tsx\n\nimport { areRoutesRelated } from \"@real-router/helpers\";\nimport { useCallback, useMemo, useRef } from \"react\";\n\nimport { useRouterSubscription } from \"./useRouterSubscription\";\nimport { useStableValue } from \"./useStableValue\";\nimport { EMPTY_PARAMS } from \"../constants\";\nimport { createActiveCheckKey } from \"../utils\";\n\nimport type { Params, Router, State, SubscribeState } from \"@real-router/core\";\n\n/**\n * Optimized hook to check if a route is active.\n * Minimizes unnecessary recalculations and re-renders.\n */\nexport function useIsActiveRoute(\n router: Router,\n routeName: string,\n routeParams: Params = EMPTY_PARAMS,\n activeStrict = false,\n ignoreQueryParams = true,\n): boolean {\n // Stabilize params reference to prevent unnecessary recalculations\n const stableParams = useStableValue(routeParams);\n\n // Create stable cache key\n const cacheKey = useMemo(\n () =>\n createActiveCheckKey(\n routeName,\n stableParams,\n activeStrict,\n ignoreQueryParams,\n ),\n [routeName, stableParams, activeStrict, ignoreQueryParams],\n );\n\n // Cache the active state\n const isActiveRef = useRef<boolean | undefined>(undefined);\n const lastCacheKey = useRef<string | undefined>(undefined);\n\n if (lastCacheKey.current !== cacheKey) {\n isActiveRef.current = undefined;\n lastCacheKey.current = cacheKey;\n }\n\n // Optimize shouldUpdate to skip unrelated routes\n const shouldUpdate = useCallback(\n (newRoute: State, prevRoute?: State) => {\n const isNewRelated = areRoutesRelated(routeName, newRoute.name);\n const isPrevRelated =\n prevRoute && areRoutesRelated(routeName, prevRoute.name);\n\n return !!(isNewRelated || isPrevRelated);\n },\n [routeName],\n );\n\n // Selector that performs active check\n const selector = useCallback(\n (sub?: SubscribeState): boolean => {\n const currentRoute = sub?.route ?? router.getState();\n\n // Fast path: if no current route, not active\n if (!currentRoute) {\n isActiveRef.current = false;\n\n return false;\n }\n\n // Fast path: skip unrelated routes\n if (!areRoutesRelated(routeName, currentRoute.name)) {\n isActiveRef.current = false;\n\n return false;\n }\n\n // Full check for related routes\n const isActive = router.isActiveRoute(\n routeName,\n stableParams,\n activeStrict,\n ignoreQueryParams,\n );\n\n isActiveRef.current = isActive;\n\n return isActive;\n },\n [router, routeName, stableParams, activeStrict, ignoreQueryParams],\n );\n\n return useRouterSubscription(router, selector, shouldUpdate);\n}\n","// packages/react/modules/components/BaseLink.tsx\n\nimport { memo, useCallback, useMemo } from \"react\";\n\nimport { EMPTY_PARAMS, EMPTY_OPTIONS } from \"../constants\";\nimport { useIsActiveRoute } from \"../hooks/useIsActiveRoute\";\nimport { useStableValue } from \"../hooks/useStableValue\";\nimport { shouldNavigate } from \"../utils\";\n\nimport type { BaseLinkProps } from \"../types\";\nimport type { RouterError, State } from \"@real-router/core\";\nimport type { FC, MouseEvent } from \"react\";\n\n/**\n * Optimized BaseLink component with memoization and performance improvements\n */\nexport const BaseLink: FC<BaseLinkProps> = memo(\n ({\n routeName,\n routeParams = EMPTY_PARAMS,\n routeOptions = EMPTY_OPTIONS,\n className,\n activeClassName = \"active\",\n activeStrict = false,\n ignoreQueryParams = true,\n onClick,\n successCallback,\n errorCallback,\n target,\n router,\n children,\n ...props\n }) => {\n // Stabilize object references to prevent unnecessary re-renders\n const stableParams = useStableValue(routeParams);\n const stableOptions = useStableValue(routeOptions);\n\n // Use optimized hook for active state checking\n const isActive = useIsActiveRoute(\n router,\n routeName,\n stableParams,\n activeStrict,\n ignoreQueryParams,\n );\n\n // Build URL with memoization\n const href = useMemo(() => {\n // Check if router has buildUrl method (from browser plugin)\n if (\"buildUrl\" in router && typeof router.buildUrl === \"function\") {\n return router.buildUrl(routeName, stableParams);\n }\n\n // Fallback to buildPath\n return router.buildPath(routeName, stableParams);\n }, [router, routeName, stableParams]);\n\n // Optimized click handler\n const handleClick = useCallback(\n (evt: MouseEvent<HTMLAnchorElement>) => {\n // Call custom onClick if provided\n if (onClick) {\n onClick(evt);\n // Respect preventDefault from custom handler\n if (evt.defaultPrevented) {\n return;\n }\n }\n\n // Check if we should handle navigation\n if (!shouldNavigate(evt) || target === \"_blank\") {\n return;\n }\n\n // Prevent default link behavior\n evt.preventDefault();\n\n // Create navigation callback if needed\n const done =\n successCallback || errorCallback\n ? (err?: RouterError, state?: State) => {\n if (err) {\n errorCallback?.(err);\n } else {\n successCallback?.(state);\n }\n }\n : undefined;\n\n // Perform navigation\n if (done) {\n router.navigate(routeName, stableParams, stableOptions, done);\n } else {\n router.navigate(routeName, stableParams, stableOptions);\n }\n },\n [\n onClick,\n target,\n router,\n routeName,\n stableParams,\n stableOptions,\n successCallback,\n errorCallback,\n ],\n );\n\n // Build className efficiently\n const finalClassName = useMemo(() => {\n if (isActive && activeClassName) {\n return className\n ? `${className} ${activeClassName}`.trim()\n : activeClassName;\n }\n\n return className ?? undefined;\n }, [isActive, className, activeClassName]);\n\n // Filter out previousRoute from props\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { previousRoute, ...restProps } = props;\n\n return (\n <a\n {...restProps}\n href={href}\n className={finalClassName}\n onClick={handleClick}\n data-route={routeName} // For event delegation if needed\n data-active={isActive} // For CSS selectors if needed\n >\n {children}\n </a>\n );\n },\n (prevProps, nextProps) => {\n // Custom comparison for better memoization\n // Check if props that affect rendering have changed\n return (\n prevProps.router === nextProps.router &&\n prevProps.routeName === nextProps.routeName &&\n JSON.stringify(prevProps.routeParams) ===\n JSON.stringify(nextProps.routeParams) &&\n JSON.stringify(prevProps.routeOptions) ===\n JSON.stringify(nextProps.routeOptions) &&\n prevProps.className === nextProps.className &&\n prevProps.activeClassName === nextProps.activeClassName &&\n prevProps.activeStrict === nextProps.activeStrict &&\n prevProps.ignoreQueryParams === nextProps.ignoreQueryParams &&\n prevProps.onClick === nextProps.onClick &&\n prevProps.successCallback === nextProps.successCallback &&\n prevProps.errorCallback === nextProps.errorCallback &&\n prevProps.target === nextProps.target &&\n prevProps.children === nextProps.children\n );\n },\n);\n\nBaseLink.displayName = \"BaseLink\";\n","// packages/react/modules/components/Link.tsx\n\nimport { useRouter } from \"@real-router/react\";\n\nimport { BaseLink } from \"./BaseLink\";\n\nimport type { BaseLinkProps } from \"./interfaces\";\nimport type { FC } from \"react\";\n\nexport const Link: FC<Omit<BaseLinkProps, \"router\">> = (props) => {\n const router = useRouter();\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { route, previousRoute, routeOptions, ...linkProps } = props;\n\n return <BaseLink router={router} {...linkProps} />;\n};\n","// packages/react/modules/components/ConnectedLink.tsx\n\nimport { useRoute } from \"@real-router/react\";\n\nimport { BaseLink } from \"./BaseLink\";\n\nimport type { BaseLinkProps } from \"./interfaces\";\nimport type { FC } from \"react\";\n\nexport const ConnectedLink: FC<\n Omit<BaseLinkProps, \"router\" | \"route\" | \"previousRoute\">\n> = (props) => {\n const { router, route } = useRoute();\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { routeOptions, ...linkProps } = props;\n\n return <BaseLink router={router} route={route} {...linkProps} />;\n};\n","// packages/react/modules/hooks/useRouteNode.tsx\n\nimport { useCallback, useMemo } from \"react\";\n\nimport { useRouter } from \"@real-router/react\";\n\nimport { useRouterSubscription } from \"./useRouterSubscription\";\nimport { getCachedShouldUpdate } from \"../utils\";\n\nimport type { RouteContext, RouteState } from \"../types\";\nimport type { State, SubscribeState } from \"@real-router/core\";\n\n/**\n * Hook that subscribes to a specific route node with optimizations.\n * Provides the current and previous route when the node is affected.\n */\nexport function useRouteNode(nodeName: string): RouteContext {\n // Get router from context with error handling\n const router = useRouter();\n\n // Get cached shouldUpdate function to avoid recreation\n const shouldUpdate = useMemo(\n () => getCachedShouldUpdate(router, nodeName),\n [router, nodeName],\n );\n\n // Stable state factory\n // useRouteNode.tsx\n const stateFactory = useCallback(\n (sub?: SubscribeState): RouteState => {\n const currentRoute = sub?.route ?? router.getState();\n\n // Проверяем, активен ли узел\n if (currentRoute && nodeName !== \"\") {\n // Корневой узел всегда активен\n const isNodeActive =\n currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`);\n\n if (!isNodeActive) {\n return {\n route: undefined,\n previousRoute: sub?.previousRoute,\n };\n }\n }\n\n return {\n route: currentRoute,\n previousRoute: sub?.previousRoute,\n };\n },\n [router, nodeName],\n );\n\n // Subscribe to router with optimization\n const state = useRouterSubscription<RouteState>(\n router,\n stateFactory,\n shouldUpdate as (newRoute: State, prevRoute?: State) => boolean,\n );\n\n // Return memoized context - useMemo ensures stable reference when deps unchanged\n return useMemo(\n (): RouteContext => ({\n router,\n route: state.route,\n previousRoute: state.previousRoute,\n }),\n [router, state.route, state.previousRoute],\n );\n}\n","// packages/react/modules/context.ts\n\nimport { createContext } from \"react\";\n\nimport type { RouteContext as RouteContextType } from \"./types\";\nimport type { Router } from \"@real-router/core\";\n\nexport const RouteContext = createContext<RouteContextType | null>(null);\n\nexport const RouterContext = createContext<Router | null>(null);\n","// packages/react/modules/hooks/useRoute.tsx\n\nimport { useContext } from \"react\";\n\nimport { RouteContext } from \"../context\";\n\nimport type { RouteContext as RouteContextType } from \"../types\";\n\nexport const useRoute = (): RouteContextType => {\n const routeContext = useContext(RouteContext);\n\n if (!routeContext) {\n throw new Error(\"useRoute must be used within a RouteProvider\");\n }\n\n return routeContext;\n};\n","// packages/react/modules/RouterProvider.tsx\n\nimport { useMemo, useSyncExternalStore } from \"react\";\n\nimport { RouteContext, RouterContext } from \"./context\";\n\nimport type { RouteState } from \"./types\";\nimport type { Router } from \"@real-router/core\";\nimport type { FC, ReactNode } from \"react\";\n\nexport interface RouteProviderProps {\n router: Router;\n children: ReactNode;\n}\n\nexport const RouterProvider: FC<RouteProviderProps> = ({\n router,\n children,\n}) => {\n // Local store state to hold route information\n const store = useMemo(() => {\n let currentState: RouteState = {\n route: router.getState(),\n previousRoute: undefined,\n };\n\n // This will be called to return the current state snapshot\n const getSnapshot = () => currentState;\n\n // Subscribe to router updates and notify React when state changes\n const subscribe = (callback: () => void) => {\n const unsubscribe = router.subscribe(({ route, previousRoute }) => {\n currentState = { route, previousRoute };\n callback(); // Notify React to trigger re-render\n });\n\n // Note: router.subscribe() always returns a function, no need to check\n return unsubscribe;\n };\n\n return { getSnapshot, subscribe };\n }, [router]);\n\n // Using useSyncExternalStore to manage subscription and state updates\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot);\n\n return (\n <RouterContext.Provider value={router}>\n <RouteContext.Provider value={{ router, ...state }}>\n {children}\n </RouteContext.Provider>\n </RouterContext.Provider>\n );\n};\n","// packages/react/modules/hooks/useRouter.tsx\n\nimport { useContext } from \"react\";\n\nimport { RouterContext } from \"../context\";\n\nimport type { Router } from \"@real-router/core\";\n\nexport const useRouter = (): Router => {\n const router = useContext(RouterContext);\n\n if (!router) {\n throw new Error(\"useRouter must be used within a RouterProvider\");\n }\n\n return router;\n};\n"]}
@@ -0,0 +1 @@
1
+ {"inputs":{"src/constants.ts":{"bytes":225,"imports":[],"format":"esm"},"src/hooks/useRouterSubscription.tsx":{"bytes":2370,"imports":[{"path":"react","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useStableValue.tsx":{"bytes":976,"imports":[{"path":"react","kind":"import-statement","external":true}],"format":"esm"},"src/utils.ts":{"bytes":1483,"imports":[],"format":"esm"},"src/hooks/useIsActiveRoute.tsx":{"bytes":2679,"imports":[{"path":"@real-router/helpers","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"src/hooks/useRouterSubscription.tsx","kind":"import-statement","original":"./useRouterSubscription"},{"path":"src/hooks/useStableValue.tsx","kind":"import-statement","original":"./useStableValue"},{"path":"src/constants.ts","kind":"import-statement","original":"../constants"},{"path":"src/utils.ts","kind":"import-statement","original":"../utils"}],"format":"esm"},"src/components/BaseLink.tsx":{"bytes":4826,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/constants.ts","kind":"import-statement","original":"../constants"},{"path":"src/hooks/useIsActiveRoute.tsx","kind":"import-statement","original":"../hooks/useIsActiveRoute"},{"path":"src/hooks/useStableValue.tsx","kind":"import-statement","original":"../hooks/useStableValue"},{"path":"src/utils.ts","kind":"import-statement","original":"../utils"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/components/Link.tsx":{"bytes":512,"imports":[{"path":"src/index.ts","kind":"import-statement","original":"@real-router/react"},{"path":"src/components/BaseLink.tsx","kind":"import-statement","original":"./BaseLink"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/components/ConnectedLink.tsx":{"bytes":563,"imports":[{"path":"src/index.ts","kind":"import-statement","original":"@real-router/react"},{"path":"src/components/BaseLink.tsx","kind":"import-statement","original":"./BaseLink"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useRouteNode.tsx":{"bytes":2096,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/index.ts","kind":"import-statement","original":"@real-router/react"},{"path":"src/hooks/useRouterSubscription.tsx","kind":"import-statement","original":"./useRouterSubscription"},{"path":"src/utils.ts","kind":"import-statement","original":"../utils"}],"format":"esm"},"src/context.ts":{"bytes":333,"imports":[{"path":"react","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useRoute.tsx":{"bytes":417,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/context.ts","kind":"import-statement","original":"../context"}],"format":"esm"},"src/RouterProvider.tsx":{"bytes":1596,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/context.ts","kind":"import-statement","original":"./context"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/hooks/useRouter.tsx":{"bytes":378,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/context.ts","kind":"import-statement","original":"../context"}],"format":"esm"},"src/index.ts":{"bytes":529,"imports":[{"path":"src/components/BaseLink.tsx","kind":"import-statement","original":"./components/BaseLink"},{"path":"src/components/Link.tsx","kind":"import-statement","original":"./components/Link"},{"path":"src/components/ConnectedLink.tsx","kind":"import-statement","original":"./components/ConnectedLink"},{"path":"src/hooks/useRouteNode.tsx","kind":"import-statement","original":"./hooks/useRouteNode"},{"path":"src/hooks/useRoute.tsx","kind":"import-statement","original":"./hooks/useRoute"},{"path":"src/RouterProvider.tsx","kind":"import-statement","original":"./RouterProvider"},{"path":"src/context.ts","kind":"import-statement","original":"./context"},{"path":"src/hooks/useRouter.tsx","kind":"import-statement","original":"./hooks/useRouter"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":26879},"dist/esm/index.mjs":{"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"@real-router/helpers","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true}],"exports":["BaseLink","ConnectedLink","Link","RouteContext","RouterContext","RouterProvider","useRoute","useRouteNode","useRouter"],"entryPoint":"src/index.ts","inputs":{"src/components/BaseLink.tsx":{"bytesInOutput":3235},"src/constants.ts":{"bytesInOutput":77},"src/hooks/useIsActiveRoute.tsx":{"bytesInOutput":1723},"src/hooks/useRouterSubscription.tsx":{"bytesInOutput":1386},"src/hooks/useStableValue.tsx":{"bytesInOutput":157},"src/utils.ts":{"bytesInOutput":816},"src/index.ts":{"bytesInOutput":0},"src/components/Link.tsx":{"bytesInOutput":243},"src/components/ConnectedLink.tsx":{"bytesInOutput":247},"src/hooks/useRouteNode.tsx":{"bytesInOutput":1064},"src/hooks/useRoute.tsx":{"bytesInOutput":230},"src/context.ts":{"bytesInOutput":120},"src/RouterProvider.tsx":{"bytesInOutput":893},"src/hooks/useRouter.tsx":{"bytesInOutput":232}},"bytes":11031}}}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@real-router/react",
3
+ "version": "0.1.0",
4
+ "type": "commonjs",
5
+ "description": "React integration for Real-Router",
6
+ "main": "./dist/cjs/index.js",
7
+ "module": "./dist/esm/index.mjs",
8
+ "types": "./dist/esm/index.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "types": {
12
+ "import": "./dist/esm/index.d.mts",
13
+ "require": "./dist/cjs/index.d.ts"
14
+ },
15
+ "import": "./dist/esm/index.mjs",
16
+ "require": "./dist/cjs/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "homepage": "https://github.com/greydragon888/real-router",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/greydragon888/real-router.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/greydragon888/real-router/issues"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "test": "vitest",
33
+ "type-check": "tsc --noEmit",
34
+ "lint": "eslint --cache --ext .ts,.tsx src/ tests/ --fix",
35
+ "lint:package": "publint",
36
+ "lint:types": "attw --pack ."
37
+ },
38
+ "keywords": [
39
+ "router",
40
+ "html5",
41
+ "history",
42
+ "tree",
43
+ "react",
44
+ "hooks",
45
+ "components",
46
+ "functional",
47
+ "real-router"
48
+ ],
49
+ "author": {
50
+ "name": "Oleg Ivanov",
51
+ "email": "greydragon888@gmail.com",
52
+ "url": "https://github.com/greydragon888"
53
+ },
54
+ "license": "MIT",
55
+ "sideEffects": false,
56
+ "dependencies": {
57
+ "@real-router/core": "workspace:^",
58
+ "@real-router/helpers": "workspace:^"
59
+ },
60
+ "devDependencies": {
61
+ "@testing-library/dom": "10.4.1",
62
+ "@testing-library/jest-dom": "6.9.1",
63
+ "@testing-library/react": "16.3.2",
64
+ "@testing-library/user-event": "14.6.1",
65
+ "vitest-react-profiler": "1.12.0"
66
+ },
67
+ "peerDependencies": {
68
+ "@types/react": ">=18.0.0",
69
+ "@types/react-dom": ">=18.0.0",
70
+ "react": ">=18.0.0",
71
+ "@real-router/browser-plugin": "workspace:^"
72
+ }
73
+ }