sv-router 0.5.0 → 0.7.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/package.json +12 -12
- package/src/create-router.svelte.js +17 -14
- package/src/gen/generate-router-code.js +20 -4
- package/src/helpers/is-active.js +5 -3
- package/src/helpers/match-route.js +13 -2
- package/src/helpers/preload.js +14 -4
- package/src/helpers/utils.js +10 -1
- package/src/helpers/validate-routes.js +4 -1
- package/src/index.d.ts +43 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sv-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Modern Svelte Routing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -36,24 +36,24 @@
|
|
|
36
36
|
"esm-env": "^1.2.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@changesets/cli": "^2.29.
|
|
40
|
-
"@eslint/js": "^9.
|
|
41
|
-
"@types/node": "^22.15.
|
|
42
|
-
"eslint": "^9.
|
|
43
|
-
"eslint-config-prettier": "^10.1.
|
|
39
|
+
"@changesets/cli": "^2.29.4",
|
|
40
|
+
"@eslint/js": "^9.28.0",
|
|
41
|
+
"@types/node": "^22.15.30",
|
|
42
|
+
"eslint": "^9.28.0",
|
|
43
|
+
"eslint-config-prettier": "^10.1.5",
|
|
44
44
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
45
|
-
"eslint-plugin-svelte": "^3.
|
|
45
|
+
"eslint-plugin-svelte": "^3.9.1",
|
|
46
46
|
"eslint-plugin-unicorn": "^59.0.1",
|
|
47
|
-
"globals": "^16.
|
|
47
|
+
"globals": "^16.2.0",
|
|
48
48
|
"prettier": "^3.5.3",
|
|
49
49
|
"prettier-plugin-jsdoc": "^1.3.2",
|
|
50
|
-
"prettier-plugin-svelte": "^3.
|
|
51
|
-
"svelte-check": "^4.1
|
|
50
|
+
"prettier-plugin-svelte": "^3.4.0",
|
|
51
|
+
"svelte-check": "^4.2.1",
|
|
52
52
|
"type-testing": "^0.2.0",
|
|
53
53
|
"typescript": "^5.8.3",
|
|
54
|
-
"typescript-eslint": "^8.
|
|
54
|
+
"typescript-eslint": "^8.33.1",
|
|
55
55
|
"vite": "^6.3.5",
|
|
56
|
-
"vitest": "^3.
|
|
56
|
+
"vitest": "^3.2.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"svelte": "^5"
|
|
@@ -2,7 +2,7 @@ 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 { constructPath, join, resolveRouteComponents } from './helpers/utils.js';
|
|
5
|
+
import { constructPath, join, resolveRouteComponents, updatedLocation } from './helpers/utils.js';
|
|
6
6
|
import { syncSearchParams } from './search-params.svelte.js';
|
|
7
7
|
|
|
8
8
|
/** @type {import('./index.d.ts').Routes} */
|
|
@@ -16,6 +16,8 @@ export let params = $state({ value: {} });
|
|
|
16
16
|
|
|
17
17
|
export let location = $state(updatedLocation());
|
|
18
18
|
|
|
19
|
+
let meta = $state({ value: {} });
|
|
20
|
+
|
|
19
21
|
let navigationIndex = 0;
|
|
20
22
|
let pendingNavigationIndex = 0;
|
|
21
23
|
|
|
@@ -51,6 +53,12 @@ export function createRouter(r) {
|
|
|
51
53
|
get params() {
|
|
52
54
|
return params.value;
|
|
53
55
|
},
|
|
56
|
+
getParams(pathname) {
|
|
57
|
+
if (!isActive(pathname)) {
|
|
58
|
+
throw new Error(`\`${pathname}\` does not match the current route`);
|
|
59
|
+
}
|
|
60
|
+
return params.value;
|
|
61
|
+
},
|
|
54
62
|
get pathname() {
|
|
55
63
|
return /** @type {import('./index.d.ts').Path<T>} */ (location.pathname);
|
|
56
64
|
},
|
|
@@ -63,6 +71,9 @@ export function createRouter(r) {
|
|
|
63
71
|
get hash() {
|
|
64
72
|
return location.hash;
|
|
65
73
|
},
|
|
74
|
+
get meta() {
|
|
75
|
+
return meta.value;
|
|
76
|
+
},
|
|
66
77
|
},
|
|
67
78
|
};
|
|
68
79
|
}
|
|
@@ -104,12 +115,12 @@ export async function onNavigate(path, options = {}) {
|
|
|
104
115
|
if (base.name && matchPath.startsWith(base.name)) {
|
|
105
116
|
matchPath = matchPath.slice(base.name.length) || '/';
|
|
106
117
|
}
|
|
107
|
-
const { match, layouts, hooks, params: newParams } = matchRoute(matchPath, routes);
|
|
118
|
+
const { match, layouts, hooks, meta: newMeta, params: newParams } = matchRoute(matchPath, routes);
|
|
108
119
|
|
|
109
120
|
for (const { beforeLoad } of hooks) {
|
|
110
121
|
try {
|
|
111
122
|
pendingNavigationIndex = currentNavigationIndex;
|
|
112
|
-
await beforeLoad?.();
|
|
123
|
+
await beforeLoad?.({ pathname: matchPath, ...options });
|
|
113
124
|
} catch {
|
|
114
125
|
return;
|
|
115
126
|
}
|
|
@@ -140,7 +151,8 @@ export async function onNavigate(path, options = {}) {
|
|
|
140
151
|
} else {
|
|
141
152
|
componentTree.value = routeComponents;
|
|
142
153
|
}
|
|
143
|
-
params.value = newParams
|
|
154
|
+
params.value = newParams;
|
|
155
|
+
meta.value = newMeta;
|
|
144
156
|
syncSearchParams();
|
|
145
157
|
Object.assign(location, updatedLocation());
|
|
146
158
|
|
|
@@ -149,7 +161,7 @@ export async function onNavigate(path, options = {}) {
|
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
for (const { afterLoad } of hooks) {
|
|
152
|
-
afterLoad?.();
|
|
164
|
+
afterLoad?.({ pathname: matchPath, ...options });
|
|
153
165
|
}
|
|
154
166
|
}
|
|
155
167
|
|
|
@@ -175,12 +187,3 @@ export function onGlobalClick(event) {
|
|
|
175
187
|
viewTransition: viewTransition === '' || viewTransition === 'true',
|
|
176
188
|
});
|
|
177
189
|
}
|
|
178
|
-
|
|
179
|
-
function updatedLocation() {
|
|
180
|
-
return {
|
|
181
|
-
pathname: globalThis.location.pathname,
|
|
182
|
-
search: globalThis.location.search,
|
|
183
|
-
state: history.state,
|
|
184
|
-
hash: globalThis.location.hash,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
@@ -14,6 +14,7 @@ const PARAM_FILENAME_REGEX = /(?<=[/.]|^)\(?\[([\w-]+)\]\)?(\.lazy)?\.svelte$/;
|
|
|
14
14
|
const CATCH_ALL_FILENAME_REGEX = /(?<=[/.]|^)\(?\[\.\.\.([\w-]+)\]\)?(\.lazy)?\.svelte$/; // [...any].svelte, [...any].lazy.svelte, ([...any]).svelte
|
|
15
15
|
const OUT_OF_LAYOUT_FILENAME_REGEX = /(?<=[/.]|^)\(\[\.?\.?\.?([\w-]+)\]\)(\.lazy)?\.svelte$/; // ([any]).svelte, ([...any]).lazy.svelte
|
|
16
16
|
const HOOKS_FILENAME_REGEX = /(?<=[/.]|^)(hooks)(\.svelte)?\.(js|ts)$/; // hooks.js, hooks.svelte.js, hooks.ts, hooks.svelte.ts
|
|
17
|
+
const META_FILENAME_REGEX = /(?<=[/.]|^)(meta)(\.svelte)?\.(js|ts)$/; // meta.js, meta.svelte.js, meta.ts, meta.svelte.ts
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* @param {string} routesPath
|
|
@@ -44,7 +45,11 @@ export function buildFileTree(routesPath) {
|
|
|
44
45
|
tree.push({ name: entry, tree: buildFileTree(path.join(routesPath, entry)) });
|
|
45
46
|
continue;
|
|
46
47
|
}
|
|
47
|
-
if (
|
|
48
|
+
if (
|
|
49
|
+
!entry.endsWith('.svelte') &&
|
|
50
|
+
!HOOKS_FILENAME_REGEX.test(entry) &&
|
|
51
|
+
!META_FILENAME_REGEX.test(entry)
|
|
52
|
+
) {
|
|
48
53
|
continue;
|
|
49
54
|
}
|
|
50
55
|
tree.push(entry);
|
|
@@ -67,6 +72,10 @@ export function createRouteMap(fileTree, prefix = '') {
|
|
|
67
72
|
result['hooks'] = prefix + entry;
|
|
68
73
|
continue;
|
|
69
74
|
}
|
|
75
|
+
if (META_FILENAME_REGEX.test(entry)) {
|
|
76
|
+
result['meta'] = prefix + entry;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
70
79
|
continue;
|
|
71
80
|
}
|
|
72
81
|
|
|
@@ -101,7 +110,8 @@ export function createRouteMap(fileTree, prefix = '') {
|
|
|
101
110
|
result['/' + filePathToRoute(entry.replace('.svelte', ''))] = prefix + entry;
|
|
102
111
|
} else {
|
|
103
112
|
const entryName = filePathToRoute(entry.name);
|
|
104
|
-
|
|
113
|
+
const paramFolder = entryName.replace(/^\[(.*)\]$/, ':$1');
|
|
114
|
+
result['/' + paramFolder] = createRouteMap(entry.tree, prefix + entryName + '/');
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
117
|
return result;
|
|
@@ -137,7 +147,11 @@ export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
|
|
|
137
147
|
for (const [key, value] of Object.entries(routes)) {
|
|
138
148
|
if (typeof value === 'object') {
|
|
139
149
|
result[key] = handleImports(value, routesPath);
|
|
140
|
-
} else if (
|
|
150
|
+
} else if (
|
|
151
|
+
key === 'hooks' ||
|
|
152
|
+
key === 'meta' ||
|
|
153
|
+
(!value.endsWith('.lazy.svelte') && !allLazy)
|
|
154
|
+
) {
|
|
141
155
|
const variableName = pathToCorrectCasing(value);
|
|
142
156
|
importsMap.set(variableName, routesPath + value);
|
|
143
157
|
result[key] = variableName;
|
|
@@ -189,6 +203,7 @@ export function pathToCorrectCasing(value) {
|
|
|
189
203
|
extractLastPart(CATCH_ALL_FILENAME_REGEX) ||
|
|
190
204
|
extractLastPart(PARAM_FILENAME_REGEX) ||
|
|
191
205
|
extractLastPart(HOOKS_FILENAME_REGEX) ||
|
|
206
|
+
extractLastPart(META_FILENAME_REGEX) ||
|
|
192
207
|
extractLastPart(FILENAME_REGEX);
|
|
193
208
|
if (!lastPart) {
|
|
194
209
|
throw new Error(`Invalid filename: ${value}`);
|
|
@@ -196,7 +211,8 @@ export function pathToCorrectCasing(value) {
|
|
|
196
211
|
parts.push(...lastPart.split('-'));
|
|
197
212
|
|
|
198
213
|
const uppercased = parts.map((part, index) => {
|
|
199
|
-
if (index === 0 && lastPart === 'hooks') return part;
|
|
214
|
+
if (index === 0 && (lastPart === 'hooks' || lastPart === 'meta')) return part;
|
|
215
|
+
part = part.replace(/^\[(.*)\]$/, '$1');
|
|
200
216
|
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
201
217
|
});
|
|
202
218
|
return uppercased.join('');
|
package/src/helpers/is-active.js
CHANGED
|
@@ -38,10 +38,12 @@ function compare(compareFn, pathname, params) {
|
|
|
38
38
|
const routeParts = location.pathname.split('/').slice(1);
|
|
39
39
|
for (const [index, pathPart] of pathParts.entries()) {
|
|
40
40
|
const routePart = routeParts[index];
|
|
41
|
-
if (
|
|
41
|
+
if (pathPart.startsWith(':')) {
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
if (pathPart !== routePart) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
|
-
return
|
|
48
|
+
return true;
|
|
47
49
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* @typedef {import('../index.d.ts').Hooks} Hooks
|
|
7
7
|
*
|
|
8
8
|
* @typedef {import('../index.d.ts').Routes} Routes
|
|
9
|
+
*
|
|
10
|
+
* @typedef {import('../index.d.ts').RouteMeta} RouteMeta
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -15,6 +17,7 @@
|
|
|
15
17
|
* match: RouteComponent | undefined;
|
|
16
18
|
* layouts: LayoutComponent[];
|
|
17
19
|
* hooks: Hooks[];
|
|
20
|
+
* meta: RouteMeta;
|
|
18
21
|
* params: Record<string, string>;
|
|
19
22
|
* breakFromLayouts: boolean;
|
|
20
23
|
* }}
|
|
@@ -39,6 +42,9 @@ export function matchRoute(pathname, routes) {
|
|
|
39
42
|
/** @type {Record<string, string>} */
|
|
40
43
|
let params = {};
|
|
41
44
|
|
|
45
|
+
/** @type {RouteMeta} */
|
|
46
|
+
let meta = {};
|
|
47
|
+
|
|
42
48
|
let breakFromLayouts = false;
|
|
43
49
|
|
|
44
50
|
outer: for (const route of allRoutes) {
|
|
@@ -69,7 +75,7 @@ export function matchRoute(pathname, routes) {
|
|
|
69
75
|
);
|
|
70
76
|
match = /** @type {RouteComponent} */ (routes[resolvedPath]);
|
|
71
77
|
break outer;
|
|
72
|
-
} else if (routePart !== pathPart) {
|
|
78
|
+
} else if (routePart !== pathPart?.toLowerCase()) {
|
|
73
79
|
break;
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -89,6 +95,10 @@ export function matchRoute(pathname, routes) {
|
|
|
89
95
|
hooks.push(routes.hooks);
|
|
90
96
|
}
|
|
91
97
|
|
|
98
|
+
if ('meta' in routes && routes.meta) {
|
|
99
|
+
meta = { ...meta, ...routes.meta };
|
|
100
|
+
}
|
|
101
|
+
|
|
92
102
|
if (typeof routeMatch === 'function') {
|
|
93
103
|
if (routeParts.length === pathParts.length) {
|
|
94
104
|
match = routeMatch;
|
|
@@ -103,6 +113,7 @@ export function matchRoute(pathname, routes) {
|
|
|
103
113
|
match = result.match;
|
|
104
114
|
params = { ...params, ...result.params };
|
|
105
115
|
hooks.push(...result.hooks);
|
|
116
|
+
meta = { ...meta, ...result.meta };
|
|
106
117
|
if (result.breakFromLayouts) {
|
|
107
118
|
layouts = [];
|
|
108
119
|
} else {
|
|
@@ -113,7 +124,7 @@ export function matchRoute(pathname, routes) {
|
|
|
113
124
|
}
|
|
114
125
|
}
|
|
115
126
|
|
|
116
|
-
return { match, layouts, hooks, params, breakFromLayouts };
|
|
127
|
+
return { match, layouts, hooks, params, meta, breakFromLayouts };
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
/**
|
package/src/helpers/preload.js
CHANGED
|
@@ -4,11 +4,12 @@ import { resolveRouteComponents } from './utils.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* @param {import('../index.js').Routes} routes
|
|
6
6
|
* @param {string} path
|
|
7
|
+
* @param {import('../index.d.ts').NavigateOptions} [options]
|
|
7
8
|
*/
|
|
8
|
-
export async function preload(routes, path) {
|
|
9
|
+
export async function preload(routes, path, options) {
|
|
9
10
|
const { match, layouts, hooks } = matchRoute(path, routes);
|
|
10
11
|
for (const { onPreload } of hooks) {
|
|
11
|
-
onPreload?.();
|
|
12
|
+
onPreload?.({ pathname: path, ...options });
|
|
12
13
|
}
|
|
13
14
|
await resolveRouteComponents(match ? [...layouts, match] : layouts);
|
|
14
15
|
}
|
|
@@ -18,7 +19,9 @@ const linkSet = new Set();
|
|
|
18
19
|
/** @param {import('../index.js').Routes} routes */
|
|
19
20
|
export function preloadOnHover(routes) {
|
|
20
21
|
const observer = new MutationObserver(() => {
|
|
21
|
-
const links =
|
|
22
|
+
const links = /** @type {NodeListOf<HTMLAnchorElement>} */ (
|
|
23
|
+
document.querySelectorAll('a[data-preload]')
|
|
24
|
+
);
|
|
22
25
|
for (const link of links) {
|
|
23
26
|
if (linkSet.has(link)) continue;
|
|
24
27
|
linkSet.add(link);
|
|
@@ -27,7 +30,14 @@ export function preloadOnHover(routes) {
|
|
|
27
30
|
link.removeEventListener('mouseenter', callback);
|
|
28
31
|
const href = link.getAttribute('href');
|
|
29
32
|
if (!href) return;
|
|
30
|
-
|
|
33
|
+
const url = new URL(link.href);
|
|
34
|
+
const { replace, state } = link.dataset;
|
|
35
|
+
preload(routes, href, {
|
|
36
|
+
replace: replace === '' || replace === 'true',
|
|
37
|
+
search: url.search,
|
|
38
|
+
state,
|
|
39
|
+
hash: url.hash,
|
|
40
|
+
});
|
|
31
41
|
});
|
|
32
42
|
}
|
|
33
43
|
});
|
package/src/helpers/utils.js
CHANGED
|
@@ -25,7 +25,7 @@ export function resolveRouteComponents(input) {
|
|
|
25
25
|
* @param {import('../index.d.ts').RouteComponent} input
|
|
26
26
|
* @returns {Promise<import('svelte').Component>}
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
function resolveRouteComponent(input) {
|
|
29
29
|
return new Promise((resolve) => {
|
|
30
30
|
if (isLazyImport(input)) {
|
|
31
31
|
Promise.resolve(input()).then((module) => {
|
|
@@ -59,3 +59,12 @@ export function join(...parts) {
|
|
|
59
59
|
}
|
|
60
60
|
return result;
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
export function updatedLocation() {
|
|
64
|
+
return {
|
|
65
|
+
pathname: globalThis.location.pathname,
|
|
66
|
+
search: globalThis.location.search,
|
|
67
|
+
state: history.state,
|
|
68
|
+
hash: globalThis.location.hash,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -26,9 +26,12 @@ export function validateRoutes(routes) {
|
|
|
26
26
|
export function getRoutePaths(routes) {
|
|
27
27
|
const paths = [];
|
|
28
28
|
for (const [key, value] of Object.entries(routes)) {
|
|
29
|
+
if (['layout', 'hooks', 'meta'].includes(key)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
29
32
|
if (typeof value === 'object') {
|
|
30
33
|
paths.push(
|
|
31
|
-
...getRoutePaths(value).map((path) => {
|
|
34
|
+
...getRoutePaths(/** @type {import('../index.d.ts').Routes} */ (value)).map((path) => {
|
|
32
35
|
if (path === '*') {
|
|
33
36
|
return key + '/*';
|
|
34
37
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -56,17 +56,17 @@ export type Hooks = {
|
|
|
56
56
|
* You can throw a `navigate` call to redirect to another route.
|
|
57
57
|
*
|
|
58
58
|
* ```js
|
|
59
|
-
* async beforeLoad() {
|
|
59
|
+
* async beforeLoad({ pathname }) {
|
|
60
60
|
* await ...
|
|
61
61
|
* throw navigate('/home');
|
|
62
62
|
* }
|
|
63
63
|
* ```
|
|
64
64
|
*/
|
|
65
|
-
beforeLoad?(): void | Promise<void>;
|
|
65
|
+
beforeLoad?(context: HooksContext): void | Promise<void>;
|
|
66
66
|
/** A function that will be called after the route is loaded. */
|
|
67
|
-
afterLoad?(): void;
|
|
67
|
+
afterLoad?(context: HooksContext): void;
|
|
68
68
|
/** A function that will be called when the route is preloaded. */
|
|
69
|
-
onPreload?(): void;
|
|
69
|
+
onPreload?(context: HooksContext): void;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
export type Routes = {
|
|
@@ -74,6 +74,7 @@ export type Routes = {
|
|
|
74
74
|
[_: `*${string}` | `(*${string})`]: RouteComponent | undefined;
|
|
75
75
|
layout?: LayoutComponent;
|
|
76
76
|
hooks?: Hooks;
|
|
77
|
+
meta?: RouteMeta;
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
export type IsActiveLink = Action<
|
|
@@ -81,6 +82,20 @@ export type IsActiveLink = Action<
|
|
|
81
82
|
{ className?: string; startsWith?: boolean } | undefined
|
|
82
83
|
>;
|
|
83
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Route metadata that can be extended via module augmentation.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* declare module 'sv-router' {
|
|
90
|
+
* interface RouteMeta {
|
|
91
|
+
* public?: boolean;
|
|
92
|
+
* requiresAuth?: boolean;
|
|
93
|
+
* }
|
|
94
|
+
* }
|
|
95
|
+
*/
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
97
|
+
export interface RouteMeta {}
|
|
98
|
+
|
|
84
99
|
export type RouterApi<T extends Routes> = {
|
|
85
100
|
/**
|
|
86
101
|
* Construct a path while ensuring type safety.
|
|
@@ -147,6 +162,17 @@ export type RouterApi<T extends Routes> = {
|
|
|
147
162
|
* 'hello-world', commentId: '123' }`.
|
|
148
163
|
*/
|
|
149
164
|
params: AllParams<T>;
|
|
165
|
+
/**
|
|
166
|
+
* Extract parameters from the given pathname. Will throw if the pathname does not match the
|
|
167
|
+
* current route.
|
|
168
|
+
*
|
|
169
|
+
* ```ts
|
|
170
|
+
* route.getParams('/posts/:slug').slug;
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* @param pathname
|
|
174
|
+
*/
|
|
175
|
+
getParams<U extends Path<T>>(pathname: U): Record<ExtractParams<U>, string>;
|
|
150
176
|
/** The reactive pathname of the URL. */
|
|
151
177
|
pathname: (Path<T, true> & {}) | (string & {});
|
|
152
178
|
/** The reactive query string part of the URL. */
|
|
@@ -155,6 +181,8 @@ export type RouterApi<T extends Routes> = {
|
|
|
155
181
|
state: unknown;
|
|
156
182
|
/** The reactive hash part of the URL. */
|
|
157
183
|
hash: string;
|
|
184
|
+
/** Arbitrary metadata associated with the route. */
|
|
185
|
+
meta: RouteMeta;
|
|
158
186
|
};
|
|
159
187
|
};
|
|
160
188
|
|
|
@@ -177,6 +205,14 @@ export type AllParams<T extends Routes> = Partial<
|
|
|
177
205
|
Record<ExtractParams<RemoveParenthesis<RecursiveKeys<T>>>, string>
|
|
178
206
|
>;
|
|
179
207
|
|
|
208
|
+
export type HooksContext = {
|
|
209
|
+
pathname: string;
|
|
210
|
+
replace?: boolean;
|
|
211
|
+
search?: string;
|
|
212
|
+
state?: string;
|
|
213
|
+
hash?: string;
|
|
214
|
+
};
|
|
215
|
+
|
|
180
216
|
export type NavigateOptions =
|
|
181
217
|
| {
|
|
182
218
|
replace?: boolean;
|
|
@@ -210,7 +246,9 @@ type StripNonRoutes<T extends Routes> = {
|
|
|
210
246
|
? never
|
|
211
247
|
: K extends 'hooks'
|
|
212
248
|
? never
|
|
213
|
-
: K
|
|
249
|
+
: K extends 'meta'
|
|
250
|
+
? never
|
|
251
|
+
: K]: T[K] extends Routes ? StripNonRoutes<T[K]> : T[K];
|
|
214
252
|
};
|
|
215
253
|
|
|
216
254
|
type RecursiveKeys<
|