diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 5bffa5f70ef3..450ecde53b1e 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,13 @@
# svelte
+## 5.36.16
+
+### Patch Changes
+
+- fix: don't update a focused input with values from its own past ([#16491](https://github.com/sveltejs/svelte/pull/16491))
+
+- fix: don't destroy effect roots created inside of deriveds ([#16492](https://github.com/sveltejs/svelte/pull/16492))
+
## 5.36.15
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 051f82ec3aad..07954026b550 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.36.15",
+ "version": "5.36.16",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js
index 7c1fccea0fbc..7c73280dd664 100644
--- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js
+++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js
@@ -8,7 +8,7 @@ import { queue_micro_task } from '../../task.js';
import { hydrating } from '../../hydration.js';
import { untrack } from '../../../runtime.js';
import { is_runes } from '../../../context.js';
-import { current_batch } from '../../../reactivity/batch.js';
+import { current_batch, previous_batch } from '../../../reactivity/batch.js';
/**
* @param {HTMLInputElement} input
@@ -76,13 +76,18 @@ export function bind_value(input, get, set = get) {
var value = get();
- if (input === document.activeElement && batches.has(/** @type {Batch} */ (current_batch))) {
+ if (input === document.activeElement) {
+ // we need both, because in non-async mode, render effects run before previous_batch is set
+ var batch = /** @type {Batch} */ (previous_batch ?? current_batch);
+
// Never rewrite the contents of a focused input. We can get here if, for example,
// an update is deferred because of async work depending on the input:
//
//
//
{await find(query)}
- return;
+ if (batches.has(batch)) {
+ return;
+ }
}
if (is_numberlike_input(input) && value === to_number(input.value)) {
diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js
index 89bad947c7fa..123bc95d163a 100644
--- a/packages/svelte/src/internal/client/reactivity/batch.js
+++ b/packages/svelte/src/internal/client/reactivity/batch.js
@@ -38,6 +38,13 @@ const batches = new Set();
/** @type {Batch | null} */
export let current_batch = null;
+/**
+ * This is needed to avoid overwriting inputs in non-async mode
+ * TODO 6.0 remove this, as non-async mode will go away
+ * @type {Batch | null}
+ */
+export let previous_batch = null;
+
/**
* When time travelling, we re-evaluate deriveds based on the temporary
* values of their dependencies rather than their actual values, and cache
@@ -71,7 +78,6 @@ let last_scheduled_effect = null;
let is_flushing = false;
let is_flushing_sync = false;
-
export class Batch {
/**
* The current values of any sources that are updated in this batch
@@ -173,6 +179,8 @@ export class Batch {
process(root_effects) {
queued_root_effects = [];
+ previous_batch = null;
+
/** @type {Map | null} */
var current_values = null;
@@ -218,6 +226,7 @@ export class Batch {
// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
// newly updated sources, which could lead to infinite loops when effects run over and over again.
+ previous_batch = current_batch;
current_batch = null;
flush_queued_effects(render_effects);
@@ -350,6 +359,7 @@ export class Batch {
deactivate() {
current_batch = null;
+ previous_batch = null;
for (const update of effect_pending_updates) {
effect_pending_updates.delete(update);
diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js
index c4edd2bf8d95..f44efa32f1ca 100644
--- a/packages/svelte/src/internal/client/reactivity/effects.js
+++ b/packages/svelte/src/internal/client/reactivity/effects.js
@@ -148,7 +148,11 @@ function create_effect(type, fn, sync, push = true) {
}
// if we're in a derived, add the effect there too
- if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) {
+ if (
+ active_reaction !== null &&
+ (active_reaction.f & DERIVED) !== 0 &&
+ (type & ROOT_EFFECT) === 0
+ ) {
var derived = /** @type {Derived} */ (active_reaction);
(derived.effects ??= []).push(effect);
}
diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts
index 72187e84a720..81f7197b806d 100644
--- a/packages/svelte/src/internal/client/reactivity/types.d.ts
+++ b/packages/svelte/src/internal/client/reactivity/types.d.ts
@@ -54,7 +54,7 @@ export interface Reaction extends Signal {
export interface Derived extends Value, Reaction {
/** The derived function */
fn: () => V;
- /** Effects created inside this signal */
+ /** Effects created inside this signal. Used to destroy those effects when the derived reruns or is cleaned up */
effects: null | Effect[];
/** Parent effect or derived */
parent: Effect | Derived | null;
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 1d469f29b0f6..5d76fc3f2963 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.36.15';
+export const VERSION = '5.36.16';
export const PUBLIC_VERSION = '5';
diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js
new file mode 100644
index 000000000000..b0772ad3c071
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js
@@ -0,0 +1,37 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target, instance }) {
+ instance.shift();
+ await tick();
+
+ const [input] = target.querySelectorAll('input');
+
+ input.focus();
+ input.value = '1';
+ input.dispatchEvent(new InputEvent('input', { bubbles: true }));
+ await tick();
+
+ assert.htmlEqual(target.innerHTML, `