pull/17902/merge
Rich Harris 5 days ago committed by GitHub
commit deed13b9b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: make SvelteSet work with async mode

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: defer batch resolution until earlier intersecting batches have committed

@ -1,8 +1,9 @@
/** @import { Source } from '#client' */
import { DEV } from 'esm-env';
import { source, set, state, increment } from '../internal/client/reactivity/sources.js';
import { source, set, state } from '../internal/client/reactivity/sources.js';
import { label, tag } from '../internal/client/dev/tracing.js';
import { get, update_version } from '../internal/client/runtime.js';
import { async_mode_flag } from '../internal/flags/index.js';
var read_methods = ['forEach', 'isDisjointFrom', 'isSubsetOf', 'isSupersetOf'];
var set_like_methods = ['difference', 'intersection', 'symmetricDifference', 'union'];
@ -47,8 +48,8 @@ var inited = false;
export class SvelteSet extends Set {
/** @type {Map<T, Source<boolean>>} */
#sources = new Map();
#version = state(0);
#size = state(0);
/** @type {Source<Set<T>>} */
#items;
#update_version = update_version || -1;
/**
@ -57,19 +58,16 @@ export class SvelteSet extends Set {
constructor(value) {
super();
if (DEV) {
// If the value is invalid then the native exception will fire here
value = new Set(value);
this.#items = state(new Set(value));
var sources = this.#sources;
tag(this.#version, 'SvelteSet version');
tag(this.#size, 'SvelteSet.size');
for (const value of this.#items.v) {
sources.set(value, this.#source(true));
}
if (value) {
for (var element of value) {
super.add(element);
}
this.#size.v = super.size;
if (DEV) {
tag(this.#items, 'SvelteSet items');
}
if (!inited) this.#init();
@ -98,18 +96,16 @@ export class SvelteSet extends Set {
for (const method of read_methods) {
// @ts-ignore
proto[method] = function (...v) {
get(this.#version);
// @ts-ignore
return set_proto[method].apply(this, v);
return set_proto[method].apply(get(this.#items), v);
};
}
for (const method of set_like_methods) {
// @ts-ignore
proto[method] = function (...v) {
get(this.#version);
// @ts-ignore
var set = /** @type {Set<T>} */ (set_proto[method].apply(this, v));
var set = /** @type {Set<T>} */ (set_proto[method].apply(get(this.#items), v));
return new SvelteSet(set);
};
}
@ -117,19 +113,18 @@ export class SvelteSet extends Set {
/** @param {T} value */
has(value) {
var has = super.has(value);
var sources = this.#sources;
var s = sources.get(value);
if (s === undefined) {
if (!has) {
if (!async_mode_flag) {
// If the value doesn't exist, track the version in case it's added later
// but don't create sources willy-nilly to track all possible values
get(this.#version);
get(this.#items);
return false;
}
s = this.#source(true);
s = this.#source(get(this.#items).has(value));
if (DEV) {
tag(s, `SvelteSet has(${label(value)})`);
@ -138,16 +133,27 @@ export class SvelteSet extends Set {
sources.set(value, s);
}
get(s);
return has;
return get(s);
}
/** @param {T} value */
add(value) {
if (!super.has(value)) {
super.add(value);
set(this.#size, super.size);
increment(this.#version);
var sources = this.#sources;
var s = sources.get(value);
if (s !== undefined) {
set(s, true);
} else {
sources.set(value, this.#source(true));
}
var items = get(this.#items);
if (!items.has(value)) {
const clone = new Set(items);
clone.add(value);
set(this.#items, clone);
}
return this;
@ -155,7 +161,8 @@ export class SvelteSet extends Set {
/** @param {T} value */
delete(value) {
var deleted = super.delete(value);
var items = get(this.#items);
var has = items.has(value);
var sources = this.#sources;
var s = sources.get(value);
@ -164,20 +171,24 @@ export class SvelteSet extends Set {
set(s, false);
}
if (deleted) {
set(this.#size, super.size);
increment(this.#version);
if (has) {
const clone = new Set(items);
clone.delete(value);
set(this.#items, clone);
}
return deleted;
return has;
}
clear() {
if (super.size === 0) {
if (get(this.#items).size === 0) {
return;
}
// Clear first, so we get nice console.log outputs with $inspect
super.clear();
set(this.#items, new Set());
var sources = this.#sources;
for (var s of sources.values()) {
@ -185,8 +196,6 @@ export class SvelteSet extends Set {
}
sources.clear();
set(this.#size, 0);
increment(this.#version);
}
keys() {
@ -194,20 +203,18 @@ export class SvelteSet extends Set {
}
values() {
get(this.#version);
return super.values();
return get(this.#items).values();
}
entries() {
get(this.#version);
return super.entries();
return get(this.#items).entries();
}
[Symbol.iterator]() {
return this.keys();
return this.values();
}
get size() {
return get(this.#size);
return get(this.#items).size;
}
}

@ -0,0 +1,145 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
await tick();
const [add, shift, pop] = target.querySelectorAll('button');
add.click();
await tick();
add.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>add</button>
<button>shift</button>
<button>pop</button>
<p>pending=2 values.size=1 values=[1]</p>
<hr>
<p>1: true</p>
<p>2: false</p>
<p>3: false</p>
<p>4: false</p>
<p>5: false</p>
<hr>
<p>1</p>
`
);
shift.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>add</button>
<button>shift</button>
<button>pop</button>
<p>pending=1 values.size=2 values=[1,2]</p>
<hr>
<p>1: true</p>
<p>2: true</p>
<p>3: false</p>
<p>4: false</p>
<p>5: false</p>
<hr>
<p>1</p>
<p>2</p>
`
);
shift.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>add</button>
<button>shift</button>
<button>pop</button>
<p>pending=0 values.size=3 values=[1,2,3]</p>
<hr>
<p>1: true</p>
<p>2: true</p>
<p>3: true</p>
<p>4: false</p>
<p>5: false</p>
<hr>
<p>1</p>
<p>2</p>
<p>3</p>
`
);
add.click();
await tick();
add.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>add</button>
<button>shift</button>
<button>pop</button>
<p>pending=2 values.size=3 values=[1,2,3]</p>
<hr>
<p>1: true</p>
<p>2: true</p>
<p>3: true</p>
<p>4: false</p>
<p>5: false</p>
<hr>
<p>1</p>
<p>2</p>
<p>3</p>
`
);
pop.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>add</button>
<button>shift</button>
<button>pop</button>
<p>pending=1 values.size=3 values=[1,2,3]</p>
<hr>
<p>1: true</p>
<p>2: true</p>
<p>3: true</p>
<p>4: false</p>
<p>5: false</p>
<hr>
<p>1</p>
<p>2</p>
<p>3</p>
`
);
pop.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>add</button>
<button>shift</button>
<button>pop</button>
<p>pending=0 values.size=5 values=[1,2,3,4,5]</p>
<hr>
<p>1: true</p>
<p>2: true</p>
<p>3: true</p>
<p>4: true</p>
<p>5: true</p>
<hr>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
`
);
}
});

@ -0,0 +1,43 @@
<script>
import { SvelteSet } from 'svelte/reactivity';
let values = new SvelteSet([1]);
const queue = [];
function push(v) {
if (v === 1) return v;
const p = Promise.withResolvers();
queue.push(() => p.resolve(v));
return p.promise;
}
function addValue() {
values.add(values.size + 1);
}
</script>
<button onclick={addValue}>add</button>
<button onclick={() => queue.shift()?.()}>shift</button>
<button onclick={() => queue.pop()?.()}>pop</button>
<p>
pending={$effect.pending()}
values.size={values.size}
values=[{Array.from(values)}]
</p>
<hr>
<p>1: {values.has(1)}</p>
<p>2: {values.has(2)}</p>
<p>3: {values.has(3)}</p>
<p>4: {values.has(4)}</p>
<p>5: {values.has(5)}</p>
<hr>
{#each values as v(v)}
<p>{await push(v)}</p>
{/each}
Loading…
Cancel
Save