@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
package/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # @real-router/svelte
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](../../LICENSE)
4
+
5
+ > Svelte 5 integration for [Real-Router](https://github.com/greydragon888/real-router) — composables, components, and context providers.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @real-router/svelte @real-router/core @real-router/browser-plugin
11
+ ```
12
+
13
+ **Peer dependency:** `svelte` >= 5.7.0
14
+
15
+ ## Quick Start
16
+
17
+ ```svelte
18
+ <!-- App.svelte -->
19
+ <script lang="ts">
20
+ import { createRouter } from "@real-router/core";
21
+ import { browserPluginFactory } from "@real-router/browser-plugin";
22
+ import { RouterProvider, RouteView, Link } from "@real-router/svelte";
23
+ import HomePage from "./HomePage.svelte";
24
+ import UsersPage from "./UsersPage.svelte";
25
+ import NotFoundPage from "./NotFoundPage.svelte";
26
+
27
+ const router = createRouter([
28
+ { name: "home", path: "/" },
29
+ {
30
+ name: "users",
31
+ path: "/users",
32
+ children: [{ name: "profile", path: "/:id" }],
33
+ },
34
+ ]);
35
+
36
+ router.usePlugin(browserPluginFactory());
37
+ router.start();
38
+ </script>
39
+
40
+ <RouterProvider {router}>
41
+ <nav>
42
+ <Link routeName="home">Home</Link>
43
+ <Link routeName="users">Users</Link>
44
+ </nav>
45
+
46
+ <RouteView nodeName="">
47
+ {#snippet home()}
48
+ <HomePage />
49
+ {/snippet}
50
+ {#snippet users()}
51
+ <UsersPage />
52
+ {/snippet}
53
+ {#snippet notFound()}
54
+ <NotFoundPage />
55
+ {/snippet}
56
+ </RouteView>
57
+ </RouterProvider>
58
+ ```
59
+
60
+ ## Composables
61
+
62
+ All composables must be called during component initialization (not inside `$effect` or event handlers). Reactive composables return `{ current: T }` getter objects — read `.current` inside a template or `$derived` to register a reactive dependency.
63
+
64
+ | Composable | Returns | Reactive? |
65
+ | ----------------------- | --------------------------------------------------------------- | ------------------------------------------ |
66
+ | `useRouter()` | `Router` | Never |
67
+ | `useNavigator()` | `Navigator` | Never (stable ref, safe to use directly) |
68
+ | `useRoute()` | `{ navigator, route: { current }, previousRoute: { current } }` | `.current` on every navigation |
69
+ | `useRouteNode(name)` | `{ navigator, route: { current }, previousRoute: { current } }` | `.current` when node activates/deactivates |
70
+ | `useRouteUtils()` | `RouteUtils` | Never |
71
+ | `useRouterTransition()` | `{ current: RouterTransitionSnapshot }` | `.current` on transition start/end |
72
+
73
+ ```svelte
74
+ <!-- useRouteNode — updates only when "users.*" changes -->
75
+ <script lang="ts">
76
+ import { useRouteNode } from "@real-router/svelte";
77
+
78
+ const { route } = useRouteNode("users");
79
+ </script>
80
+
81
+ {#if route.current}
82
+ {#if route.current.name === "users"}
83
+ <UsersList />
84
+ {:else if route.current.name === "users.profile"}
85
+ <UserProfile id={route.current.params.id} />
86
+ {/if}
87
+ {/if}
88
+ ```
89
+
90
+ ```svelte
91
+ <!-- useNavigator — stable reference, never reactive -->
92
+ <script lang="ts">
93
+ import { useNavigator } from "@real-router/svelte";
94
+
95
+ const navigator = useNavigator();
96
+ </script>
97
+
98
+ <button onclick={() => navigator.navigate("home")}>Back</button>
99
+ ```
100
+
101
+ ```svelte
102
+ <!-- useRouterTransition — progress bars, loading states -->
103
+ <script lang="ts">
104
+ import { useRouterTransition } from "@real-router/svelte";
105
+
106
+ const transition = useRouterTransition();
107
+ </script>
108
+
109
+ {#if transition.current.isTransitioning}
110
+ <div class="progress-bar"></div>
111
+ {/if}
112
+ ```
113
+
114
+ ## Components
115
+
116
+ ### `<Link>`
117
+
118
+ Navigation link with automatic active state detection. Uses `$derived` for href and class — only the DOM attributes update when active state changes.
119
+
120
+ ```svelte
121
+ <Link
122
+ routeName="users.profile"
123
+ routeParams={{ id: "123" }}
124
+ activeClassName="active"
125
+ activeStrict={false}
126
+ ignoreQueryParams={true}
127
+ routeOptions={{ replace: true }}
128
+ >
129
+ View Profile
130
+ </Link>
131
+ ```
132
+
133
+ **Props:**
134
+
135
+ | Prop | Type | Default | Description |
136
+ | ------------------- | ------------------- | ----------- | --------------------------------------- |
137
+ | `routeName` | `string` | required | Target route name |
138
+ | `routeParams` | `Params` | `{}` | Route parameters |
139
+ | `routeOptions` | `NavigationOptions` | `{}` | Navigation options (replace, etc.) |
140
+ | `class` | `string` | `undefined` | CSS class |
141
+ | `activeClassName` | `string` | `"active"` | Class added when route is active |
142
+ | `activeStrict` | `boolean` | `false` | Exact match only (no ancestor matching) |
143
+ | `ignoreQueryParams` | `boolean` | `true` | Query params don't affect active state |
144
+ | `target` | `string` | `undefined` | Link target (`_blank`, etc.) |
145
+
146
+ All other props are spread onto the `<a>` element.
147
+
148
+ ### `<Lazy>`
149
+
150
+ Lazy-load route content with a fallback component while loading. Useful for code-splitting and dynamic imports.
151
+
152
+ ```svelte
153
+ <RouteView nodeName="">
154
+ {#snippet dashboard()}
155
+ <Lazy loader={() => import('./Dashboard.svelte')} fallback={Spinner} />
156
+ {/snippet}
157
+ </RouteView>
158
+ ```
159
+
160
+ **Props:**
161
+
162
+ | Prop | Type | Default | Description |
163
+ | ---------- | --------------------------------------- | ----------- | ----------------------------------------- |
164
+ | `loader` | `() => Promise<{ default: Component }>` | required | Async function that imports the component |
165
+ | `fallback` | `Component` | `undefined` | Component to render while loading |
166
+
167
+ The `loader` function should return a dynamic import promise. The `fallback` component is rendered while the import is pending. If an error occurs during loading, an error message is displayed.
168
+
169
+ ### `<RouteView>`
170
+
171
+ Declarative route matching. Renders the snippet whose name matches the active route segment.
172
+
173
+ ```svelte
174
+ <RouteView nodeName="">
175
+ {#snippet users()}
176
+ <UsersPage />
177
+ {/snippet}
178
+ {#snippet settings()}
179
+ <SettingsPage />
180
+ {/snippet}
181
+ {#snippet notFound()}
182
+ <NotFoundPage />
183
+ {/snippet}
184
+ </RouteView>
185
+ ```
186
+
187
+ **Props:**
188
+
189
+ | Prop | Type | Description |
190
+ | ----------- | --------- | ------------------------------------------- |
191
+ | `nodeName` | `string` | Route node to match against. `""` for root. |
192
+ | `notFound` | `Snippet` | Rendered when route is `UNKNOWN_ROUTE` |
193
+ | `[segment]` | `Snippet` | Named snippet matching a route segment |
194
+
195
+ Snippet names must be valid JavaScript identifiers and match the first segment of the active route after `nodeName`. For a route `users.profile` with `nodeName=""`, the snippet named `users` matches.
196
+
197
+ > **Note:** `keepAlive` is not supported. Svelte has no equivalent of React's `<Activity>` API or Vue's `<KeepAlive>`. Components are destroyed when navigating away.
198
+
199
+ ## Actions
200
+
201
+ ### `createLinkAction`
202
+
203
+ Factory function that creates a low-level action for adding navigation to any element. Must be called during component initialization to capture the router context.
204
+
205
+ ```svelte
206
+ <script lang="ts">
207
+ import { createLinkAction } from "@real-router/svelte";
208
+
209
+ const link = createLinkAction();
210
+ </script>
211
+
212
+ <a use:link={{ name: "users.profile", params: { id: "123" } }}>
213
+ User Profile
214
+ </a>
215
+
216
+ <button use:link={{ name: "home" }}>
217
+ Go Home
218
+ </button>
219
+
220
+ <div use:link={{ name: "settings", params: {}, options: { replace: true } }} role="link" tabindex="0">
221
+ Settings
222
+ </div>
223
+ ```
224
+
225
+ **Parameters:**
226
+
227
+ | Property | Type | Default | Description |
228
+ | --------- | -------- | ------- | ---------------------------------- |
229
+ | `name` | `string` | — | Target route name |
230
+ | `params` | `Params` | `{}` | Route parameters |
231
+ | `options` | `object` | `{}` | Navigation options (replace, etc.) |
232
+
233
+ The action automatically adds `role="link"` + `tabindex="0"` to non-interactive elements for accessibility. It handles click events and Enter key navigation.
234
+
235
+ ## Reactive Primitives
236
+
237
+ ### `createReactiveSource`
238
+
239
+ Public building block that bridges any `RouterSource<T>` to Svelte's reactivity system. Returns a `{ current: T }` getter object that lazily subscribes via `createSubscriber`.
240
+
241
+ ```svelte
242
+ <script lang="ts">
243
+ import { createReactiveSource, useRouter } from "@real-router/svelte";
244
+ import { createActiveRouteSource } from "@real-router/sources";
245
+
246
+ const router = useRouter();
247
+ const isActive = createReactiveSource(
248
+ createActiveRouteSource(router, "users.profile", {})
249
+ );
250
+ </script>
251
+
252
+ {#if isActive.current}
253
+ <span class="badge">Active</span>
254
+ {/if}
255
+ ```
256
+
257
+ Use cases: custom active route indicators, domain-specific composables, integration with other reactive primitives.
258
+
259
+ ## Svelte-Specific Patterns
260
+
261
+ ### Reading .current in Reactive Contexts
262
+
263
+ Unlike Vue (ShallowRefs) or Solid (Accessors), Svelte composables return `{ current: T }` getter objects. Read `.current` inside a template or `$derived` to register a reactive dependency:
264
+
265
+ ```svelte
266
+ <script lang="ts">
267
+ import { useRoute } from "@real-router/svelte";
268
+
269
+ const { route } = useRoute();
270
+
271
+ // CORRECT — $derived registers a reactive dependency
272
+ const routeName = $derived(route.current?.name);
273
+
274
+ // WRONG — read outside reactive context, no subscription
275
+ console.log(route.current?.name);
276
+ </script>
277
+
278
+ <!-- CORRECT — template is a reactive context -->
279
+ <p>{route.current?.name}</p>
280
+ ```
281
+
282
+ ### Reacting to Route Changes
283
+
284
+ Use `$effect` to run side effects when the route changes:
285
+
286
+ ```svelte
287
+ <script lang="ts">
288
+ import { useRouteNode } from "@real-router/svelte";
289
+
290
+ const { route } = useRouteNode("users");
291
+
292
+ $effect(() => {
293
+ if (route.current) {
294
+ document.title = `Users — ${route.current.params.id ?? "list"}`;
295
+ }
296
+ });
297
+ </script>
298
+ ```
299
+
300
+ ### Nested RouteView
301
+
302
+ For nested routes, use `RouteView` at each level with the appropriate `nodeName`:
303
+
304
+ ```svelte
305
+ <!-- Top-level: matches "users", "settings", etc. -->
306
+ <RouteView nodeName="">
307
+ {#snippet users()}
308
+ <!-- Nested: matches "users.list", "users.profile", etc. -->
309
+ <RouteView nodeName="users">
310
+ {#snippet list()}
311
+ <UsersList />
312
+ {/snippet}
313
+ {#snippet profile()}
314
+ <UserProfile />
315
+ {/snippet}
316
+ </RouteView>
317
+ {/snippet}
318
+ </RouteView>
319
+ ```
320
+
321
+ ## Accessibility
322
+
323
+ Enable screen reader announcements for route changes:
324
+
325
+ ```svelte
326
+ <RouterProvider {router} announceNavigation>
327
+ {/* Your app */}
328
+ </RouterProvider>
329
+ ```
330
+
331
+ When enabled, a visually hidden `aria-live` region announces each navigation. Focus moves to the first `<h1>` on the new page. See [Accessibility guide](https://github.com/greydragon888/real-router/wiki/Accessibility) for details.
332
+
333
+ ## Documentation
334
+
335
+ Full documentation: [Wiki](https://github.com/greydragon888/real-router/wiki)
336
+
337
+ - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [Link](https://github.com/greydragon888/real-router/wiki/Link)
338
+ - [useRouter](https://github.com/greydragon888/real-router/wiki/useRouter) · [useRoute](https://github.com/greydragon888/real-router/wiki/useRoute) · [useRouteNode](https://github.com/greydragon888/real-router/wiki/useRouteNode) · [useNavigator](https://github.com/greydragon888/real-router/wiki/useNavigator) · [useRouteUtils](https://github.com/greydragon888/real-router/wiki/useRouteUtils) · [useRouterTransition](https://github.com/greydragon888/real-router/wiki/useRouterTransition)
339
+
340
+ ## Related Packages
341
+
342
+ | Package | Description |
343
+ | ---------------------------------------------------------------------------------------- | ------------------------------------ |
344
+ | [@real-router/core](https://www.npmjs.com/package/@real-router/core) | Core router (required dependency) |
345
+ | [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) | Browser History API integration |
346
+ | [@real-router/sources](https://www.npmjs.com/package/@real-router/sources) | Subscription layer (used internally) |
347
+ | [@real-router/route-utils](https://www.npmjs.com/package/@real-router/route-utils) | Route tree queries (`useRouteUtils`) |
348
+
349
+ ## Contributing
350
+
351
+ See [contributing guidelines](../../CONTRIBUTING.md) for development setup and PR process.
352
+
353
+ ## License
354
+
355
+ [MIT](../../LICENSE) © [Oleg Ivanov](https://github.com/greydragon888)
@@ -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,10 @@
1
+ import type { Router } from "@real-router/core";
2
+ import type { Snippet } from "svelte";
3
+ type $$ComponentProps = {
4
+ router: Router;
5
+ children: Snippet;
6
+ announceNavigation?: boolean;
7
+ };
8
+ declare const RouterProvider: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type RouterProvider = ReturnType<typeof RouterProvider>;
10
+ export default RouterProvider;
@@ -0,0 +1,26 @@
1
+ import type { ActionReturn } from "svelte/action";
2
+ import type { Params, NavigationOptions } from "@real-router/core";
3
+ export interface LinkActionParams {
4
+ name: string;
5
+ params?: Params;
6
+ options?: NavigationOptions;
7
+ }
8
+ /**
9
+ * Factory function that captures router context during component initialization.
10
+ * Must be called during component init (not inside event handlers or effects).
11
+ *
12
+ * @returns Action function for use with `use:` directive
13
+ * @throws Error if called outside RouterProvider
14
+ *
15
+ * @example
16
+ * ```svelte
17
+ * <script>
18
+ * import { createLinkAction } from '@real-router/svelte';
19
+ * const link = createLinkAction();
20
+ * </script>
21
+ *
22
+ * <button use:link={{ name: 'home' }}>Home</button>
23
+ * <a use:link={{ name: 'users', params: { id: '123' } }}>User Profile</a>
24
+ * ```
25
+ */
26
+ export declare function createLinkAction(): (node: HTMLElement, params: LinkActionParams) => ActionReturn<LinkActionParams>;
@@ -0,0 +1,61 @@
1
+ import { getContext } from "svelte";
2
+ import { ROUTER_KEY } from "../context";
3
+ import { shouldNavigate, applyLinkA11y } from "dom-utils";
4
+ /**
5
+ * Factory function that captures router context during component initialization.
6
+ * Must be called during component init (not inside event handlers or effects).
7
+ *
8
+ * @returns Action function for use with `use:` directive
9
+ * @throws Error if called outside RouterProvider
10
+ *
11
+ * @example
12
+ * ```svelte
13
+ * <script>
14
+ * import { createLinkAction } from '@real-router/svelte';
15
+ * const link = createLinkAction();
16
+ * </script>
17
+ *
18
+ * <button use:link={{ name: 'home' }}>Home</button>
19
+ * <a use:link={{ name: 'users', params: { id: '123' } }}>User Profile</a>
20
+ * ```
21
+ */
22
+ export function createLinkAction() {
23
+ const router = getContext(ROUTER_KEY);
24
+ if (!router) {
25
+ throw new Error("createLinkAction must be called inside a RouterProvider");
26
+ }
27
+ return function link(node, params) {
28
+ let currentParams = params;
29
+ applyLinkA11y(node);
30
+ function handleClick(evt) {
31
+ if (!shouldNavigate(evt))
32
+ return;
33
+ evt.preventDefault();
34
+ // router is guaranteed to exist due to check in factory
35
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
36
+ router
37
+ .navigate(currentParams.name, currentParams.params ?? {}, currentParams.options ?? {})
38
+ .catch(() => { });
39
+ }
40
+ function handleKeyDown(evt) {
41
+ if (evt.key === "Enter" && !(node instanceof HTMLButtonElement)) {
42
+ // router is guaranteed to exist due to check in factory
43
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
44
+ router
45
+ .navigate(currentParams.name, currentParams.params ?? {}, currentParams.options ?? {})
46
+ .catch(() => { });
47
+ }
48
+ }
49
+ node.addEventListener("click", handleClick);
50
+ node.addEventListener("keydown", handleKeyDown);
51
+ return {
52
+ update(newParams) {
53
+ currentParams = newParams;
54
+ },
55
+ destroy() {
56
+ node.removeEventListener("click", handleClick);
57
+ node.removeEventListener("keydown", handleKeyDown);
58
+ },
59
+ };
60
+ };
61
+ }
@@ -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,10 @@
1
+ import type { Component } from "svelte";
2
+ type $$ComponentProps = {
3
+ loader: () => Promise<{
4
+ default: Component;
5
+ }>;
6
+ fallback?: Component | undefined;
7
+ };
8
+ declare const Lazy: Component<$$ComponentProps, {}, "">;
9
+ type Lazy = ReturnType<typeof Lazy>;
10
+ export default Lazy;
@@ -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,18 @@
1
+ import type { NavigationOptions, Params } from "@real-router/core";
2
+ import type { Snippet } from "svelte";
3
+ type $$ComponentProps = {
4
+ routeName: string;
5
+ routeParams?: Params;
6
+ routeOptions?: NavigationOptions;
7
+ class?: string;
8
+ activeClassName?: string;
9
+ activeStrict?: boolean;
10
+ ignoreQueryParams?: boolean;
11
+ target?: string;
12
+ children?: Snippet;
13
+ onclick?: (evt: MouseEvent) => void;
14
+ [key: string]: unknown;
15
+ };
16
+ declare const Link: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type Link = ReturnType<typeof Link>;
18
+ export default Link;