@real-router/svelte 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 +355 -0
- package/dist/RouterProvider.svelte +51 -0
- package/dist/RouterProvider.svelte.d.ts +10 -0
- package/dist/actions/link.svelte.d.ts +26 -0
- package/dist/actions/link.svelte.js +61 -0
- package/dist/components/Lazy.svelte +47 -0
- package/dist/components/Lazy.svelte.d.ts +10 -0
- package/dist/components/Link.svelte +69 -0
- package/dist/components/Link.svelte.d.ts +18 -0
- package/dist/components/RouteView.svelte +46 -0
- package/dist/components/RouteView.svelte.d.ts +9 -0
- package/dist/composables/useIsActiveRoute.svelte.d.ts +4 -0
- package/dist/composables/useIsActiveRoute.svelte.js +11 -0
- package/dist/composables/useNavigator.svelte.d.ts +2 -0
- package/dist/composables/useNavigator.svelte.js +9 -0
- package/dist/composables/useRoute.svelte.d.ts +2 -0
- package/dist/composables/useRoute.svelte.js +9 -0
- package/dist/composables/useRouteNode.svelte.d.ts +2 -0
- package/dist/composables/useRouteNode.svelte.js +27 -0
- package/dist/composables/useRouteUtils.svelte.d.ts +2 -0
- package/dist/composables/useRouteUtils.svelte.js +7 -0
- package/dist/composables/useRouter.svelte.d.ts +2 -0
- package/dist/composables/useRouter.svelte.js +9 -0
- package/dist/composables/useRouterTransition.svelte.d.ts +4 -0
- package/dist/composables/useRouterTransition.svelte.js +8 -0
- package/dist/context.d.ts +3 -0
- package/dist/context.js +3 -0
- package/dist/createReactiveSource.svelte.d.ts +4 -0
- package/dist/createReactiveSource.svelte.js +14 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +18 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +1 -0
- package/package.json +74 -0
- package/src/RouterProvider.svelte +51 -0
- package/src/actions/link.svelte.ts +90 -0
- package/src/components/Lazy.svelte +47 -0
- package/src/components/Link.svelte +69 -0
- package/src/components/RouteView.svelte +46 -0
- package/src/composables/useIsActiveRoute.svelte.ts +22 -0
- package/src/composables/useNavigator.svelte.ts +15 -0
- package/src/composables/useRoute.svelte.ts +15 -0
- package/src/composables/useRouteNode.svelte.ts +33 -0
- package/src/composables/useRouteUtils.svelte.ts +12 -0
- package/src/composables/useRouter.svelte.ts +15 -0
- package/src/composables/useRouterTransition.svelte.ts +16 -0
- package/src/context.ts +5 -0
- package/src/createReactiveSource.svelte.ts +21 -0
- package/src/index.ts +39 -0
- package/src/types.ts +23 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { UNKNOWN_ROUTE } from "@real-router/core";
|
|
3
|
+
import { startsWithSegment } from "@real-router/route-utils";
|
|
4
|
+
|
|
5
|
+
import { useRouteNode } from "../composables/useRouteNode.svelte";
|
|
6
|
+
|
|
7
|
+
import type { Snippet } from "svelte";
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
nodeName,
|
|
11
|
+
notFound,
|
|
12
|
+
...segmentSnippets
|
|
13
|
+
}: {
|
|
14
|
+
nodeName: string;
|
|
15
|
+
notFound?: Snippet;
|
|
16
|
+
[key: string]: Snippet | string | undefined;
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
const routeContext = useRouteNode(nodeName);
|
|
20
|
+
|
|
21
|
+
function getActiveSegment(
|
|
22
|
+
routeName: string,
|
|
23
|
+
node: string,
|
|
24
|
+
snippets: Record<string, unknown>,
|
|
25
|
+
): string {
|
|
26
|
+
for (const segment of Object.keys(snippets)) {
|
|
27
|
+
const fullSegmentName = node ? `${node}.${segment}` : segment;
|
|
28
|
+
|
|
29
|
+
if (startsWithSegment(routeName, fullSegmentName)) {
|
|
30
|
+
return segment;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
{#if routeContext.route.current}
|
|
39
|
+
{@const route = routeContext.route.current}
|
|
40
|
+
{@const segment = getActiveSegment(route.name, nodeName, segmentSnippets)}
|
|
41
|
+
{#if segment && segmentSnippets[segment]}
|
|
42
|
+
{@render (segmentSnippets[segment] as Snippet)()}
|
|
43
|
+
{:else if route.name === UNKNOWN_ROUTE && notFound}
|
|
44
|
+
{@render notFound()}
|
|
45
|
+
{/if}
|
|
46
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
nodeName: string;
|
|
4
|
+
notFound?: Snippet;
|
|
5
|
+
[key: string]: Snippet | string | undefined;
|
|
6
|
+
};
|
|
7
|
+
declare const RouteView: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type RouteView = ReturnType<typeof RouteView>;
|
|
9
|
+
export default RouteView;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createActiveRouteSource } from "@real-router/sources";
|
|
2
|
+
import { createReactiveSource } from "../createReactiveSource.svelte";
|
|
3
|
+
import { useRouter } from "./useRouter.svelte";
|
|
4
|
+
export function useIsActiveRoute(routeName, params, strict = false, ignoreQueryParams = true) {
|
|
5
|
+
const router = useRouter();
|
|
6
|
+
const source = createActiveRouteSource(router, routeName, params, {
|
|
7
|
+
strict,
|
|
8
|
+
ignoreQueryParams,
|
|
9
|
+
});
|
|
10
|
+
return createReactiveSource(source);
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getContext } from "svelte";
|
|
2
|
+
import { NAVIGATOR_KEY } from "../context";
|
|
3
|
+
export const useNavigator = () => {
|
|
4
|
+
const navigator = getContext(NAVIGATOR_KEY);
|
|
5
|
+
if (!navigator) {
|
|
6
|
+
throw new Error("useNavigator must be used within a RouterProvider");
|
|
7
|
+
}
|
|
8
|
+
return navigator;
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getContext } from "svelte";
|
|
2
|
+
import { ROUTE_KEY } from "../context";
|
|
3
|
+
export const useRoute = () => {
|
|
4
|
+
const routeContext = getContext(ROUTE_KEY);
|
|
5
|
+
if (!routeContext) {
|
|
6
|
+
throw new Error("useRoute must be used within a RouterProvider");
|
|
7
|
+
}
|
|
8
|
+
return routeContext;
|
|
9
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getNavigator } from "@real-router/core";
|
|
2
|
+
import { createRouteNodeSource } from "@real-router/sources";
|
|
3
|
+
import { createReactiveSource } from "../createReactiveSource.svelte";
|
|
4
|
+
import { useRouter } from "./useRouter.svelte";
|
|
5
|
+
export function useRouteNode(nodeName) {
|
|
6
|
+
const router = useRouter();
|
|
7
|
+
const navigator = getNavigator(router);
|
|
8
|
+
const source = createRouteNodeSource(router, nodeName);
|
|
9
|
+
const reactive = createReactiveSource(source);
|
|
10
|
+
return {
|
|
11
|
+
navigator,
|
|
12
|
+
get route() {
|
|
13
|
+
return {
|
|
14
|
+
get current() {
|
|
15
|
+
return reactive.current.route;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
get previousRoute() {
|
|
20
|
+
return {
|
|
21
|
+
get current() {
|
|
22
|
+
return reactive.current.previousRoute;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getPluginApi } from "@real-router/core/api";
|
|
2
|
+
import { getRouteUtils } from "@real-router/route-utils";
|
|
3
|
+
import { useRouter } from "./useRouter.svelte";
|
|
4
|
+
export const useRouteUtils = () => {
|
|
5
|
+
const router = useRouter();
|
|
6
|
+
return getRouteUtils(getPluginApi(router).getTree());
|
|
7
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getContext } from "svelte";
|
|
2
|
+
import { ROUTER_KEY } from "../context";
|
|
3
|
+
export const useRouter = () => {
|
|
4
|
+
const router = getContext(ROUTER_KEY);
|
|
5
|
+
if (!router) {
|
|
6
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
7
|
+
}
|
|
8
|
+
return router;
|
|
9
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createTransitionSource } from "@real-router/sources";
|
|
2
|
+
import { createReactiveSource } from "../createReactiveSource.svelte";
|
|
3
|
+
import { useRouter } from "./useRouter.svelte";
|
|
4
|
+
export function useRouterTransition() {
|
|
5
|
+
const router = useRouter();
|
|
6
|
+
const source = createTransitionSource(router);
|
|
7
|
+
return createReactiveSource(source);
|
|
8
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createSubscriber } from "svelte/reactivity";
|
|
2
|
+
export function createReactiveSource(source) {
|
|
3
|
+
const subscribe = createSubscriber((update) => {
|
|
4
|
+
return source.subscribe(() => {
|
|
5
|
+
update();
|
|
6
|
+
});
|
|
7
|
+
});
|
|
8
|
+
return {
|
|
9
|
+
get current() {
|
|
10
|
+
subscribe();
|
|
11
|
+
return source.getSnapshot();
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { default as RouteView } from "./components/RouteView.svelte";
|
|
2
|
+
export { default as Link } from "./components/Link.svelte";
|
|
3
|
+
export { default as Lazy } from "./components/Lazy.svelte";
|
|
4
|
+
export { createReactiveSource } from "./createReactiveSource.svelte";
|
|
5
|
+
export { useRouter } from "./composables/useRouter.svelte";
|
|
6
|
+
export { useNavigator } from "./composables/useNavigator.svelte";
|
|
7
|
+
export { useRouteUtils } from "./composables/useRouteUtils.svelte";
|
|
8
|
+
export { useRoute } from "./composables/useRoute.svelte";
|
|
9
|
+
export { useRouteNode } from "./composables/useRouteNode.svelte";
|
|
10
|
+
export { useRouterTransition } from "./composables/useRouterTransition.svelte";
|
|
11
|
+
export { createLinkAction } from "./actions/link.svelte";
|
|
12
|
+
export type { LinkActionParams } from "./actions/link.svelte";
|
|
13
|
+
export { default as RouterProvider } from "./RouterProvider.svelte";
|
|
14
|
+
export { ROUTER_KEY, NAVIGATOR_KEY, ROUTE_KEY } from "./context";
|
|
15
|
+
export type { LinkProps, RouteContext } from "./types";
|
|
16
|
+
export type { Navigator } from "@real-router/core";
|
|
17
|
+
export type { RouterTransitionSnapshot } from "@real-router/sources";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { default as RouteView } from "./components/RouteView.svelte";
|
|
3
|
+
export { default as Link } from "./components/Link.svelte";
|
|
4
|
+
export { default as Lazy } from "./components/Lazy.svelte";
|
|
5
|
+
// Reactive Primitives
|
|
6
|
+
export { createReactiveSource } from "./createReactiveSource.svelte";
|
|
7
|
+
// Composables
|
|
8
|
+
export { useRouter } from "./composables/useRouter.svelte";
|
|
9
|
+
export { useNavigator } from "./composables/useNavigator.svelte";
|
|
10
|
+
export { useRouteUtils } from "./composables/useRouteUtils.svelte";
|
|
11
|
+
export { useRoute } from "./composables/useRoute.svelte";
|
|
12
|
+
export { useRouteNode } from "./composables/useRouteNode.svelte";
|
|
13
|
+
export { useRouterTransition } from "./composables/useRouterTransition.svelte";
|
|
14
|
+
// Actions
|
|
15
|
+
export { createLinkAction } from "./actions/link.svelte";
|
|
16
|
+
// Context
|
|
17
|
+
export { default as RouterProvider } from "./RouterProvider.svelte";
|
|
18
|
+
export { ROUTER_KEY, NAVIGATOR_KEY, ROUTE_KEY } from "./context";
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NavigationOptions, Navigator, Params, State } from "@real-router/core";
|
|
2
|
+
export interface RouteContext {
|
|
3
|
+
readonly navigator: Navigator;
|
|
4
|
+
readonly route: {
|
|
5
|
+
readonly current: State | undefined;
|
|
6
|
+
};
|
|
7
|
+
readonly previousRoute: {
|
|
8
|
+
readonly current: State | undefined;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface LinkProps<P extends Params = Params> {
|
|
12
|
+
readonly routeName: string;
|
|
13
|
+
readonly routeParams?: P;
|
|
14
|
+
readonly routeOptions?: NavigationOptions;
|
|
15
|
+
readonly class?: string;
|
|
16
|
+
readonly activeClassName?: string;
|
|
17
|
+
readonly activeStrict?: boolean;
|
|
18
|
+
readonly ignoreQueryParams?: boolean;
|
|
19
|
+
readonly target?: string;
|
|
20
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@real-router/svelte",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Svelte 5 integration for Real-Router",
|
|
6
|
+
"svelte": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"svelte": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/greydragon888/real-router",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/greydragon888/real-router.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/greydragon888/real-router/issues"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "svelte-package -i src -o dist",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"test:stress": "vitest run --config vitest.config.stress.mts",
|
|
31
|
+
"type-check": "svelte-check --tsconfig ./tsconfig.json",
|
|
32
|
+
"lint": "eslint --cache src/ tests/ --fix --max-warnings 0"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"router",
|
|
36
|
+
"html5",
|
|
37
|
+
"history",
|
|
38
|
+
"tree",
|
|
39
|
+
"svelte",
|
|
40
|
+
"svelte5",
|
|
41
|
+
"runes",
|
|
42
|
+
"reactive",
|
|
43
|
+
"components",
|
|
44
|
+
"real-router"
|
|
45
|
+
],
|
|
46
|
+
"author": {
|
|
47
|
+
"name": "Oleg Ivanov",
|
|
48
|
+
"email": "greydragon888@gmail.com",
|
|
49
|
+
"url": "https://github.com/greydragon888"
|
|
50
|
+
},
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"sideEffects": false,
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@real-router/core": "workspace:^",
|
|
55
|
+
"@real-router/route-utils": "workspace:^",
|
|
56
|
+
"@real-router/sources": "workspace:^",
|
|
57
|
+
"dom-utils": "workspace:^"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@real-router/browser-plugin": "workspace:^",
|
|
61
|
+
"@sveltejs/package": "2.5.7",
|
|
62
|
+
"@sveltejs/vite-plugin-svelte": "6.2.4",
|
|
63
|
+
"@testing-library/jest-dom": "6.9.1",
|
|
64
|
+
"@testing-library/svelte": "5.3.1",
|
|
65
|
+
"@testing-library/user-event": "14.6.1",
|
|
66
|
+
"eslint-plugin-svelte": "3.15.2",
|
|
67
|
+
"svelte": "5.54.0",
|
|
68
|
+
"svelte-check": "4.4.5",
|
|
69
|
+
"svelte-eslint-parser": "1.6.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"svelte": ">=5.7.0"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getNavigator } from "@real-router/core";
|
|
3
|
+
import { createRouteSource } from "@real-router/sources";
|
|
4
|
+
import { createRouteAnnouncer } from "dom-utils";
|
|
5
|
+
import { setContext } from "svelte";
|
|
6
|
+
|
|
7
|
+
import { createReactiveSource } from "./createReactiveSource.svelte";
|
|
8
|
+
import { NAVIGATOR_KEY, ROUTE_KEY, ROUTER_KEY } from "./context";
|
|
9
|
+
|
|
10
|
+
import type { Router } from "@real-router/core";
|
|
11
|
+
import type { Snippet } from "svelte";
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
router,
|
|
15
|
+
children,
|
|
16
|
+
announceNavigation,
|
|
17
|
+
}: { router: Router; children: Snippet; announceNavigation?: boolean } =
|
|
18
|
+
$props();
|
|
19
|
+
|
|
20
|
+
$effect(() => {
|
|
21
|
+
if (!announceNavigation) return;
|
|
22
|
+
const announcer = createRouteAnnouncer(router);
|
|
23
|
+
return () => announcer.destroy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const navigator = getNavigator(router);
|
|
27
|
+
const source = createRouteSource(router);
|
|
28
|
+
const reactive = createReactiveSource(source);
|
|
29
|
+
|
|
30
|
+
setContext(ROUTER_KEY, router);
|
|
31
|
+
setContext(NAVIGATOR_KEY, navigator);
|
|
32
|
+
setContext(ROUTE_KEY, {
|
|
33
|
+
navigator,
|
|
34
|
+
get route() {
|
|
35
|
+
return {
|
|
36
|
+
get current() {
|
|
37
|
+
return reactive.current.route;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
get previousRoute() {
|
|
42
|
+
return {
|
|
43
|
+
get current() {
|
|
44
|
+
return reactive.current.previousRoute;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
{@render children()}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getContext } from "svelte";
|
|
2
|
+
import type { ActionReturn } from "svelte/action";
|
|
3
|
+
import type { Router, Params, NavigationOptions } from "@real-router/core";
|
|
4
|
+
import { ROUTER_KEY } from "../context";
|
|
5
|
+
import { shouldNavigate, applyLinkA11y } from "dom-utils";
|
|
6
|
+
|
|
7
|
+
export interface LinkActionParams {
|
|
8
|
+
name: string;
|
|
9
|
+
params?: Params;
|
|
10
|
+
options?: NavigationOptions;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Factory function that captures router context during component initialization.
|
|
15
|
+
* Must be called during component init (not inside event handlers or effects).
|
|
16
|
+
*
|
|
17
|
+
* @returns Action function for use with `use:` directive
|
|
18
|
+
* @throws Error if called outside RouterProvider
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```svelte
|
|
22
|
+
* <script>
|
|
23
|
+
* import { createLinkAction } from '@real-router/svelte';
|
|
24
|
+
* const link = createLinkAction();
|
|
25
|
+
* </script>
|
|
26
|
+
*
|
|
27
|
+
* <button use:link={{ name: 'home' }}>Home</button>
|
|
28
|
+
* <a use:link={{ name: 'users', params: { id: '123' } }}>User Profile</a>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function createLinkAction(): (
|
|
32
|
+
node: HTMLElement,
|
|
33
|
+
params: LinkActionParams,
|
|
34
|
+
) => ActionReturn<LinkActionParams> {
|
|
35
|
+
const router = getContext<Router | undefined>(ROUTER_KEY);
|
|
36
|
+
|
|
37
|
+
if (!router) {
|
|
38
|
+
throw new Error("createLinkAction must be called inside a RouterProvider");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return function link(
|
|
42
|
+
node: HTMLElement,
|
|
43
|
+
params: LinkActionParams,
|
|
44
|
+
): ActionReturn<LinkActionParams> {
|
|
45
|
+
let currentParams = params;
|
|
46
|
+
|
|
47
|
+
applyLinkA11y(node);
|
|
48
|
+
|
|
49
|
+
function handleClick(evt: MouseEvent) {
|
|
50
|
+
if (!shouldNavigate(evt)) return;
|
|
51
|
+
evt.preventDefault();
|
|
52
|
+
// router is guaranteed to exist due to check in factory
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
54
|
+
router!
|
|
55
|
+
.navigate(
|
|
56
|
+
currentParams.name,
|
|
57
|
+
currentParams.params ?? {},
|
|
58
|
+
currentParams.options ?? {},
|
|
59
|
+
)
|
|
60
|
+
.catch(() => {});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleKeyDown(evt: KeyboardEvent) {
|
|
64
|
+
if (evt.key === "Enter" && !(node instanceof HTMLButtonElement)) {
|
|
65
|
+
// router is guaranteed to exist due to check in factory
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
67
|
+
router!
|
|
68
|
+
.navigate(
|
|
69
|
+
currentParams.name,
|
|
70
|
+
currentParams.params ?? {},
|
|
71
|
+
currentParams.options ?? {},
|
|
72
|
+
)
|
|
73
|
+
.catch(() => {});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
node.addEventListener("click", handleClick);
|
|
78
|
+
node.addEventListener("keydown", handleKeyDown);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
update(newParams: LinkActionParams) {
|
|
82
|
+
currentParams = newParams;
|
|
83
|
+
},
|
|
84
|
+
destroy() {
|
|
85
|
+
node.removeEventListener("click", handleClick);
|
|
86
|
+
node.removeEventListener("keydown", handleKeyDown);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component } from "svelte";
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
loader,
|
|
6
|
+
fallback,
|
|
7
|
+
}: {
|
|
8
|
+
loader: () => Promise<{ default: Component }>;
|
|
9
|
+
fallback?: Component | undefined;
|
|
10
|
+
} = $props();
|
|
11
|
+
|
|
12
|
+
let LoadedComponent = $state<Component | null>(null);
|
|
13
|
+
let error = $state<Error | null>(null);
|
|
14
|
+
let loading = $state(true);
|
|
15
|
+
|
|
16
|
+
$effect(() => {
|
|
17
|
+
loading = true;
|
|
18
|
+
error = null;
|
|
19
|
+
LoadedComponent = null;
|
|
20
|
+
let active = true;
|
|
21
|
+
|
|
22
|
+
loader()
|
|
23
|
+
.then((module) => {
|
|
24
|
+
if (!active) return;
|
|
25
|
+
LoadedComponent = module.default;
|
|
26
|
+
loading = false;
|
|
27
|
+
})
|
|
28
|
+
.catch((err) => {
|
|
29
|
+
if (!active) return;
|
|
30
|
+
error = err;
|
|
31
|
+
loading = false;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
active = false;
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
{#if loading && fallback}
|
|
41
|
+
{@const Fallback = fallback}
|
|
42
|
+
<Fallback />
|
|
43
|
+
{:else if error}
|
|
44
|
+
<p>Error loading component: {error.message}</p>
|
|
45
|
+
{:else if LoadedComponent}
|
|
46
|
+
<LoadedComponent />
|
|
47
|
+
{/if}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useIsActiveRoute } from "../composables/useIsActiveRoute.svelte";
|
|
3
|
+
import { useRouter } from "../composables/useRouter.svelte";
|
|
4
|
+
import { shouldNavigate, buildHref, buildActiveClassName } from "dom-utils";
|
|
5
|
+
|
|
6
|
+
import type { NavigationOptions, Params } from "@real-router/core";
|
|
7
|
+
import type { Snippet } from "svelte";
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
routeName,
|
|
11
|
+
routeParams = {} as Params,
|
|
12
|
+
routeOptions = {} as NavigationOptions,
|
|
13
|
+
class: className = undefined,
|
|
14
|
+
activeClassName = "active",
|
|
15
|
+
activeStrict = false,
|
|
16
|
+
ignoreQueryParams = true,
|
|
17
|
+
target = undefined,
|
|
18
|
+
children = undefined,
|
|
19
|
+
onclick: userOnClick = undefined,
|
|
20
|
+
...restProps
|
|
21
|
+
}: {
|
|
22
|
+
routeName: string;
|
|
23
|
+
routeParams?: Params;
|
|
24
|
+
routeOptions?: NavigationOptions;
|
|
25
|
+
class?: string;
|
|
26
|
+
activeClassName?: string;
|
|
27
|
+
activeStrict?: boolean;
|
|
28
|
+
ignoreQueryParams?: boolean;
|
|
29
|
+
target?: string;
|
|
30
|
+
children?: Snippet;
|
|
31
|
+
onclick?: (evt: MouseEvent) => void;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
} = $props();
|
|
34
|
+
|
|
35
|
+
const router = useRouter();
|
|
36
|
+
const activeState = useIsActiveRoute(
|
|
37
|
+
routeName,
|
|
38
|
+
routeParams,
|
|
39
|
+
activeStrict,
|
|
40
|
+
ignoreQueryParams,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const href = $derived(buildHref(router, routeName, routeParams));
|
|
44
|
+
|
|
45
|
+
const finalClassName = $derived(
|
|
46
|
+
buildActiveClassName(activeState.current, activeClassName, className),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
function handleClick(evt: MouseEvent) {
|
|
50
|
+
if (userOnClick) {
|
|
51
|
+
userOnClick(evt);
|
|
52
|
+
|
|
53
|
+
if (evt.defaultPrevented) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!shouldNavigate(evt) || target === "_blank") {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
evt.preventDefault();
|
|
63
|
+
router.navigate(routeName, routeParams, routeOptions).catch(() => {});
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<a {href} class={finalClassName} {target} onclick={handleClick} {...restProps}>
|
|
68
|
+
{@render children?.()}
|
|
69
|
+
</a>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { UNKNOWN_ROUTE } from "@real-router/core";
|
|
3
|
+
import { startsWithSegment } from "@real-router/route-utils";
|
|
4
|
+
|
|
5
|
+
import { useRouteNode } from "../composables/useRouteNode.svelte";
|
|
6
|
+
|
|
7
|
+
import type { Snippet } from "svelte";
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
nodeName,
|
|
11
|
+
notFound,
|
|
12
|
+
...segmentSnippets
|
|
13
|
+
}: {
|
|
14
|
+
nodeName: string;
|
|
15
|
+
notFound?: Snippet;
|
|
16
|
+
[key: string]: Snippet | string | undefined;
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
const routeContext = useRouteNode(nodeName);
|
|
20
|
+
|
|
21
|
+
function getActiveSegment(
|
|
22
|
+
routeName: string,
|
|
23
|
+
node: string,
|
|
24
|
+
snippets: Record<string, unknown>,
|
|
25
|
+
): string {
|
|
26
|
+
for (const segment of Object.keys(snippets)) {
|
|
27
|
+
const fullSegmentName = node ? `${node}.${segment}` : segment;
|
|
28
|
+
|
|
29
|
+
if (startsWithSegment(routeName, fullSegmentName)) {
|
|
30
|
+
return segment;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
{#if routeContext.route.current}
|
|
39
|
+
{@const route = routeContext.route.current}
|
|
40
|
+
{@const segment = getActiveSegment(route.name, nodeName, segmentSnippets)}
|
|
41
|
+
{#if segment && segmentSnippets[segment]}
|
|
42
|
+
{@render (segmentSnippets[segment] as Snippet)()}
|
|
43
|
+
{:else if route.name === UNKNOWN_ROUTE && notFound}
|
|
44
|
+
{@render notFound()}
|
|
45
|
+
{/if}
|
|
46
|
+
{/if}
|