@real-router/react 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -2
- package/dist/cjs/index.d.ts +12 -77
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/legacy.d.ts +74 -3
- package/dist/cjs/legacy.js +1 -1
- package/dist/cjs/legacy.js.map +1 -1
- package/dist/cjs/metafile-cjs.json +1 -1
- package/dist/esm/chunk-35HV34W6.mjs +1 -0
- package/dist/esm/chunk-35HV34W6.mjs.map +1 -0
- package/dist/esm/index.d.mts +12 -77
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/legacy.d.mts +74 -3
- package/dist/esm/legacy.mjs +1 -1
- package/dist/esm/metafile-esm.json +1 -1
- package/package.json +3 -3
- package/src/components/modern/RouteView/RouteView.tsx +47 -0
- package/src/components/modern/RouteView/components.tsx +13 -0
- package/src/components/modern/RouteView/helpers.tsx +119 -0
- package/src/components/modern/RouteView/index.ts +7 -0
- package/src/components/modern/RouteView/types.ts +24 -0
- package/src/index.ts +2 -2
- package/src/legacy.ts +0 -8
- package/dist/esm/chunk-A47RNKTS.mjs +0 -1
- package/dist/esm/chunk-A47RNKTS.mjs.map +0 -1
- package/src/components/RouteView.tsx +0 -139
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { UNKNOWN_ROUTE } from "@real-router/core";
|
|
2
|
+
import { startsWithSegment } from "@real-router/route-utils";
|
|
3
|
+
import { Activity, Children, Fragment, isValidElement } from "react";
|
|
4
|
+
|
|
5
|
+
import { Match, NotFound } from "./components";
|
|
6
|
+
|
|
7
|
+
import type { MatchProps, NotFoundProps } from "./types";
|
|
8
|
+
import type { ReactElement, ReactNode } from "react";
|
|
9
|
+
|
|
10
|
+
function isSegmentMatch(
|
|
11
|
+
routeName: string,
|
|
12
|
+
fullSegmentName: string,
|
|
13
|
+
exact: boolean,
|
|
14
|
+
): boolean {
|
|
15
|
+
if (exact) {
|
|
16
|
+
return routeName === fullSegmentName;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return startsWithSegment(routeName, fullSegmentName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function collectElements(
|
|
23
|
+
children: ReactNode,
|
|
24
|
+
result: ReactElement[],
|
|
25
|
+
): void {
|
|
26
|
+
// eslint-disable-next-line @eslint-react/no-children-to-array
|
|
27
|
+
for (const child of Children.toArray(children)) {
|
|
28
|
+
if (!isValidElement(child)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (child.type === Match || child.type === NotFound) {
|
|
33
|
+
result.push(child);
|
|
34
|
+
} else {
|
|
35
|
+
collectElements(
|
|
36
|
+
(child.props as { readonly children: ReactNode }).children,
|
|
37
|
+
result,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function renderMatchElement(
|
|
44
|
+
matchChildren: ReactNode,
|
|
45
|
+
fullSegmentName: string,
|
|
46
|
+
keepAlive: boolean,
|
|
47
|
+
mode: "visible" | "hidden",
|
|
48
|
+
): ReactElement {
|
|
49
|
+
if (keepAlive) {
|
|
50
|
+
return (
|
|
51
|
+
<Activity mode={mode} key={fullSegmentName}>
|
|
52
|
+
{matchChildren}
|
|
53
|
+
</Activity>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return <Fragment key={fullSegmentName}>{matchChildren}</Fragment>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildRenderList(
|
|
61
|
+
elements: ReactElement[],
|
|
62
|
+
routeName: string,
|
|
63
|
+
nodeName: string,
|
|
64
|
+
hasBeenActivated: Set<string>,
|
|
65
|
+
): { rendered: ReactElement[]; activeMatchFound: boolean } {
|
|
66
|
+
let notFoundChildren: ReactNode = null;
|
|
67
|
+
let activeMatchFound = false;
|
|
68
|
+
const rendered: ReactElement[] = [];
|
|
69
|
+
|
|
70
|
+
for (const child of elements) {
|
|
71
|
+
if (child.type === NotFound) {
|
|
72
|
+
notFoundChildren = (child.props as NotFoundProps).children;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const {
|
|
77
|
+
segment,
|
|
78
|
+
exact = false,
|
|
79
|
+
keepAlive = false,
|
|
80
|
+
} = child.props as MatchProps;
|
|
81
|
+
const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
|
|
82
|
+
const isActive =
|
|
83
|
+
!activeMatchFound && isSegmentMatch(routeName, fullSegmentName, exact);
|
|
84
|
+
|
|
85
|
+
if (isActive) {
|
|
86
|
+
activeMatchFound = true;
|
|
87
|
+
hasBeenActivated.add(fullSegmentName);
|
|
88
|
+
rendered.push(
|
|
89
|
+
renderMatchElement(
|
|
90
|
+
(child.props as MatchProps).children,
|
|
91
|
+
fullSegmentName,
|
|
92
|
+
keepAlive,
|
|
93
|
+
"visible",
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
} else if (keepAlive && hasBeenActivated.has(fullSegmentName)) {
|
|
97
|
+
rendered.push(
|
|
98
|
+
renderMatchElement(
|
|
99
|
+
(child.props as MatchProps).children,
|
|
100
|
+
fullSegmentName,
|
|
101
|
+
keepAlive,
|
|
102
|
+
"hidden",
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
!activeMatchFound &&
|
|
110
|
+
routeName === UNKNOWN_ROUTE &&
|
|
111
|
+
notFoundChildren !== null
|
|
112
|
+
) {
|
|
113
|
+
rendered.push(
|
|
114
|
+
<Fragment key="__route-view-not-found__">{notFoundChildren}</Fragment>,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { rendered, activeMatchFound };
|
|
119
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface RouteViewProps {
|
|
4
|
+
/** Route tree node name to subscribe to. "" for root. */
|
|
5
|
+
readonly nodeName: string;
|
|
6
|
+
/** <RouteView.Match> and <RouteView.NotFound> elements. */
|
|
7
|
+
readonly children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MatchProps {
|
|
11
|
+
/** Route segment to match against. */
|
|
12
|
+
readonly segment: string;
|
|
13
|
+
/** Exact match only (no descendants). Defaults to false. */
|
|
14
|
+
readonly exact?: boolean;
|
|
15
|
+
/** Preserve component state when deactivated (React Activity). Defaults to false. */
|
|
16
|
+
readonly keepAlive?: boolean;
|
|
17
|
+
/** Content to render when matched. */
|
|
18
|
+
readonly children: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface NotFoundProps {
|
|
22
|
+
/** Content to render on UNKNOWN_ROUTE. */
|
|
23
|
+
readonly children: ReactNode;
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Components
|
|
4
4
|
export { Link } from "./components/Link";
|
|
5
5
|
|
|
6
|
-
export { RouteView } from "./components/RouteView";
|
|
6
|
+
export { RouteView } from "./components/modern/RouteView";
|
|
7
7
|
|
|
8
8
|
// Hooks
|
|
9
9
|
export { useRouteNode } from "./hooks/useRouteNode";
|
|
@@ -32,7 +32,7 @@ export type {
|
|
|
32
32
|
RouteViewProps,
|
|
33
33
|
RouteViewMatchProps,
|
|
34
34
|
RouteViewNotFoundProps,
|
|
35
|
-
} from "./components/RouteView";
|
|
35
|
+
} from "./components/modern/RouteView";
|
|
36
36
|
|
|
37
37
|
export type { Navigator } from "@real-router/core";
|
|
38
38
|
|
package/src/legacy.ts
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
// Components
|
|
4
4
|
export { Link } from "./components/Link";
|
|
5
5
|
|
|
6
|
-
export { RouteView } from "./components/RouteView";
|
|
7
|
-
|
|
8
6
|
// Hooks
|
|
9
7
|
export { useRouteNode } from "./hooks/useRouteNode";
|
|
10
8
|
|
|
@@ -28,12 +26,6 @@ export { RouterContext, RouteContext, NavigatorContext } from "./context";
|
|
|
28
26
|
// Types
|
|
29
27
|
export type { LinkProps } from "./types";
|
|
30
28
|
|
|
31
|
-
export type {
|
|
32
|
-
RouteViewProps,
|
|
33
|
-
RouteViewMatchProps,
|
|
34
|
-
RouteViewNotFoundProps,
|
|
35
|
-
} from "./components/RouteView";
|
|
36
|
-
|
|
37
29
|
export type { Navigator } from "@real-router/core";
|
|
38
30
|
|
|
39
31
|
export type { RouterTransitionSnapshot } from "@real-router/sources";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createContext as e,memo as t,useMemo as r,useCallback as n,use as o,useSyncExternalStore as u,Children as i,isValidElement as a}from"react";import{createActiveRouteSource as s,createRouteNodeSource as c,createTransitionSource as l,createRouteSource as m}from"@real-router/sources";import{jsx as d,Fragment as h}from"react/jsx-runtime";import{UNKNOWN_ROUTE as f,getNavigator as p,getPluginApi as v}from"@real-router/core";import{startsWithSegment as g,getRouteUtils as y}from"@real-router/route-utils";var N=e(null),b=e(null),S=e(null),w=()=>{const e=o(b);if(!e)throw new Error("useRouter must be used within a RouterProvider");return e};function P(e){const t=JSON.stringify(e);return r(()=>e,[t])}function R(e,t,n=!1,o=!0){const i=w(),a=P(t),c=r(()=>s(i,e,a,{strict:n,ignoreQueryParams:o}),[i,e,a,n,o]);return u(c.subscribe,c.getSnapshot,c.getSnapshot)}var O=Object.freeze({}),C=Object.freeze({}),k=t(({routeName:e,routeParams:t=O,routeOptions:o=C,className:u,activeClassName:i="active",activeStrict:a=!1,ignoreQueryParams:s=!0,onClick:c,target:l,children:m,...h})=>{const f=w(),p=P(t),v=P(o),g=R(e,p,a,s),y=r(()=>"function"==typeof f.buildUrl?f.buildUrl(e,p):f.buildPath(e,p),[f,e,p]),N=n(t=>{c&&(c(t),t.defaultPrevented)||function(e){return!(0!==e.button||e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}(t)&&"_blank"!==l&&(t.preventDefault(),f.navigate(e,p,v).catch(()=>{}))},[c,l,f,e,p,v]),b=r(()=>g&&i?u?`${u} ${i}`.trim():i:u??void 0,[g,u,i]);return d("a",{...h,href:y,className:b,onClick:N,children:m})},function(e,t){return e.routeName===t.routeName&&e.className===t.className&&e.activeClassName===t.activeClassName&&e.activeStrict===t.activeStrict&&e.ignoreQueryParams===t.ignoreQueryParams&&e.onClick===t.onClick&&e.target===t.target&&e.style===t.style&&e.children===t.children&&JSON.stringify(e.routeParams)===JSON.stringify(t.routeParams)&&JSON.stringify(e.routeOptions)===JSON.stringify(t.routeOptions)});function F(e){const t=w(),n=r(()=>c(t,e),[t,e]),{route:o,previousRoute:i}=u(n.subscribe,n.getSnapshot,n.getSnapshot),a=r(()=>p(t),[t]);return r(()=>({navigator:a,route:o,previousRoute:i}),[a,o,i])}function J(e,t,r){return r?e===t:g(e,t)}function j(e,t){for(const r of i.toArray(e))a(r)&&(r.type===$||r.type===x?t.push(r):j(r.props.children,t))}function K(e,t,r){const n=[];j(r,n);let o=null;for(const r of n){if(r.type===x){o=r.props.children;continue}const{segment:n,exact:u=!1}=r.props;if(J(e,t?`${t}.${n}`:n,u))return{matched:r.props.children,notFoundChildren:o}}return{matched:null,notFoundChildren:o}}function Q({nodeName:e,children:t}){const{route:r}=F(e);if(!r)return null;const{matched:n,notFoundChildren:o}=K(r.name,e,t);return null!==n?d(h,{children:n}):r.name===f&&null!==o?d(h,{children:o}):null}function $(e){return null}function x(e){return null}k.displayName="Link",Q.displayName="RouteView",$.displayName="RouteView.Match",x.displayName="RouteView.NotFound";var E=Object.assign(Q,{Match:$,NotFound:x}),V=()=>{const e=o(N);if(!e)throw new Error("useRoute must be used within a RouteProvider");return e},z=()=>{const e=o(S);if(!e)throw new Error("useNavigator must be used within a RouterProvider");return e},M=()=>{const e=w();return y(v(e).getTree())};function U(){const e=w(),t=r(()=>l(e),[e]);return u(t.subscribe,t.getSnapshot,t.getSnapshot)}var A=({router:e,children:t})=>{const n=r(()=>p(e),[e]),o=r(()=>m(e),[e]),{route:i,previousRoute:a}=u(o.subscribe,o.getSnapshot,o.getSnapshot),s=r(()=>({navigator:n,route:i,previousRoute:a}),[n,i,a]);return d(b,{value:e,children:d(S,{value:n,children:d(N,{value:s,children:t})})})};export{k as Link,S as NavigatorContext,N as RouteContext,E as RouteView,b as RouterContext,A as RouterProvider,R as useIsActiveRoute,z as useNavigator,V as useRoute,F as useRouteNode,M as useRouteUtils,w as useRouter,U as useRouterTransition};//# sourceMappingURL=chunk-A47RNKTS.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/context.ts","../../src/hooks/useRouter.tsx","../../src/hooks/useStableValue.tsx","../../src/hooks/useIsActiveRoute.tsx","../../src/constants.ts","../../src/utils.ts","../../src/components/Link.tsx","../../src/hooks/useRouteNode.tsx","../../src/components/RouteView.tsx","../../src/hooks/useRoute.tsx","../../src/hooks/useNavigator.tsx","../../src/hooks/useRouteUtils.tsx","../../src/hooks/useRouterTransition.tsx","../../src/RouterProvider.tsx"],"names":["useMemo","useSyncExternalStore","jsx","use","getNavigator"],"mappings":";AAOO,IAAM,YAAA,GAAe,cAAuC,IAAI;AAEhE,IAAM,aAAA,GAAgB,cAA6B,IAAI;AAEvD,IAAM,gBAAA,GAAmB,cAAgC,IAAI;ACH7D,IAAM,YAAY,MAAc;AACrC,EAAA,MAAM,MAAA,GAAS,IAAI,aAAa,CAAA;AAEhC,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,EAClE;AAEA,EAAA,OAAO,MAAA;AACT;ACMO,SAAS,eAAkB,KAAA,EAAa;AAC7C,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AAIvC,EAAA,OAAO,OAAA,CAAQ,MAAM,KAAA,EAAO,CAAC,UAAU,CAAC,CAAA;AAC1C;;;ACpBO,SAAS,iBACd,SAAA,EACA,MAAA,EACA,MAAA,GAAS,KAAA,EACT,oBAAoB,IAAA,EACX;AACT,EAAA,MAAM,SAAS,SAAA,EAAU;AAKzB,EAAA,MAAM,YAAA,GAAe,eAAe,MAAM,CAAA;AAE1C,EAAA,MAAM,KAAA,GAAQA,OAAAA;AAAA,IACZ,MACE,uBAAA,CAAwB,MAAA,EAAQ,SAAA,EAAW,YAAA,EAAc;AAAA,MACvD,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,IACH,CAAC,MAAA,EAAQ,SAAA,EAAW,YAAA,EAAc,QAAQ,iBAAiB;AAAA,GAC7D;AAEA,EAAA,OAAO,oBAAA;AAAA,IACL,KAAA,CAAM,SAAA;AAAA,IACN,KAAA,CAAM,WAAA;AAAA,IACN,KAAA,CAAM;AAAA;AAAA,GACR;AACF;;;AC9BO,IAAM,YAAA,GAAe,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA;AAKrC,IAAM,aAAA,GAAgB,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA;;;ACLtC,SAAS,eAAe,GAAA,EAA0B;AACvD,EAAA,OACE,IAAI,MAAA,KAAW,CAAA;AAAA,EACf,CAAC,GAAA,CAAI,OAAA,IACL,CAAC,GAAA,CAAI,UACL,CAAC,GAAA,CAAI,OAAA,IACL,CAAC,GAAA,CAAI,QAAA;AAET;ACFA,SAAS,iBAAA,CACP,MACA,IAAA,EACS;AACT,EAAA,OACE,IAAA,CAAK,SAAA,KAAc,IAAA,CAAK,SAAA,IACxB,IAAA,CAAK,cAAc,IAAA,CAAK,SAAA,IACxB,IAAA,CAAK,eAAA,KAAoB,IAAA,CAAK,eAAA,IAC9B,KAAK,YAAA,KAAiB,IAAA,CAAK,YAAA,IAC3B,IAAA,CAAK,iBAAA,KAAsB,IAAA,CAAK,iBAAA,IAChC,IAAA,CAAK,OAAA,KAAY,IAAA,CAAK,OAAA,IACtB,IAAA,CAAK,MAAA,KAAW,IAAA,CAAK,UACrB,IAAA,CAAK,KAAA,KAAU,IAAA,CAAK,KAAA,IACpB,IAAA,CAAK,QAAA,KAAa,KAAK,QAAA,IACvB,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,WAAW,CAAA,KAAM,KAAK,SAAA,CAAU,IAAA,CAAK,WAAW,CAAA,IACpE,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,YAAY,CAAA,KAAM,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,YAAY,CAAA;AAE1E;AAEO,IAAM,IAAA,GAAsB,IAAA;AAAA,EACjC,CAAC;AAAA,IACC,SAAA;AAAA,IACA,WAAA,GAAc,YAAA;AAAA,IACd,YAAA,GAAe,aAAA;AAAA,IACf,SAAA;AAAA,IACA,eAAA,GAAkB,QAAA;AAAA,IAClB,YAAA,GAAe,KAAA;AAAA,IACf,iBAAA,GAAoB,IAAA;AAAA,IACpB,OAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,GAAG;AAAA,GACL,KAAM;AACJ,IAAA,MAAM,SAAS,SAAA,EAAU;AAEzB,IAAA,MAAM,YAAA,GAAe,eAAe,WAAW,CAAA;AAC/C,IAAA,MAAM,aAAA,GAAgB,eAAe,YAAY,CAAA;AAEjD,IAAA,MAAM,QAAA,GAAW,gBAAA;AAAA,MACf,SAAA;AAAA,MACA,YAAA;AAAA,MACA,YAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,MAAM,IAAA,GAAOA,QAAQ,MAAM;AACzB,MAAA,IAAI,OAAO,MAAA,CAAO,QAAA,KAAa,UAAA,EAAY;AACzC,QAAA,OAAO,MAAA,CAAO,QAAA,CAAS,SAAA,EAAW,YAAY,CAAA;AAAA,MAChD;AAEA,MAAA,OAAO,MAAA,CAAO,SAAA,CAAU,SAAA,EAAW,YAAY,CAAA;AAAA,IACjD,CAAA,EAAG,CAAC,MAAA,EAAQ,SAAA,EAAW,YAAY,CAAC,CAAA;AAEpC,IAAA,MAAM,WAAA,GAAc,WAAA;AAAA,MAClB,CAAC,GAAA,KAAuC;AACtC,QAAA,IAAI,OAAA,EAAS;AACX,UAAA,OAAA,CAAQ,GAAG,CAAA;AAEX,UAAA,IAAI,IAAI,gBAAA,EAAkB;AACxB,YAAA;AAAA,UACF;AAAA,QACF;AAEA,QAAA,IAAI,CAAC,cAAA,CAAe,GAAG,CAAA,IAAK,WAAW,QAAA,EAAU;AAC/C,UAAA;AAAA,QACF;AAEA,QAAA,GAAA,CAAI,cAAA,EAAe;AACnB,QAAA,MAAA,CAAO,SAAS,SAAA,EAAW,YAAA,EAAc,aAAa,CAAA,CAAE,MAAM,MAAM;AAAA,QAAC,CAAC,CAAA;AAAA,MACxE,CAAA;AAAA,MACA,CAAC,OAAA,EAAS,MAAA,EAAQ,MAAA,EAAQ,SAAA,EAAW,cAAc,aAAa;AAAA,KAClE;AAEA,IAAA,MAAM,cAAA,GAAiBA,QAAQ,MAAM;AACnC,MAAA,IAAI,YAAY,eAAA,EAAiB;AAC/B,QAAA,OAAO,YACH,CAAA,EAAG,SAAS,IAAI,eAAe,CAAA,CAAA,CAAG,MAAK,GACvC,eAAA;AAAA,MACN;AAEA,MAAA,OAAO,SAAA,IAAa,MAAA;AAAA,IACtB,CAAA,EAAG,CAAC,QAAA,EAAU,SAAA,EAAW,eAAe,CAAC,CAAA;AAEzC,IAAA,uBACE,GAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACE,GAAG,KAAA;AAAA,QACJ,IAAA;AAAA,QACA,SAAA,EAAW,cAAA;AAAA,QACX,OAAA,EAAS,WAAA;AAAA,QAER;AAAA;AAAA,KACH;AAAA,EAEJ,CAAA;AAAA,EACA;AACF;AAEA,IAAA,CAAK,WAAA,GAAc,MAAA;ACpGZ,SAAS,aAAa,QAAA,EAAgC;AAC3D,EAAA,MAAM,SAAS,SAAA,EAAU;AAEzB,EAAA,MAAM,KAAA,GAAQA,OAAAA;AAAA,IACZ,MAAM,qBAAA,CAAsB,MAAA,EAAQ,QAAQ,CAAA;AAAA,IAC5C,CAAC,QAAQ,QAAQ;AAAA,GACnB;AAEA,EAAA,MAAM,EAAE,KAAA,EAAO,aAAA,EAAc,GAAIC,oBAAAA;AAAA,IAC/B,KAAA,CAAM,SAAA;AAAA,IACN,KAAA,CAAM,WAAA;AAAA,IACN,KAAA,CAAM;AAAA;AAAA,GACR;AAEA,EAAA,MAAM,SAAA,GAAYD,QAAQ,MAAM,YAAA,CAAa,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAE9D,EAAA,OAAOA,OAAAA;AAAA,IACL,OAAqB,EAAE,SAAA,EAAW,KAAA,EAAO,aAAA,EAAc,CAAA;AAAA,IACvD,CAAC,SAAA,EAAW,KAAA,EAAO,aAAa;AAAA,GAClC;AACF;ACCA,SAAS,cAAA,CACP,SAAA,EACA,eAAA,EACA,KAAA,EACS;AACT,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,OAAO,SAAA,KAAc,eAAA;AAAA,EACvB;AAEA,EAAA,OAAO,iBAAA,CAAkB,WAAW,eAAe,CAAA;AACrD;AAEA,SAAS,eAAA,CAAgB,UAAqB,MAAA,EAA8B;AAI1E,EAAA,KAAA,MAAW,KAAA,IAAS,QAAA,CAAS,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC9C,IAAA,IAAI,CAAC,cAAA,CAAe,KAAK,CAAA,EAAG;AAC1B,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,KAAA,CAAM,IAAA,KAAS,KAAA,IAAS,KAAA,CAAM,SAAS,QAAA,EAAU;AACnD,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,IACnB,CAAA,MAAO;AACL,MAAA,eAAA;AAAA,QACG,MAAM,KAAA,CAA2C,QAAA;AAAA,QAClD;AAAA,OACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,SAAA,CACP,SAAA,EACA,QAAA,EACA,QAAA,EAC4D;AAC5D,EAAA,MAAM,WAA2B,EAAC;AAElC,EAAA,eAAA,CAAgB,UAAU,QAAQ,CAAA;AAClC,EAAA,IAAI,gBAAA,GAA8B,IAAA;AAElC,EAAA,KAAA,MAAW,SAAS,QAAA,EAAU;AAC5B,IAAA,IAAI,KAAA,CAAM,SAAS,QAAA,EAAU;AAC3B,MAAA,gBAAA,GAAoB,MAAM,KAAA,CAAwB,QAAA;AAClD,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,OAAA,EAAS,KAAA,GAAQ,KAAA,KAAU,KAAA,CAAM,KAAA;AACzC,IAAA,MAAM,kBAAkB,QAAA,GAAW,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,GAAK,OAAA;AAE9D,IAAA,IAAI,cAAA,CAAe,SAAA,EAAW,eAAA,EAAiB,KAAK,CAAA,EAAG;AACrD,MAAA,OAAO;AAAA,QACL,OAAA,EAAU,MAAM,KAAA,CAAqB,QAAA;AAAA,QACrC;AAAA,OACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,gBAAA,EAAiB;AAC3C;AAEA,SAAS,aAAA,CAAc;AAAA,EACrB,QAAA;AAAA,EACA;AACF,CAAA,EAAwC;AACtC,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,YAAA,CAAa,QAAQ,CAAA;AAEvC,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,EAAE,OAAA,EAAS,gBAAA,EAAiB,GAAI,SAAA;AAAA,IACpC,KAAA,CAAM,IAAA;AAAA,IACN,QAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,IAAI,YAAY,IAAA,EAAM;AACpB,IAAA,uBAAOE,GAAAA,CAAA,QAAA,EAAA,EAAG,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,EACpB;AAEA,EAAA,IAAI,KAAA,CAAM,IAAA,KAAS,aAAA,IAAiB,gBAAA,KAAqB,IAAA,EAAM;AAC7D,IAAA,uBAAOA,GAAAA,CAAA,QAAA,EAAA,EAAG,QAAA,EAAA,gBAAA,EAAiB,CAAA;AAAA,EAC7B;AAEA,EAAA,OAAO,IAAA;AACT;AAEA,aAAA,CAAc,WAAA,GAAc,WAAA;AAE5B,SAAS,MAAM,MAAA,EAA0B;AACvC,EAAA,OAAO,IAAA;AACT;AAEA,KAAA,CAAM,WAAA,GAAc,iBAAA;AAEpB,SAAS,SAAS,MAAA,EAA6B;AAC7C,EAAA,OAAO,IAAA;AACT;AAEA,QAAA,CAAS,WAAA,GAAc,oBAAA;AAEhB,IAAM,YAAY,MAAA,CAAO,MAAA,CAAO,eAAe,EAAE,KAAA,EAAO,UAAU;AC5HlE,IAAM,WAAW,MAAwB;AAC9C,EAAA,MAAM,YAAA,GAAeC,IAAI,YAAY,CAAA;AAErC,EAAA,IAAI,CAAC,YAAA,EAAc;AACjB,IAAA,MAAM,IAAI,MAAM,8CAA8C,CAAA;AAAA,EAChE;AAEA,EAAA,OAAO,YAAA;AACT;ACRO,IAAM,eAAe,MAAiB;AAC3C,EAAA,MAAM,SAAA,GAAYA,IAAI,gBAAgB,CAAA;AAEtC,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,EACrE;AAEA,EAAA,OAAO,SAAA;AACT;ACeO,IAAM,gBAAgB,MAAkB;AAC7C,EAAA,MAAM,SAAS,SAAA,EAAU;AAEzB,EAAA,OAAO,aAAA,CAAc,YAAA,CAAa,MAAM,CAAA,CAAE,SAAS,CAAA;AACrD;AC5BO,SAAS,mBAAA,GAAgD;AAC9D,EAAA,MAAM,SAAS,SAAA,EAAU;AAEzB,EAAA,MAAM,KAAA,GAAQH,QAAQ,MAAM,sBAAA,CAAuB,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEpE,EAAA,OAAOC,oBAAAA;AAAA,IACL,KAAA,CAAM,SAAA;AAAA,IACN,KAAA,CAAM,WAAA;AAAA,IACN,KAAA,CAAM;AAAA,GACR;AACF;ACHO,IAAM,iBAAyC,CAAC;AAAA,EACrD,MAAA;AAAA,EACA;AACF,CAAA,KAAM;AACJ,EAAA,MAAM,SAAA,GAAYD,QAAQ,MAAMI,YAAAA,CAAa,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAK9D,EAAA,MAAM,KAAA,GAAQJ,QAAQ,MAAM,iBAAA,CAAkB,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAC/D,EAAA,MAAM,EAAE,KAAA,EAAO,aAAA,EAAc,GAAIC,oBAAAA;AAAA,IAC/B,KAAA,CAAM,SAAA;AAAA,IACN,KAAA,CAAM,WAAA;AAAA,IACN,KAAA,CAAM;AAAA;AAAA,GACR;AAEA,EAAA,MAAM,iBAAA,GAAoBD,OAAAA;AAAA,IACxB,OAAO,EAAE,SAAA,EAAW,KAAA,EAAO,aAAA,EAAc,CAAA;AAAA,IACzC,CAAC,SAAA,EAAW,KAAA,EAAO,aAAa;AAAA,GAClC;AAEA,EAAA,uBACEE,GAAAA,CAAC,aAAA,EAAA,EAAc,KAAA,EAAO,MAAA,EACpB,0BAAAA,GAAAA,CAAC,gBAAA,EAAA,EAAiB,KAAA,EAAO,SAAA,EACvB,0BAAAA,GAAAA,CAAC,YAAA,EAAA,EAAa,OAAO,iBAAA,EAAoB,QAAA,EAAS,GACpD,CAAA,EACF,CAAA;AAEJ","file":"chunk-A47RNKTS.mjs","sourcesContent":["// packages/react/modules/context.ts\n\nimport { createContext } from \"react\";\n\nimport type { RouteContext as RouteContextType } from \"./types\";\nimport type { Router, Navigator } from \"@real-router/core\";\n\nexport const RouteContext = createContext<RouteContextType | null>(null);\n\nexport const RouterContext = createContext<Router | null>(null);\n\nexport const NavigatorContext = createContext<Navigator | null>(null);\n","// packages/react/modules/hooks/useRouter.tsx\n\nimport { use } from \"react\";\n\nimport { RouterContext } from \"../context\";\n\nimport type { Router } from \"@real-router/core\";\n\nexport const useRouter = (): Router => {\n const router = use(RouterContext);\n\n if (!router) {\n throw new Error(\"useRouter must be used within a RouterProvider\");\n }\n\n return router;\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","import { createActiveRouteSource } from \"@real-router/sources\";\nimport { useMemo, useSyncExternalStore } from \"react\";\n\nimport { useRouter } from \"./useRouter\";\nimport { useStableValue } from \"./useStableValue\";\n\nimport type { Params } from \"@real-router/core\";\n\nexport function useIsActiveRoute(\n routeName: string,\n params?: Params,\n strict = false,\n ignoreQueryParams = true,\n): boolean {\n const router = useRouter();\n\n // useStableValue: JSON.stringify memoization of params object.\n // Without it, every render with a new params reference (e.g.,\n // <Link routeParams={{ id: '123' }} />) would recreate the store.\n const stableParams = useStableValue(params);\n\n const store = useMemo(\n () =>\n createActiveRouteSource(router, routeName, stableParams, {\n strict,\n ignoreQueryParams,\n }),\n [router, routeName, stableParams, strict, ignoreQueryParams],\n );\n\n return useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getSnapshot, // SSR: router returns same state on server and client\n );\n}\n","// 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","import type { MouseEvent } from \"react\";\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","import { memo, useCallback, useMemo } from \"react\";\n\nimport { EMPTY_PARAMS, EMPTY_OPTIONS } from \"../constants\";\nimport { useIsActiveRoute } from \"../hooks/useIsActiveRoute\";\nimport { useRouter } from \"../hooks/useRouter\";\nimport { useStableValue } from \"../hooks/useStableValue\";\nimport { shouldNavigate } from \"../utils\";\n\nimport type { LinkProps } from \"../types\";\nimport type { FC, MouseEvent } from \"react\";\n\nfunction areLinkPropsEqual(\n prev: Readonly<LinkProps>,\n next: Readonly<LinkProps>,\n): boolean {\n return (\n prev.routeName === next.routeName &&\n prev.className === next.className &&\n prev.activeClassName === next.activeClassName &&\n prev.activeStrict === next.activeStrict &&\n prev.ignoreQueryParams === next.ignoreQueryParams &&\n prev.onClick === next.onClick &&\n prev.target === next.target &&\n prev.style === next.style &&\n prev.children === next.children &&\n JSON.stringify(prev.routeParams) === JSON.stringify(next.routeParams) &&\n JSON.stringify(prev.routeOptions) === JSON.stringify(next.routeOptions)\n );\n}\n\nexport const Link: FC<LinkProps> = 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 target,\n children,\n ...props\n }) => {\n const router = useRouter();\n\n const stableParams = useStableValue(routeParams);\n const stableOptions = useStableValue(routeOptions);\n\n const isActive = useIsActiveRoute(\n routeName,\n stableParams,\n activeStrict,\n ignoreQueryParams,\n );\n\n const href = useMemo(() => {\n if (typeof router.buildUrl === \"function\") {\n return router.buildUrl(routeName, stableParams);\n }\n\n return router.buildPath(routeName, stableParams);\n }, [router, routeName, stableParams]);\n\n const handleClick = useCallback(\n (evt: MouseEvent<HTMLAnchorElement>) => {\n if (onClick) {\n onClick(evt);\n\n if (evt.defaultPrevented) {\n return;\n }\n }\n\n if (!shouldNavigate(evt) || target === \"_blank\") {\n return;\n }\n\n evt.preventDefault();\n router.navigate(routeName, stableParams, stableOptions).catch(() => {});\n },\n [onClick, target, router, routeName, stableParams, stableOptions],\n );\n\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 return (\n <a\n {...props}\n href={href}\n className={finalClassName}\n onClick={handleClick}\n >\n {children}\n </a>\n );\n },\n areLinkPropsEqual,\n);\n\nLink.displayName = \"Link\";\n","import { getNavigator } from \"@real-router/core\";\nimport { createRouteNodeSource } from \"@real-router/sources\";\nimport { useMemo, useSyncExternalStore } from \"react\";\n\nimport { useRouter } from \"./useRouter\";\n\nimport type { RouteContext } from \"../types\";\n\nexport function useRouteNode(nodeName: string): RouteContext {\n const router = useRouter();\n\n const store = useMemo(\n () => createRouteNodeSource(router, nodeName),\n [router, nodeName],\n );\n\n const { route, previousRoute } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getSnapshot, // SSR: router returns same state on server and client\n );\n\n const navigator = useMemo(() => getNavigator(router), [router]);\n\n return useMemo(\n (): RouteContext => ({ navigator, route, previousRoute }),\n [navigator, route, previousRoute],\n );\n}\n","import { UNKNOWN_ROUTE } from \"@real-router/core\";\nimport { startsWithSegment } from \"@real-router/route-utils\";\nimport { Children, isValidElement } from \"react\";\n\nimport { useRouteNode } from \"../hooks/useRouteNode\";\n\nimport type { ReactElement, ReactNode } from \"react\";\n\ninterface RouteViewProps {\n /** Route tree node name to subscribe to. \"\" for root. */\n readonly nodeName: string;\n /** <RouteView.Match> and <RouteView.NotFound> elements. */\n readonly children: ReactNode;\n}\n\ninterface MatchProps {\n /** Route segment to match against. */\n readonly segment: string;\n /** Exact match only (no descendants). Defaults to false. */\n readonly exact?: boolean;\n /** Content to render when matched. */\n readonly children: ReactNode;\n}\n\ninterface NotFoundProps {\n /** Content to render on UNKNOWN_ROUTE. */\n readonly children: ReactNode;\n}\n\nfunction isSegmentMatch(\n routeName: string,\n fullSegmentName: string,\n exact: boolean,\n): boolean {\n if (exact) {\n return routeName === fullSegmentName;\n }\n\n return startsWithSegment(routeName, fullSegmentName);\n}\n\nfunction collectElements(children: ReactNode, result: ReactElement[]): void {\n // Children.toArray flattens arrays but does NOT unwrap Fragments,\n // so we recurse into Fragment children manually.\n // eslint-disable-next-line @eslint-react/no-children-to-array\n for (const child of Children.toArray(children)) {\n if (!isValidElement(child)) {\n continue;\n }\n\n if (child.type === Match || child.type === NotFound) {\n result.push(child);\n } else {\n collectElements(\n (child.props as { readonly children: ReactNode }).children,\n result,\n );\n }\n }\n}\n\nfunction findMatch(\n routeName: string,\n nodeName: string,\n children: ReactNode,\n): { matched: ReactNode | null; notFoundChildren: ReactNode } {\n const elements: ReactElement[] = [];\n\n collectElements(children, elements);\n let notFoundChildren: ReactNode = null;\n\n for (const child of elements) {\n if (child.type === NotFound) {\n notFoundChildren = (child.props as NotFoundProps).children;\n continue;\n }\n\n const { segment, exact = false } = child.props as MatchProps;\n const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;\n\n if (isSegmentMatch(routeName, fullSegmentName, exact)) {\n return {\n matched: (child.props as MatchProps).children,\n notFoundChildren,\n };\n }\n }\n\n return { matched: null, notFoundChildren };\n}\n\nfunction RouteViewRoot({\n nodeName,\n children,\n}: RouteViewProps): ReactElement | null {\n const { route } = useRouteNode(nodeName);\n\n if (!route) {\n return null;\n }\n\n const { matched, notFoundChildren } = findMatch(\n route.name,\n nodeName,\n children,\n );\n\n if (matched !== null) {\n return <>{matched}</>;\n }\n\n if (route.name === UNKNOWN_ROUTE && notFoundChildren !== null) {\n return <>{notFoundChildren}</>;\n }\n\n return null;\n}\n\nRouteViewRoot.displayName = \"RouteView\";\n\nfunction Match(_props: MatchProps): null {\n return null;\n}\n\nMatch.displayName = \"RouteView.Match\";\n\nfunction NotFound(_props: NotFoundProps): null {\n return null;\n}\n\nNotFound.displayName = \"RouteView.NotFound\";\n\nexport const RouteView = Object.assign(RouteViewRoot, { Match, NotFound });\n\nexport type { RouteViewProps };\n\nexport type { MatchProps as RouteViewMatchProps };\n\nexport type { NotFoundProps as RouteViewNotFoundProps };\n","// packages/react/modules/hooks/useRoute.tsx\n\nimport { use } from \"react\";\n\nimport { RouteContext } from \"../context\";\n\nimport type { RouteContext as RouteContextType } from \"../types\";\n\nexport const useRoute = (): RouteContextType => {\n const routeContext = use(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/hooks/useNavigator.tsx\n\nimport { use } from \"react\";\n\nimport { NavigatorContext } from \"../context\";\n\nimport type { Navigator } from \"@real-router/core\";\n\nexport const useNavigator = (): Navigator => {\n const navigator = use(NavigatorContext);\n\n if (!navigator) {\n throw new Error(\"useNavigator must be used within a RouterProvider\");\n }\n\n return navigator;\n};\n","// packages/react/modules/hooks/useRouteUtils.tsx\n\nimport { getPluginApi } from \"@real-router/core\";\nimport { getRouteUtils } from \"@real-router/route-utils\";\n\nimport { useRouter } from \"./useRouter\";\n\nimport type { RouteUtils } from \"@real-router/route-utils\";\n\n/**\n * Returns a pre-computed {@link RouteUtils} instance for the current router.\n *\n * Internally retrieves the route tree via `getPluginApi` and delegates\n * to `getRouteUtils`, which caches instances per tree reference (WeakMap).\n *\n * @returns RouteUtils instance with pre-computed chains and siblings\n *\n * @example\n * ```tsx\n * const utils = useRouteUtils();\n *\n * utils.getChain(\"users.profile\");\n * // → [\"users\", \"users.profile\"]\n *\n * utils.getSiblings(\"users\");\n * // → [\"admin\"]\n *\n * utils.isDescendantOf(\"users.profile\", \"users\");\n * // → true\n * ```\n */\nexport const useRouteUtils = (): RouteUtils => {\n const router = useRouter();\n\n return getRouteUtils(getPluginApi(router).getTree());\n};\n","import { createTransitionSource } from \"@real-router/sources\";\nimport { useMemo, useSyncExternalStore } from \"react\";\n\nimport { useRouter } from \"./useRouter\";\n\nimport type { RouterTransitionSnapshot } from \"@real-router/sources\";\n\nexport function useRouterTransition(): RouterTransitionSnapshot {\n const router = useRouter();\n\n const store = useMemo(() => createTransitionSource(router), [router]);\n\n return useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getSnapshot,\n );\n}\n","import { getNavigator } from \"@real-router/core\";\nimport { createRouteSource } from \"@real-router/sources\";\nimport { useMemo, useSyncExternalStore } from \"react\";\n\nimport { NavigatorContext, RouteContext, RouterContext } from \"./context\";\n\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 const navigator = useMemo(() => getNavigator(router), [router]);\n\n // useSyncExternalStore manages the router subscription lifecycle:\n // subscribe connects to router on first listener, unsubscribes on last.\n // This is Strict Mode safe — no useEffect cleanup needed.\n const store = useMemo(() => createRouteSource(router), [router]);\n const { route, previousRoute } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getSnapshot, // SSR: router returns same state on server and client\n );\n\n const routeContextValue = useMemo(\n () => ({ navigator, route, previousRoute }),\n [navigator, route, previousRoute],\n );\n\n return (\n <RouterContext value={router}>\n <NavigatorContext value={navigator}>\n <RouteContext value={routeContextValue}>{children}</RouteContext>\n </NavigatorContext>\n </RouterContext>\n );\n};\n"]}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { UNKNOWN_ROUTE } from "@real-router/core";
|
|
2
|
-
import { startsWithSegment } from "@real-router/route-utils";
|
|
3
|
-
import { Children, isValidElement } from "react";
|
|
4
|
-
|
|
5
|
-
import { useRouteNode } from "../hooks/useRouteNode";
|
|
6
|
-
|
|
7
|
-
import type { ReactElement, ReactNode } from "react";
|
|
8
|
-
|
|
9
|
-
interface RouteViewProps {
|
|
10
|
-
/** Route tree node name to subscribe to. "" for root. */
|
|
11
|
-
readonly nodeName: string;
|
|
12
|
-
/** <RouteView.Match> and <RouteView.NotFound> elements. */
|
|
13
|
-
readonly children: ReactNode;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface MatchProps {
|
|
17
|
-
/** Route segment to match against. */
|
|
18
|
-
readonly segment: string;
|
|
19
|
-
/** Exact match only (no descendants). Defaults to false. */
|
|
20
|
-
readonly exact?: boolean;
|
|
21
|
-
/** Content to render when matched. */
|
|
22
|
-
readonly children: ReactNode;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface NotFoundProps {
|
|
26
|
-
/** Content to render on UNKNOWN_ROUTE. */
|
|
27
|
-
readonly children: ReactNode;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function isSegmentMatch(
|
|
31
|
-
routeName: string,
|
|
32
|
-
fullSegmentName: string,
|
|
33
|
-
exact: boolean,
|
|
34
|
-
): boolean {
|
|
35
|
-
if (exact) {
|
|
36
|
-
return routeName === fullSegmentName;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return startsWithSegment(routeName, fullSegmentName);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function collectElements(children: ReactNode, result: ReactElement[]): void {
|
|
43
|
-
// Children.toArray flattens arrays but does NOT unwrap Fragments,
|
|
44
|
-
// so we recurse into Fragment children manually.
|
|
45
|
-
// eslint-disable-next-line @eslint-react/no-children-to-array
|
|
46
|
-
for (const child of Children.toArray(children)) {
|
|
47
|
-
if (!isValidElement(child)) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (child.type === Match || child.type === NotFound) {
|
|
52
|
-
result.push(child);
|
|
53
|
-
} else {
|
|
54
|
-
collectElements(
|
|
55
|
-
(child.props as { readonly children: ReactNode }).children,
|
|
56
|
-
result,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function findMatch(
|
|
63
|
-
routeName: string,
|
|
64
|
-
nodeName: string,
|
|
65
|
-
children: ReactNode,
|
|
66
|
-
): { matched: ReactNode | null; notFoundChildren: ReactNode } {
|
|
67
|
-
const elements: ReactElement[] = [];
|
|
68
|
-
|
|
69
|
-
collectElements(children, elements);
|
|
70
|
-
let notFoundChildren: ReactNode = null;
|
|
71
|
-
|
|
72
|
-
for (const child of elements) {
|
|
73
|
-
if (child.type === NotFound) {
|
|
74
|
-
notFoundChildren = (child.props as NotFoundProps).children;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { segment, exact = false } = child.props as MatchProps;
|
|
79
|
-
const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
|
|
80
|
-
|
|
81
|
-
if (isSegmentMatch(routeName, fullSegmentName, exact)) {
|
|
82
|
-
return {
|
|
83
|
-
matched: (child.props as MatchProps).children,
|
|
84
|
-
notFoundChildren,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { matched: null, notFoundChildren };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function RouteViewRoot({
|
|
93
|
-
nodeName,
|
|
94
|
-
children,
|
|
95
|
-
}: RouteViewProps): ReactElement | null {
|
|
96
|
-
const { route } = useRouteNode(nodeName);
|
|
97
|
-
|
|
98
|
-
if (!route) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const { matched, notFoundChildren } = findMatch(
|
|
103
|
-
route.name,
|
|
104
|
-
nodeName,
|
|
105
|
-
children,
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
if (matched !== null) {
|
|
109
|
-
return <>{matched}</>;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (route.name === UNKNOWN_ROUTE && notFoundChildren !== null) {
|
|
113
|
-
return <>{notFoundChildren}</>;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
RouteViewRoot.displayName = "RouteView";
|
|
120
|
-
|
|
121
|
-
function Match(_props: MatchProps): null {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
Match.displayName = "RouteView.Match";
|
|
126
|
-
|
|
127
|
-
function NotFound(_props: NotFoundProps): null {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
NotFound.displayName = "RouteView.NotFound";
|
|
132
|
-
|
|
133
|
-
export const RouteView = Object.assign(RouteViewRoot, { Match, NotFound });
|
|
134
|
-
|
|
135
|
-
export type { RouteViewProps };
|
|
136
|
-
|
|
137
|
-
export type { MatchProps as RouteViewMatchProps };
|
|
138
|
-
|
|
139
|
-
export type { NotFoundProps as RouteViewNotFoundProps };
|