@snapstrat/switchboard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +323 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/router/BrowserRouter.svelte +64 -0
- package/dist/router/BrowserRouter.svelte.d.ts +20 -0
- package/dist/router/Layout.svelte +79 -0
- package/dist/router/Layout.svelte.d.ts +13 -0
- package/dist/router/Link.svelte +27 -0
- package/dist/router/Link.svelte.d.ts +12 -0
- package/dist/router/PageInfo.svelte +23 -0
- package/dist/router/PageInfo.svelte.d.ts +8 -0
- package/dist/router/Route.svelte +59 -0
- package/dist/router/Route.svelte.d.ts +17 -0
- package/dist/router/Route404.svelte +43 -0
- package/dist/router/Route404.svelte.d.ts +9 -0
- package/dist/router/impl/index.d.ts +1 -0
- package/dist/router/impl/index.js +1 -0
- package/dist/router/impl/webRouter.svelte.d.ts +7 -0
- package/dist/router/impl/webRouter.svelte.js +172 -0
- package/dist/router/index.d.ts +13 -0
- package/dist/router/index.js +13 -0
- package/dist/router/internals/windowUtils.d.ts +2 -0
- package/dist/router/internals/windowUtils.js +29 -0
- package/dist/router/router.svelte.d.ts +178 -0
- package/dist/router/router.svelte.js +167 -0
- package/package.json +60 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SnapStrat Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1><img alt="SwitchBoard Logo" width="480" src="switchboard-logo.png"/></h1>
|
|
3
|
+
<p><i>A simple, extensible, component-based Svelte 5 SPA router.</i></p>
|
|
4
|
+
<img alt="License" src="https://img.shields.io/github/license/snapstrat/switchboard">
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
# Features
|
|
8
|
+
|
|
9
|
+
- A declarative, component-based method to declare routes.
|
|
10
|
+
- A simple API to programmatically navigate between routes.
|
|
11
|
+
- Layouts, nested routes, and route parameters.
|
|
12
|
+
- Svelte 5 compatibility, with TypeScript support.
|
|
13
|
+
|
|
14
|
+
# Why Switchboard?
|
|
15
|
+
|
|
16
|
+
Switchboard is designed to be a simple, extensible router for Svelte 5 applications.
|
|
17
|
+
Using composition and a declarative API, developers are able to quickly create SPA routes without the complexity of larger routing libraries, or dealing with SSR.
|
|
18
|
+
|
|
19
|
+
Switchboard is ideal for applications which:
|
|
20
|
+
- don't require server-side rendering. See [Use with SvelteKit](#use-with-sveltekit) for more info.
|
|
21
|
+
- want a simple, component-based routing solution.
|
|
22
|
+
- want to stray away from strictly file-based routing systems, such as SvelteKit's routing.
|
|
23
|
+
- want more flexibility in defining routes and layouts.
|
|
24
|
+
- want to be able to define routes in a way that fits their application's architecture.
|
|
25
|
+
|
|
26
|
+
# Getting Started
|
|
27
|
+
|
|
28
|
+
To install Switchboard, run:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
# npm
|
|
32
|
+
npm install @snapstrat/switchboard
|
|
33
|
+
# yarn
|
|
34
|
+
yarn add @snapstrat/switchboard
|
|
35
|
+
# pnpm
|
|
36
|
+
pnpm install @snapstrat/switchboard
|
|
37
|
+
# bun
|
|
38
|
+
bun install @snapstrat/switchboard
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
# Basic Usage
|
|
42
|
+
|
|
43
|
+
Switchboard exposes several components and functions to help you set up routing in your Svelte application.
|
|
44
|
+
|
|
45
|
+
## Creating a Router
|
|
46
|
+
Switchboard leaves the router setup to you. You can either use some form of global state management to hold a reference to it,
|
|
47
|
+
or create it directly in your root component.
|
|
48
|
+
|
|
49
|
+
Switchboard includes a `createWebRouter()` function to create a router instance that works with the browser's history API.
|
|
50
|
+
|
|
51
|
+
### Global Access
|
|
52
|
+
> main.ts
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import App from './App.svelte';
|
|
56
|
+
import { createWebRouter } from '@snapstrat/switchboard';
|
|
57
|
+
|
|
58
|
+
// now anything can import router from here to access the router!
|
|
59
|
+
export const router = createWebRouter();
|
|
60
|
+
|
|
61
|
+
mount(App, {
|
|
62
|
+
target: document.body,
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Creation in Root Component
|
|
67
|
+
> App.svelte
|
|
68
|
+
```svelte
|
|
69
|
+
<script lang="ts">
|
|
70
|
+
import { BrowserRouter, createWebRouter } from '$lib';
|
|
71
|
+
import PersistenceLayout from './PersistenceLayout.svelte';
|
|
72
|
+
|
|
73
|
+
// we can also create the router here!
|
|
74
|
+
const router = createWebRouter()
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<!-- all BrowserRouter needs is a Router instance, constructed from anywhere. -->
|
|
78
|
+
<!-- any component nested in BrowserRouter gets access to getRouter() using Svelte contexts. -->
|
|
79
|
+
<BrowserRouter {router}>
|
|
80
|
+
<!-- Your routes and layouts go here -->
|
|
81
|
+
</BrowserRouter>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Defining Routes
|
|
85
|
+
Switchboard is very unopinionated about how you define your routes.
|
|
86
|
+
The main building blocks are the `Route`, `Layout`, `PageInfo` and `Link` components.
|
|
87
|
+
|
|
88
|
+
```svelte
|
|
89
|
+
<script lang="ts">
|
|
90
|
+
import { BrowserRouter, Route, Link } from '@snapstrat/switchboard'
|
|
91
|
+
|
|
92
|
+
// maybe make a router here? or import one.
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<BrowserRouter router={your_router_instance}>
|
|
96
|
+
<Route path="/">
|
|
97
|
+
<h1>Home Page</h1>
|
|
98
|
+
</Route>
|
|
99
|
+
|
|
100
|
+
<Route path="/users/:id">
|
|
101
|
+
<h1>You're looking for a user with the id: {getRouteParams().id}</h1>
|
|
102
|
+
</Route>
|
|
103
|
+
|
|
104
|
+
<!-- You can use Route404 to catch all unmatched routes -->
|
|
105
|
+
<Route404>
|
|
106
|
+
<h1>404 - Page Not Found</h1>
|
|
107
|
+
</Route404>
|
|
108
|
+
</BrowserRouter>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Links
|
|
112
|
+
Links can be created using the `Link` component, which is a very, very thin wrapper around an `<a>` tag.
|
|
113
|
+
Alternatively, you can use the `Router.switchTo()` method to programmatically navigate between routes, or use
|
|
114
|
+
the `{@attach href('my/path')}` attachment to attach hrefs to any element.
|
|
115
|
+
|
|
116
|
+
```svelte
|
|
117
|
+
|
|
118
|
+
<script lang="ts">
|
|
119
|
+
import { Link } from '@snapstrat/switchboard';
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<!-- both are equivalent: -->
|
|
123
|
+
<Link href="/some/path">Go to Some Path</Link>
|
|
124
|
+
<a {@attach href("/some/path")}>Go to Some Path</a>
|
|
125
|
+
|
|
126
|
+
<!-- not recommended for accessibility, but possible: -->
|
|
127
|
+
<button on:click={() => router.switchTo('/some/path')}>
|
|
128
|
+
Go to Some Path
|
|
129
|
+
</button>
|
|
130
|
+
|
|
131
|
+
<!-- not recommended at all, this will reload the page and lose SPA state: -->
|
|
132
|
+
<a href="/some/path">Go to Some Path</a>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### PageInfo
|
|
136
|
+
|
|
137
|
+
The `PageInfo` component allows you to declaratively set metadata for the current page, such as the document title and meta tags.
|
|
138
|
+
|
|
139
|
+
```svelte
|
|
140
|
+
<script lang="ts">
|
|
141
|
+
import { PageInfo, Route } from '@snapstrat/switchboard';
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<Route path="/my-cool-page">
|
|
145
|
+
<PageInfo title="My Page Title" icon="/page-icon.ico" />
|
|
146
|
+
</Route>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Layouts
|
|
150
|
+
Layouts can be used to simplify routes that share common structure. State of a layout
|
|
151
|
+
will persist between __nested__ route changes.
|
|
152
|
+
For example, if you start at `/user/1`, modify some state in the layout, then navigate to `/user/1/profile`, the layout state will persist.
|
|
153
|
+
However, if you navigate to `/user/2`, or `/`, or `/any-other-route-outside-of-this-layout` the layout will be re-created and state will be destroyed since the route parameter changed.
|
|
154
|
+
|
|
155
|
+
```svelte
|
|
156
|
+
<BrowserRouter router={your_router_instance}>
|
|
157
|
+
<!-- Layouts can have parameters too! -->
|
|
158
|
+
<!-- Layouts have two snippets they take in: `routes` and `layout` -->
|
|
159
|
+
<Layout path="/user/:id">
|
|
160
|
+
<!-- layout() holds the common layout structure. -->
|
|
161
|
+
{#snippet layout(route: Snippet)}
|
|
162
|
+
<p>You're looking at something for {getRouteParams().id}!</h1>
|
|
163
|
+
{@render route()}
|
|
164
|
+
{/snippet}
|
|
165
|
+
|
|
166
|
+
<!-- routes() holds all nested routes -->
|
|
167
|
+
{#snippet routes()}
|
|
168
|
+
<Route path="profile">
|
|
169
|
+
<h2>This is the profile page for user {getRouteParams().id}!</h2>
|
|
170
|
+
</Route>
|
|
171
|
+
|
|
172
|
+
<Route path="settings">
|
|
173
|
+
<h2>This is the settings page for user {getRouteParams().id}!</h2>
|
|
174
|
+
</Route>
|
|
175
|
+
|
|
176
|
+
<!-- in this context, / will match for /user/:id/ -->
|
|
177
|
+
<Route path="/">
|
|
178
|
+
<h2>This is the settings page for user {getRouteParams().id}!</h2>
|
|
179
|
+
</Route>
|
|
180
|
+
|
|
181
|
+
<!-- You can also have a Route404 for unmatched nested routes. -->
|
|
182
|
+
<Route404>
|
|
183
|
+
<h1>404 - User Not Found</h1>
|
|
184
|
+
</Route404>
|
|
185
|
+
{/snippet}
|
|
186
|
+
</Layout>
|
|
187
|
+
|
|
188
|
+
<!-- You can use Route404 to catch all unmatched routes. -->
|
|
189
|
+
<Route404>
|
|
190
|
+
<h1>404 - Page Not Found</h1>
|
|
191
|
+
</Route404>
|
|
192
|
+
</BrowserRouter>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Use with SvelteKit
|
|
196
|
+
Switchboard is primarily designed for client-side routing in Svelte applications, and does NOT support server-side rendering (SSR), nor do we plan to ever support SSR.
|
|
197
|
+
|
|
198
|
+
However, you can still use Switchboard within a SvelteKit application for client-side routing needs. To do this, you'll need two main things:
|
|
199
|
+
1. A single, top-level SvelteKit route that will serve as the entry point for your Switchboard-powered SPA.
|
|
200
|
+
2. SSR disabled for that route.
|
|
201
|
+
|
|
202
|
+
In practice, this looks like:
|
|
203
|
+
```
|
|
204
|
+
- /src
|
|
205
|
+
- /routes
|
|
206
|
+
- /[...any] <-- catch-all used to route all traffic your only +page.svelte, containing your Switchboard app
|
|
207
|
+
- +page.svelte <-- your Switchboard app goes here
|
|
208
|
+
- +page.ts <-- disable SSR here with `export const ssr = false;`
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
In a normal Svelte application, you don't need to worry about any of this, and can just place a `BrowserRouter` at the root of your component tree (probably in `App.svelte`).
|
|
212
|
+
|
|
213
|
+
# Advanced Usage
|
|
214
|
+
Advanced usage of Switchboard comes from the natural composition of its components. You can create your own custom route components, layouts, and even
|
|
215
|
+
extend the router itself to add functionality.
|
|
216
|
+
|
|
217
|
+
Any Route or Layout can be declared anywhere that is within a `BrowserRouter`, or a `Layout` component, and will function as expected, no matter how these
|
|
218
|
+
are nested, constructed, composed, or imported.
|
|
219
|
+
|
|
220
|
+
## Custom Route Component
|
|
221
|
+
You can wrap around the `Route` component to create your own custom route components.
|
|
222
|
+
|
|
223
|
+
For example, you could create an authenticated route component that checks if a user is logged in before rendering the route.
|
|
224
|
+
|
|
225
|
+
> [!CAUTION]
|
|
226
|
+
> Note that this isn't inherently secure, as all routing is done client-side. Make sure that when dealing with sensitive data, you also have server-side checks in place.
|
|
227
|
+
|
|
228
|
+
> AuthenticatedRoute.svelte
|
|
229
|
+
```svelte
|
|
230
|
+
<script lang="ts">
|
|
231
|
+
import { Route, getRouter } from '@snapstrat/switchboard';
|
|
232
|
+
import { onMount } from 'svelte';
|
|
233
|
+
import { isUserAuthed } from './auth'; // your auth logic here
|
|
234
|
+
|
|
235
|
+
const { children, path } = $props();
|
|
236
|
+
</script>
|
|
237
|
+
|
|
238
|
+
<Route path={path}>
|
|
239
|
+
{#if isUserAuthed()}
|
|
240
|
+
{@render children()}
|
|
241
|
+
{:else}
|
|
242
|
+
<h1>Please log in to access this page.</h1>
|
|
243
|
+
{/if}
|
|
244
|
+
</Route>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
> App.svelte
|
|
248
|
+
```svelte
|
|
249
|
+
<script lang="ts">
|
|
250
|
+
import { BrowserRouter } from '@snapstrat/switchboard';
|
|
251
|
+
import AuthenticatedRoute from './AuthenticatedRoute.svelte';
|
|
252
|
+
</script>
|
|
253
|
+
|
|
254
|
+
<BrowserRouter router={your_router_instance}>
|
|
255
|
+
<AuthenticatedRoute path="/dashboard">
|
|
256
|
+
<h1>Welcome to your dashboard!</h1>
|
|
257
|
+
</AuthenticatedRoute>
|
|
258
|
+
</BrowserRouter>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Escaping Layouts
|
|
262
|
+
If you need to escape a layout for a specific route, you can set a custom 'container' on any route.
|
|
263
|
+
|
|
264
|
+
```svelte
|
|
265
|
+
<script lang="ts">
|
|
266
|
+
import { BrowserRouter, Layout, LayoutData, Route } from '@snapstrat/switchboard';
|
|
267
|
+
|
|
268
|
+
let otherLayout: LayoutData = $state();
|
|
269
|
+
</script>
|
|
270
|
+
|
|
271
|
+
<BrowserRouter router={your_router_instance}>
|
|
272
|
+
<Layout path="/app">
|
|
273
|
+
{#snippet layout(route: Snippet)}
|
|
274
|
+
<div class="app-layout">
|
|
275
|
+
<nav>...navigation...</nav>
|
|
276
|
+
<main>
|
|
277
|
+
{@render route()}
|
|
278
|
+
</main>
|
|
279
|
+
</div>
|
|
280
|
+
{/snippet}
|
|
281
|
+
|
|
282
|
+
{#snippet routes()}
|
|
283
|
+
<Route path="dashboard">
|
|
284
|
+
<h1>Dashboard</h1>
|
|
285
|
+
</Route>
|
|
286
|
+
|
|
287
|
+
<Route path="settings">
|
|
288
|
+
<h1>Settings</h1>
|
|
289
|
+
</Route>
|
|
290
|
+
|
|
291
|
+
<!--
|
|
292
|
+
container can either be a Router instance or another Layout.
|
|
293
|
+
setting to whatever your router instance is completely disables the layout.
|
|
294
|
+
-->
|
|
295
|
+
<Route path="/login" container={your_router_instance}>
|
|
296
|
+
<h1>Login Page</h1>
|
|
297
|
+
</Route>
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
<!--
|
|
301
|
+
setting container to another layout makes the route use that layout instead
|
|
302
|
+
of the current one.
|
|
303
|
+
|
|
304
|
+
note that this will NOT impact the path; this route still exists
|
|
305
|
+
at /app/landing-page, NOT /other/landing-page.
|
|
306
|
+
-->
|
|
307
|
+
<Route path="/landing-page" container={outerLayout}>
|
|
308
|
+
<h1>Landing Page</h1>
|
|
309
|
+
</Route>
|
|
310
|
+
{/snippet}
|
|
311
|
+
</Layout>
|
|
312
|
+
|
|
313
|
+
<Layout path="/other" bind:ref={otherLayout}>
|
|
314
|
+
<!-- another layout... -->
|
|
315
|
+
</Layout>
|
|
316
|
+
</BrowserRouter>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The same principle can be applied to Layouts nested within other Layouts as well, with the same `container` prop, and
|
|
320
|
+
same rules regarding how paths are resolved.
|
|
321
|
+
|
|
322
|
+
> [!WARNING]
|
|
323
|
+
> When escaping a layout, you **must** have the layout you're referencing defined above the layout you're escaping from.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
The BrowserRouter component is the main entry point for routing in an application using Switchboard.
|
|
4
|
+
It initializes the routes, listens for changes in the URL to switch routes accordingly,
|
|
5
|
+
and displays the appropriate component based on the current route.
|
|
6
|
+
-->
|
|
7
|
+
<script module lang="ts">
|
|
8
|
+
import type { Router } from './router.svelte';
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
|
|
11
|
+
export type PageRouterProps = {
|
|
12
|
+
/**
|
|
13
|
+
* The router instance that manages the application's routes.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link Router}
|
|
16
|
+
* @see {@link createWebRouter}
|
|
17
|
+
*/
|
|
18
|
+
router: Router;
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
};
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<script lang="ts">
|
|
24
|
+
import { onMount, tick } from 'svelte';
|
|
25
|
+
import { setRouterContext, type LayoutData, getAllLayouts, Route404 } from './index';
|
|
26
|
+
import { parseWindowSearchParams } from './internals/windowUtils';
|
|
27
|
+
|
|
28
|
+
let { router, children }: PageRouterProps = $props();
|
|
29
|
+
|
|
30
|
+
onMount(async () => {
|
|
31
|
+
await tick();
|
|
32
|
+
const params = parseWindowSearchParams();
|
|
33
|
+
|
|
34
|
+
router.switchTo(window.location.pathname, params, false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
setRouterContext(router);
|
|
38
|
+
|
|
39
|
+
const currentAppRoute = $derived(router.currentRoute?.route);
|
|
40
|
+
const layouts = $derived(getAllLayouts(currentAppRoute?.layout));
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<Route404>
|
|
44
|
+
<!-- This is blank because it's simply a fallback in case there is no 404 page -->
|
|
45
|
+
<svelte:fragment />
|
|
46
|
+
</Route404>
|
|
47
|
+
|
|
48
|
+
{@render children?.()}
|
|
49
|
+
|
|
50
|
+
{#snippet layoutRender(remaining: LayoutData[])}
|
|
51
|
+
{#if remaining.length === 0}
|
|
52
|
+
{@render currentAppRoute?.component?.()}
|
|
53
|
+
{:else}
|
|
54
|
+
{@const next = remaining[0]}
|
|
55
|
+
|
|
56
|
+
{#snippet renderer()}
|
|
57
|
+
{@render layoutRender(remaining.slice(1))}
|
|
58
|
+
{/snippet}
|
|
59
|
+
|
|
60
|
+
{@render next.renderer(renderer)}
|
|
61
|
+
{/if}
|
|
62
|
+
{/snippet}
|
|
63
|
+
|
|
64
|
+
{@render layoutRender(layouts)}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Router } from './router.svelte';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export type PageRouterProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The router instance that manages the application's routes.
|
|
6
|
+
*
|
|
7
|
+
* @see {@link Router}
|
|
8
|
+
* @see {@link createWebRouter}
|
|
9
|
+
*/
|
|
10
|
+
router: Router;
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The BrowserRouter component is the main entry point for routing in an application using Switchboard.
|
|
15
|
+
* It initializes the routes, listens for changes in the URL to switch routes accordingly,
|
|
16
|
+
* and displays the appropriate component based on the current route.
|
|
17
|
+
*/
|
|
18
|
+
declare const BrowserRouter: import("svelte").Component<PageRouterProps, {}, "">;
|
|
19
|
+
type BrowserRouter = ReturnType<typeof BrowserRouter>;
|
|
20
|
+
export default BrowserRouter;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { LayoutSnippet, RouteContainer } from './router.svelte';
|
|
5
|
+
|
|
6
|
+
export type LayoutProps = {
|
|
7
|
+
path: string;
|
|
8
|
+
layout: LayoutSnippet;
|
|
9
|
+
container?: RouteContainer;
|
|
10
|
+
routes: Snippet;
|
|
11
|
+
ref?: LayoutData;
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
type ApplicationRoute, getAllCanonicalLayouts,
|
|
19
|
+
getLayout,
|
|
20
|
+
getRouter,
|
|
21
|
+
type LayoutData,
|
|
22
|
+
RoutePath,
|
|
23
|
+
type Router
|
|
24
|
+
} from '..';
|
|
25
|
+
import { setLayoutContext } from '..';
|
|
26
|
+
import { onDestroy } from 'svelte';
|
|
27
|
+
|
|
28
|
+
let { path, layout: renderer, container, routes, ref = $bindable() }: LayoutProps = $props();
|
|
29
|
+
|
|
30
|
+
type LayoutInternals = { routes: ApplicationRoute[], _routes: ApplicationRoute[] }
|
|
31
|
+
|
|
32
|
+
let parent: LayoutData | undefined;
|
|
33
|
+
if (!container) {
|
|
34
|
+
parent = getLayout();
|
|
35
|
+
} else {
|
|
36
|
+
parent = container.isRouter() ? undefined : container as LayoutData;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const layoutData: LayoutData & LayoutInternals = {
|
|
40
|
+
_routes: [],
|
|
41
|
+
get routes() {
|
|
42
|
+
return this._routes.toReversed();
|
|
43
|
+
},
|
|
44
|
+
path: path ? RoutePath.normalizePath(path) : undefined,
|
|
45
|
+
parent,
|
|
46
|
+
canonicalParent: getLayout(),
|
|
47
|
+
registerRoute(route: ApplicationRoute) {
|
|
48
|
+
getRouter().registerRoute(route);
|
|
49
|
+
this._routes.push(route);
|
|
50
|
+
},
|
|
51
|
+
registerRoute404(route: ApplicationRoute) {
|
|
52
|
+
getRouter().registerRoute404(route);
|
|
53
|
+
this._routes.push(route);
|
|
54
|
+
},
|
|
55
|
+
unregisterRoute(route: ApplicationRoute) {
|
|
56
|
+
getRouter()?.unregisterRoute(route);
|
|
57
|
+
this._routes.splice(this.routes.indexOf(route), 1);
|
|
58
|
+
},
|
|
59
|
+
renderer,
|
|
60
|
+
get joinedPath() {
|
|
61
|
+
return getAllCanonicalLayouts(this)
|
|
62
|
+
.map((l) => l.path)
|
|
63
|
+
.filter((p) => p !== undefined && p !== '')
|
|
64
|
+
.join('/');
|
|
65
|
+
},
|
|
66
|
+
isRouter(): this is Router { return false; }
|
|
67
|
+
}
|
|
68
|
+
ref = layoutData;
|
|
69
|
+
|
|
70
|
+
setLayoutContext(layoutData);
|
|
71
|
+
|
|
72
|
+
onDestroy(() => {
|
|
73
|
+
setLayoutContext(layoutData.parent);
|
|
74
|
+
})
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
{@render routes()}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { LayoutSnippet, RouteContainer } from './router.svelte';
|
|
3
|
+
export type LayoutProps = {
|
|
4
|
+
path: string;
|
|
5
|
+
layout: LayoutSnippet;
|
|
6
|
+
container?: RouteContainer;
|
|
7
|
+
routes: Snippet;
|
|
8
|
+
ref?: LayoutData;
|
|
9
|
+
};
|
|
10
|
+
import { type LayoutData } from '..';
|
|
11
|
+
declare const Layout: import("svelte").Component<LayoutProps, {}, "ref">;
|
|
12
|
+
type Layout = ReturnType<typeof Layout>;
|
|
13
|
+
export default Layout;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Represents a link that can be used to navigate within the application.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script module lang="ts">
|
|
7
|
+
import { type Snippet } from 'svelte';
|
|
8
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
|
9
|
+
|
|
10
|
+
export type LinkProps = {
|
|
11
|
+
/**
|
|
12
|
+
* The content to display inside the link.
|
|
13
|
+
*/
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
} & HTMLAnchorAttributes;
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import { href } from './router.svelte';
|
|
21
|
+
|
|
22
|
+
const { children, ...attrs }: LinkProps = $props();
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<a {...attrs} {@attach href(attrs.href)}>
|
|
26
|
+
{@render children?.()}
|
|
27
|
+
</a>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
|
3
|
+
export type LinkProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The content to display inside the link.
|
|
6
|
+
*/
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
} & HTMLAnchorAttributes;
|
|
9
|
+
/** Represents a link that can be used to navigate within the application. */
|
|
10
|
+
declare const Link: import("svelte").Component<LinkProps, {}, "">;
|
|
11
|
+
type Link = ReturnType<typeof Link>;
|
|
12
|
+
export default Link;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Provides a page title and icon for the current route.
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts" module>
|
|
6
|
+
export type PageInfoProps = {
|
|
7
|
+
title?: string;
|
|
8
|
+
icon?: string;
|
|
9
|
+
};
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { getRouter } from '..';
|
|
14
|
+
|
|
15
|
+
const { title, icon }: PageInfoProps = $props();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<svelte:head>
|
|
19
|
+
<title>{title ?? getRouter().options.defaultTitle}</title>
|
|
20
|
+
{#if icon !== undefined}
|
|
21
|
+
<link rel="icon" href={icon} />
|
|
22
|
+
{/if}
|
|
23
|
+
</svelte:head>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type PageInfoProps = {
|
|
2
|
+
title?: string;
|
|
3
|
+
icon?: string;
|
|
4
|
+
};
|
|
5
|
+
/** Provides a page title and icon for the current route. */
|
|
6
|
+
declare const PageInfo: import("svelte").Component<PageInfoProps, {}, "">;
|
|
7
|
+
type PageInfo = ReturnType<typeof PageInfo>;
|
|
8
|
+
export default PageInfo;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
The Route component is used to define a route in the application.
|
|
4
|
+
It registers the route with the router when mounted and unregisters it when destroyed.
|
|
5
|
+
Once registered, the route can be navigated to using the router's switchTo method.
|
|
6
|
+
|
|
7
|
+
The children of this component make the content that will be displayed when the route is active.
|
|
8
|
+
-->
|
|
9
|
+
<script lang="ts" module>
|
|
10
|
+
import type { Snippet } from 'svelte';
|
|
11
|
+
import type { RouteContainer } from './router.svelte';
|
|
12
|
+
|
|
13
|
+
export type RouteProps = {
|
|
14
|
+
path: string;
|
|
15
|
+
container?: RouteContainer;
|
|
16
|
+
children?: Snippet;
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import { onDestroy, onMount } from 'svelte';
|
|
22
|
+
import {
|
|
23
|
+
type ApplicationRoute, getLayout, getRouteContainer, getRouter, type LayoutData, RoutePath
|
|
24
|
+
} from '..';
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
path,
|
|
28
|
+
container,
|
|
29
|
+
children,
|
|
30
|
+
}: RouteProps = $props();
|
|
31
|
+
|
|
32
|
+
let router = getRouter();
|
|
33
|
+
|
|
34
|
+
let route : ApplicationRoute | undefined;
|
|
35
|
+
|
|
36
|
+
onMount(() => {
|
|
37
|
+
const layout = getLayout();
|
|
38
|
+
|
|
39
|
+
container ??= getRouteContainer();
|
|
40
|
+
|
|
41
|
+
const layoutPath = layout?.joinedPath ?? '';
|
|
42
|
+
|
|
43
|
+
const combinedPath = RoutePath.concatPaths(layoutPath, path);
|
|
44
|
+
|
|
45
|
+
route = {
|
|
46
|
+
path: RoutePath.fromString(combinedPath),
|
|
47
|
+
component: children,
|
|
48
|
+
layout: container.isRouter() ? undefined : container as LayoutData
|
|
49
|
+
};
|
|
50
|
+
container.registerRoute(route);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
onDestroy(() => {
|
|
54
|
+
if (route) {
|
|
55
|
+
const container = getLayout() ?? router;
|
|
56
|
+
container.unregisterRoute(route);
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { RouteContainer } from './router.svelte';
|
|
3
|
+
export type RouteProps = {
|
|
4
|
+
path: string;
|
|
5
|
+
container?: RouteContainer;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The Route component is used to define a route in the application.
|
|
10
|
+
* It registers the route with the router when mounted and unregisters it when destroyed.
|
|
11
|
+
* Once registered, the route can be navigated to using the router's switchTo method.
|
|
12
|
+
*
|
|
13
|
+
* The children of this component make the content that will be displayed when the route is active.
|
|
14
|
+
*/
|
|
15
|
+
declare const Route: import("svelte").Component<RouteProps, {}, "">;
|
|
16
|
+
type Route = ReturnType<typeof Route>;
|
|
17
|
+
export default Route;
|