@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.
Files changed (50) hide show
  1. package/README.md +355 -0
  2. package/dist/RouterProvider.svelte +51 -0
  3. package/dist/RouterProvider.svelte.d.ts +10 -0
  4. package/dist/actions/link.svelte.d.ts +26 -0
  5. package/dist/actions/link.svelte.js +61 -0
  6. package/dist/components/Lazy.svelte +47 -0
  7. package/dist/components/Lazy.svelte.d.ts +10 -0
  8. package/dist/components/Link.svelte +69 -0
  9. package/dist/components/Link.svelte.d.ts +18 -0
  10. package/dist/components/RouteView.svelte +46 -0
  11. package/dist/components/RouteView.svelte.d.ts +9 -0
  12. package/dist/composables/useIsActiveRoute.svelte.d.ts +4 -0
  13. package/dist/composables/useIsActiveRoute.svelte.js +11 -0
  14. package/dist/composables/useNavigator.svelte.d.ts +2 -0
  15. package/dist/composables/useNavigator.svelte.js +9 -0
  16. package/dist/composables/useRoute.svelte.d.ts +2 -0
  17. package/dist/composables/useRoute.svelte.js +9 -0
  18. package/dist/composables/useRouteNode.svelte.d.ts +2 -0
  19. package/dist/composables/useRouteNode.svelte.js +27 -0
  20. package/dist/composables/useRouteUtils.svelte.d.ts +2 -0
  21. package/dist/composables/useRouteUtils.svelte.js +7 -0
  22. package/dist/composables/useRouter.svelte.d.ts +2 -0
  23. package/dist/composables/useRouter.svelte.js +9 -0
  24. package/dist/composables/useRouterTransition.svelte.d.ts +4 -0
  25. package/dist/composables/useRouterTransition.svelte.js +8 -0
  26. package/dist/context.d.ts +3 -0
  27. package/dist/context.js +3 -0
  28. package/dist/createReactiveSource.svelte.d.ts +4 -0
  29. package/dist/createReactiveSource.svelte.js +14 -0
  30. package/dist/index.d.ts +17 -0
  31. package/dist/index.js +18 -0
  32. package/dist/types.d.ts +20 -0
  33. package/dist/types.js +1 -0
  34. package/package.json +74 -0
  35. package/src/RouterProvider.svelte +51 -0
  36. package/src/actions/link.svelte.ts +90 -0
  37. package/src/components/Lazy.svelte +47 -0
  38. package/src/components/Link.svelte +69 -0
  39. package/src/components/RouteView.svelte +46 -0
  40. package/src/composables/useIsActiveRoute.svelte.ts +22 -0
  41. package/src/composables/useNavigator.svelte.ts +15 -0
  42. package/src/composables/useRoute.svelte.ts +15 -0
  43. package/src/composables/useRouteNode.svelte.ts +33 -0
  44. package/src/composables/useRouteUtils.svelte.ts +12 -0
  45. package/src/composables/useRouter.svelte.ts +15 -0
  46. package/src/composables/useRouterTransition.svelte.ts +16 -0
  47. package/src/context.ts +5 -0
  48. package/src/createReactiveSource.svelte.ts +21 -0
  49. package/src/index.ts +39 -0
  50. 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,4 @@
1
+ import type { Params } from "@real-router/core";
2
+ export declare function useIsActiveRoute(routeName: string, params?: Params, strict?: boolean, ignoreQueryParams?: boolean): {
3
+ readonly current: boolean;
4
+ };
@@ -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,2 @@
1
+ import type { Navigator } from "@real-router/core";
2
+ export declare const useNavigator: () => Navigator;
@@ -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,2 @@
1
+ import type { RouteContext } from "../types";
2
+ export declare const useRoute: () => RouteContext;
@@ -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,2 @@
1
+ import type { RouteContext } from "../types";
2
+ export declare function useRouteNode(nodeName: string): RouteContext;
@@ -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,2 @@
1
+ import type { RouteUtils } from "@real-router/route-utils";
2
+ export declare const useRouteUtils: () => RouteUtils;
@@ -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,2 @@
1
+ import type { Router } from "@real-router/core";
2
+ export declare const useRouter: () => Router;
@@ -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,4 @@
1
+ import type { RouterTransitionSnapshot } from "@real-router/sources";
2
+ export declare function useRouterTransition(): {
3
+ readonly current: RouterTransitionSnapshot;
4
+ };
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export declare const ROUTER_KEY = "real-router:router";
2
+ export declare const NAVIGATOR_KEY = "real-router:navigator";
3
+ export declare const ROUTE_KEY = "real-router:route";
@@ -0,0 +1,3 @@
1
+ export const ROUTER_KEY = "real-router:router";
2
+ export const NAVIGATOR_KEY = "real-router:navigator";
3
+ export const ROUTE_KEY = "real-router:route";
@@ -0,0 +1,4 @@
1
+ import type { RouterSource } from "@real-router/sources";
2
+ export declare function createReactiveSource<T>(source: RouterSource<T>): {
3
+ readonly current: T;
4
+ };
@@ -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
+ }
@@ -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";
@@ -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}