pull/15844/head
Rich Harris 3 months ago
commit e13b0d96f3

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: abort and reschedule effect processing after state change in user effect

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: account for mounting when `select_option` in `attribute_effect`

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: do not proxify the value assigned to a derived

@ -1,5 +1,13 @@
# svelte
## 5.35.3
### Patch Changes
- fix: account for mounting when `select_option` in `attribute_effect` ([#16309](https://github.com/sveltejs/svelte/pull/16309))
- fix: do not proxify the value assigned to a derived ([#16302](https://github.com/sveltejs/svelte/pull/16302))
## 5.35.2
### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.35.2",
"version": "5.35.3",
"type": "module",
"types": "./types/index.d.ts",
"engines": {

@ -19,12 +19,13 @@ export const INSPECT_EFFECT = 1 << 17;
export const HEAD_EFFECT = 1 << 18;
export const EFFECT_PRESERVED = 1 << 19;
export const EFFECT_IS_UPDATING = 1 << 20;
export const USER_EFFECT = 1 << 21;
// Flags used for async
export const REACTION_IS_UPDATING = 1 << 21;
export const EFFECT_ASYNC = 1 << 22;
export const REACTION_IS_UPDATING = 1 << 22;
export const EFFECT_ASYNC = 1 << 23;
export const ERROR_VALUE = 1 << 23;
export const ERROR_VALUE = 1 << 24;
export const STATE_SYMBOL = Symbol('$state');
export const LEGACY_PROPS = Symbol('legacy props');

@ -9,7 +9,7 @@ import {
set_active_effect,
set_active_reaction
} from './runtime.js';
import { effect, teardown } from './reactivity/effects.js';
import { create_user_effect, teardown } from './reactivity/effects.js';
import { async_mode_flag, legacy_mode_flag } from '../flags/index.js';
import { FILENAME } from '../../constants.js';
@ -189,7 +189,7 @@ export function pop(component) {
var component_effect = component_effects[i];
set_active_effect(component_effect.effect);
set_active_reaction(component_effect.reaction);
effect(component_effect.fn);
create_user_effect(component_effect.fn);
}
} finally {
set_active_effect(previous_effect);

@ -9,7 +9,8 @@ import {
EFFECT_ASYNC,
INERT,
RENDER_EFFECT,
ROOT_EFFECT
ROOT_EFFECT,
USER_EFFECT
} from '#client/constants';
import { async_mode_flag } from '../../flags/index.js';
import { deferred, define_property } from '../../shared/utils.js';
@ -23,7 +24,8 @@ import {
set_is_updating_effect,
set_queued_root_effects,
set_signal_status,
update_effect
update_effect,
write_version
} from '../runtime.js';
import * as e from '../errors.js';
import { flush_tasks } from '../dom/task.js';
@ -513,7 +515,7 @@ function infinite_loop_guard() {
* @param {Array<Effect>} effects
* @returns {void}
*/
export function flush_queued_effects(effects) {
function flush_queued_effects(effects) {
var length = effects.length;
if (length === 0) return;
@ -522,6 +524,8 @@ export function flush_queued_effects(effects) {
if ((effect.f & (DESTROYED | INERT)) === 0) {
if (check_dirtiness(effect)) {
var wv = write_version;
update_effect(effect);
// Effects with no dependencies or teardown do not get added to the effect tree.
@ -538,9 +542,19 @@ export function flush_queued_effects(effects) {
effect.fn = null;
}
}
// if state is written in a user effect, abort and re-schedule, lest we run
// effects that should be removed as a result of the state change
if (write_version > wv && (effect.f & USER_EFFECT) !== 0) {
break;
}
}
}
}
for (; i < length; i += 1) {
schedule_effect(effects[i]);
}
}
/**

@ -32,7 +32,8 @@ import {
MAYBE_DIRTY,
EFFECT_PRESERVED,
BOUNDARY_EFFECT,
STALE_REACTION
STALE_REACTION,
USER_EFFECT
} from '#client/constants';
import * as e from '../errors.js';
import { DEV } from 'esm-env';
@ -203,11 +204,17 @@ export function user_effect(fn) {
reaction: active_reaction
});
} else {
var signal = effect(fn);
return signal;
return create_user_effect(fn);
}
}
/**
* @param {() => void | (() => void)} fn
*/
export function create_user_effect(fn) {
return create_effect(EFFECT | USER_EFFECT, fn, false);
}
/**
* Internal representation of `$effect.pre(...)`
* @param {() => void | (() => void)} fn
@ -220,7 +227,7 @@ export function user_pre_effect(fn) {
value: '$effect.pre'
});
}
return render_effect(fn);
return create_effect(RENDER_EFFECT | USER_EFFECT, fn, true);
}
/** @param {() => void | (() => void)} fn */

@ -133,7 +133,7 @@ export function set_untracked_writes(value) {
* @type {number} Used by sources and deriveds for handling updates.
* Version starts from 1 so that unowned deriveds differentiate between a created effect and a run one for tracing
**/
let write_version = 1;
export let write_version = 1;
/** @type {number} Used to version each read of a source of derived to avoid duplicating depedencies inside a reaction */
let read_version = 0;

@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
export const VERSION = '5.35.2';
export const VERSION = '5.35.3';
export const PUBLIC_VERSION = '5';

@ -0,0 +1,11 @@
<script>
import B from './B.svelte';
let { boolean, closed } = $props();
$effect(() => {
console.log(boolean);
});
</script>
<B {closed} />

@ -0,0 +1,9 @@
<script>
import { close } from './Child.svelte';
let { closed } = $props();
$effect(() => {
if (closed) close();
});
</script>

@ -0,0 +1,20 @@
<script module>
let object = $state();
export function open() {
object = { boolean: true };
}
export function close() {
object = undefined;
}
</script>
<script>
let { children } = $props();
</script>
{#if object?.boolean}
<!-- error occurs here, this is executed when the if should already make it falsy -->
{@render children(object.boolean)}
{/if}

@ -0,0 +1,13 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
const [open, close] = target.querySelectorAll('button');
flushSync(() => open.click());
flushSync(() => close.click());
assert.deepEqual(logs, [true]);
}
});

@ -0,0 +1,23 @@
<script>
import A from './A.svelte';
import Child, { open } from './Child.svelte';
let closed = $state(false);
</script>
<button onclick={open}>
open
</button>
<button onclick={() => closed = true}>
close
</button>
<hr>
<Child>
{#snippet children(boolean)}
<A {closed} {boolean} />
{/snippet}
</Child>

@ -0,0 +1,9 @@
<script>
import B from './B.svelte';
let { boolean, closed } = $props();
</script>
<span>{boolean}</span>
<B {closed} />

@ -0,0 +1,9 @@
<script>
import { close } from './Child.svelte';
let { closed } = $props();
$effect.pre(() => {
if (closed) close();
});
</script>

@ -0,0 +1,20 @@
<script module>
let object = $state();
export function open() {
object = { nested: { boolean: true } };
}
export function close() {
object = undefined;
}
</script>
<script>
let { children } = $props();
</script>
{#if object?.nested}
<!-- error occurs here, this is executed when the if should already make it falsy -->
{@render children(object.nested)}
{/if}

@ -0,0 +1,15 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
skip: true,
async test({ assert, target, logs }) {
const [open, close] = target.querySelectorAll('button');
flushSync(() => open.click());
flushSync(() => close.click());
assert.deepEqual(logs, [true]);
}
});

@ -0,0 +1,22 @@
<script>
import A from './A.svelte';
import Child, { open } from './Child.svelte';
let closed = $state(false);
</script>
<button onclick={open}>
open
</button>
<button onclick={() => closed = true}>
close
</button>
<hr>
<Child>
{#snippet children(nested)}
<A {closed} boolean={nested.boolean} />
{/snippet}
</Child>
Loading…
Cancel
Save