sv-router 0.8.0 → 0.9.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 +2 -1
- package/package.json +1 -1
- package/src/RecursiveComponentTree.svelte +5 -8
- package/src/create-router.svelte.js +45 -40
- package/src/gen/generate-router-code.js +5 -4
- package/src/gen/write-router-code.js +4 -1
- package/src/helpers/match-route.js +1 -0
- package/src/helpers/preload.js +7 -2
- package/src/helpers/utils.js +83 -2
- package/src/index.d.ts +29 -11
- package/src/search-params.svelte.js +21 -15
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="./docs/public/logo.svg" alt="" height="128px">
|
|
3
|
+
<img src="./docs/public/logo.svg" alt="" width="128px" height="128px">
|
|
4
4
|
|
|
5
5
|
# sv-router
|
|
6
6
|
|
|
@@ -26,6 +26,7 @@ A feature-rich yet intuitive routing library for Svelte single-page apps.
|
|
|
26
26
|
- 🧩 **Familiar API**: Follows established conventions from popular meta frameworks.
|
|
27
27
|
- 🪶 **Lightweight**: Minimal impact on your bundle size.
|
|
28
28
|
- 🚀 **Made for Svelte 5**: True Svelte 5 implementation with the latest features.
|
|
29
|
+
- #️⃣ **Hash-based routing**: Hash-based routing enables usage in local environments.
|
|
29
30
|
|
|
30
31
|
## Getting Started
|
|
31
32
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { params } from './create-router.svelte.js';
|
|
3
2
|
import RecursiveComponentTree from './RecursiveComponentTree.svelte';
|
|
4
3
|
|
|
5
4
|
/** @type {{ tree: import('svelte').Component[] }} */
|
|
@@ -9,10 +8,8 @@
|
|
|
9
8
|
const restTree = $derived(tree.slice(1));
|
|
10
9
|
</script>
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</FirstComponent>
|
|
18
|
-
{/key}
|
|
11
|
+
<FirstComponent>
|
|
12
|
+
{#if restTree.length > 0}
|
|
13
|
+
<RecursiveComponentTree tree={restTree}></RecursiveComponentTree>
|
|
14
|
+
{/if}
|
|
15
|
+
</FirstComponent>
|
|
@@ -4,8 +4,11 @@ import { matchRoute } from './helpers/match-route.js';
|
|
|
4
4
|
import { preload, preloadOnHover } from './helpers/preload.js';
|
|
5
5
|
import {
|
|
6
6
|
constructPath,
|
|
7
|
+
constructUrl,
|
|
7
8
|
join,
|
|
9
|
+
parseSearch,
|
|
8
10
|
resolveRouteComponents,
|
|
11
|
+
serializeSearch,
|
|
9
12
|
stripBase,
|
|
10
13
|
updatedLocation,
|
|
11
14
|
} from './helpers/utils.js';
|
|
@@ -22,11 +25,11 @@ export const base = {
|
|
|
22
25
|
/** @type {{ value: import('svelte').Component[] }} */
|
|
23
26
|
export let componentTree = $state({ value: [] });
|
|
24
27
|
|
|
25
|
-
/** @type {{ value: Record<string, string> }} */
|
|
26
|
-
export let params = $state({ value: {} });
|
|
27
|
-
|
|
28
28
|
export let location = $state(updatedLocation());
|
|
29
29
|
|
|
30
|
+
/** @type {{ value: Record<string, string> }} */
|
|
31
|
+
let params = $state({ value: {} });
|
|
32
|
+
|
|
30
33
|
let meta = $state({ value: {} });
|
|
31
34
|
|
|
32
35
|
let navigationIndex = 0;
|
|
@@ -70,7 +73,7 @@ export function createRouter(r) {
|
|
|
70
73
|
preloadOnHover(routes);
|
|
71
74
|
|
|
72
75
|
return {
|
|
73
|
-
p:
|
|
76
|
+
p: constructUrl,
|
|
74
77
|
navigate,
|
|
75
78
|
isActive,
|
|
76
79
|
async preload(pathname) {
|
|
@@ -90,7 +93,7 @@ export function createRouter(r) {
|
|
|
90
93
|
return /** @type {import('./index.d.ts').Path<T>} */ (stripBase(location.pathname));
|
|
91
94
|
},
|
|
92
95
|
get search() {
|
|
93
|
-
return location.search;
|
|
96
|
+
return parseSearch(location.search);
|
|
94
97
|
},
|
|
95
98
|
get state() {
|
|
96
99
|
return location.state;
|
|
@@ -107,7 +110,10 @@ export function createRouter(r) {
|
|
|
107
110
|
|
|
108
111
|
/**
|
|
109
112
|
* @param {string | number} path
|
|
110
|
-
* @param {import('./index.d.ts').NavigateOptions & {
|
|
113
|
+
* @param {import('./index.d.ts').NavigateOptions & {
|
|
114
|
+
* params?: Record<string, string>;
|
|
115
|
+
* search?: import('./index.d.ts').Search;
|
|
116
|
+
* }} options
|
|
111
117
|
*/
|
|
112
118
|
function navigate(path, options = {}) {
|
|
113
119
|
if (typeof path === 'number') {
|
|
@@ -116,37 +122,14 @@ function navigate(path, options = {}) {
|
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
path = constructPath(path, options.params);
|
|
119
|
-
if (options.search && !options.search.startsWith('?')) {
|
|
120
|
-
options.search = '?' + options.search;
|
|
121
|
-
}
|
|
122
|
-
if (options.hash && !options.hash.startsWith('#') && base.name !== '#') {
|
|
123
|
-
options.hash = '#' + options.hash;
|
|
124
|
-
}
|
|
125
125
|
if (base.name === '#') {
|
|
126
126
|
path = new URL(path).hash;
|
|
127
|
+
} else if (options.hash && !options.hash.startsWith('#')) {
|
|
128
|
+
options.hash = '#' + options.hash;
|
|
127
129
|
}
|
|
128
130
|
onNavigate(path, options);
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
/** @param {string} [path] */
|
|
132
|
-
function getMatchPath(path) {
|
|
133
|
-
let matchPath = '';
|
|
134
|
-
|
|
135
|
-
if (path) {
|
|
136
|
-
matchPath = path;
|
|
137
|
-
} else if (base.name === '#') {
|
|
138
|
-
matchPath = globalThis.location.hash.slice(1);
|
|
139
|
-
} else {
|
|
140
|
-
matchPath = globalThis.location.pathname;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (base.name && matchPath.startsWith(base.name)) {
|
|
144
|
-
matchPath = matchPath.slice(base.name.length) || '/';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return stripBase(matchPath);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
133
|
/**
|
|
151
134
|
* @param {string} [path]
|
|
152
135
|
* @param {import('./index.d.ts').NavigateOptions} options
|
|
@@ -162,16 +145,19 @@ export async function onNavigate(path, options = {}) {
|
|
|
162
145
|
let matchPath = getMatchPath(path);
|
|
163
146
|
const { match, layouts, hooks, meta: newMeta, params: newParams } = matchRoute(matchPath, routes);
|
|
164
147
|
|
|
148
|
+
const search = parseSearch(options.search);
|
|
149
|
+
const hooksContext = { pathname: matchPath, meta: newMeta, ...options, search };
|
|
150
|
+
|
|
165
151
|
let errorHooks = [];
|
|
166
152
|
for (const hook of hooks) {
|
|
167
153
|
try {
|
|
168
154
|
const { beforeLoad } = hook;
|
|
169
155
|
errorHooks.push(hook);
|
|
170
156
|
pendingNavigationIndex = currentNavigationIndex;
|
|
171
|
-
await beforeLoad?.(
|
|
157
|
+
await beforeLoad?.(hooksContext);
|
|
172
158
|
} catch (error) {
|
|
173
159
|
for (const { onError } of errorHooks) {
|
|
174
|
-
void onError?.(error,
|
|
160
|
+
void onError?.(error, hooksContext);
|
|
175
161
|
}
|
|
176
162
|
return;
|
|
177
163
|
}
|
|
@@ -184,7 +170,7 @@ export async function onNavigate(path, options = {}) {
|
|
|
184
170
|
routeComponents = await resolveRouteComponents(match ? [...layouts, match] : layouts);
|
|
185
171
|
} catch (error) {
|
|
186
172
|
for (const { onError } of hooks) {
|
|
187
|
-
void onError?.(error,
|
|
173
|
+
void onError?.(error, hooksContext);
|
|
188
174
|
}
|
|
189
175
|
throw error;
|
|
190
176
|
}
|
|
@@ -196,18 +182,18 @@ export async function onNavigate(path, options = {}) {
|
|
|
196
182
|
}
|
|
197
183
|
|
|
198
184
|
if (path) {
|
|
199
|
-
|
|
200
|
-
url
|
|
201
|
-
|
|
185
|
+
const search = serializeSearch(options.search);
|
|
186
|
+
const url = new URL(globalThis.location.toString());
|
|
187
|
+
url.search = search || '';
|
|
188
|
+
url.hash = options.hash || '';
|
|
202
189
|
if (base.name === '#') {
|
|
203
190
|
url.hash = path;
|
|
204
191
|
} else {
|
|
205
|
-
if (options.hash) path += options.hash;
|
|
206
192
|
url.pathname = base.name ? join(base.name, path) : path;
|
|
207
193
|
}
|
|
208
194
|
const historyMethod = options.replace ? 'replaceState' : 'pushState';
|
|
209
195
|
globalThis.history[historyMethod](options.state || {}, '', url.toString());
|
|
210
|
-
syncSearchParams(
|
|
196
|
+
syncSearchParams(search);
|
|
211
197
|
} else {
|
|
212
198
|
syncSearchParams(globalThis.location.search);
|
|
213
199
|
}
|
|
@@ -228,8 +214,27 @@ export async function onNavigate(path, options = {}) {
|
|
|
228
214
|
}
|
|
229
215
|
|
|
230
216
|
for (const { afterLoad } of hooks) {
|
|
231
|
-
void afterLoad?.(
|
|
217
|
+
void afterLoad?.(hooksContext);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** @param {string} [path] */
|
|
222
|
+
function getMatchPath(path) {
|
|
223
|
+
let matchPath = '';
|
|
224
|
+
|
|
225
|
+
if (path) {
|
|
226
|
+
matchPath = path;
|
|
227
|
+
} else if (base.name === '#') {
|
|
228
|
+
matchPath = globalThis.location.hash.slice(1);
|
|
229
|
+
} else {
|
|
230
|
+
matchPath = globalThis.location.pathname;
|
|
232
231
|
}
|
|
232
|
+
|
|
233
|
+
if (base.name && matchPath.startsWith(base.name)) {
|
|
234
|
+
matchPath = matchPath.slice(base.name.length) || '/';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return stripBase(matchPath);
|
|
233
238
|
}
|
|
234
239
|
|
|
235
240
|
/** @param {Event} event */
|
|
@@ -18,7 +18,7 @@ const META_FILENAME_REGEX = /(?<=[/.]|^)(meta)(\.svelte)?\.(js|ts)$/; // meta.js
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* @param {string} routesPath
|
|
21
|
-
* @param {{ allLazy?: boolean }} [options]
|
|
21
|
+
* @param {{ allLazy?: boolean; js?: boolean }} [options]
|
|
22
22
|
* @returns {string}
|
|
23
23
|
*/
|
|
24
24
|
export function generateRouterCode(routesPath, options) {
|
|
@@ -130,10 +130,10 @@ function filePathToRoute(filename) {
|
|
|
130
130
|
/**
|
|
131
131
|
* @param {GeneratedRoutes} routes
|
|
132
132
|
* @param {string} routesPath
|
|
133
|
-
* @param {{ allLazy?: boolean }} [options]
|
|
133
|
+
* @param {{ allLazy?: boolean; js?: boolean }} [options]
|
|
134
134
|
* @returns {string}
|
|
135
135
|
*/
|
|
136
|
-
export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
|
|
136
|
+
export function createRouterCode(routes, routesPath, { allLazy = false, js = false } = {}) {
|
|
137
137
|
if (!routesPath.endsWith('/')) {
|
|
138
138
|
routesPath += '/';
|
|
139
139
|
}
|
|
@@ -178,8 +178,9 @@ export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
|
|
|
178
178
|
...imports,
|
|
179
179
|
'',
|
|
180
180
|
`const routes = ${stringifiedRoutes};`,
|
|
181
|
-
'export type Routes = typeof routes;',
|
|
181
|
+
...(js ? [] : ['export type Routes = typeof routes;']),
|
|
182
182
|
'export const { p, navigate, isActive, preload, route } = createRouter(routes);',
|
|
183
|
+
'',
|
|
183
184
|
].join('\n');
|
|
184
185
|
}
|
|
185
186
|
|
|
@@ -32,7 +32,10 @@ export function writeRouterCode() {
|
|
|
32
32
|
writeFileIfDifferent(genConfig.tsconfigPath, JSON.stringify(tsConfig, undefined, 2));
|
|
33
33
|
|
|
34
34
|
// Write `.router/router.ts` file
|
|
35
|
-
const routerCode = generateRouterCode(genConfig.routesPath, {
|
|
35
|
+
const routerCode = generateRouterCode(genConfig.routesPath, {
|
|
36
|
+
allLazy: genConfig.allLazy,
|
|
37
|
+
js: genConfig.routesInJs,
|
|
38
|
+
});
|
|
36
39
|
const written = writeFileIfDifferent(genConfig.routerPath, routerCode);
|
|
37
40
|
|
|
38
41
|
if (written) {
|
package/src/helpers/preload.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { matchRoute } from './match-route.js';
|
|
2
|
-
import { resolveRouteComponents } from './utils.js';
|
|
2
|
+
import { parseSearch, resolveRouteComponents } from './utils.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @param {import('../index.js').Routes} routes
|
|
@@ -9,7 +9,12 @@ import { resolveRouteComponents } from './utils.js';
|
|
|
9
9
|
export async function preload(routes, path, options) {
|
|
10
10
|
const { match, layouts, hooks, meta } = matchRoute(path, routes);
|
|
11
11
|
for (const { onPreload } of hooks) {
|
|
12
|
-
void onPreload?.({
|
|
12
|
+
void onPreload?.({
|
|
13
|
+
pathname: path,
|
|
14
|
+
meta,
|
|
15
|
+
...options,
|
|
16
|
+
search: parseSearch(options?.search),
|
|
17
|
+
});
|
|
13
18
|
}
|
|
14
19
|
await resolveRouteComponents(match ? [...layouts, match] : layouts);
|
|
15
20
|
}
|
package/src/helpers/utils.js
CHANGED
|
@@ -2,13 +2,13 @@ import { base } from '../create-router.svelte.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @param {string} path
|
|
5
|
-
* @param {Record<string, string>} [params]
|
|
5
|
+
* @param {Record<string, string | number | boolean>} [params]
|
|
6
6
|
* @returns {string}
|
|
7
7
|
*/
|
|
8
8
|
export function constructPath(path, params) {
|
|
9
9
|
if (params) {
|
|
10
10
|
for (const key in params) {
|
|
11
|
-
path = path.replace(`:${key}`, params[key]);
|
|
11
|
+
path = path.replace(`:${key}`, String(params[key]));
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -23,6 +23,24 @@ export function constructPath(path, params) {
|
|
|
23
23
|
return path;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} path
|
|
28
|
+
* @param {import('../index.d.ts').ConstructUrlOptions & {
|
|
29
|
+
* params?: Record<string, string | number | boolean>;
|
|
30
|
+
* }} [options]
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function constructUrl(path, options) {
|
|
34
|
+
let result = constructPath(path, options?.params);
|
|
35
|
+
if (options?.search) {
|
|
36
|
+
result += serializeSearch(options.search);
|
|
37
|
+
}
|
|
38
|
+
if (options?.hash && !options.hash.startsWith('#')) {
|
|
39
|
+
result += '#' + options.hash;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
/**
|
|
27
45
|
* @param {import('../index.d.ts').RouteComponent[]} input
|
|
28
46
|
* @returns {Promise<import('svelte').Component[]>}
|
|
@@ -95,3 +113,66 @@ export function updatedLocation() {
|
|
|
95
113
|
hash,
|
|
96
114
|
};
|
|
97
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {import('../index.d.ts').Search} [value]
|
|
119
|
+
* @returns {string | undefined}
|
|
120
|
+
*/
|
|
121
|
+
export function serializeSearch(value) {
|
|
122
|
+
if (!value) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (typeof value === 'string') {
|
|
127
|
+
if (!value.startsWith('?')) {
|
|
128
|
+
value = '?' + value;
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const stringValues = Object.fromEntries(
|
|
134
|
+
Object.entries(value).map(([key, value]) => [key, String(value)]),
|
|
135
|
+
);
|
|
136
|
+
const urlSearchParams = new URLSearchParams(stringValues);
|
|
137
|
+
return '?' + urlSearchParams.toString();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {import('../index.d.ts').Search} [value]
|
|
142
|
+
* @returns {Record<string, string | number | boolean>}
|
|
143
|
+
*/
|
|
144
|
+
export function parseSearch(value) {
|
|
145
|
+
if (!value) {
|
|
146
|
+
return {};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof value === 'string') {
|
|
150
|
+
const searchParams = new URLSearchParams(value);
|
|
151
|
+
return Object.fromEntries(
|
|
152
|
+
searchParams.entries().map(([key, value]) => [key, parseSearchValue(value)]),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {string} value
|
|
161
|
+
* @returns {string | number | boolean}
|
|
162
|
+
*/
|
|
163
|
+
export function parseSearchValue(value) {
|
|
164
|
+
if (value === '') {
|
|
165
|
+
return '';
|
|
166
|
+
}
|
|
167
|
+
if (value === 'true') {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (value === 'false') {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const number = Number(value);
|
|
174
|
+
if (!Number.isNaN(number)) {
|
|
175
|
+
return number;
|
|
176
|
+
}
|
|
177
|
+
return value;
|
|
178
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -183,7 +183,7 @@ export type RouterApi<T extends Routes> = {
|
|
|
183
183
|
/** The reactive pathname of the URL. */
|
|
184
184
|
pathname: (Path<T, true> & {}) | (string & {});
|
|
185
185
|
/** The reactive query string part of the URL. */
|
|
186
|
-
search: string
|
|
186
|
+
search: Record<string, string | number | boolean>;
|
|
187
187
|
/** The reactive history state that can be passed to the `navigate` function. */
|
|
188
188
|
state: unknown;
|
|
189
189
|
/** The reactive hash part of the URL. */
|
|
@@ -198,7 +198,9 @@ export type Path<T extends Routes, AnyParam extends boolean = false> = RemovePar
|
|
|
198
198
|
>;
|
|
199
199
|
|
|
200
200
|
export type ConstructPathArgs<TPath extends string> = {
|
|
201
|
-
[Path in TPath]: PathParams<Path> extends never
|
|
201
|
+
[Path in TPath]: PathParams<Path> extends never
|
|
202
|
+
? [Path] | [Path, ConstructUrlOptions]
|
|
203
|
+
: [Path, ConstructUrlOptions & { params: PathParams<Path> }];
|
|
202
204
|
}[TPath];
|
|
203
205
|
|
|
204
206
|
export type IsActiveArgs<
|
|
@@ -223,19 +225,21 @@ export type AllParams<TRoutes extends Routes> = Partial<
|
|
|
223
225
|
Record<ExtractParams<RemoveParenthesis<RecursiveKeys<TRoutes>>>, string>
|
|
224
226
|
>;
|
|
225
227
|
|
|
228
|
+
export type Search = string | Record<string, string | number | boolean>;
|
|
229
|
+
|
|
226
230
|
export type HooksContext = {
|
|
227
231
|
hash?: string;
|
|
228
232
|
meta: RouteMeta;
|
|
229
233
|
pathname: string;
|
|
230
234
|
replace?: boolean;
|
|
231
|
-
search
|
|
235
|
+
search: Record<string, string | number | boolean>;
|
|
232
236
|
state?: string;
|
|
233
237
|
};
|
|
234
238
|
|
|
235
239
|
export type NavigateOptions =
|
|
236
240
|
| {
|
|
237
241
|
replace?: boolean;
|
|
238
|
-
search?:
|
|
242
|
+
search?: Search;
|
|
239
243
|
state?: string;
|
|
240
244
|
hash?: string;
|
|
241
245
|
scrollToTop?: ScrollBehavior | false;
|
|
@@ -243,17 +247,31 @@ export type NavigateOptions =
|
|
|
243
247
|
}
|
|
244
248
|
| undefined;
|
|
245
249
|
|
|
246
|
-
export type
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
export type ConstructUrlOptions =
|
|
251
|
+
| {
|
|
252
|
+
search?: Search;
|
|
253
|
+
hash?: string;
|
|
254
|
+
}
|
|
255
|
+
| undefined;
|
|
256
|
+
|
|
257
|
+
export type SearchParams = Omit<
|
|
258
|
+
URLSearchParams,
|
|
259
|
+
'append' | 'delete' | 'entries' | 'get' | 'getAll' | 'set' | 'sort' | 'values'
|
|
260
|
+
> & {
|
|
261
|
+
append(name: string, value: string | number | boolean, options?: { replace?: boolean }): void;
|
|
262
|
+
delete(name: string, value?: string | number | boolean, options?: { replace?: boolean }): void;
|
|
263
|
+
entries(): IterableIterator<[string, string | number | boolean]>;
|
|
264
|
+
get(name: string): string | number | boolean | null;
|
|
265
|
+
getAll(name: string): (string | number | boolean)[];
|
|
266
|
+
set(name: string, value: string | number | boolean, options?: { replace?: boolean }): void;
|
|
267
|
+
sort(options?: { replace?: boolean }): void;
|
|
268
|
+
values(): IterableIterator<string | number | boolean>;
|
|
251
269
|
};
|
|
252
270
|
|
|
253
271
|
type NavigateArgs<T extends string> =
|
|
254
272
|
| (PathParams<T> extends never
|
|
255
|
-
? [T] | [T, NavigateOptions]
|
|
256
|
-
: [T, NavigateOptions & { params: PathParams<T> }])
|
|
273
|
+
? [T] | [T, Omit<NavigateOptions, 'search'> & { search: Search }]
|
|
274
|
+
: [T, Omit<NavigateOptions, 'search'> & { search: Search; params: PathParams<T> }])
|
|
257
275
|
| [number];
|
|
258
276
|
|
|
259
277
|
type StripNonRoutes<T extends Routes> = {
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
|
2
|
+
import { parseSearchValue } from './helpers/utils.js';
|
|
2
3
|
|
|
3
4
|
let searchParams = new SvelteURLSearchParams(globalThis.location.search);
|
|
4
5
|
|
|
5
6
|
/** @type {import('./index.js').SearchParams} */
|
|
6
7
|
const shell = {
|
|
7
8
|
append(name, value, options) {
|
|
8
|
-
searchParams.append(name, value);
|
|
9
|
+
searchParams.append(name, String(value));
|
|
9
10
|
updateUrlSearchParams(options);
|
|
10
11
|
},
|
|
11
12
|
delete(name, value, options) {
|
|
12
|
-
searchParams.delete(name, value);
|
|
13
|
+
searchParams.delete(name, value === undefined ? undefined : String(value));
|
|
13
14
|
updateUrlSearchParams(options);
|
|
14
15
|
},
|
|
15
16
|
entries() {
|
|
16
|
-
return searchParams.entries();
|
|
17
|
+
return searchParams.entries().map(([key, value]) => [key, parseSearchValue(value)]);
|
|
17
18
|
},
|
|
18
19
|
forEach(...args) {
|
|
19
|
-
// eslint-disable-next-line unicorn/no-array-for-each
|
|
20
20
|
return searchParams.forEach(...args);
|
|
21
21
|
},
|
|
22
22
|
get(...args) {
|
|
23
|
-
|
|
23
|
+
const value = searchParams.get(...args);
|
|
24
|
+
if (value === null) return null;
|
|
25
|
+
return parseSearchValue(value);
|
|
24
26
|
},
|
|
25
27
|
getAll(...args) {
|
|
26
|
-
return searchParams.getAll(...args);
|
|
28
|
+
return searchParams.getAll(...args).map(parseSearchValue);
|
|
27
29
|
},
|
|
28
30
|
has(...args) {
|
|
29
31
|
return searchParams.has(...args);
|
|
@@ -32,7 +34,7 @@ const shell = {
|
|
|
32
34
|
return searchParams.keys();
|
|
33
35
|
},
|
|
34
36
|
set(name, value, options) {
|
|
35
|
-
searchParams.set(name, value);
|
|
37
|
+
searchParams.set(name, String(value));
|
|
36
38
|
updateUrlSearchParams(options);
|
|
37
39
|
},
|
|
38
40
|
sort(options) {
|
|
@@ -43,7 +45,7 @@ const shell = {
|
|
|
43
45
|
return searchParams.toString();
|
|
44
46
|
},
|
|
45
47
|
values() {
|
|
46
|
-
return searchParams.values();
|
|
48
|
+
return searchParams.values().map(parseSearchValue);
|
|
47
49
|
},
|
|
48
50
|
get size() {
|
|
49
51
|
return searchParams.size;
|
|
@@ -57,19 +59,23 @@ export { shell as searchParams };
|
|
|
57
59
|
|
|
58
60
|
/** @param {string} [search] */
|
|
59
61
|
export function syncSearchParams(search) {
|
|
60
|
-
if (searchParams.toString()
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
if (searchParams.toString() === search) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const newSearch = new URLSearchParams(search);
|
|
66
|
+
for (const [key, value] of newSearch) {
|
|
67
|
+
searchParams.set(key, value);
|
|
68
|
+
}
|
|
69
|
+
for (const key of searchParams.keys()) {
|
|
70
|
+
if (!newSearch.has(key)) {
|
|
71
|
+
searchParams.delete(key);
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
/** @param {{ replace?: boolean }} [options] */
|
|
70
77
|
function updateUrlSearchParams(options) {
|
|
71
|
-
|
|
78
|
+
const url = new URL(globalThis.location.toString());
|
|
72
79
|
url.search = searchParams.toString();
|
|
73
|
-
|
|
74
80
|
globalThis.history[options?.replace ? 'replaceState' : 'pushState']({}, '', url);
|
|
75
81
|
}
|