diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 976e78cea5f3..a4a986089f0c 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -855,6 +855,7 @@ The following initialisation options can be provided: | `props` | `{}` | An object of properties to supply to the component | `hydrate` | `false` | See below | `intro` | `false` | If `true`, will play transitions on initial render, rather than waiting for subsequent state changes +| `slots` | `{}` | An object with keys - slot names, values - element or array of elements Existing children of `target` are left where they are. diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 504b5ffe05f0..9bf0876b0bf7 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -436,11 +436,21 @@ export default function dom( } else { const superclass = options.dev ? 'SvelteComponentDev' : 'SvelteComponent'; + let slots_block = ''; + if (component.slots.size > 0) { + slots_block = '\n' + deindent` + if (options.slots) { + options.props = options.props || {}; + options.props.$$scope = {}; + options.props.$$slots = @create_root_component_slots(options.slots); + }`; + } + builder.add_block(deindent` class ${name} extends @${superclass} { constructor(options) { super(${options.dev && `options`}); - ${should_add_css && `if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} + ${should_add_css && `if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}${slots_block} @init(this, options, ${definition}, create_fragment, ${not_equal}, ${prop_names}); ${options.dev && `@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name}", options, id: create_fragment.name });`} diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index ae80ae38c1f3..bb908e1ba680 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -1,7 +1,7 @@ import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler'; import { current_component, set_current_component } from './lifecycle'; import { blank_object, is_function, run, run_all, noop } from './utils'; -import { children } from './dom'; +import { children, insert, detach } from './dom'; import { transition_in } from './transitions'; // eslint-disable-next-line @typescript-eslint/class-name-casing @@ -197,3 +197,37 @@ export class SvelteComponent { // overridden by instance, if it has props } } + +function create_root_component_slot_fn(elements) { + return function create_root_component_slot() { + return { + c: noop, + + m: function mount(target, anchor) { + elements.forEach(element => { + insert(target, element, anchor); + }); + }, + + d: function destroy(detaching) { + if (detaching) { + elements.forEach(element => detach(element)); + } + }, + + l: noop, + }; + }; +} + +export function create_root_component_slots(slots) { + const root_component_slots = {}; + for (const slot_name in slots) { + let elements = slots[slot_name]; + if (!Array.isArray(elements)) { + elements = [elements]; + } + root_component_slots[slot_name] = [create_root_component_slot_fn(elements)]; + } + return root_component_slots; +} diff --git a/test/js/samples/root-component-slot/expected.js b/test/js/samples/root-component-slot/expected.js new file mode 100644 index 000000000000..82c56b453f28 --- /dev/null +++ b/test/js/samples/root-component-slot/expected.js @@ -0,0 +1,126 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponent, + append, + create_root_component_slots, + create_slot, + detach, + element, + get_slot_changes, + get_slot_context, + init, + insert, + safe_not_equal, + space, + transition_in, + transition_out +} from "svelte/internal"; + +const get_slot1_slot_changes = () => ({}); +const get_slot1_slot_context = () => ({}); + +function create_fragment(ctx) { + var div, t, current; + + const default_slot_template = ctx.$$slots.default; + const default_slot = create_slot(default_slot_template, ctx, null); + + const slot1_slot_template = ctx.$$slots.slot1; + const slot1_slot = create_slot(slot1_slot_template, ctx, get_slot1_slot_context); + + return { + c() { + div = element("div"); + + if (default_slot) default_slot.c(); + t = space(); + + if (slot1_slot) slot1_slot.c(); + }, + + l(nodes) { + if (default_slot) default_slot.l(div_nodes); + + if (slot1_slot) slot1_slot.l(div_nodes); + }, + + m(target, anchor) { + insert(target, div, anchor); + + if (default_slot) { + default_slot.m(div, null); + } + + append(div, t); + + if (slot1_slot) { + slot1_slot.m(div, null); + } + + current = true; + }, + + p(changed, ctx) { + if (default_slot && default_slot.p && changed.$$scope) { + default_slot.p( + get_slot_changes(default_slot_template, ctx, changed, null), + get_slot_context(default_slot_template, ctx, null) + ); + } + + if (slot1_slot && slot1_slot.p && changed.$$scope) { + slot1_slot.p( + get_slot_changes(slot1_slot_template, ctx, changed, get_slot1_slot_changes), + get_slot_context(slot1_slot_template, ctx, get_slot1_slot_context) + ); + } + }, + + i(local) { + if (current) return; + transition_in(default_slot, local); + transition_in(slot1_slot, local); + current = true; + }, + + o(local) { + transition_out(default_slot, local); + transition_out(slot1_slot, local); + current = false; + }, + + d(detaching) { + if (detaching) { + detach(div); + } + + if (default_slot) default_slot.d(detaching); + + if (slot1_slot) slot1_slot.d(detaching); + } + }; +} + +function instance($$self, $$props, $$invalidate) { + let { $$slots = {}, $$scope } = $$props; + + $$self.$set = $$props => { + if ('$$scope' in $$props) $$invalidate('$$scope', $$scope = $$props.$$scope); + }; + + return { $$slots, $$scope }; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + if (options.slots) { + options.props = options.props || {}; + options.props.$$scope = {}; + options.props.$$slots = create_root_component_slots(options.slots); + } + init(this, options, instance, create_fragment, safe_not_equal, []); + } +} + +export default Component; diff --git a/test/js/samples/root-component-slot/input.svelte b/test/js/samples/root-component-slot/input.svelte new file mode 100644 index 000000000000..0507e5c658b8 --- /dev/null +++ b/test/js/samples/root-component-slot/input.svelte @@ -0,0 +1,4 @@ +