diff --git a/.changeset/eager-flowers-stay.md b/.changeset/eager-flowers-stay.md
new file mode 100644
index 0000000000..0e3ff95489
--- /dev/null
+++ b/.changeset/eager-flowers-stay.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: force the first run innerText/innerHTML/textContent written for content_editable_bind
diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js
index 5b10abdc4c..77259c62e4 100644
--- a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js
+++ b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js
@@ -1,4 +1,5 @@
import { render_effect, teardown } from '../../../reactivity/effects.js';
+import { hydrating } from '../../hydration.js';
import { listen } from './shared.js';
/**
@@ -14,10 +15,12 @@ export function bind_content_editable(property, element, get, set = get) {
set(element[property]);
});
+ var first_run = true;
render_effect(() => {
var value = get();
- if (element[property] !== value) {
+ if (element[property] !== value || (first_run && !hydrating)) {
+ first_run = false;
if (value == null) {
// @ts-ignore
var non_null_value = element[property];
diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable-1/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable-1/_config.js
new file mode 100644
index 0000000000..8907a84b50
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable-1/_config.js
@@ -0,0 +1,20 @@
+import { flushSync } from '../../../../src/index-client';
+import { test } from '../../test';
+
+export default test({
+ test({ assert, target }) {
+ const h1 = /** @type {HTMLDivElement} */ (target.querySelector('h1'));
+
+ assert.equal(h1.textContent, 'h');
+ // don't use h1.textContent because h1.textContent will overwrite all childNodes
+ // The childNode created by {myTextBlock.current} is the reason for duplication
+ const textNode =
+ [...h1.childNodes].findLast((n) => n.nodeType === Node.TEXT_NODE) ??
+ h1.appendChild(document.createTextNode(''));
+ textNode.nodeValue = 'he';
+ h1.dispatchEvent(new window.Event('input'));
+ flushSync();
+
+ assert.equal(h1.textContent, 'he');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable-1/main.svelte
new file mode 100644
index 0000000000..f0ae61de39
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable-1/main.svelte
@@ -0,0 +1,12 @@
+
+
+
+ {myTextBlock.current}
+
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable/_config.js
new file mode 100644
index 0000000000..6abade95ef
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable/_config.js
@@ -0,0 +1,19 @@
+import { flushSync } from '../../../../src/index-client';
+import { test } from '../../test';
+
+export default test({
+ test({ assert, target }) {
+ const h1 = /** @type {HTMLDivElement} */ (target.querySelector('h1'));
+ assert.equal(h1.textContent, '');
+ // don't use h1.textContent because h1.textContent will overwrite all childNodes
+ // The childNode created by {myTextBlock.current} is the reason for duplication
+ const textNode =
+ [...h1.childNodes].findLast((n) => n.nodeType === Node.TEXT_NODE) ??
+ h1.appendChild(document.createTextNode(''));
+ textNode.nodeValue = 'h';
+ h1.dispatchEvent(new window.Event('input'));
+ flushSync();
+
+ assert.equal(h1.textContent, 'h');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable/main.svelte
new file mode 100644
index 0000000000..f067dcae23
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-contenteditable/main.svelte
@@ -0,0 +1,12 @@
+
+
+
+ {myTextBlock.current}
+
\ No newline at end of file