Skip to content

Commit 53c73ca

Browse files
authored
feat: add reactive url search params (#28)
1 parent 4ef06c5 commit 53c73ca

File tree

6 files changed

+118
-4
lines changed

6 files changed

+118
-4
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'sv-router': patch
3+
---
4+
5+
Add reactive url search params

examples/file-based/src/App.svelte

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
<script lang="ts">
2-
import { isActiveLink, Router } from 'sv-router';
2+
import { isActiveLink, Router, searchParams } from 'sv-router';
33
import { navigate, p } from 'sv-router/generated';
44
</script>
55

66
<a href={p('/')}>Home</a>
77
<a use:isActiveLink href={p('/about')}>About</a>
88
<a href={p('/posts')} data-preload>Posts</a>
9-
<button onclick={() => navigate('/posts/:id', { params: { id: 'programmatic' } })}>
9+
<button
10+
onclick={() =>
11+
navigate('/posts/:id', { params: { id: 'programmatic' }, search: searchParams.toString() })}
12+
>
1013
Programmatic
1114
</button>
15+
<button
16+
onclick={() => {
17+
searchParams.set('test', searchParams.has('test') ? 'ok' : '1');
18+
}}
19+
>
20+
ok
21+
</button>
1222
<Router />
1323

1424
<style>

src/create-router.svelte.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isActive } from './helpers/is-active.js';
33
import { matchRoute } from './helpers/match-route.js';
44
import { preloadOnHover } from './helpers/preload-on-hover.js';
55
import { constructPath, resolveRouteComponents } from './helpers/utils.js';
6+
import { syncSearchParams } from './search-params.svelte.js';
67

78
/** @type {import('./index.d.ts').Routes} */
89
export let routes;
@@ -42,7 +43,7 @@ export function createRouter(r) {
4243
path = constructPath(path, options.params);
4344
}
4445
if (options.search) {
45-
path += options.search;
46+
path += (options.search.startsWith('?') ? '' : '?') + options.search;
4647
}
4748
if (options.hash) {
4849
path += options.hash;
@@ -76,7 +77,11 @@ export function onNavigate() {
7677
if (!routes) {
7778
throw new Error('Router not initialized: `createRouter` was not called.');
7879
}
80+
81+
syncSearchParams();
82+
7983
Object.assign(location, updatedLocation());
84+
8085
const { match, layouts, params: newParams } = matchRoute(globalThis.location.pathname, routes);
8186
params.value = newParams || {};
8287
resolveRouteComponents(match ? [...layouts, match] : layouts).then((components) => {

src/index.d.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import type { Component, Snippet } from 'svelte';
22
import type { Action } from 'svelte/action';
33

4+
/**
5+
* A Svelte action that will add a class to the anchor if its `href` matches the current route. It
6+
* can have an optional `className` parameter to specify the class to add, otherwise it will default
7+
* to `is-active`.
8+
*
9+
* ```svelte
10+
* <a href="/about" use:isActiveLink={{ className: 'active-link' }}>
11+
* ```
12+
*/
13+
export const isActiveLink: IsActiveLink;
414
/**
515
* Setup a new router instance with the given routes.
616
*
@@ -13,8 +23,13 @@ import type { Action } from 'svelte/action';
1323
* ```
1424
*/
1525
export function createRouter<T extends Routes>(r: T): RouterApi<T>;
16-
export const isActiveLink: IsActiveLink;
26+
/** The component that will render the current route. */
1727
export const Router: Component;
28+
/**
29+
* The reactive search params of the URL. It is just a wrapper around `SvelteURLSearchParam` that
30+
* will update the url on change.
31+
*/
32+
export const searchParams: URLSearchParams;
1833

1934
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
2035
type BaseProps = {};

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { isActiveLink } from './actions.svelte.js';
22
export { createRouter } from './create-router.svelte.js';
33
export { default as Router } from './Router.svelte';
4+
export { searchParams } from './search-params.svelte.js';

src/search-params.svelte.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { SvelteURLSearchParams } from 'svelte/reactivity';
2+
3+
let searchParams = new SvelteURLSearchParams(globalThis.location.search);
4+
5+
/** @type {URLSearchParams} */
6+
const shell = {
7+
append(...args) {
8+
searchParams.append(...args);
9+
updateUrlSearchParams();
10+
},
11+
delete(...args) {
12+
searchParams.delete(...args);
13+
updateUrlSearchParams();
14+
},
15+
entries() {
16+
return searchParams.entries();
17+
},
18+
forEach(...args) {
19+
// eslint-disable-next-line unicorn/no-array-for-each
20+
return searchParams.forEach(...args);
21+
},
22+
get(...args) {
23+
return searchParams.get(...args);
24+
},
25+
getAll(...args) {
26+
return searchParams.getAll(...args);
27+
},
28+
has(...args) {
29+
return searchParams.has(...args);
30+
},
31+
keys() {
32+
return searchParams.keys();
33+
},
34+
set(...args) {
35+
searchParams.set(...args);
36+
updateUrlSearchParams();
37+
},
38+
sort() {
39+
searchParams.sort();
40+
updateUrlSearchParams();
41+
},
42+
toString() {
43+
return searchParams.toString();
44+
},
45+
values() {
46+
return searchParams.values();
47+
},
48+
get size() {
49+
return searchParams.size;
50+
},
51+
[Symbol.iterator]() {
52+
return searchParams[Symbol.iterator]();
53+
},
54+
};
55+
56+
export { shell as searchParams };
57+
58+
export function syncSearchParams() {
59+
const newSearchParams = new URLSearchParams(globalThis.location.search);
60+
if (searchParams.toString() === newSearchParams.toString()) {
61+
return;
62+
}
63+
64+
for (const key of searchParams.keys()) {
65+
searchParams.delete(key);
66+
}
67+
for (const [key, value] of newSearchParams.entries()) {
68+
searchParams.append(key, value);
69+
}
70+
}
71+
72+
function updateUrlSearchParams() {
73+
let url = globalThis.location.origin + globalThis.location.pathname;
74+
if (searchParams.size > 0) {
75+
url += '?' + searchParams.toString();
76+
}
77+
globalThis.history.pushState({}, '', url);
78+
}

0 commit comments

Comments
 (0)