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

+ fallback +

named fallback

` ); @@ -23,8 +23,8 @@ export default test({ assert.htmlEqual( ce.shadowRoot.innerHTML, ` - -

named fallback

+ fallback +

named fallback

` ); } diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/html-slots/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/html-slots/_config.js index 287e2987b8..e3e8ce072b 100644 --- a/packages/svelte/tests/runtime-browser/custom-elements-samples/html-slots/_config.js +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/html-slots/_config.js @@ -3,10 +3,7 @@ const tick = () => Promise.resolve(); export default test({ async test({ assert, target }) { - target.innerHTML = ` - - slotted - `; + target.innerHTML = ''; await tick(); await tick(); @@ -16,7 +13,26 @@ export default test({ const div = el.shadowRoot.children[0]; const [slot0, slot1] = div.children; - assert.equal(slot0.assignedNodes()[1], target.querySelector('strong')); - assert.equal(slot1.innerHTML, 'foo fallback content'); + assert.equal(slot0.localName, 'slot'); + assert.equal(slot0.assignedNodes().length, 0); + assert.equal(slot0.innerHTML, '

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 = ''; + target.innerHTML = 'slotted'; await tick(); await tick(); @@ -15,5 +15,6 @@ export default test({ assert.equal(el.shadowRoot, null); assert.equal(h1.innerHTML, 'Hello world!'); assert.equal(getComputedStyle(h1).color, 'rgb(255, 0, 0)'); + assert.equal(el.querySelector('slot').innerHTML, ''); } }); diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-shadow-dom/main.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-shadow-dom/main.svelte index 36fea3c43e..1c14fb98d8 100644 --- a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-shadow-dom/main.svelte +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-shadow-dom/main.svelte @@ -5,6 +5,7 @@

Hello {name}!

+fallback