Skip to content

Commit

Permalink
feat: reactive url
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau committed Apr 13, 2024
1 parent d5776c3 commit baedc06
Show file tree
Hide file tree
Showing 8 changed files with 445 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/tidy-chefs-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

feat: reactive url
1 change: 1 addition & 0 deletions packages/svelte/src/reactivity/index-client.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { ReactiveDate as Date } from './date.js';
export { ReactiveSet as Set } from './set.js';
export { ReactiveMap as Map } from './map.js';
export { ReactiveURL as URL, ReactiveURLSearchParams as URLSearchParams } from './url.js';
2 changes: 2 additions & 0 deletions packages/svelte/src/reactivity/index-server.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const Date = globalThis.Date;
export const Set = globalThis.Set;
export const Map = globalThis.Map;
export const URL = globalThis.URL;
export const URLSearchParams = globalThis.URLSearchParams;
287 changes: 287 additions & 0 deletions packages/svelte/src/reactivity/url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import { source, set } from '../internal/client/reactivity/sources.js';
import { get } from '../internal/client/runtime.js';
import { map } from './utils.js';

const UPDATE = Symbol.for('UPDATE');

export class ReactiveURL extends URL {
#url = {
protocol: source(super.protocol),
username: source(super.username),
password: source(super.password),
hostname: source(super.hostname),
port: source(super.port),
pathname: source(super.pathname),
search: source(super.search),
hash: source(super.hash)
};
#searchParams = new ReactiveURLSearchParams(super.searchParams, this.#url.search);

