@real-router/preact 0.0.1
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 +194 -0
- package/dist/cjs/index.d.ts +84 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/metafile-cjs.json +1 -0
- package/dist/esm/index.d.mts +84 -0
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/metafile-esm.json +1 -0
- package/package.json +76 -0
- package/src/RouterProvider.tsx +59 -0
- package/src/components/Link.tsx +102 -0
- package/src/components/RouteView/RouteView.tsx +39 -0
- package/src/components/RouteView/components.tsx +13 -0
- package/src/components/RouteView/helpers.tsx +88 -0
- package/src/components/RouteView/index.ts +7 -0
- package/src/components/RouteView/types.ts +17 -0
- package/src/constants.ts +9 -0
- package/src/context.ts +10 -0
- package/src/hooks/useIsActiveRoute.tsx +34 -0
- package/src/hooks/useNavigator.tsx +15 -0
- package/src/hooks/useRoute.tsx +15 -0
- package/src/hooks/useRouteNode.tsx +30 -0
- package/src/hooks/useRouteUtils.tsx +12 -0
- package/src/hooks/useRouter.tsx +15 -0
- package/src/hooks/useRouterTransition.tsx +19 -0
- package/src/hooks/useStableValue.tsx +8 -0
- package/src/index.ts +35 -0
- package/src/types.ts +33 -0
- package/src/useSyncExternalStore.ts +32 -0
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@real-router/preact",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"description": "Preact 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
|
+
"development": "./src/index.ts",
|
|
12
|
+
"types": {
|
|
13
|
+
"import": "./dist/esm/index.d.mts",
|
|
14
|
+
"require": "./dist/cjs/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"import": "./dist/esm/index.mjs",
|
|
17
|
+
"require": "./dist/cjs/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"homepage": "https://github.com/greydragon888/real-router",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/greydragon888/real-router.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/greydragon888/real-router/issues"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"test": "vitest",
|
|
35
|
+
"test:stress": "vitest run --config vitest.config.stress.mts",
|
|
36
|
+
"type-check": "tsc --noEmit",
|
|
37
|
+
"lint": "eslint --cache --ext .ts,.tsx src/ tests/ --fix --max-warnings 0",
|
|
38
|
+
"lint:package": "publint",
|
|
39
|
+
"lint:types": "attw --pack ."
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"router",
|
|
43
|
+
"html5",
|
|
44
|
+
"history",
|
|
45
|
+
"tree",
|
|
46
|
+
"preact",
|
|
47
|
+
"hooks",
|
|
48
|
+
"components",
|
|
49
|
+
"functional",
|
|
50
|
+
"real-router"
|
|
51
|
+
],
|
|
52
|
+
"author": {
|
|
53
|
+
"name": "Oleg Ivanov",
|
|
54
|
+
"email": "greydragon888@gmail.com",
|
|
55
|
+
"url": "https://github.com/greydragon888"
|
|
56
|
+
},
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"sideEffects": false,
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@real-router/core": "workspace:^",
|
|
61
|
+
"@real-router/route-utils": "workspace:^",
|
|
62
|
+
"@real-router/sources": "workspace:^",
|
|
63
|
+
"dom-utils": "workspace:^"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@real-router/browser-plugin": "workspace:^",
|
|
67
|
+
"@testing-library/dom": "10.4.1",
|
|
68
|
+
"@testing-library/jest-dom": "6.9.1",
|
|
69
|
+
"@testing-library/preact": "3.2.4",
|
|
70
|
+
"@testing-library/user-event": "14.6.1",
|
|
71
|
+
"preact": "10.25.4"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"preact": ">=10.0.0"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { getNavigator } from "@real-router/core";
|
|
2
|
+
import { createRouteSource } from "@real-router/sources";
|
|
3
|
+
import { createRouteAnnouncer } from "dom-utils";
|
|
4
|
+
import { useEffect, useMemo } from "preact/hooks";
|
|
5
|
+
|
|
6
|
+
import { NavigatorContext, RouteContext, RouterContext } from "./context";
|
|
7
|
+
import { useSyncExternalStore } from "./useSyncExternalStore";
|
|
8
|
+
|
|
9
|
+
import type { Router } from "@real-router/core";
|
|
10
|
+
import type { FunctionComponent, ComponentChildren } from "preact";
|
|
11
|
+
|
|
12
|
+
export interface RouteProviderProps {
|
|
13
|
+
router: Router;
|
|
14
|
+
children: ComponentChildren;
|
|
15
|
+
announceNavigation?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const RouterProvider: FunctionComponent<RouteProviderProps> = ({
|
|
19
|
+
router,
|
|
20
|
+
children,
|
|
21
|
+
announceNavigation,
|
|
22
|
+
}) => {
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!announceNavigation) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const announcer = createRouteAnnouncer(router);
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
announcer.destroy();
|
|
32
|
+
};
|
|
33
|
+
}, [announceNavigation, router]);
|
|
34
|
+
const navigator = useMemo(() => getNavigator(router), [router]);
|
|
35
|
+
|
|
36
|
+
// useSyncExternalStore manages the router subscription lifecycle:
|
|
37
|
+
// subscribe connects to router on first listener, unsubscribes on last.
|
|
38
|
+
const store = useMemo(() => createRouteSource(router), [router]);
|
|
39
|
+
const { route, previousRoute } = useSyncExternalStore(
|
|
40
|
+
store.subscribe,
|
|
41
|
+
store.getSnapshot,
|
|
42
|
+
store.getSnapshot, // SSR: router returns same state on server and client
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const routeContextValue = useMemo(
|
|
46
|
+
() => ({ navigator, route, previousRoute }),
|
|
47
|
+
[navigator, route, previousRoute],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<RouterContext.Provider value={router}>
|
|
52
|
+
<NavigatorContext.Provider value={navigator}>
|
|
53
|
+
<RouteContext.Provider value={routeContextValue}>
|
|
54
|
+
{children}
|
|
55
|
+
</RouteContext.Provider>
|
|
56
|
+
</NavigatorContext.Provider>
|
|
57
|
+
</RouterContext.Provider>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { shouldNavigate, buildHref, buildActiveClassName } from "dom-utils";
|
|
2
|
+
import { memo } from "preact/compat";
|
|
3
|
+
import { useCallback, useMemo } from "preact/hooks";
|
|
4
|
+
|
|
5
|
+
import { EMPTY_PARAMS, EMPTY_OPTIONS } from "../constants";
|
|
6
|
+
import { useIsActiveRoute } from "../hooks/useIsActiveRoute";
|
|
7
|
+
import { useRouter } from "../hooks/useRouter";
|
|
8
|
+
import { useStableValue } from "../hooks/useStableValue";
|
|
9
|
+
|
|
10
|
+
import type { LinkProps } from "../types";
|
|
11
|
+
import type { FunctionComponent, JSX } from "preact";
|
|
12
|
+
|
|
13
|
+
function areLinkPropsEqual(
|
|
14
|
+
prev: Readonly<LinkProps>,
|
|
15
|
+
next: Readonly<LinkProps>,
|
|
16
|
+
): boolean {
|
|
17
|
+
return (
|
|
18
|
+
prev.routeName === next.routeName &&
|
|
19
|
+
prev.className === next.className &&
|
|
20
|
+
prev.activeClassName === next.activeClassName &&
|
|
21
|
+
prev.activeStrict === next.activeStrict &&
|
|
22
|
+
prev.ignoreQueryParams === next.ignoreQueryParams &&
|
|
23
|
+
prev.onClick === next.onClick &&
|
|
24
|
+
prev.target === next.target &&
|
|
25
|
+
prev.style === next.style &&
|
|
26
|
+
prev.children === next.children &&
|
|
27
|
+
JSON.stringify(prev.routeParams) === JSON.stringify(next.routeParams) &&
|
|
28
|
+
JSON.stringify(prev.routeOptions) === JSON.stringify(next.routeOptions)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Link: FunctionComponent<LinkProps> = memo(
|
|
33
|
+
({
|
|
34
|
+
routeName,
|
|
35
|
+
routeParams = EMPTY_PARAMS,
|
|
36
|
+
routeOptions = EMPTY_OPTIONS,
|
|
37
|
+
className,
|
|
38
|
+
activeClassName = "active",
|
|
39
|
+
activeStrict = false,
|
|
40
|
+
ignoreQueryParams = true,
|
|
41
|
+
onClick,
|
|
42
|
+
target,
|
|
43
|
+
children,
|
|
44
|
+
...props
|
|
45
|
+
}) => {
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
|
|
48
|
+
const stableParams = useStableValue(routeParams);
|
|
49
|
+
const stableOptions = useStableValue(routeOptions);
|
|
50
|
+
|
|
51
|
+
const isActive = useIsActiveRoute(
|
|
52
|
+
routeName,
|
|
53
|
+
stableParams,
|
|
54
|
+
activeStrict,
|
|
55
|
+
ignoreQueryParams,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const href = useMemo(
|
|
59
|
+
() => buildHref(router, routeName, stableParams),
|
|
60
|
+
[router, routeName, stableParams],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const handleClick = useCallback(
|
|
64
|
+
(evt: JSX.TargetedMouseEvent<HTMLAnchorElement>) => {
|
|
65
|
+
if (onClick) {
|
|
66
|
+
onClick(evt);
|
|
67
|
+
|
|
68
|
+
if (evt.defaultPrevented) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!shouldNavigate(evt) || target === "_blank") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
evt.preventDefault();
|
|
78
|
+
router.navigate(routeName, stableParams, stableOptions).catch(() => {});
|
|
79
|
+
},
|
|
80
|
+
[onClick, target, router, routeName, stableParams, stableOptions],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const finalClassName = useMemo(
|
|
84
|
+
() => buildActiveClassName(isActive, activeClassName, className),
|
|
85
|
+
[isActive, activeClassName, className],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<a
|
|
90
|
+
{...props}
|
|
91
|
+
href={href}
|
|
92
|
+
className={finalClassName}
|
|
93
|
+
onClick={handleClick}
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
</a>
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
areLinkPropsEqual,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
Link.displayName = "Link";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Match, NotFound } from "./components";
|
|
2
|
+
import { buildRenderList, collectElements } from "./helpers";
|
|
3
|
+
import { useRouteNode } from "../../hooks/useRouteNode";
|
|
4
|
+
|
|
5
|
+
import type { RouteViewProps } from "./types";
|
|
6
|
+
import type { VNode } from "preact";
|
|
7
|
+
|
|
8
|
+
function RouteViewRoot({
|
|
9
|
+
nodeName,
|
|
10
|
+
children,
|
|
11
|
+
}: Readonly<RouteViewProps>): VNode | null {
|
|
12
|
+
const { route } = useRouteNode(nodeName);
|
|
13
|
+
|
|
14
|
+
if (!route) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const elements: VNode[] = [];
|
|
19
|
+
|
|
20
|
+
collectElements(children, elements);
|
|
21
|
+
|
|
22
|
+
const { rendered } = buildRenderList(elements, route.name, nodeName);
|
|
23
|
+
|
|
24
|
+
if (rendered.length > 0) {
|
|
25
|
+
return <>{rendered}</>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
RouteViewRoot.displayName = "RouteView";
|
|
32
|
+
|
|
33
|
+
export const RouteView = Object.assign(RouteViewRoot, { Match, NotFound });
|
|
34
|
+
|
|
35
|
+
export type {
|
|
36
|
+
RouteViewProps,
|
|
37
|
+
MatchProps as RouteViewMatchProps,
|
|
38
|
+
NotFoundProps as RouteViewNotFoundProps,
|
|
39
|
+
} from "./types";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MatchProps, NotFoundProps } from "./types";
|
|
2
|
+
|
|
3
|
+
export function Match(_props: MatchProps): null {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
Match.displayName = "RouteView.Match";
|
|
8
|
+
|
|
9
|
+
export function NotFound(_props: NotFoundProps): null {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
NotFound.displayName = "RouteView.NotFound";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { UNKNOWN_ROUTE } from "@real-router/core";
|
|
2
|
+
import { startsWithSegment } from "@real-router/route-utils";
|
|
3
|
+
import { Fragment, isValidElement, toChildArray } from "preact";
|
|
4
|
+
import { Suspense } from "preact/compat";
|
|
5
|
+
|
|
6
|
+
import { Match, NotFound } from "./components";
|
|
7
|
+
|
|
8
|
+
import type { MatchProps, NotFoundProps } from "./types";
|
|
9
|
+
import type { VNode, ComponentChildren } from "preact";
|
|
10
|
+
|
|
11
|
+
function isSegmentMatch(
|
|
12
|
+
routeName: string,
|
|
13
|
+
fullSegmentName: string,
|
|
14
|
+
exact: boolean,
|
|
15
|
+
): boolean {
|
|
16
|
+
if (exact) {
|
|
17
|
+
return routeName === fullSegmentName;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return startsWithSegment(routeName, fullSegmentName);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function collectElements(
|
|
24
|
+
children: ComponentChildren,
|
|
25
|
+
result: VNode[],
|
|
26
|
+
): void {
|
|
27
|
+
for (const child of toChildArray(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: ComponentChildren }).children,
|
|
37
|
+
result,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildRenderList(
|
|
44
|
+
elements: VNode[],
|
|
45
|
+
routeName: string,
|
|
46
|
+
nodeName: string,
|
|
47
|
+
): { rendered: VNode[]; activeMatchFound: boolean } {
|
|
48
|
+
let notFoundChildren: ComponentChildren = null;
|
|
49
|
+
let activeMatchFound = false;
|
|
50
|
+
const rendered: VNode[] = [];
|
|
51
|
+
|
|
52
|
+
for (const child of elements) {
|
|
53
|
+
if (child.type === NotFound) {
|
|
54
|
+
notFoundChildren = (child.props as NotFoundProps).children;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { segment, exact = false, fallback } = child.props as MatchProps;
|
|
59
|
+
const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
|
|
60
|
+
const isActive =
|
|
61
|
+
!activeMatchFound && isSegmentMatch(routeName, fullSegmentName, exact);
|
|
62
|
+
|
|
63
|
+
if (isActive) {
|
|
64
|
+
activeMatchFound = true;
|
|
65
|
+
const matchChildren = (child.props as MatchProps).children;
|
|
66
|
+
const content =
|
|
67
|
+
fallback === undefined ? (
|
|
68
|
+
matchChildren
|
|
69
|
+
) : (
|
|
70
|
+
<Suspense fallback={fallback}>{matchChildren}</Suspense>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
rendered.push(<Fragment key={fullSegmentName}>{content}</Fragment>);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
!activeMatchFound &&
|
|
79
|
+
routeName === UNKNOWN_ROUTE &&
|
|
80
|
+
notFoundChildren !== null
|
|
81
|
+
) {
|
|
82
|
+
rendered.push(
|
|
83
|
+
<Fragment key="__route-view-not-found__">{notFoundChildren}</Fragment>,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { rendered, activeMatchFound };
|
|
88
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ComponentChildren } from "preact";
|
|
2
|
+
|
|
3
|
+
export interface RouteViewProps {
|
|
4
|
+
readonly nodeName: string;
|
|
5
|
+
readonly children: ComponentChildren;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface MatchProps {
|
|
9
|
+
readonly segment: string;
|
|
10
|
+
readonly exact?: boolean;
|
|
11
|
+
readonly fallback?: ComponentChildren;
|
|
12
|
+
readonly children: ComponentChildren;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NotFoundProps {
|
|
16
|
+
readonly children: ComponentChildren;
|
|
17
|
+
}
|
package/src/constants.ts
ADDED
package/src/context.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext } from "preact";
|
|
2
|
+
|
|
3
|
+
import type { RouteContext as RouteContextType } from "./types";
|
|
4
|
+
import type { Router, Navigator } from "@real-router/core";
|
|
5
|
+
|
|
6
|
+
export const RouteContext = createContext<RouteContextType | null>(null);
|
|
7
|
+
|
|
8
|
+
export const RouterContext = createContext<Router | null>(null);
|
|
9
|
+
|
|
10
|
+
export const NavigatorContext = createContext<Navigator | null>(null);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createActiveRouteSource } from "@real-router/sources";
|
|
2
|
+
import { useMemo } from "preact/hooks";
|
|
3
|
+
|
|
4
|
+
import { useSyncExternalStore } from "../useSyncExternalStore";
|
|
5
|
+
import { useRouter } from "./useRouter";
|
|
6
|
+
import { useStableValue } from "./useStableValue";
|
|
7
|
+
|
|
8
|
+
import type { Params } from "@real-router/core";
|
|
9
|
+
|
|
10
|
+
export function useIsActiveRoute(
|
|
11
|
+
routeName: string,
|
|
12
|
+
params?: Params,
|
|
13
|
+
strict = false,
|
|
14
|
+
ignoreQueryParams = true,
|
|
15
|
+
): boolean {
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
|
|
18
|
+
const stableParams = useStableValue(params);
|
|
19
|
+
|
|
20
|
+
const store = useMemo(
|
|
21
|
+
() =>
|
|
22
|
+
createActiveRouteSource(router, routeName, stableParams, {
|
|
23
|
+
strict,
|
|
24
|
+
ignoreQueryParams,
|
|
25
|
+
}),
|
|
26
|
+
[router, routeName, stableParams, strict, ignoreQueryParams],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return useSyncExternalStore(
|
|
30
|
+
store.subscribe,
|
|
31
|
+
store.getSnapshot,
|
|
32
|
+
store.getSnapshot,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useContext } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
import { NavigatorContext } from "../context";
|
|
4
|
+
|
|
5
|
+
import type { Navigator } from "@real-router/core";
|
|
6
|
+
|
|
7
|
+
export const useNavigator = (): Navigator => {
|
|
8
|
+
const navigator = useContext(NavigatorContext);
|
|
9
|
+
|
|
10
|
+
if (!navigator) {
|
|
11
|
+
throw new Error("useNavigator must be used within a RouterProvider");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return navigator;
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useContext } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
import { RouteContext } from "../context";
|
|
4
|
+
|
|
5
|
+
import type { RouteContext as RouteContextType } from "../types";
|
|
6
|
+
|
|
7
|
+
export const useRoute = (): RouteContextType => {
|
|
8
|
+
const routeContext = useContext(RouteContext);
|
|
9
|
+
|
|
10
|
+
if (!routeContext) {
|
|
11
|
+
throw new Error("useRoute must be used within a RouteProvider");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return routeContext;
|
|
15
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getNavigator } from "@real-router/core";
|
|
2
|
+
import { createRouteNodeSource } from "@real-router/sources";
|
|
3
|
+
import { useMemo } from "preact/hooks";
|
|
4
|
+
|
|
5
|
+
import { useSyncExternalStore } from "../useSyncExternalStore";
|
|
6
|
+
import { useRouter } from "./useRouter";
|
|
7
|
+
|
|
8
|
+
import type { RouteContext } from "../types";
|
|
9
|
+
|
|
10
|
+
export function useRouteNode(nodeName: string): RouteContext {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
|
|
13
|
+
const store = useMemo(
|
|
14
|
+
() => createRouteNodeSource(router, nodeName),
|
|
15
|
+
[router, nodeName],
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const { route, previousRoute } = useSyncExternalStore(
|
|
19
|
+
store.subscribe,
|
|
20
|
+
store.getSnapshot,
|
|
21
|
+
store.getSnapshot,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const navigator = useMemo(() => getNavigator(router), [router]);
|
|
25
|
+
|
|
26
|
+
return useMemo(
|
|
27
|
+
(): RouteContext => ({ navigator, route, previousRoute }),
|
|
28
|
+
[navigator, route, previousRoute],
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getPluginApi } from "@real-router/core/api";
|
|
2
|
+
import { getRouteUtils } from "@real-router/route-utils";
|
|
3
|
+
|
|
4
|
+
import { useRouter } from "./useRouter";
|
|
5
|
+
|
|
6
|
+
import type { RouteUtils } from "@real-router/route-utils";
|
|
7
|
+
|
|
8
|
+
export const useRouteUtils = (): RouteUtils => {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
|
|
11
|
+
return getRouteUtils(getPluginApi(router).getTree());
|
|
12
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useContext } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
import { RouterContext } from "../context";
|
|
4
|
+
|
|
5
|
+
import type { Router } from "@real-router/core";
|
|
6
|
+
|
|
7
|
+
export const useRouter = (): Router => {
|
|
8
|
+
const router = useContext(RouterContext);
|
|
9
|
+
|
|
10
|
+
if (!router) {
|
|
11
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return router;
|
|
15
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createTransitionSource } from "@real-router/sources";
|
|
2
|
+
import { useMemo } from "preact/hooks";
|
|
3
|
+
|
|
4
|
+
import { useSyncExternalStore } from "../useSyncExternalStore";
|
|
5
|
+
import { useRouter } from "./useRouter";
|
|
6
|
+
|
|
7
|
+
import type { RouterTransitionSnapshot } from "@real-router/sources";
|
|
8
|
+
|
|
9
|
+
export function useRouterTransition(): RouterTransitionSnapshot {
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
|
|
12
|
+
const store = useMemo(() => createTransitionSource(router), [router]);
|
|
13
|
+
|
|
14
|
+
return useSyncExternalStore(
|
|
15
|
+
store.subscribe,
|
|
16
|
+
store.getSnapshot,
|
|
17
|
+
store.getSnapshot,
|
|
18
|
+
);
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { RouteView } from "./components/RouteView";
|
|
3
|
+
|
|
4
|
+
export { Link } from "./components/Link";
|
|
5
|
+
|
|
6
|
+
// Hooks
|
|
7
|
+
export { useRouter } from "./hooks/useRouter";
|
|
8
|
+
|
|
9
|
+
export { useNavigator } from "./hooks/useNavigator";
|
|
10
|
+
|
|
11
|
+
export { useRouteUtils } from "./hooks/useRouteUtils";
|
|
12
|
+
|
|
13
|
+
export { useRoute } from "./hooks/useRoute";
|
|
14
|
+
|
|
15
|
+
export { useRouteNode } from "./hooks/useRouteNode";
|
|
16
|
+
|
|
17
|
+
export { useRouterTransition } from "./hooks/useRouterTransition";
|
|
18
|
+
|
|
19
|
+
// Context
|
|
20
|
+
export { RouterProvider } from "./RouterProvider";
|
|
21
|
+
|
|
22
|
+
export { RouterContext, NavigatorContext, RouteContext } from "./context";
|
|
23
|
+
|
|
24
|
+
// Types
|
|
25
|
+
export type { LinkProps } from "./types";
|
|
26
|
+
|
|
27
|
+
export type {
|
|
28
|
+
RouteViewProps,
|
|
29
|
+
RouteViewMatchProps,
|
|
30
|
+
RouteViewNotFoundProps,
|
|
31
|
+
} from "./components/RouteView";
|
|
32
|
+
|
|
33
|
+
export type { Navigator } from "@real-router/core";
|
|
34
|
+
|
|
35
|
+
export type { RouterTransitionSnapshot } from "@real-router/sources";
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NavigationOptions,
|
|
3
|
+
Params,
|
|
4
|
+
Navigator,
|
|
5
|
+
State,
|
|
6
|
+
} from "@real-router/core";
|
|
7
|
+
import type { JSX } from "preact";
|
|
8
|
+
|
|
9
|
+
export interface RouteState<
|
|
10
|
+
P extends Params = Params,
|
|
11
|
+
MP extends Params = Params,
|
|
12
|
+
> {
|
|
13
|
+
route: State<P, MP> | undefined;
|
|
14
|
+
previousRoute?: State | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type RouteContext = {
|
|
18
|
+
navigator: Navigator;
|
|
19
|
+
} & RouteState;
|
|
20
|
+
|
|
21
|
+
export interface LinkProps<P extends Params = Params> extends Omit<
|
|
22
|
+
JSX.HTMLAttributes<HTMLAnchorElement>,
|
|
23
|
+
"className"
|
|
24
|
+
> {
|
|
25
|
+
routeName: string;
|
|
26
|
+
routeParams?: P;
|
|
27
|
+
routeOptions?: NavigationOptions;
|
|
28
|
+
className?: string;
|
|
29
|
+
activeClassName?: string;
|
|
30
|
+
activeStrict?: boolean;
|
|
31
|
+
ignoreQueryParams?: boolean;
|
|
32
|
+
target?: string;
|
|
33
|
+
}
|