diff --git a/.changeset/open-parents-stare.md b/.changeset/open-parents-stare.md
new file mode 100644
index 0000000000..cb94267ea0
--- /dev/null
+++ b/.changeset/open-parents-stare.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: more robust rendering of Svelte custom element slots
diff --git a/packages/svelte/src/internal/client/dom/blocks/slot.js b/packages/svelte/src/internal/client/dom/blocks/slot.js
index 10b2959582..491f04a5ea 100644
--- a/packages/svelte/src/internal/client/dom/blocks/slot.js
+++ b/packages/svelte/src/internal/client/dom/blocks/slot.js
@@ -1,4 +1,6 @@
import { hydrate_next, hydrating } from '../hydration.js';
+import { create_element, create_text } from '../operations.js';
+import { append } from '../template.js';
/**
* @param {Comment} anchor
@@ -12,6 +14,23 @@ export function slot(anchor, $$props, name, slot_props, fallback_fn) {
hydrate_next();
}
+ // Custom element slots are native DOM slots.
+ // Use the stored reference because the shadow root may be closed.
+ if ($$props.$$host?.$$shadowRoot) {
+ const element = create_element('slot');
+ if (name !== 'default') element.name = name;
+
+ append(anchor, element);
+
+ if (fallback_fn !== null) {
+ const fallback_anchor = create_text();
+ element.append(fallback_anchor);
+ fallback_fn(fallback_anchor);
+ }
+
+ return;
+ }
+
var slot_fn = $$props.$$slots?.[name];
// Interop: Can use snippets to fill slots
var is_interop = false;
diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js
index 8ade347a79..3fd5a09888 100644
--- a/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js
+++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js
@@ -14,8 +14,8 @@ export default test({
assert.htmlEqual(
ce.shadowRoot.innerHTML,
`
-
named fallback
+named fallback
named fallback
+named fallback
default fallback content
'); + assert.equal(slot1.localName, 'slot'); + assert.equal(slot1.name, 'foo'); + assert.equal(slot1.assignedNodes().length, 0); + assert.equal(slot1.innerHTML, 'foo fallback content
'); + + const default_content = document.createElement('strong'); + default_content.textContent = 'default content'; + el.append(default_content); + + const named_content = document.createElement('strong'); + named_content.slot = 'foo'; + named_content.textContent = 'named content'; + el.append(named_content); + + assert.equal(slot0.assignedNodes().length, 1); + assert.equal(slot0.assignedNodes()[0], default_content); + assert.equal(slot1.assignedNodes().length, 1); + assert.equal(slot1.assignedNodes()[0], named_content); } }); diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-shadow-dom/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-shadow-dom/_config.js index cd8550120a..e83491418a 100644 --- a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-shadow-dom/_config.js +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-shadow-dom/_config.js @@ -3,7 +3,7 @@ const tick = () => Promise.resolve(); export default test({ async test({ assert, target }) { - target.innerHTML = '