get hash() {
get(this.#url.hash);
return super.hash;
}

set hash(value) {
super.hash = value;
set(this.#url.hash, super.hash);
}

get host() {
get(this.#url.hostname);
get(this.#url.port);
return super.host;
}

set host(value) {
super.host = value;
set(this.#url.hostname, super.hostname);
set(this.#url.port, super.port);
}

get hostname() {
get(this.#url.hostname);
return super.hostname;
}

set hostname(value) {
super.hostname = value;
set(this.#url.hostname, super.hostname);
}

get href() {
get(this.#url.protocol);
get(this.#url.username);
get(this.#url.password);
get(this.#url.hostname);
get(this.#url.port);
get(this.#url.pathname);
get(this.#url.search);
get(this.#url.hash);
return super.href;
}

set href(value) {
super.href = value;
set(this.#url.protocol, super.protocol);
set(this.#url.username, super.username);
set(this.#url.password, super.password);
set(this.#url.hostname, super.hostname);
set(this.#url.port, super.port);
set(this.#url.pathname, super.pathname);
set(this.#url.search, super.search);
set(this.#url.hash, super.hash);
this.#searchParams[UPDATE](super.searchParams);
}

get password() {
get(this.#url.password);
return super.password;
}

set password(value) {
super.password = value;
set(this.#url.password, super.password);
}

get pathname() {
get(this.#url.pathname);
return super.pathname;
}

set pathname(value) {
super.pathname = value;
set(this.#url.pathname, super.pathname);
}

get port() {
get(this.#url.port);
return super.port;
}

set port(value) {
super.port = value;
set(this.#url.port, super.port);
}

get protocol() {
get(this.#url.protocol);
return super.protocol;
}

set protocol(value) {
super.protocol = value;
set(this.#url.protocol, super.protocol);
}

get search() {
get(this.#url.search);
return super.search;
}

set search(value) {
super.search = value;
set(this.#url.search, super.search);
this.#searchParams[UPDATE](super.searchParams);
}

get username() {
get(this.#url.username);
return super.username;
}

set username(value) {
super.username = value;
set(this.#url.username, super.username);
}

get origin() {
get(this.#url.protocol);
get(this.#url.hostname);
get(this.#url.port);
return super.origin;
}

get searchParams() {
return this.#searchParams;
}

toString() {
this.href;
return super.toString();
}

toJSON() {
this.href;
return super.toJSON();
}

/**
* @param {string} input
* @param {string=} base
*/
constructor(input, base) {
super(input, base);
}
}

export class ReactiveURLSearchParams extends URLSearchParams {
#url_search_params;
#search;
#version = source(0);

#increment_version() {
set(this.#version, this.#version.v + 1);
}
#update_search() {
set(this.#search, '?' + this.#url_search_params.toString());
}

/**
*
* @param {URLSearchParams} value
*/
[UPDATE](value) {
this.#url_search_params = value;
this.#increment_version();
}
/**
*
* @param {URLSearchParams} url_search_params
* @param {import('../internal/client/reactivity/types.js').Source<string>} search
*/
constructor(url_search_params, search) {
super();
this.#url_search_params = url_search_params;
this.#search = search;
}

/**
*
* @param {string} name
* @param {string} value
* @returns {void}
*/
append(name, value) {
this.#increment_version();
this.#update_search();
return this.#url_search_params.append(name, value);
}
/**
*
* @param {string} name
* @param {string=} value
* @returns {void}
*/
delete(name, value) {
this.#increment_version();
this.#update_search();
return this.#url_search_params.delete(name, value);
}
/**
*
* @param {string} name
* @returns {string|null}
*/
get(name) {
get(this.#version);
return this.#url_search_params.get(name);
}
/**
*
* @param {string} name
* @returns {string[]}
*/
getAll(name) {
get(this.#version);
return this.#url_search_params.getAll(name);
}
/**
*
* @param {string} name
* @param {string=} value
* @returns {boolean}
*/
has(name, value) {
get(this.#version);
return this.#url_search_params.has(name, value);
}
keys() {
get(this.#version);
return this.#url_search_params.keys();
}
/**
*
* @param {string} name
* @param {string} value
* @returns {void}
*/
set(name, value) {
this.#increment_version();
this.#update_search();
return this.#url_search_params.set(name, value);
}
sort() {
this.#increment_version();
this.#update_search();
return this.#url_search_params.sort();
}
toString() {
get(this.#version);
return this.#url_search_params.toString();
}
values() {
get(this.#version);
return this.#url_search_params.values();
}
entries() {
get(this.#version);
return this.#url_search_params.entries();
}
[Symbol.iterator]() {
return this.entries();
}
get size() {
return this.#url_search_params.size;
}
}
78 changes: 78 additions & 0 deletions packages/svelte/src/reactivity/url.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { render_effect, effect_root } from '../internal/client/reactivity/effects.js';
import { flushSync } from '../index-client.js';
import { ReactiveURL } from './url.js';
import { assert, test } from 'vitest';

test('url.hash', () => {
const url = new ReactiveURL('http://google.com');
const log: any = [];

const cleanup = effect_root(() => {
render_effect(() => {
log.push(url.hash);
});
});

flushSync(() => {
url.hash = 'abc';
});

flushSync(() => {
url.href = 'http://google.com/a/b/c#def';
});

flushSync(() => {
// does not affect hash
url.pathname = 'e/f';
});

assert.deepEqual(log, ['', '#abc', '#def']);

cleanup();
});

test('url.searchParams', () => {
const url = new ReactiveURL('https://svelte.dev?foo=bar&t=123');
const log: any = [];

const cleanup = effect_root(() => {
render_effect(() => {
log.push('search: ' + url.search);
});
render_effect(() => {
log.push('foo: ' + url.searchParams.get('foo'));
});
render_effect(() => {
log.push('q: ' + url.searchParams.has('q'));
});
});

flushSync(() => {
url.search = '?q=kit&foo=baz';
});

flushSync(() => {
url.searchParams.append('foo', 'qux');
});

flushSync(() => {
url.searchParams.delete('foo');
});

assert.deepEqual(log, [
'search: ?foo=bar&t=123',
'foo: bar',
'q: false',
'search: ?q=kit&foo=baz',
'foo: baz',
'q: true',
'search: ?q=kit&foo=baz&foo=qux',
'foo: baz',
'q: true',
'search: ?q=kit',
'foo: null',
'q: true'
]);

cleanup();
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { Date } from 'svelte/reactivity';
import { URL } from 'svelte/reactivity';
let date = new Date('2024-02-23T15:00:00Z');
</script>
Expand Down
Loading

0 comments on commit baedc06

Please sign in to comment.