sv-router 0.7.1 → 0.7.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sv-router",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Modern Svelte Routing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@changesets/cli": "^2.29.4",
|
|
40
40
|
"@eslint/js": "^9.28.0",
|
|
41
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
41
|
+
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
|
42
42
|
"@testing-library/jest-dom": "^6.6.3",
|
|
43
43
|
"@testing-library/svelte": "^5.2.8",
|
|
44
44
|
"@types/node": "^22.15.30",
|
package/src/actions.svelte.js
CHANGED
|
@@ -12,10 +12,11 @@ export function isActiveLink(node, { className = 'is-active', startsWith = false
|
|
|
12
12
|
if (base.name) {
|
|
13
13
|
pathname = join(base.name, pathname);
|
|
14
14
|
}
|
|
15
|
+
const tokens = className.split(' ').filter(Boolean) ?? [];
|
|
15
16
|
if (startsWith ? location.pathname.startsWith(pathname) : location.pathname === pathname) {
|
|
16
|
-
node.classList.add(
|
|
17
|
+
node.classList.add(...tokens);
|
|
17
18
|
} else {
|
|
18
|
-
node.classList.remove(
|
|
19
|
+
node.classList.remove(...tokens);
|
|
19
20
|
}
|
|
20
21
|
});
|
|
21
22
|
}
|
|
@@ -2,7 +2,13 @@ import { BROWSER, DEV } from 'esm-env';
|
|
|
2
2
|
import { isActive } from './helpers/is-active.js';
|
|
3
3
|
import { matchRoute } from './helpers/match-route.js';
|
|
4
4
|
import { preload, preloadOnHover } from './helpers/preload.js';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
constructPath,
|
|
7
|
+
join,
|
|
8
|
+
resolveRouteComponents,
|
|
9
|
+
stripBase,
|
|
10
|
+
updatedLocation,
|
|
11
|
+
} from './helpers/utils.js';
|
|
6
12
|
import { syncSearchParams } from './search-params.svelte.js';
|
|
7
13
|
|
|
8
14
|
/** @type {import('./index.d.ts').Routes} */
|
|
@@ -60,7 +66,7 @@ export function createRouter(r) {
|
|
|
60
66
|
return params.value;
|
|
61
67
|
},
|
|
62
68
|
get pathname() {
|
|
63
|
-
return /** @type {import('./index.d.ts').Path<T>} */ (location.pathname);
|
|
69
|
+
return /** @type {import('./index.d.ts').Path<T>} */ (stripBase(location.pathname));
|
|
64
70
|
},
|
|
65
71
|
get search() {
|
|
66
72
|
return location.search;
|
|
@@ -111,16 +117,13 @@ export async function onNavigate(path, options = {}) {
|
|
|
111
117
|
navigationIndex++;
|
|
112
118
|
const currentNavigationIndex = navigationIndex;
|
|
113
119
|
|
|
114
|
-
|
|
115
|
-
if (base.name && matchPath.startsWith(base.name)) {
|
|
116
|
-
matchPath = matchPath.slice(base.name.length) || '/';
|
|
117
|
-
}
|
|
120
|
+
const matchPath = stripBase(path || globalThis.location.pathname);
|
|
118
121
|
const { match, layouts, hooks, meta: newMeta, params: newParams } = matchRoute(matchPath, routes);
|
|
119
122
|
|
|
120
123
|
for (const { beforeLoad } of hooks) {
|
|
121
124
|
try {
|
|
122
125
|
pendingNavigationIndex = currentNavigationIndex;
|
|
123
|
-
await beforeLoad?.({ pathname: matchPath, ...options });
|
|
126
|
+
await beforeLoad?.({ pathname: matchPath, meta: newMeta, ...options });
|
|
124
127
|
} catch {
|
|
125
128
|
return;
|
|
126
129
|
}
|
|
@@ -161,7 +164,7 @@ export async function onNavigate(path, options = {}) {
|
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
for (const { afterLoad } of hooks) {
|
|
164
|
-
afterLoad?.({ pathname: matchPath, ...options });
|
|
167
|
+
void afterLoad?.({ pathname: matchPath, meta: newMeta, ...options });
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -177,7 +177,9 @@ export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
|
|
|
177
177
|
`import { createRouter } from 'sv-router';`,
|
|
178
178
|
...imports,
|
|
179
179
|
'',
|
|
180
|
-
`
|
|
180
|
+
`const routes = ${stringifiedRoutes};`,
|
|
181
|
+
'export type Routes = typeof routes;',
|
|
182
|
+
'export const { p, navigate, isActive, preload, route } = createRouter(routes);',
|
|
181
183
|
].join('\n');
|
|
182
184
|
}
|
|
183
185
|
|
|
@@ -109,7 +109,7 @@ export function matchRoute(pathname, routes) {
|
|
|
109
109
|
|
|
110
110
|
const nestedPathname = '/' + pathParts.slice(index + 1).join('/');
|
|
111
111
|
const result = matchRoute(nestedPathname, routeMatch);
|
|
112
|
-
if (result) {
|
|
112
|
+
if (result.match) {
|
|
113
113
|
match = result.match;
|
|
114
114
|
params = { ...params, ...result.params };
|
|
115
115
|
hooks.push(...result.hooks);
|
|
@@ -119,6 +119,8 @@ export function matchRoute(pathname, routes) {
|
|
|
119
119
|
} else {
|
|
120
120
|
layouts.push(...result.layouts);
|
|
121
121
|
}
|
|
122
|
+
} else {
|
|
123
|
+
continue;
|
|
122
124
|
}
|
|
123
125
|
break outer;
|
|
124
126
|
}
|
|
@@ -141,7 +143,7 @@ export function sortRoutes(routes) {
|
|
|
141
143
|
*/
|
|
142
144
|
function getRoutePriority(route) {
|
|
143
145
|
if (route === '' || route === '/') return 1;
|
|
144
|
-
if (route.
|
|
146
|
+
if (route.includes('*')) return 4;
|
|
145
147
|
if (route.includes(':')) return 3;
|
|
146
148
|
return 2;
|
|
147
149
|
}
|
package/src/helpers/preload.js
CHANGED
|
@@ -7,9 +7,9 @@ import { resolveRouteComponents } from './utils.js';
|
|
|
7
7
|
* @param {import('../index.d.ts').NavigateOptions} [options]
|
|
8
8
|
*/
|
|
9
9
|
export async function preload(routes, path, options) {
|
|
10
|
-
const { match, layouts, hooks } = matchRoute(path, routes);
|
|
10
|
+
const { match, layouts, hooks, meta } = matchRoute(path, routes);
|
|
11
11
|
for (const { onPreload } of hooks) {
|
|
12
|
-
onPreload?.({ pathname: path, ...options });
|
|
12
|
+
void onPreload?.({ pathname: path, meta, ...options });
|
|
13
13
|
}
|
|
14
14
|
await resolveRouteComponents(match ? [...layouts, match] : layouts);
|
|
15
15
|
}
|
package/src/helpers/utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { base } from '../create-router.svelte.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @param {string} path
|
|
3
5
|
* @param {Record<string, string>} [params]
|
|
@@ -60,6 +62,17 @@ export function join(...parts) {
|
|
|
60
62
|
return result;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @param {string} pathname
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
69
|
+
export function stripBase(pathname) {
|
|
70
|
+
if (base.name && pathname.startsWith(base.name)) {
|
|
71
|
+
pathname = pathname.slice(base.name.length) || '/';
|
|
72
|
+
}
|
|
73
|
+
return pathname;
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
export function updatedLocation() {
|
|
64
77
|
return {
|
|
65
78
|
pathname: globalThis.location.pathname,
|
package/src/index.d.ts
CHANGED
|
@@ -64,9 +64,9 @@ export type Hooks = {
|
|
|
64
64
|
*/
|
|
65
65
|
beforeLoad?(context: HooksContext): void | Promise<void>;
|
|
66
66
|
/** A function that will be called after the route is loaded. */
|
|
67
|
-
afterLoad?(context: HooksContext): void
|
|
67
|
+
afterLoad?(context: HooksContext): void | Promise<void>;
|
|
68
68
|
/** A function that will be called when the route is preloaded. */
|
|
69
|
-
onPreload?(context: HooksContext): void
|
|
69
|
+
onPreload?(context: HooksContext): void | Promise<void>;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
export type Routes = {
|
|
@@ -79,7 +79,11 @@ export type Routes = {
|
|
|
79
79
|
|
|
80
80
|
export type IsActiveLink = Action<
|
|
81
81
|
HTMLAnchorElement,
|
|
82
|
-
|
|
82
|
+
| {
|
|
83
|
+
className?: string;
|
|
84
|
+
startsWith?: boolean;
|
|
85
|
+
}
|
|
86
|
+
| undefined
|
|
83
87
|
>;
|
|
84
88
|
|
|
85
89
|
/**
|
|
@@ -143,7 +147,7 @@ export type RouterApi<T extends Routes> = {
|
|
|
143
147
|
*/
|
|
144
148
|
isActive: {
|
|
145
149
|
<U extends Path<T>>(...args: IsActiveArgs<U>): boolean;
|
|
146
|
-
startsWith<U extends Path<T>>(...args: IsActiveArgs<U>): boolean;
|
|
150
|
+
startsWith<U extends Path<T>>(...args: IsActiveArgs<U, true>): boolean;
|
|
147
151
|
};
|
|
148
152
|
|
|
149
153
|
/**
|
|
@@ -190,27 +194,39 @@ export type Path<T extends Routes, AnyParam extends boolean = false> = RemovePar
|
|
|
190
194
|
RemoveLastSlash<RecursiveKeys<StripNonRoutes<T>, '', AnyParam>>
|
|
191
195
|
>;
|
|
192
196
|
|
|
193
|
-
export type ConstructPathArgs<
|
|
194
|
-
PathParams<
|
|
197
|
+
export type ConstructPathArgs<TPath extends string> = {
|
|
198
|
+
[Path in TPath]: PathParams<Path> extends never ? [Path] : [Path, PathParams<Path>];
|
|
199
|
+
}[TPath];
|
|
195
200
|
|
|
196
|
-
export type IsActiveArgs<
|
|
197
|
-
|
|
201
|
+
export type IsActiveArgs<
|
|
202
|
+
TPath extends string,
|
|
203
|
+
StartsWith extends boolean = false,
|
|
204
|
+
> = StartsWith extends true
|
|
205
|
+
? {
|
|
206
|
+
[Path in TPath]: PathParams<Path> extends never
|
|
207
|
+
? [PathPrefixes<Path>]
|
|
208
|
+
: [PathPrefixes<Path>] | [PathPrefixes<Path>, PathParams<Path>];
|
|
209
|
+
}[TPath]
|
|
210
|
+
: {
|
|
211
|
+
[Path in TPath]: PathParams<Path> extends never ? [Path] : [Path] | [Path, PathParams<Path>];
|
|
212
|
+
}[TPath];
|
|
198
213
|
|
|
199
|
-
export type PathParams<
|
|
200
|
-
ExtractParams<RemoveParenthesis<
|
|
214
|
+
export type PathParams<TPath extends string> =
|
|
215
|
+
ExtractParams<RemoveParenthesis<TPath>> extends never
|
|
201
216
|
? never
|
|
202
|
-
: Record<ExtractParams<RemoveParenthesis<
|
|
217
|
+
: Record<ExtractParams<RemoveParenthesis<TPath>>, string>;
|
|
203
218
|
|
|
204
|
-
export type AllParams<
|
|
205
|
-
Record<ExtractParams<RemoveParenthesis<RecursiveKeys<
|
|
219
|
+
export type AllParams<TRoutes extends Routes> = Partial<
|
|
220
|
+
Record<ExtractParams<RemoveParenthesis<RecursiveKeys<TRoutes>>>, string>
|
|
206
221
|
>;
|
|
207
222
|
|
|
208
223
|
export type HooksContext = {
|
|
224
|
+
hash?: string;
|
|
225
|
+
meta: RouteMeta;
|
|
209
226
|
pathname: string;
|
|
210
227
|
replace?: boolean;
|
|
211
228
|
search?: string;
|
|
212
229
|
state?: string;
|
|
213
|
-
hash?: string;
|
|
214
230
|
};
|
|
215
231
|
|
|
216
232
|
export type NavigateOptions =
|
|
@@ -288,3 +304,15 @@ type ExtractParams<T extends string> = T extends `${string}:${infer Param}/${inf
|
|
|
288
304
|
? never
|
|
289
305
|
: Param
|
|
290
306
|
: never;
|
|
307
|
+
|
|
308
|
+
type PathPrefixes<T extends string, Acc extends string = ''> = T extends '/'
|
|
309
|
+
? '/'
|
|
310
|
+
: T extends `/${infer Segment}/${infer Rest}`
|
|
311
|
+
? Acc extends ''
|
|
312
|
+
? PathPrefixes<`/${Rest}`, `/${Segment}`> | `/${Segment}`
|
|
313
|
+
: PathPrefixes<`/${Rest}`, `${Acc}/${Segment}`> | `${Acc}/${Segment}` | Acc
|
|
314
|
+
: T extends `/${infer Segment}`
|
|
315
|
+
? Acc extends ''
|
|
316
|
+
? `/${Segment}`
|
|
317
|
+
: `${Acc}/${Segment}` | Acc
|
|
318
|
+
: Acc;
|