svelte-crumbs 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/README.md +247 -0
- package/dist/breadcrumbs/create-breadcrumbs.svelte.d.ts +19 -0
- package/dist/breadcrumbs/create-breadcrumbs.svelte.js +35 -0
- package/dist/demo/docs.remote.d.ts +1 -0
- package/dist/demo/docs.remote.js +11 -0
- package/dist/demo/greeting.remote.d.ts +2 -0
- package/dist/demo/greeting.remote.js +12 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/routing/build-breadcrumb-map.d.ts +6 -0
- package/dist/routing/build-breadcrumb-map.js +20 -0
- package/dist/routing/get-resolvers-for-route.d.ts +6 -0
- package/dist/routing/get-resolvers-for-route.js +23 -0
- package/dist/routing/match-route.d.ts +11 -0
- package/dist/routing/match-route.js +26 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# svelte-crumbs
|
|
2
|
+
|
|
3
|
+
Automatic, SSR-ready breadcrumbs for SvelteKit via route-level metadata exports. Zero config, fully reactive, server-rendered with top-level await.
|
|
4
|
+
|
|
5
|
+
**Svelte 5 + SvelteKit 2 only. Data layer only — bring your own rendering.**
|
|
6
|
+
|
|
7
|
+
**[Documentation & Live Demo](https://svelte-crumbs.vercel.app/)**
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
### 1. Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install svelte-crumbs
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 2. Export breadcrumbs from your routes
|
|
18
|
+
|
|
19
|
+
```svelte
|
|
20
|
+
<!-- src/routes/products/+page.svelte -->
|
|
21
|
+
<script lang="ts" module>
|
|
22
|
+
import type { BreadcrumbMeta } from 'svelte-crumbs';
|
|
23
|
+
|
|
24
|
+
export const breadcrumb: BreadcrumbMeta = async () => ({
|
|
25
|
+
label: 'Products'
|
|
26
|
+
});
|
|
27
|
+
</script>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 3. Render in your layout
|
|
31
|
+
|
|
32
|
+
```svelte
|
|
33
|
+
<!-- src/routes/+layout.svelte -->
|
|
34
|
+
<script lang="ts">
|
|
35
|
+
import { createBreadcrumbs } from 'svelte-crumbs';
|
|
36
|
+
|
|
37
|
+
const getBreadcrumbs = createBreadcrumbs();
|
|
38
|
+
const crumbs = $derived(await getBreadcrumbs());
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<nav>
|
|
42
|
+
{#each crumbs as crumb, i}
|
|
43
|
+
{#if i > 0} / {/if}
|
|
44
|
+
<a href={crumb.url}>{crumb.label}</a>
|
|
45
|
+
{/each}
|
|
46
|
+
</nav>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
No `{#await}` blocks needed. Breadcrumbs resolve during SSR and update reactively on client navigation.
|
|
50
|
+
|
|
51
|
+
## Examples
|
|
52
|
+
|
|
53
|
+
### Static breadcrumb
|
|
54
|
+
|
|
55
|
+
```svelte
|
|
56
|
+
<script lang="ts" module>
|
|
57
|
+
import type { BreadcrumbMeta } from 'svelte-crumbs';
|
|
58
|
+
|
|
59
|
+
export const breadcrumb: BreadcrumbMeta = async () => ({
|
|
60
|
+
label: 'Settings'
|
|
61
|
+
});
|
|
62
|
+
</script>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### From load data
|
|
66
|
+
|
|
67
|
+
The breadcrumb resolver receives the full `page` object, including `page.data`. Use `+layout.server.ts` (not `+page.server.ts`) so the data is available to child routes' breadcrumbs too:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// src/routes/products/[id]/+layout.server.ts
|
|
71
|
+
export async function load({ params }) {
|
|
72
|
+
const product = await db.products.find(params.id);
|
|
73
|
+
return { product };
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```svelte
|
|
78
|
+
<!-- src/routes/products/[id]/+page.svelte -->
|
|
79
|
+
<script lang="ts" module>
|
|
80
|
+
import type { BreadcrumbMeta } from 'svelte-crumbs';
|
|
81
|
+
|
|
82
|
+
export const breadcrumb: BreadcrumbMeta = async (page) => ({
|
|
83
|
+
label: page.data.product.name
|
|
84
|
+
});
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<script lang="ts">
|
|
88
|
+
let { data } = $props();
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<h1>{data.product.name}</h1>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **Why `+layout.server.ts`?** Breadcrumb resolvers run for every segment of the URL. When visiting `/products/42/edit`, the resolver for `/products/[id]` fires too. If you put the load in `+page.server.ts`, `page.data` on child routes won't have `product` — layout data cascades down, page data doesn't.
|
|
95
|
+
|
|
96
|
+
### From a remote function
|
|
97
|
+
|
|
98
|
+
Breadcrumb resolvers can call [remote functions](https://svelte.dev/docs/kit/remote-functions) that run on the server:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// src/lib/products.remote.ts
|
|
102
|
+
import { query } from '$app/server';
|
|
103
|
+
|
|
104
|
+
export const getProductName = query('unchecked', async (id: string) => {
|
|
105
|
+
const product = await db.products.find(id);
|
|
106
|
+
return product.name;
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```svelte
|
|
111
|
+
<!-- src/routes/products/[id]/+page.svelte -->
|
|
112
|
+
<script lang="ts" module>
|
|
113
|
+
import type { BreadcrumbMeta } from 'svelte-crumbs';
|
|
114
|
+
import { getProductName } from '$lib/products.remote';
|
|
115
|
+
|
|
116
|
+
export const breadcrumb: BreadcrumbMeta = async (page) => ({
|
|
117
|
+
label: await getProductName(page.params.id ?? '')
|
|
118
|
+
});
|
|
119
|
+
</script>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Multi-route breadcrumb
|
|
123
|
+
|
|
124
|
+
For dynamic routes that map to known paths:
|
|
125
|
+
|
|
126
|
+
```svelte
|
|
127
|
+
<script lang="ts" module>
|
|
128
|
+
import type { BreadcrumbMeta } from 'svelte-crumbs';
|
|
129
|
+
|
|
130
|
+
export const breadcrumb: BreadcrumbMeta = {
|
|
131
|
+
routes: {
|
|
132
|
+
'/docs/getting-started': async () => ({ label: 'Getting Started' }),
|
|
133
|
+
'/docs/api-reference': async () => ({ label: 'API Reference' })
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
</script>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### With icon
|
|
140
|
+
|
|
141
|
+
```svelte
|
|
142
|
+
<script lang="ts" module>
|
|
143
|
+
import type { BreadcrumbMeta } from 'svelte-crumbs';
|
|
144
|
+
import HomeIcon from './HomeIcon.svelte';
|
|
145
|
+
|
|
146
|
+
export const breadcrumb: BreadcrumbMeta = async () => ({
|
|
147
|
+
label: 'Home',
|
|
148
|
+
icon: HomeIcon
|
|
149
|
+
});
|
|
150
|
+
</script>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Custom rendering
|
|
154
|
+
|
|
155
|
+
Since `svelte-crumbs` only provides data, you render however you want:
|
|
156
|
+
|
|
157
|
+
```svelte
|
|
158
|
+
<script lang="ts">
|
|
159
|
+
import { createBreadcrumbs } from 'svelte-crumbs';
|
|
160
|
+
|
|
161
|
+
const getBreadcrumbs = createBreadcrumbs();
|
|
162
|
+
const crumbs = $derived(await getBreadcrumbs());
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<ol class="breadcrumb-list">
|
|
166
|
+
{#each crumbs as crumb}
|
|
167
|
+
<li>
|
|
168
|
+
{#if crumb.icon}
|
|
169
|
+
{@const Icon = crumb.icon}
|
|
170
|
+
<Icon />
|
|
171
|
+
{/if}
|
|
172
|
+
<a href={crumb.url}>{crumb.label}</a>
|
|
173
|
+
</li>
|
|
174
|
+
{/each}
|
|
175
|
+
</ol>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## API Reference
|
|
179
|
+
|
|
180
|
+
### `createBreadcrumbs()`
|
|
181
|
+
|
|
182
|
+
Creates a reactive breadcrumb resolver. Returns a getter function `() => Promise<Breadcrumb[]>`.
|
|
183
|
+
|
|
184
|
+
Call `createBreadcrumbs()` once to set up the reactive state, then use the returned getter inside `$derived(await ...)` to get breadcrumbs that update on navigation and resolve during SSR.
|
|
185
|
+
|
|
186
|
+
### Types
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// What you export from +page.svelte
|
|
190
|
+
type BreadcrumbMeta = BreadcrumbResolver | { routes: Record<string, BreadcrumbResolver> };
|
|
191
|
+
|
|
192
|
+
// Resolver function
|
|
193
|
+
type BreadcrumbResolver = (page: Page) => Promise<BreadcrumbData | undefined>;
|
|
194
|
+
|
|
195
|
+
// Data for one breadcrumb
|
|
196
|
+
type BreadcrumbData = { label: string; icon?: Component<any> };
|
|
197
|
+
|
|
198
|
+
// Resolved breadcrumb with URL
|
|
199
|
+
type Breadcrumb = BreadcrumbData & { url: string };
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Utility exports
|
|
203
|
+
|
|
204
|
+
- `buildBreadcrumbMap()` — manually build the route-to-resolver map
|
|
205
|
+
- `filePathToRoute(filePath)` — convert glob file path to route
|
|
206
|
+
- `matchDynamicRoute(map, route)` — match a concrete path against dynamic patterns
|
|
207
|
+
- `getResolversForRoute(map, route)` — collect resolvers for a given route path
|
|
208
|
+
|
|
209
|
+
## How It Works
|
|
210
|
+
|
|
211
|
+
1. `import.meta.glob` eagerly imports all `+page.svelte` files at build time
|
|
212
|
+
2. Each file's `breadcrumb` export is collected into a `Map<route, resolver>`
|
|
213
|
+
3. Route groups like `(app)` are stripped from paths
|
|
214
|
+
4. On navigation, the root (`/`) resolver is checked first, then each segment is walked from left to right with dynamic `[param]` matching
|
|
215
|
+
5. Matching resolvers run in parallel, producing the final breadcrumb array
|
|
216
|
+
6. On SSR, top-level `await` ensures breadcrumbs are rendered in the initial HTML
|
|
217
|
+
7. On the client, `$derived` re-evaluates when the route changes
|
|
218
|
+
|
|
219
|
+
## Requirements
|
|
220
|
+
|
|
221
|
+
- **SvelteKit 2** — relies on `$app/state` and `import.meta.glob`
|
|
222
|
+
- **Svelte 5** — uses runes (`$derived`)
|
|
223
|
+
- Route groups (`(group)`) are stripped from paths
|
|
224
|
+
|
|
225
|
+
### Optional: enable async and remote functions
|
|
226
|
+
|
|
227
|
+
The library works without any experimental flags — you can use load functions or resolve breadcrumbs manually. However, to unlock top-level `await` in components and remote function support, enable these flags:
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
// svelte.config.js
|
|
231
|
+
const config = {
|
|
232
|
+
compilerOptions: {
|
|
233
|
+
experimental: {
|
|
234
|
+
async: true // top-level await in components
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
kit: {
|
|
238
|
+
experimental: {
|
|
239
|
+
remoteFunctions: true // call server functions from breadcrumb resolvers
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Breadcrumb } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a reactive breadcrumb resolver that automatically tracks the current route.
|
|
4
|
+
* Uses top-level await with SvelteKit's async experimental compiler option for SSR support.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <script lang="ts">
|
|
9
|
+
* import { createBreadcrumbs } from 'svelte-breadcrumbs';
|
|
10
|
+
* const getBreadcrumbs = createBreadcrumbs();
|
|
11
|
+
* const crumbs = $derived(await getBreadcrumbs());
|
|
12
|
+
* </script>
|
|
13
|
+
*
|
|
14
|
+
* {#each crumbs as crumb}
|
|
15
|
+
* <a href={crumb.url}>{crumb.label}</a>
|
|
16
|
+
* {/each}
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function createBreadcrumbs(): () => Promise<Breadcrumb[]>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { page } from '$app/state';
|
|
2
|
+
import { buildBreadcrumbMap } from '../routing/build-breadcrumb-map.js';
|
|
3
|
+
import { getResolversForRoute } from '../routing/get-resolvers-for-route.js';
|
|
4
|
+
async function resolve(resolvers) {
|
|
5
|
+
const promises = Array.from(resolvers).map(async ([url, resolver]) => {
|
|
6
|
+
const data = await resolver(page);
|
|
7
|
+
if (!data)
|
|
8
|
+
return undefined;
|
|
9
|
+
return { ...data, url };
|
|
10
|
+
});
|
|
11
|
+
const results = await Promise.all(promises);
|
|
12
|
+
return results.filter((b) => b !== undefined);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a reactive breadcrumb resolver that automatically tracks the current route.
|
|
16
|
+
* Uses top-level await with SvelteKit's async experimental compiler option for SSR support.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* ```svelte
|
|
20
|
+
* <script lang="ts">
|
|
21
|
+
* import { createBreadcrumbs } from 'svelte-breadcrumbs';
|
|
22
|
+
* const getBreadcrumbs = createBreadcrumbs();
|
|
23
|
+
* const crumbs = $derived(await getBreadcrumbs());
|
|
24
|
+
* </script>
|
|
25
|
+
*
|
|
26
|
+
* {#each crumbs as crumb}
|
|
27
|
+
* <a href={crumb.url}>{crumb.label}</a>
|
|
28
|
+
* {/each}
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function createBreadcrumbs() {
|
|
32
|
+
const map = buildBreadcrumbMap();
|
|
33
|
+
const resolversForRoute = $derived(getResolversForRoute(map, page.url.pathname));
|
|
34
|
+
return () => resolve(resolversForRoute);
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getDocTitle: import("@sveltejs/kit").RemoteQueryFunction<string, string>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { query } from '$app/server';
|
|
2
|
+
const docs = {
|
|
3
|
+
'getting-started': 'Getting Started',
|
|
4
|
+
'api-reference': 'API Reference',
|
|
5
|
+
'migration-guide': 'Migration Guide'
|
|
6
|
+
};
|
|
7
|
+
export const getDocTitle = query('unchecked', async (slug) => {
|
|
8
|
+
// Simulate database/CMS lookup
|
|
9
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
10
|
+
return docs[slug] ?? slug;
|
|
11
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { command, query } from '$app/server';
|
|
2
|
+
let currentNickname = 'Visitor';
|
|
3
|
+
export const getNickname = query(async () => {
|
|
4
|
+
// Simulate server-side lookup (e.g. user profile, session, database)
|
|
5
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
6
|
+
return currentNickname;
|
|
7
|
+
});
|
|
8
|
+
export const setNickname = command('unchecked', async (name) => {
|
|
9
|
+
// Simulate server-side write
|
|
10
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
11
|
+
currentNickname = name.trim() || 'Visitor';
|
|
12
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createBreadcrumbs } from './breadcrumbs/create-breadcrumbs.svelte.js';
|
|
2
|
+
export { buildBreadcrumbMap } from './routing/build-breadcrumb-map.js';
|
|
3
|
+
export { filePathToRoute, matchDynamicRoute } from './routing/match-route.js';
|
|
4
|
+
export { getResolversForRoute } from './routing/get-resolvers-for-route.js';
|
|
5
|
+
export type { Breadcrumb, BreadcrumbData, BreadcrumbMap, BreadcrumbMeta, BreadcrumbResolver } from './types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createBreadcrumbs } from './breadcrumbs/create-breadcrumbs.svelte.js';
|
|
2
|
+
export { buildBreadcrumbMap } from './routing/build-breadcrumb-map.js';
|
|
3
|
+
export { filePathToRoute, matchDynamicRoute } from './routing/match-route.js';
|
|
4
|
+
export { getResolversForRoute } from './routing/get-resolvers-for-route.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { filePathToRoute } from './match-route.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scans all +page.svelte files via import.meta.glob for `breadcrumb` exports
|
|
4
|
+
* and builds a map from route pattern to resolver function.
|
|
5
|
+
*/
|
|
6
|
+
export function buildBreadcrumbMap() {
|
|
7
|
+
const map = new Map();
|
|
8
|
+
const pageModules = import.meta.glob('/src/routes/**/+page.svelte', { eager: true });
|
|
9
|
+
for (const [filePath, module] of Object.entries(pageModules)) {
|
|
10
|
+
if (!module?.breadcrumb)
|
|
11
|
+
continue;
|
|
12
|
+
const route = filePathToRoute(filePath);
|
|
13
|
+
const meta = module.breadcrumb;
|
|
14
|
+
const routes = 'routes' in meta ? meta.routes : { [route]: meta };
|
|
15
|
+
for (const [routeKey, resolver] of Object.entries(routes)) {
|
|
16
|
+
map.set(routeKey, resolver);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return map;
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BreadcrumbMap } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Walks route segments from root to leaf, collecting ordered resolvers.
|
|
4
|
+
* For `/products/123/edit`, checks `/`, `/products`, `/products/123`, `/products/123/edit`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getResolversForRoute(map: BreadcrumbMap, route: string): BreadcrumbMap;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { matchDynamicRoute } from './match-route.js';
|
|
2
|
+
/**
|
|
3
|
+
* Walks route segments from root to leaf, collecting ordered resolvers.
|
|
4
|
+
* For `/products/123/edit`, checks `/`, `/products`, `/products/123`, `/products/123/edit`.
|
|
5
|
+
*/
|
|
6
|
+
export function getResolversForRoute(map, route) {
|
|
7
|
+
const resolvers = new Map();
|
|
8
|
+
// Always check root first
|
|
9
|
+
const rootResolver = map.get('/');
|
|
10
|
+
if (rootResolver) {
|
|
11
|
+
resolvers.set('/', rootResolver);
|
|
12
|
+
}
|
|
13
|
+
const segments = route.split('/').filter(Boolean);
|
|
14
|
+
let currentRoute = '';
|
|
15
|
+
for (const segment of segments) {
|
|
16
|
+
currentRoute += `/${segment}`;
|
|
17
|
+
const resolver = map.get(currentRoute) ?? matchDynamicRoute(map, currentRoute);
|
|
18
|
+
if (!resolver)
|
|
19
|
+
continue;
|
|
20
|
+
resolvers.set(currentRoute, resolver);
|
|
21
|
+
}
|
|
22
|
+
return resolvers;
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BreadcrumbMap, BreadcrumbResolver } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Converts a file path from import.meta.glob to a clean route.
|
|
4
|
+
* `/src/routes/(group)/products/+page.svelte` → `/products`
|
|
5
|
+
*/
|
|
6
|
+
export declare function filePathToRoute(filePath: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Matches a concrete route against dynamic patterns in the breadcrumb map.
|
|
9
|
+
* `/products/123` matches `/products/[id]`
|
|
10
|
+
*/
|
|
11
|
+
export declare function matchDynamicRoute(map: BreadcrumbMap, route: string): BreadcrumbResolver | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a file path from import.meta.glob to a clean route.
|
|
3
|
+
* `/src/routes/(group)/products/+page.svelte` → `/products`
|
|
4
|
+
*/
|
|
5
|
+
export function filePathToRoute(filePath) {
|
|
6
|
+
return (filePath
|
|
7
|
+
.replace(/^\/src\/routes/, '')
|
|
8
|
+
.replace(/\/\+page\.svelte$/, '')
|
|
9
|
+
.replace(/\/\(.*?\)/g, '') || '/');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Matches a concrete route against dynamic patterns in the breadcrumb map.
|
|
13
|
+
* `/products/123` matches `/products/[id]`
|
|
14
|
+
*/
|
|
15
|
+
export function matchDynamicRoute(map, route) {
|
|
16
|
+
const routeSegments = route.split('/').filter(Boolean);
|
|
17
|
+
for (const [pattern, resolver] of map) {
|
|
18
|
+
const patternSegments = pattern.split('/').filter(Boolean);
|
|
19
|
+
if (routeSegments.length !== patternSegments.length)
|
|
20
|
+
continue;
|
|
21
|
+
const isMatch = patternSegments.every((segment, i) => (segment.startsWith('[') && segment.endsWith(']')) || segment === routeSegments[i]);
|
|
22
|
+
if (isMatch)
|
|
23
|
+
return resolver;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import type { Page } from '@sveltejs/kit';
|
|
3
|
+
/** What users export from +page.svelte as `breadcrumb` */
|
|
4
|
+
export type BreadcrumbMeta = BreadcrumbResolver | {
|
|
5
|
+
routes: Record<string, BreadcrumbResolver>;
|
|
6
|
+
};
|
|
7
|
+
/** Async resolver function that receives the current page and returns breadcrumb data */
|
|
8
|
+
export type BreadcrumbResolver = (page: Page) => Promise<BreadcrumbData | undefined>;
|
|
9
|
+
/** Resolved data for one breadcrumb */
|
|
10
|
+
export type BreadcrumbData = {
|
|
11
|
+
label: string;
|
|
12
|
+
icon?: Component<any>;
|
|
13
|
+
};
|
|
14
|
+
/** Final breadcrumb with its URL */
|
|
15
|
+
export type Breadcrumb = BreadcrumbData & {
|
|
16
|
+
url: string;
|
|
17
|
+
};
|
|
18
|
+
/** Internal map from route pattern to resolver */
|
|
19
|
+
export type BreadcrumbMap = Map<string, BreadcrumbResolver>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "svelte-crumbs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automatic breadcrumbs for SvelteKit via route-level metadata exports",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/focause/svelte-crumbs.git"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "vite dev",
|
|
12
|
+
"build": "vite build && npm run prepack",
|
|
13
|
+
"preview": "vite preview",
|
|
14
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
15
|
+
"prepack": "svelte-kit sync && svelte-package && publint",
|
|
16
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
17
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
18
|
+
"lint": "prettier --check . && eslint .",
|
|
19
|
+
"format": "prettier --write .",
|
|
20
|
+
"test:unit": "vitest",
|
|
21
|
+
"test": "npm run test:unit -- --run"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"!dist/**/*.test.*",
|
|
26
|
+
"!dist/**/*.spec.*"
|
|
27
|
+
],
|
|
28
|
+
"sideEffects": [
|
|
29
|
+
"**/*.css"
|
|
30
|
+
],
|
|
31
|
+
"svelte": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"type": "module",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"svelte": "./dist/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@sveltejs/kit": "^2.0.0",
|
|
42
|
+
"svelte": "^5.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@changesets/cli": "^2.29.8",
|
|
46
|
+
"@eslint/compat": "^2.0.2",
|
|
47
|
+
"@eslint/js": "^9.39.3",
|
|
48
|
+
"@sveltejs/adapter-auto": "^7.0.1",
|
|
49
|
+
"@sveltejs/kit": "^2.53.0",
|
|
50
|
+
"@sveltejs/package": "^2.5.7",
|
|
51
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
52
|
+
"@tailwindcss/vite": "^4.2.0",
|
|
53
|
+
"@types/node": "^24.10.13",
|
|
54
|
+
"@vitest/browser-playwright": "^4.0.18",
|
|
55
|
+
"eslint": "^9.39.3",
|
|
56
|
+
"eslint-config-prettier": "^10.1.8",
|
|
57
|
+
"eslint-plugin-svelte": "^3.15.0",
|
|
58
|
+
"globals": "^17.3.0",
|
|
59
|
+
"playwright": "^1.58.2",
|
|
60
|
+
"prettier": "^3.8.1",
|
|
61
|
+
"prettier-plugin-svelte": "^3.5.0",
|
|
62
|
+
"publint": "^0.3.17",
|
|
63
|
+
"shiki": "^3.22.0",
|
|
64
|
+
"svelte": "^5.53.2",
|
|
65
|
+
"svelte-check": "^4.4.3",
|
|
66
|
+
"tailwindcss": "^4.2.0",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
68
|
+
"typescript-eslint": "^8.56.0",
|
|
69
|
+
"vite": "^7.3.1",
|
|
70
|
+
"vitest": "^4.0.18",
|
|
71
|
+
"vitest-browser-svelte": "^2.0.2"
|
|
72
|
+
},
|
|
73
|
+
"keywords": [
|
|
74
|
+
"svelte",
|
|
75
|
+
"sveltekit",
|
|
76
|
+
"breadcrumbs",
|
|
77
|
+
"navigation"
|
|
78
|
+
]
|
|
79
|
+
}
|