mirror of https://github.com/sveltejs/svelte
parent
d5776c3ec3
commit
4d23a6ab2a
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
feat: reactive url
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
@ -0,0 +1,46 @@
|
||||
import { flushSync } from '../../../../src/index-client';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: `<div>href: https://svelte.dev/repl/hello-world?version=5.0</div><div>host: svelte.dev</div><div>pathname: /repl/hello-world</div><div>search: ?version=5.0</div><div>version: 5.0</div><div>t:</div><button>update hostname</button><button>update pathname</button><button>update search</button><button>update href</button>`,
|
||||
|
||||
test({ assert, target }) {
|
||||
const [btn, btn2, btn3, btn4] = target.querySelectorAll('button');
|
||||
|
||||
flushSync(() => {
|
||||
btn?.click();
|
||||
});
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<div>href: https://kit.svelte.dev/repl/hello-world?version=5.0</div><div>host: kit.svelte.dev</div><div>pathname: /repl/hello-world</div><div>search: ?version=5.0</div><div>version: 5.0</div><div>t:</div><button>update hostname</button><button>update pathname</button><button>update search</button><button>update href</button>`
|
||||
);
|
||||
|
||||
flushSync(() => {
|
||||
btn2?.click();
|
||||
});
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<div>href: https://kit.svelte.dev/docs/introduction?version=5.0</div><div>host: kit.svelte.dev</div><div>pathname: /docs/introduction</div><div>search: ?version=5.0</div><div>version: 5.0</div><div>t:</div><button>update hostname</button><button>update pathname</button><button>update search</button><button>update href</button>`
|
||||
);
|
||||
|
||||
flushSync(() => {
|
||||
btn3?.click();
|
||||
});
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<div>href: https://kit.svelte.dev/docs/introduction?t=123</div><div>host: kit.svelte.dev</div><div>pathname: /docs/introduction</div><div>search: ?t=123</div><div>version:</div><div>t: 123</div><button>update hostname</button><button>update pathname</button><button>update search</button><button>update href</button>`
|
||||
);
|
||||
|
||||
flushSync(() => {
|
||||
btn4?.click();
|
||||
});
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<div>href: https://google.com/search?version=3</div><div>host: google.com</div><div>pathname: /search</div><div>search: ?version=3</div><div>version: 3</div><div>t:</div><button>update hostname</button><button>update pathname</button><button>update search</button><button>update href</button>`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import { URL } from 'svelte/reactivity';
|
||||
|
||||
let url = new URL('https://svelte.dev/repl/hello-world?version=5.0');
|
||||
</script>
|
||||
|
||||
<div>href: {url.href}</div>
|
||||
<div>host: {url.host}</div>
|
||||
<div>pathname: {url.pathname}</div>
|
||||
<div>search: {url.search}</div>
|
||||
<div>version: {url.searchParams.get('version')}</div>
|
||||
<div>t: {url.searchParams.get('t')}</div>
|
||||
|
||||
<button onclick={() => {
|
||||
url.hostname = 'kit.svelte.dev';
|
||||
}}>update hostname</button>
|
||||
<button onclick={() => {
|
||||
url.pathname = 'docs/introduction';
|
||||
}}>update pathname</button>
|
||||
<button onclick={() => {
|
||||
url.search = '?t=123';
|
||||
}}>update search</button>
|
||||
<button onclick={() => {
|
||||
url.href = 'https://google.com/search?version=3';
|
||||
}}>update href</button>
|
||||
Loading…
Reference in new issue