From 00d04053dfb8177248ae506a11849ec0e87fd737 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 6 Apr 2023 17:12:35 +0200 Subject: [PATCH] handle dynamic slot content --- .../compile/render_dom/wrappers/Slot.ts | 4 +++ src/runtime/internal/Component.ts | 26 ++++++++++++++++--- .../$$slot-dynamic-content/main.svelte | 10 +++++++ .../$$slot-dynamic-content/my-widget.svelte | 4 +++ .../samples/$$slot-dynamic-content/test.js | 19 ++++++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 test/custom-elements/samples/$$slot-dynamic-content/main.svelte create mode 100644 test/custom-elements/samples/$$slot-dynamic-content/my-widget.svelte create mode 100644 test/custom-elements/samples/$$slot-dynamic-content/test.js diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 0a589e3394..d0cb2c318d 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -132,6 +132,10 @@ export default class SlotWrapper extends Wrapper { const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name}; const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}); ${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} + ${has_fallback && this.renderer.options.customElement && this.renderer.component.component_options.tag + // This ensures that fallback content is rendered into the element given by the custom element wrapper + ? b`if (${slot_or_fallback}.$$c_e) { ${slot_or_fallback}.$$c_e = ${this.fallback.name}(#ctx); }` + : null} `); block.chunks.create.push( diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 1afb139ae7..415224ff6e 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -172,25 +172,36 @@ if (typeof HTMLElement === 'function') { connectedCallback() { this.$$connected = true; if (!this.$$component) { - function create_slot(name: string) { + function create_slot(name: string, $$c_e = false) { return () => { let node: HTMLSlotElement; - return { + const obj = { c: function create() { node = document.createElement('slot'); if (name !== 'default') { node.setAttribute('name', name); } + if (typeof obj.$$c_e === 'object') { + (obj.$$c_e as any).c() + } }, m: function mount(target: HTMLElement, anchor?: HTMLElement) { insert(target, node, anchor); + if (typeof obj.$$c_e === 'object') { + (obj.$$c_e as any).m(node, anchor) + } }, d: function destroy(detaching: boolean) { if (detaching) { + if (typeof obj.$$c_e === 'object') { + (obj.$$c_e as any).d(detaching) + } detach(node); } - } + }, + $$c_e }; + return obj; }; } @@ -202,6 +213,15 @@ if (typeof HTMLElement === 'function') { } } + if (!Object.keys($$slots).length && this.$$slots.length) { + // There are potentially slots, but we didn't find any. This could be due to Svelte adding the child nodes + // only after the component is created (in the mount phase). In this case, pass in placeholder slots. + // The drawback is that all $$slots properties will be `true`, even if the component doesn't use them. + for (const name of this.$$slots) { + $$slots[name] = [create_slot(name, true)]; + } + } + for (const attribute of this.attributes) { // this.$$data takes precedence over this.attributes const name = this.$$get_prop_name(attribute.name); diff --git a/test/custom-elements/samples/$$slot-dynamic-content/main.svelte b/test/custom-elements/samples/$$slot-dynamic-content/main.svelte new file mode 100644 index 0000000000..251ff1e70f --- /dev/null +++ b/test/custom-elements/samples/$$slot-dynamic-content/main.svelte @@ -0,0 +1,10 @@ + + + + + +

default {name}

+
diff --git a/test/custom-elements/samples/$$slot-dynamic-content/my-widget.svelte b/test/custom-elements/samples/$$slot-dynamic-content/my-widget.svelte new file mode 100644 index 0000000000..2fa0eb2a0d --- /dev/null +++ b/test/custom-elements/samples/$$slot-dynamic-content/my-widget.svelte @@ -0,0 +1,4 @@ + + +fallback +

named fallback

diff --git a/test/custom-elements/samples/$$slot-dynamic-content/test.js b/test/custom-elements/samples/$$slot-dynamic-content/test.js new file mode 100644 index 0000000000..348cd5aa86 --- /dev/null +++ b/test/custom-elements/samples/$$slot-dynamic-content/test.js @@ -0,0 +1,19 @@ +import * as assert from 'assert'; +import Component from './main.svelte'; + +export default function (target) { + const component = new Component({ target, props: { name: 'slot' } }); + + const ce = target.querySelector('my-widget'); + + assert.htmlEqual(ce.shadowRoot.innerHTML, ` + fallback +

named fallback

+ `); + + component.name = 'slot2'; + assert.htmlEqual(ce.shadowRoot.innerHTML, ` + fallback +

named fallback

+ `); +}