feat: conditional slots csr

pull/8304/head
tanhauhau 4 years ago
parent 7578af3a11
commit 9acdaea3ab

@ -289,5 +289,16 @@ export default {
invalid_style_directive_modifier: (valid: string) => ({ invalid_style_directive_modifier: (valid: string) => ({
code: 'invalid-style-directive-modifier', code: 'invalid-style-directive-modifier',
message: `Valid modifiers for style directives are: ${valid}` message: `Valid modifiers for style directives are: ${valid}`
}) }),
invalid_mix_element_and_conditional_slot: {
code: 'invalid-mix-element-and-conditional-slot',
message: 'Do not mix <svelte:fragment> and other elements under the same {#if}{:else} group. Default slot content should be wrapped with <svelte:fragment slot="default">'
},
duplicate_slot_name_in_component: (slot_name: string, component_name: string) => ({
code: 'duplicate-slot-name-in-component',
message:
slot_name === "default"
? 'Found elements without slot attribute when using slot="default"'
: `Duplicate slot name "${slot_name}" in <${component_name}>`,
}),
}; };

@ -11,6 +11,8 @@ import { INode } from './interfaces';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { regex_only_whitespaces } from '../../utils/patterns'; import { regex_only_whitespaces } from '../../utils/patterns';
import SlotTemplateIfBlock, { validate_get_slot_names } from './SlotTemplateIfBlock';
import SlotTemplate from './SlotTemplate';
export default class InlineComponent extends Node { export default class InlineComponent extends Node {
type: 'InlineComponent'; type: 'InlineComponent';
@ -145,9 +147,15 @@ export default class InlineComponent extends Node {
info.children.splice(i, 1); info.children.splice(i, 1);
} else if (child.type === 'Comment' && children.length > 0) { } else if (child.type === 'Comment' && children.length > 0) {
children[children.length - 1].children.unshift(child); children[children.length - 1].children.unshift(child);
info.children.splice(i, 1);
} else if (child.type === 'IfBlock' && child.children.some(if_child => if_child.type === 'SlotTemplate')) {
children.push({
...child,
type: 'SlotTemplateIfBlock'
});
info.children.splice(i, 1);
} }
} }
if (info.children.some(node => not_whitespace_text(node))) { if (info.children.some(node => not_whitespace_text(node))) {
children.push({ children.push({
start: info.start, start: info.start,
@ -159,12 +167,18 @@ export default class InlineComponent extends Node {
}); });
} }
this.children = map_children(component, this, this.scope, children); this.children = map_children(component, this, this.scope, children.reverse());
this.validate_duplicate_slot_name();
} }
get slot_template_name() { get slot_template_name() {
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string; return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
} }
validate_duplicate_slot_name() {
validate_get_slot_names(this.children, this.component, this.name);
}
} }
function not_whitespace_text(node) { function not_whitespace_text(node) {

@ -66,7 +66,12 @@ export default class SlotTemplate extends Node {
} }
validate_slot_template_placement() { validate_slot_template_placement() {
if (this.parent.type !== 'InlineComponent') { let parent = this.parent;
while (parent.type === 'SlotTemplateIfBlock' || parent.type === 'SlotTemplateElseBlock') parent = parent.parent;
if (parent.type === 'IfBlock' || parent.type === 'ElseBlock') {
return this.component.error(this, compiler_errors.invalid_mix_element_and_conditional_slot);
}
if (parent.type !== 'InlineComponent') {
return this.component.error(this, compiler_errors.invalid_slotted_content_fragment); return this.component.error(this, compiler_errors.invalid_slotted_content_fragment);
} }
} }

@ -0,0 +1,50 @@
import Component from '../Component';
import Expression from './shared/Expression';
import TemplateScope from './shared/TemplateScope';
import Node from './shared/Node';
import Let from './Let';
import Attribute from './Attribute';
import { INode } from './interfaces';
import compiler_errors from '../compiler_errors';
import get_const_tags from './shared/get_const_tags';
import ConstTag from './ConstTag';
import AbstractBlock from './shared/AbstractBlock';
import { regex_only_whitespaces } from '../../utils/patterns';
export default class SlotTemplateElseBlock extends AbstractBlock {
type: 'SlotTemplateElseBlock';
expression: Expression;
scope: TemplateScope;
const_tags: ConstTag[];
constructor(
component: Component,
parent: INode,
scope: TemplateScope,
info: any
) {
super(component, parent, scope, info);
this.scope = scope.child();
const children = [];
for (const child of info.children) {
if (child.type === 'SlotTemplate' || child.type === 'ConstTag') {
children.push(child);
} else if (child.type === 'Comment') {
// ignore
} else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) {
// ignore
} else if (child.type === 'IfBlock') {
children.push({
...child,
type: 'SlotTemplateIfBlock'
});
} else {
this.component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot);
}
}
([this.const_tags, this.children] = get_const_tags(children, component, this, this));
}
}

@ -0,0 +1,90 @@
import SlotTemplateElseBlock from './SlotTemplateElseBlock';
import Component from '../Component';
import AbstractBlock from './shared/AbstractBlock';
import Expression from './shared/Expression';
import TemplateScope from './shared/TemplateScope';
import Node from './shared/Node';
import compiler_errors from '../compiler_errors';
import get_const_tags from './shared/get_const_tags';
import { TemplateNode } from '../../interfaces';
import ConstTag from './ConstTag';
import { regex_only_whitespaces } from '../../utils/patterns';
import SlotTemplate from './SlotTemplate';
import { INode } from './interfaces';
export default class SlotTemplateIfBlock extends AbstractBlock {
type: 'SlotTemplateIfBlock';
expression: Expression;
else: SlotTemplateElseBlock;
scope: TemplateScope;
const_tags: ConstTag[];
slot_names = new Set<string>();
constructor(
component: Component,
parent: Node,
scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, scope, info);
this.scope = scope.child();
const children = [];
for (const child of info.children) {
if (child.type === 'SlotTemplate' || child.type === 'ConstTag') {
children.push(child);
} else if (child.type === 'Comment') {
// ignore
} else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) {
// ignore
} else if (child.type === 'IfBlock') {
children.push({
...child,
type: 'SlotTemplateIfBlock'
});
} else {
this.component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot);
}
}
this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(children, component, this, this));
this.else = info.else
? new SlotTemplateElseBlock(component, this, scope, { ...info.else, type: 'SlotTemplateElseBlock' })
: null;
}
validate_duplicate_slot_name(component_name: string): Map<string, SlotTemplate> {
const if_slot_names = validate_get_slot_names(this.children, this.component, component_name);
if (!this.else) {
return if_slot_names;
}
const else_slot_names = validate_get_slot_names(this.else.children, this.component, component_name);
return new Map([...if_slot_names, ...else_slot_names]);
}
}
export function validate_get_slot_names(children: Array<INode>, component: Component, component_name: string) {
const slot_names = new Map<string, SlotTemplate>();
function add_slot_name(slot_name: string, child: SlotTemplate) {
if (slot_names.has(slot_name)) {
component.error(child, compiler_errors.duplicate_slot_name_in_component(slot_name, component_name));
}
slot_names.set(slot_name, child);
}
for (const child of children) {
if (child.type === 'SlotTemplateIfBlock') {
const child_slot_names = child.validate_duplicate_slot_name(component_name);
for (const [slot_name, child] of child_slot_names) {
add_slot_name(slot_name, child);
}
} else if (child.type === 'SlotTemplate') {
add_slot_name(child.slot_template_name, child);
}
}
return slot_names;
}

@ -33,6 +33,8 @@ import ThenBlock from './ThenBlock';
import Title from './Title'; import Title from './Title';
import Transition from './Transition'; import Transition from './Transition';
import Window from './Window'; import Window from './Window';
import SlotTemplateIfBlock from './SlotTemplateIfBlock';
import SlottemplateElseBlock from './SlotTemplateElseBlock';
// note: to write less types each of types in union below should have type defined as literal // note: to write less types each of types in union below should have type defined as literal
// https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
@ -63,6 +65,8 @@ export type INode = Action
| RawMustacheTag | RawMustacheTag
| Slot | Slot
| SlotTemplate | SlotTemplate
| SlotTemplateIfBlock
| SlottemplateElseBlock
| StyleDirective | StyleDirective
| Tag | Tag
| Text | Text
@ -78,4 +82,6 @@ export type INodeAllowConstTag =
| CatchBlock | CatchBlock
| ThenBlock | ThenBlock
| InlineComponent | InlineComponent
| SlotTemplate; | SlotTemplate
| SlotTemplateIfBlock
| SlottemplateElseBlock;

@ -19,6 +19,7 @@ import Title from '../Title';
import Window from '../Window'; import Window from '../Window';
import { TemplateNode } from '../../../interfaces'; import { TemplateNode } from '../../../interfaces';
import { push_array } from '../../../utils/push_array'; import { push_array } from '../../../utils/push_array';
import SlotTemplateIfBlock from '../SlotTemplateIfBlock';
export type Children = ReturnType<typeof map_children>; export type Children = ReturnType<typeof map_children>;
@ -40,6 +41,7 @@ function get_constructor(type) {
case 'DebugTag': return DebugTag; case 'DebugTag': return DebugTag;
case 'Slot': return Slot; case 'Slot': return Slot;
case 'SlotTemplate': return SlotTemplate; case 'SlotTemplate': return SlotTemplate;
case 'SlotTemplateIfBlock': return SlotTemplateIfBlock;
case 'Text': return Text; case 'Text': return Text;
case 'Title': return Title; case 'Title': return Title;
case 'Window': return Window; case 'Window': return Window;

@ -86,7 +86,7 @@ export default function dom(
let compute_slots: Node[] | undefined; let compute_slots: Node[] | undefined;
if (uses_slots) { if (uses_slots) {
compute_slots = b` compute_slots = b`
const $$slots = @compute_slots(#slots); let $$slots = @compute_slots(#slots);
`; `;
} }
@ -114,7 +114,13 @@ export default function dom(
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};`
)} )}
${component.slots.size > 0 && ${component.slots.size > 0 &&
b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} b`
if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};
if ('$$slots' in ${$$props}) {
${renderer.invalidate('#slots', x`#slots = ${$$props}.$$slots`)};
${uses_slots ? renderer.invalidate('$$slots', x`$$slots = @compute_slots(#slots)`) : null}
}
`}
} }
` `
: null; : null;

@ -9,8 +9,6 @@ import { sanitize } from '../../../../utils/names';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import { b, x, p } from 'code-red'; import { b, x, p } from 'code-red';
import Attribute from '../../../nodes/Attribute'; import Attribute from '../../../nodes/Attribute';
import TemplateScope from '../../../nodes/shared/TemplateScope';
import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this'; import bind_this from '../shared/bind_this';
import { Node, Identifier, ObjectExpression } from 'estree'; import { Node, Identifier, ObjectExpression } from 'estree';
import EventHandler from '../Element/EventHandler'; import EventHandler from '../Element/EventHandler';
@ -22,17 +20,18 @@ import { is_head } from '../shared/is_head';
import compiler_warnings from '../../../compiler_warnings'; import compiler_warnings from '../../../compiler_warnings';
import { namespaces } from '../../../../utils/namespaces'; import { namespaces } from '../../../../utils/namespaces';
import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore'; import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore';
import SlotTemplateIfBlockWrapper from '../SlotTemplateIfBlock';
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }; import SlotTemplateIfBlock from '../../../nodes/SlotTemplateIfBlock';
import { collect_slot_dynamic_dependencies, collect_slot_fragment_dependencies } from '../shared/slots';
const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g; const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g;
export default class InlineComponentWrapper extends Wrapper { export default class InlineComponentWrapper extends Wrapper {
var: Identifier; var: Identifier;
slots: Map<string, SlotDefinition> = new Map();
node: InlineComponent; node: InlineComponent;
fragment: FragmentWrapper; fragment: FragmentWrapper;
children: Array<Wrapper | FragmentWrapper> = []; children: Array<SlotTemplateWrapper | SlotTemplateIfBlockWrapper> = [];
has_conditional_slots: boolean = false;
constructor( constructor(
renderer: Renderer, renderer: Renderer,
@ -89,20 +88,17 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
}); });
this.children = this.node.children.map(child => new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling)); for (const child of this.node.children) {
} if (child.type === 'SlotTemplate') {
this.children.push(new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling));
block.add_outro(); } else if (child.type === 'SlotTemplateIfBlock') {
this.has_conditional_slots = true;
this.children.push(new SlotTemplateIfBlockWrapper(renderer, block, this, child as SlotTemplateIfBlock, strip_whitespace, next_sibling));
} }
set_slot(name: string, slot_definition: SlotDefinition) {
if (this.slots.has(name)) {
if (name === 'default') {
throw new Error('Found elements without slot attribute when using slot="default"');
} }
throw new Error(`Duplicate slot name "${name}" in <${this.node.name}>`);
} }
this.slots.set(name, slot_definition);
block.add_outro();
} }
warn_if_reactive() { warn_if_reactive() {
@ -137,25 +133,30 @@ export default class InlineComponentWrapper extends Wrapper {
const statements: Array<Node | Node[]> = []; const statements: Array<Node | Node[]> = [];
const updates: Array<Node | Node[]> = []; const updates: Array<Node | Node[]> = [];
const name_changes = block.get_unique_name(`${name.name}_changes`);
this.children.forEach((child) => { const should_cache_slot_definition = this.has_conditional_slots;
for (const slot of this.children) {
this.renderer.add_to_context('$$scope', true); this.renderer.add_to_context('$$scope', true);
child.render(block, null, x`#nodes` as Identifier); slot.render_slot_template_content(should_cache_slot_definition);
}); }
let get_slots_definition: Node = null;
if (this.has_conditional_slots) {
get_slots_definition = block.renderer.component.get_unique_name(`${name.name}_slots_definition`);
this.renderer.blocks.push(b`
function ${get_slots_definition}(#ctx) {
const #slots_definition = {};
${this.children.map(slot => slot.render_slot_template_definition(block))}
return #slots_definition;
}
`);
}
let props: Identifier | undefined; let props: Identifier | undefined;
const name_changes = block.get_unique_name(`${name.name}_changes`);
const uses_spread = !!this.node.attributes.find(a => a.is_spread); const uses_spread = !!this.node.attributes.find(a => a.is_spread);
// removing empty slot
for (const slot of this.slots.keys()) {
if (!this.slots.get(slot).block.has_content()) {
this.renderer.remove_block(this.slots.get(slot).block);
this.slots.delete(slot);
}
}
const has_css_custom_properties = this.node.css_custom_properties.length > 0; const has_css_custom_properties = this.node.css_custom_properties.length > 0;
const is_svg_namespace = this.node.namespace === namespaces.svg; const is_svg_namespace = this.node.namespace === namespaces.svg;
const css_custom_properties_wrapper_element = is_svg_namespace ? 'g' : 'div'; const css_custom_properties_wrapper_element = is_svg_namespace ? 'g' : 'div';
@ -164,16 +165,17 @@ export default class InlineComponentWrapper extends Wrapper {
block.add_variable(css_custom_properties_wrapper); block.add_variable(css_custom_properties_wrapper);
} }
const initial_props = this.slots.size > 0 const initial_props = this.has_conditional_slots
? [
p`$$slots: ${get_slots_definition}(#ctx)`,
p`$$scope: { ctx: #ctx }`
]
: this.children.length > 0
? [ ? [
p`$$slots: { p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => { ${this.children.map((slot: SlotTemplateWrapper) => p`${slot.slot_template_name}: ${slot.slot_definition}`)}
return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`;
})}
}`, }`,
p`$$scope: { p`$$scope: { ctx: #ctx }`
ctx: #ctx
}`
] ]
: []; : [];
@ -201,19 +203,12 @@ export default class InlineComponentWrapper extends Wrapper {
component_opts.properties.push(p`$$inline: true`); component_opts.properties.push(p`$$inline: true`);
} }
const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []); const fragment_dependencies = new Set(this.children.length ? ['$$scope'] : []);
this.slots.forEach(slot => { collect_slot_fragment_dependencies(renderer, this.children, fragment_dependencies);
slot.block.dependencies.forEach(name => {
const is_let = slot.scope.is_let(name);
const variable = renderer.component.var_lookup.get(name);
if (is_let || is_dynamic(variable)) fragment_dependencies.add(name);
});
});
const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0); const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0);
if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0)) { if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0 || this.has_conditional_slots)) {
updates.push(b`const ${name_changes} = {};`); updates.push(b`const ${name_changes} = {};`);
} }
@ -309,6 +304,16 @@ export default class InlineComponentWrapper extends Wrapper {
}`); }`);
} }
if (this.has_conditional_slots) {
const dependencies = collect_slot_dynamic_dependencies(this.children);
updates.push(b`
if (${renderer.dirty(Array.from(dependencies))}) {
${name_changes}.$$slots = ${get_slots_definition}(#ctx);
}
`)
}
const munged_bindings = this.node.bindings.map(binding => { const munged_bindings = this.node.bindings.map(binding => {
component.has_reactive_assignments = true; component.has_reactive_assignments = true;

@ -125,37 +125,47 @@ export default class SlotWrapper extends Wrapper {
} }
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`); const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`); const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
const slot_or_fallback = has_fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot; const anchor = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || x`null`;
block.chunks.init.push(b` block.chunks.init.push(b`
const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name}; ${has_fallback
const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}); ? b`const ${slot} = @create_slot_with_fallback(${renderer.context_lookup.get('#slots').index}, '${slot_name}', ${renderer.context_lookup.get('$$scope').index}, #ctx, ${get_slot_context_fn}, ${this.fallback.name});`
${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} : b`const ${slot} = @create_slot(${renderer.context_lookup.get('#slots').index}, '${slot_name}', ${renderer.context_lookup.get('$$scope').index}, #ctx, ${get_slot_context_fn});`
}
`); `);
block.chunks.create.push( block.chunks.create.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.c();` b`${slot}.c();`
); );
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});` b`${slot}.l(${parent_nodes});`
); );
} }
block.chunks.mount.push(b` block.chunks.mount.push(b`
if (${slot_or_fallback}) { ${slot}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});
${slot_or_fallback}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});
}
`); `);
if (needs_anchor) {
block.add_element(
anchor as Identifier,
x`@empty()`,
parent_nodes && x`@empty()`,
parent_node
);
}
block.chunks.hydrate.push(b`${slot}.a = ${anchor};`);
block.chunks.intro.push( block.chunks.intro.push(
b`@transition_in(${slot_or_fallback}, #local);` b`@transition_in(${slot}, #local);`
); );
block.chunks.outro.push( block.chunks.outro.push(
b`@transition_out(${slot_or_fallback}, #local);` b`@transition_out(${slot}, #local);`
); );
const dynamic_dependencies = Array.from(this.dependencies).filter((name) => this.is_dependency_dynamic(name)); const dynamic_dependencies = Array.from(this.dependencies).filter((name) => this.is_dependency_dynamic(name));
@ -178,17 +188,17 @@ export default class SlotWrapper extends Wrapper {
let slot_update: Node[]; let slot_update: Node[];
if (all_dirty_condition) { if (all_dirty_condition) {
const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference('$$scope')}) : @get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})`; const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference('$$scope')}) : @get_slot_changes(${slot}.x, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})`;
slot_update = b` slot_update = b`
if (${slot}.p && ${condition}) { if (${slot}.s && ${slot}.s.p && ${condition}) {
@update_slot_base(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${dirty}, ${get_slot_context_fn}); @update_slot_base(${slot}.s, ${slot}.x, #ctx, ${renderer.reference('$$scope')}, ${dirty}, ${get_slot_context_fn});
} }
`; `;
} else { } else {
slot_update = b` slot_update = b`
if (${slot}.p && ${condition}) { if (${slot}.s && ${slot}.s.p && ${condition}) {
@update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn}); @update_slot(${slot}.s, ${slot}.x, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn});
} }
`; `;
} }
@ -201,29 +211,33 @@ export default class SlotWrapper extends Wrapper {
} }
const fallback_update = has_fallback && fallback_dynamic_dependencies.length > 0 && b` const fallback_update = has_fallback && fallback_dynamic_dependencies.length > 0 && b`
if (${slot_or_fallback} && ${slot_or_fallback}.p && ${fallback_condition}) { if (${slot}.f.p && ${fallback_condition}) {
${slot_or_fallback}.p(#ctx, ${fallback_dirty}); ${slot}.f.p(#ctx, ${fallback_dirty});
} }
`; `;
if (fallback_update) { const slot_or_fallback_update = fallback_update
block.chunks.update.push(b` ? b`
if (${slot}) { if (${slot}.s) {
${slot_update} ${slot_update}
} else { } else {
${fallback_update} ${fallback_update}
} }
`); `
} else { : b`
block.chunks.update.push(b` if (${slot}.s) {
if (${slot}) {
${slot_update} ${slot_update}
} }
`); `;
block.chunks.update.push(b`
if (!(${renderer.dirty(['#slots'])} && ${slot}.p(#ctx))) {
${slot_or_fallback_update}
} }
`)
block.chunks.destroy.push( block.chunks.destroy.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);` b`if (${slot}) ${slot}.d(detaching);`
); );
} }

@ -3,20 +3,25 @@ import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import FragmentWrapper from './Fragment'; import FragmentWrapper from './Fragment';
import create_debugging_comment from './shared/create_debugging_comment'; import create_debugging_comment from './shared/create_debugging_comment';
import { get_slot_definition } from './shared/get_slot_definition'; import { get_slot_definition, SlotDefinition } from './shared/get_slot_definition';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import { sanitize } from '../../../utils/names'; import { sanitize } from '../../../utils/names';
import { Identifier } from 'estree'; import { Identifier, Node } from 'estree';
import InlineComponentWrapper from './InlineComponent'; import InlineComponentWrapper from './InlineComponent';
import { extract_names } from 'periscopic'; import { extract_names } from 'periscopic';
import SlotTemplate from '../../nodes/SlotTemplate'; import SlotTemplate from '../../nodes/SlotTemplate';
import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'; import { add_const_tags, add_const_tags_context } from './shared/add_const_tags';
import TemplateScope from '../../nodes/shared/TemplateScope';
import SlotTemplateIfBlockWrapper from './SlotTemplateIfBlock';
export default class SlotTemplateWrapper extends Wrapper { export default class SlotTemplateWrapper extends Wrapper {
node: SlotTemplate; node: SlotTemplate;
fragment: FragmentWrapper; fragment: FragmentWrapper;
block: Block; block: Block;
parent: InlineComponentWrapper; parent: InlineComponentWrapper | SlotTemplateIfBlockWrapper;
slot_template_name: string;
slot_definition: Node;
scope: TemplateScope;
constructor( constructor(
renderer: Renderer, renderer: Renderer,
@ -48,14 +53,16 @@ export default class SlotTemplateWrapper extends Wrapper {
this.renderer.blocks.push(this.block); this.renderer.blocks.push(this.block);
const seen = new Set(lets.map(l => l.name.name)); const seen = new Set(lets.map(l => l.name.name));
this.parent.node.lets.forEach(l => { const component_parent = this.get_component_parent();
component_parent.node.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
this.parent.set_slot( const slot_definition = get_slot_definition(this.block, scope, lets);
slot_template_name,
get_slot_definition(this.block, scope, lets) this.slot_template_name = slot_template_name;
); this.slot_definition = x`[${slot_definition.block.name}, ${slot_definition.get_context || null}, ${slot_definition.get_changes || null}]`;
this.scope = scope;
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,
@ -69,13 +76,29 @@ export default class SlotTemplateWrapper extends Wrapper {
this.block.parent.add_dependencies(this.block.dependencies); this.block.parent.add_dependencies(this.block.dependencies);
} }
render() { render_slot_template_content(should_cache: boolean) {
this.fragment.render(this.block, null, x`#nodes` as Identifier); this.fragment.render(this.block, null, x`#nodes` as Identifier);
if (this.node.const_tags.length > 0) { if (this.node.const_tags.length > 0) {
this.render_get_context(); this.render_get_context();
} }
if (!this.block.has_content()) {
this.renderer.remove_block(this.block);
this.slot_definition = null;
}
if (this.slot_definition && should_cache) {
const cached_name = this.renderer.component.get_unique_name(`${this.slot_template_name}_definition`);
this.renderer.blocks.push(b`var ${cached_name} = ${this.slot_definition};`);
this.slot_definition = cached_name;
}
} }
render_slot_template_definition(_block: Block) {
return b`#slots_definition["${this.slot_template_name}"] = ${this.slot_definition};`;
}
render_get_context() { render_get_context() {
const get_context = this.block.renderer.component.get_unique_name('get_context'); const get_context = this.block.renderer.component.get_unique_name('get_context');
this.block.renderer.blocks.push(b` this.block.renderer.blocks.push(b`
@ -88,4 +111,10 @@ export default class SlotTemplateWrapper extends Wrapper {
this.block.chunks.update.unshift(b`${get_context}(#ctx)`); this.block.chunks.update.unshift(b`${get_context}(#ctx)`);
} }
} }
get_component_parent() {
let parent: Wrapper = this.parent;
while (parent.node.type !== 'InlineComponent') parent = parent.parent;
return parent as InlineComponentWrapper;
}
} }

@ -0,0 +1,74 @@
import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import { Identifier } from 'estree';
import SlotTemplateIfBlock from '../../nodes/SlotTemplateIfBlock';
import SlotTemplateWrapper from './SlotTemplate';
import SlotTemplate from '../../nodes/SlotTemplate';
import { b } from 'code-red';
import InlineComponentWrapper from './InlineComponent';
import TemplateScope from '../../nodes/shared/TemplateScope';
export default class SlotTemplateIfBlockWrapper extends Wrapper {
node: SlotTemplateIfBlock;
needs_update = false;
var: Identifier = { type: 'Identifier', name: 'if_block' };
children: Array<SlotTemplateWrapper | SlotTemplateIfBlockWrapper> = [];
else: Array<SlotTemplateWrapper | SlotTemplateIfBlockWrapper> = [];
parent: SlotTemplateIfBlockWrapper | InlineComponentWrapper;
scope: TemplateScope
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: SlotTemplateIfBlock,
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.scope = node.scope;
for (const child of this.node.children) {
if (child.type === 'SlotTemplate') {
this.children.push(new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling));
} else if (child.type === 'SlotTemplateIfBlock') {
this.children.push(new SlotTemplateIfBlockWrapper(renderer, block, this, child as SlotTemplateIfBlock, strip_whitespace, next_sibling));
}
}
if (node.else) {
for (const child of node.else.children) {
if (child.type === 'SlotTemplate') {
this.else.push(new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling));
} else if (child.type === 'SlotTemplateIfBlock') {
this.else.push(new SlotTemplateIfBlockWrapper(renderer, block, this, child as SlotTemplateIfBlock, strip_whitespace, next_sibling));
}
}
}
}
render_slot_template_content(should_cache: boolean) {
this.children.forEach(slot => slot.render_slot_template_content(should_cache));
this.else.forEach(slot => slot.render_slot_template_content(should_cache));
}
render_slot_template_definition(block: Block) {
if (this.else.length > 0) {
return b`
if (${this.node.expression.manipulate(block, '#ctx')}) {
${this.children.map(slot => slot.render_slot_template_definition(block))}
} else {
${this.else.map(slot => slot.render_slot_template_definition(block))}
}
`;
}
return b`
if (${this.node.expression.manipulate(block, '#ctx')}) {
${this.children.map(slot => slot.render_slot_template_definition(block))}
}
`;
}
}

@ -2,9 +2,11 @@ import Let from '../../../nodes/Let';
import { x, p } from 'code-red'; import { x, p } from 'code-red';
import Block from '../../Block'; import Block from '../../Block';
import TemplateScope from '../../../nodes/shared/TemplateScope'; import TemplateScope from '../../../nodes/shared/TemplateScope';
import { BinaryExpression, Identifier } from 'estree'; import { BinaryExpression, Identifier, Node } from 'estree';
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) { export type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]): SlotDefinition {
if (lets.length === 0) return { block, scope }; if (lets.length === 0) return { block, scope };
const context_input = { const context_input = {

@ -0,0 +1,52 @@
import Renderer from "../../Renderer";
import SlotTemplateWrapper from "../SlotTemplate";
import SlotTemplateIfBlockWrapper from "../SlotTemplateIfBlock";
import is_dynamic from "./is_dynamic";
export function collect_slot_fragment_dependencies(
renderer: Renderer,
children: Array<SlotTemplateIfBlockWrapper | SlotTemplateWrapper>,
fragment_dependencies: Set<string>
) {
function collect(
children: Array<SlotTemplateWrapper | SlotTemplateIfBlockWrapper>
) {
for (const child of children) {
if (child instanceof SlotTemplateIfBlockWrapper) {
collect(child.children);
collect(child.else);
for (const dep of child.node.expression.dependencies) {
const is_let = child.scope.is_let(dep);
const variable = renderer.component.var_lookup.get(dep);
if (is_let || is_dynamic(variable)) fragment_dependencies.add(dep);
}
} else {
for (const dep of child.block.dependencies) {
const is_let = child.scope.is_let(dep);
const variable = renderer.component.var_lookup.get(dep);
if (is_let || is_dynamic(variable)) fragment_dependencies.add(dep);
}
}
}
}
collect(children);
}
export function collect_slot_dynamic_dependencies(children: Array<SlotTemplateIfBlockWrapper | SlotTemplateWrapper>) {
const result = new Set<string>();
function collect(children: Array<SlotTemplateIfBlockWrapper | SlotTemplateWrapper>) {
for (const child of children) {
if (child instanceof SlotTemplateIfBlockWrapper) {
for (const dep of child.node.expression.dynamic_dependencies()) {
result.add(dep);
}
collect(child.children);
collect(child.else);
}
}
}
collect(children);
return result;
}

@ -1,4 +1,5 @@
import { Readable } from 'svelte/store'; import { Readable } from 'svelte/store';
import { check_outros, group_outros, transition_in, transition_out } from './transitions';
export function noop() { } export function noop() { }
@ -84,11 +85,109 @@ export function component_subscribe(component, store, callback) {
component.$$.on_destroy.push(subscribe(store, callback)); component.$$.on_destroy.push(subscribe(store, callback));
} }
export function create_slot(definition, ctx, $$scope, fn) { export function create_slot(definition_index: number, definition_name: string, $$scope_index: number, ctx, get_slot_context_fn) {
let definition;
let slot_block;
function init() {
slot.x = definition = ctx[definition_index][definition_name];
const $$scope = ctx[$$scope_index];
if (definition) { if (definition) {
const slot_ctx = get_slot_context(definition, ctx, $$scope, fn); const slot_ctx = get_slot_context(definition, ctx, $$scope, get_slot_context_fn);
return definition[0](slot_ctx); slot.s = slot_block = definition[0](slot_ctx);
} } else {
slot.s = slot_block = null;
}
}
let _target;
const slot = {
a: null,
s: null,
f: null,
x: definition,
c: () => slot_block && slot_block.c(),
m: (target, anchor) => {
_target = target;
slot_block && slot_block.m(target, anchor);
},
p: (ctx) => {
if ((definition !== (definition = ctx[definition_index][definition_name]))) {
if (slot_block) {
if (slot_block.o) {
group_outros();
transition_out(slot_block, 1, 1, () => {});
check_outros();
} else {
slot_block.d(1);
}
}
init();
if (slot_block) {
slot_block.c();
transition_in(slot_block, 1);
slot_block.m(_target, slot.a);
}
return true;
}
},
i: (local) => transition_in(slot_block, local),
o: (local) => transition_out(slot_block, local),
d: (detaching) => slot_block && slot_block.d(detaching),
}
init();
return slot;
}
export function create_slot_with_fallback(definition_index: number, definition_name: string, $$scope_index: number, ctx, get_slot_context_fn, fallback) {
let definition;
let slot_or_fallback;
function init() {
slot.x = definition = ctx[definition_index][definition_name];
const $$scope = ctx[$$scope_index];
if (definition) {
const slot_ctx = get_slot_context(definition, ctx, $$scope, get_slot_context_fn);
slot.s = slot_or_fallback = definition[0](slot_ctx);
slot.f = null;
} else {
slot.s = null;
slot.f = slot_or_fallback = fallback(ctx);
}
}
let _target;
const slot = {
a: null,
s: null,
f: null,
x: definition,
c: () => slot_or_fallback.c(),
m: (target, anchor) => {
slot_or_fallback.m(_target = target, anchor);
},
p: (ctx) => {
if ((definition !== (definition = ctx[definition_index][definition_name]))) {
if (slot_or_fallback.o) {
group_outros();
transition_out(slot_or_fallback, 1, 1, () => {});
check_outros();
} else {
slot_or_fallback.d(1);
}
init();
slot_or_fallback.c();
transition_in(slot_or_fallback, 1);
slot_or_fallback.m(_target, slot.a);
return true;
}
},
i: (local) => transition_in(slot_or_fallback, local),
o: (local) => transition_out(slot_or_fallback, local),
d: (detaching) => slot_or_fallback.d(detaching),
}
init();
return slot;
} }
function get_slot_context(definition, ctx, $$scope, fn) { function get_slot_context(definition, ctx, $$scope, fn) {

@ -207,6 +207,7 @@ export function addLineNumbers(code) {
} }
export function showOutput(cwd, options = {}, compile = svelte.compile) { export function showOutput(cwd, options = {}, compile = svelte.compile) {
return;
glob('**/*.svelte', { cwd }).forEach(file => { glob('**/*.svelte', { cwd }).forEach(file => {
if (file[0] === '_') return; if (file[0] === '_') return;

@ -0,0 +1,27 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: '<p>Fallback</p>',
test({ assert, component, target }) {
component.value = 1;
assert.htmlEqual(target.innerHTML, `
<p>One</p>
`);
component.value = 2;
assert.htmlEqual(target.innerHTML, `
<p>Two</p>
`);
component.value = 3;
assert.htmlEqual(target.innerHTML, `
<p>Fallback</p>
`);
component.value = 4;
assert.htmlEqual(target.innerHTML, `
<p>Fallback</p>
`);
}
};

@ -0,0 +1,12 @@
<script>
import Nested from './Nested.svelte';
export let value;
</script>
<Nested>
{#if value === 1}
<svelte:fragment>One</svelte:fragment>
{:else if value === 2}
<svelte:fragment>Two</svelte:fragment>
{/if}
</Nested>

@ -0,0 +1,15 @@
<div>Slot A</div>
<slot name="a" />
<div>Slot B</div>
<slot name="b"><div>Fallback B</div></slot>
<div>Slot C</div>
<slot name="c"><div>Fallback C</div></slot>
<div>Slot D</div>
<slot name="d" />

@ -0,0 +1,81 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
<div>Slot A</div>
4A
<div>Slot B</div>
4B
<div>Slot C</div>
<div>Fallback C</div>
<div>Slot D</div>
`,
test({ assert, component, target }) {
component.value = 1;
assert.htmlEqual(target.innerHTML, `
<div>Slot A</div>
A
<div>Slot B</div>
<div>Fallback B</div>
<div>Slot C</div>
<div>Fallback C</div>
<div>Slot D</div>
`);
component.value = 2;
assert.htmlEqual(target.innerHTML, `
<div>Slot A</div>
2A
<div>Slot B</div>
2B
<div>Slot C</div>
2C
<div>Slot D</div>
`);
component.value = 3;
assert.htmlEqual(target.innerHTML, `
<div>Slot A</div>
3A
<div>Slot B</div>
3B
<div>Slot C</div>
<div>Fallback C</div>
<div>Slot D</div>
`);
component.condition = false;
assert.htmlEqual(target.innerHTML, `
<div>Slot A</div>
3A
<div>Slot B</div>
<div>Fallback B</div>
<div>Slot C</div>
<div>Fallback C</div>
<div>Slot D</div>
3D
`);
component.value = 4;
assert.htmlEqual(target.innerHTML, `
<div>Slot A</div>
<div>Slot B</div>
<div>Fallback B</div>
<div>Slot C</div>
<div>Fallback C</div>
<div>Slot D</div>
`);
component.value = 5;
assert.htmlEqual(target.innerHTML, `
<div>Slot A</div>
5A
<div>Slot B</div>
<div>Fallback B</div>
<div>Slot C</div>
<div>Fallback C</div>
<div>Slot D</div>
`);
}
};

@ -0,0 +1,31 @@
<script>
import Nested from './Nested.svelte';
export let value;
export let condition = true;
</script>
<Nested>
{#if value === 1}
<svelte:fragment slot="a">A</svelte:fragment>
{:else if value === 2}
<svelte:fragment slot="a">2A</svelte:fragment>
<svelte:fragment slot="b">2B</svelte:fragment>
<svelte:fragment slot="c">2C</svelte:fragment>
{:else if value === 3}
<svelte:fragment slot="a">3A</svelte:fragment>
{#if condition}
<svelte:fragment slot="b">3B</svelte:fragment>
{:else}
<svelte:fragment slot="d">3D</svelte:fragment>
{/if}
{:else}
{#if condition}
<svelte:fragment slot="a">4A</svelte:fragment>
<svelte:fragment slot="b">4B</svelte:fragment>
{:else}
{#if value === 5}
<svelte:fragment slot="a">5A</svelte:fragment>
{/if}
{/if}
{/if}
</Nested>

@ -0,0 +1,16 @@
<script>
export let value;
</script>
<div id={value}>
value: {value}
<hr />
<slot name="folder">
<span>fallback folder</span>
</slot>
<hr />
<slot name="file">
<span>fallback file</span>
</slot>
</div>

@ -0,0 +1,81 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
<div id="_">
value: _
<hr>
<div id="a">
value: a
<hr>
<div id="b">
value: b
<hr>
<span>fallback folder</span>
<hr>
<div>#2 level</div>
</div>
<hr>
<span>fallback file</span>
</div>
<hr>
<span>fallback file</span>
</div>
`,
test({ assert, component, target }) {
const lvl1 = target.querySelector('#a');
const lvl2 = target.querySelector('#b');
component.paths = ["x", "y", "z"];
assert.htmlEqual(target.innerHTML, `
<div id="_">
value: _
<hr>
<div id="x">
value: x
<hr>
<div id="y">
value: y
<hr>
<div id="z">
value: z
<hr>
<span>fallback folder</span>
<hr>
<div>#3 level</div>
</div>
<hr>
<span>fallback file</span>
</div>
<hr>
<span>fallback file</span>
</div>
<hr>
<span>fallback file</span>
</div>
`);
assert.equal(lvl1, target.querySelector('#x'));
assert.equal(lvl2, target.querySelector('#y'));
const lvl3 = target.querySelector('#z');
component.paths = ["p"];
assert.htmlEqual(target.innerHTML, `
<div id="_">
value: _
<hr>
<div id="p">
value: p
<hr>
<span>fallback folder</span>
<hr>
<div>#1 level</div>
</div>
<hr>
<span>fallback file</span>
</div>
`);
assert.equal(lvl1, target.querySelector('#p'));
}
};

@ -0,0 +1,46 @@
<script>
import Nested from './Nested.svelte';
export let paths = ["a", "b"];
</script>
<Nested value="_">
{#if paths[0]}
<svelte:fragment slot="folder">
<Nested value={paths[0]}>
{#if paths[1]}
<svelte:fragment slot="folder">
<Nested value={paths[1]}>
{#if paths[2]}
<svelte:fragment slot="folder">
<Nested value={paths[2]}>
{#if paths[3]}
<svelte:fragment slot="folder">
<Nested value={paths[3]}/>
</svelte:fragment>
{:else}
<svelte:fragment slot="file">
<div>#3 level</div>
</svelte:fragment>
{/if}
</Nested>
</svelte:fragment>
{:else}
<svelte:fragment slot="file">
<div>#2 level</div>
</svelte:fragment>
{/if}
</Nested>
</svelte:fragment>
{:else}
<svelte:fragment slot="file">
<div>#1 level</div>
</svelte:fragment>
{/if}
</Nested>
</svelte:fragment>
{:else}
<svelte:fragment slot="file">
<div>#0 level</div>
</svelte:fragment>
{/if}
</Nested>

@ -0,0 +1,7 @@
<script>
export let value;
export let a;
export let b;
</script>
<slot {a} {b}>{value}</slot>

@ -0,0 +1,30 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `a`,
test({ assert, component, target }) {
component.a = 'foo';
assert.htmlEqual(target.innerHTML, 'foo');
component.condition = 2;
assert.htmlEqual(target.innerHTML, 'foo + b = foob');
component.b = 'bar';
assert.htmlEqual(target.innerHTML, 'foo + bar = foobar');
component.condition = 3;
assert.htmlEqual(target.innerHTML, 'b: bar');
component.condition = 4;
assert.htmlEqual(target.innerHTML, 'value');
component.value = 'xxx';
assert.htmlEqual(target.innerHTML, 'xxx');
component.condition = 2;
component.b = 'baz';
component.a = 'qux';
assert.htmlEqual(target.innerHTML, 'qux + baz = quxbaz');
}
};

@ -0,0 +1,23 @@
<script>
import Nested from './Nested.svelte';
export let condition = 1;
export let value = 'value';
export let a = 'a';
export let b = 'b';
</script>
<Nested {value} {a} {b}>
{#if condition === 1}
<svelte:fragment let:a>
{a}
</svelte:fragment>
{:else if condition === 2}
<svelte:fragment let:a let:b>
{a} + {b} = {a + b}
</svelte:fragment>
{:else if condition === 3}
<svelte:fragment let:b>
b: {b}
</svelte:fragment>
{/if}
</Nested>

@ -0,0 +1,5 @@
<slot name="top">top fallback</slot>
<hr/>
<slot name="middle">middle fallback</slot>
<hr/>
<slot name="bottom">bottom fallback</slot>

@ -0,0 +1,13 @@
<script>
import Bar from './Bar.svelte'
</script>
<Bar>
{#if $$slots.top}
<svelte:fragment slot="top"><slot name="top" /></svelte:fragment>
{/if}
<svelte:fragment slot="middle"><slot name="middle">Middle</slot></svelte:fragment>
{#if $$slots.bottom}
<svelte:fragment slot="bottom"><slot name="bottom" /></svelte:fragment>
{/if}
</Bar>

@ -0,0 +1,28 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
<div>
Top content
<hr>
Middle
<hr>
bottom fallback
</div>
<div>
Top content
<hr>
Middle
<hr>
Bottom content
</div>
<div>
top fallback
<hr>
Middle Content
<hr>
bottom fallback
</div>
`,
};

@ -0,0 +1,22 @@
<script>
import Foo from './Foo.svelte';
</script>
<div>
<Foo>
<svelte:fragment slot="top">Top content</svelte:fragment>
</Foo>
</div>
<div>
<Foo>
<svelte:fragment slot="top">Top content</svelte:fragment>
<svelte:fragment slot="bottom">Bottom content</svelte:fragment>
</Foo>
</div>
<div>
<Foo>
<svelte:fragment slot="middle">Middle Content</svelte:fragment>
</Foo>
</div>

@ -0,0 +1,5 @@
<slot name="top">top fallback</slot>
<hr/>
<slot name="middle">middle fallback</slot>
<hr/>
<slot name="bottom">bottom fallback</slot>

@ -0,0 +1,13 @@
<script>
import Bar from './Bar.svelte'
</script>
<Bar>
{#if $$slots.top}
<svelte:fragment slot="top"><slot name="top" /></svelte:fragment>
{/if}
<svelte:fragment slot="middle"><slot name="middle">Middle</slot></svelte:fragment>
{#if $$slots.bottom}
<svelte:fragment slot="bottom"><slot name="bottom" /></svelte:fragment>
{/if}
</Bar>

@ -0,0 +1,33 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
top fallback
<hr>
Middle
<hr>
bottom fallback
`,
test({ assert, component, target }) {
component.top = true;
assert.htmlEqual(target.innerHTML, `
Top content
<hr>
Middle
<hr>
bottom fallback
`);
component.top = false;
component.middle = true;
component.bottom = true;
assert.htmlEqual(target.innerHTML, `
top fallback
<hr>
Middle content
<hr>
Bottom content
`);
}
};

@ -0,0 +1,18 @@
<script>
import Foo from './Foo.svelte';
export let top = false;
export let middle = false;
export let bottom = false;
</script>
<Foo>
{#if top}
<svelte:fragment slot="top">Top content</svelte:fragment>
{/if}
{#if middle}
<svelte:fragment slot="middle">Middle content</svelte:fragment>
{/if}
{#if bottom}
<svelte:fragment slot="bottom">Bottom content</svelte:fragment>
{/if}
</Foo>

@ -1,3 +0,0 @@
export default {
error: 'Duplicate slot name "foo" in <Nested>'
};

@ -1,3 +0,0 @@
export default {
error: 'Duplicate slot name "foo" in <Nested>'
};

@ -1,3 +0,0 @@
export default {
error: 'Found elements without slot attribute when using slot="default"'
};

@ -1,3 +0,0 @@
export default {
error: 'Duplicate slot name "foo" in <Nested>'
};

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Duplicate slot name \"foo\" in <Nested>",
"start": { "line": 7, "column": 1, "character": 96 },
"end": { "line": 7, "column": 26, "character": 121 },
"pos": 96
}
]

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Duplicate slot name \"a\" in <Nested>",
"start": { "line": 11, "column": 2, "character": 179 },
"end": { "line": 13, "column": 20, "character": 245 },
"pos": 179
}
]

@ -0,0 +1,15 @@
<script>
import Nested from './Nested.svelte';
export let condition;
</script>
<Nested>
{#if condition}
<svelte:fragment slot="a">
<div>test</div>
</svelte:fragment>
<svelte:fragment slot="a">
<div>test</div>
</svelte:fragment>
{/if}
</Nested>

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Duplicate slot name \"foo\" in <Nested>",
"start": { "line": 7, "column": 1, "character": 124 },
"end": { "line": 7, "column": 54, "character": 177 },
"pos": 124
}
]

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Duplicate slot name \"foo\" in <Nested>",
"start": { "line": 7, "column": 1, "character": 124 },
"end": { "line": 7, "column": 26, "character": 149 },
"pos": 124
}
]

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Found elements without slot attribute when using slot=\"default\"",
"start": { "line": 6, "column": 1, "character": 69 },
"end": { "line": 6, "column": 56, "character": 124 },
"pos": 69
}
]

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Found elements without slot attribute when using slot=\"default\"",
"start": { "line": 11, "column": 2, "character": 154 },
"end": { "line": 13, "column": 20, "character": 226 },
"pos": 154
}
]

@ -0,0 +1,15 @@
<script>
import Nested from './Nested.svelte';
export let condition;
</script>
<Nested>
{#if condition}
<span>test</span>
{/if}
{#if condition}
<svelte:fragment slot="default">
<div>test</div>
</svelte:fragment>
{/if}
</Nested>

@ -0,0 +1,66 @@
<script>
import Nested from './Nested.svelte';
export let condition;
</script>
<Nested>
{#if condition}
<svelte:fragment slot="a">
<div>test</div>
</svelte:fragment>
{:else}
<svelte:fragment slot="a">
<div>test</div>
</svelte:fragment>
{/if}
{#if condition}
<svelte:fragment slot="b">
<div>test</div>
</svelte:fragment>
{:else if condition}
<svelte:fragment slot="b">
<div>test</div>
</svelte:fragment>
{/if}
{#if condition}
<svelte:fragment slot="c">
<div>test</div>
</svelte:fragment>
{:else}
{#if condition}
<svelte:fragment slot="c">
<div>test</div>
</svelte:fragment>
{:else}
<svelte:fragment slot="c">
<div>test</div>
</svelte:fragment>
{/if}
{/if}
{#if condition}
<svelte:fragment slot="d">
<div>test</div>
</svelte:fragment>
{:else}
{#if condition}
{#if condition}
{#if condition}
<svelte:fragment slot="d">
<div>test</div>
</svelte:fragment>
{/if}
{/if}
{:else}
{#if condition}
{#if condition}
<svelte:fragment slot="d">
<div>test</div>
</svelte:fragment>
{/if}
{/if}
{/if}
{/if}
</Nested>

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Duplicate slot name \"a\" in <Nested>",
"start": { "line": 20, "column": 5, "character": 333 },
"end": { "line": 22, "column": 23, "character": 405 },
"pos": 333
}
]

@ -0,0 +1,31 @@
<script>
import Nested from './Nested.svelte';
export let condition;
</script>
<Nested>
{#if condition}
<svelte:fragment slot="a">
<div>test</div>
</svelte:fragment>
{/if}
{#if condition}
<svelte:fragment slot="b">
<div>test</div>
</svelte:fragment>
{#if condition}
{#if condition}
{#if condition}
<svelte:fragment slot="a">
<div>test</div>
</svelte:fragment>
{/if}
{/if}
{/if}
{:else if condition}
<svelte:fragment slot="b">
<div>test</div>
</svelte:fragment>
{/if}
</Nested>

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Duplicate slot name \"a\" in <Nested>",
"start": { "line": 24, "column": 5, "character": 424 },
"end": { "line": 26, "column": 23, "character": 496 },
"pos": 424
}
]

@ -0,0 +1,31 @@
<script>
import Nested from './Nested.svelte';
export let condition;
</script>
<Nested>
{#if condition}
<svelte:fragment slot="a">
<div>test</div>
</svelte:fragment>
{/if}
{#if condition}
<svelte:fragment slot="b">
<div>test</div>
</svelte:fragment>
{:else if condition}
<svelte:fragment slot="b">
<div>test</div>
</svelte:fragment>
{#if condition}
{#if condition}
{#if condition}
<svelte:fragment slot="a">
<div>test</div>
</svelte:fragment>
{/if}
{/if}
{/if}
{/if}
</Nested>

@ -0,0 +1,9 @@
[
{
"code": "duplicate-slot-name-in-component",
"message": "Duplicate slot name \"d\" in <Component>",
"start": { "line": 13, "column": 1, "character": 195 },
"end": { "line": 15, "column": 19, "character": 259 },
"pos": 195
}
]

@ -0,0 +1,16 @@
<script>
import Component from './Component.svelte';
export let condition;
</script>
<Component>
{#if condition}
<svelte:fragment slot="d">
<div>test</div>
</svelte:fragment>
{/if}
<svelte:fragment slot="d">
<div>test</div>
</svelte:fragment>
</Component>

@ -0,0 +1,9 @@
[
{
"code": "invalid-mix-element-and-conditional-slot",
"message": "Do not mix <svelte:fragment> and other elements under the same {#if}{:else} group. Default slot content should be wrapped with <svelte:fragment slot=\"default\">",
"start": { "line": 10, "column": 2, "character": 139 },
"end": { "line": 12, "column": 20, "character": 196 },
"pos": 139
}
]

@ -0,0 +1,14 @@
<script>
import Nested from './Nested.svelte';
export let condition;
</script>
<Nested>
{#if condition}
<span>test</span>
{:else}
<svelte:fragment>
<div>test</div>
</svelte:fragment>
{/if}
</Nested>

@ -0,0 +1,18 @@
<script>
import Nested from './Nested.svelte';
export let condition;
</script>
<Nested>
{#if condition}
<span>test</span>
{/if}
{#if condition}
<svelte:fragment slot="bar">
<div>test</div>
</svelte:fragment>
<svelte:fragment slot="foo">
<div>test</div>
</svelte:fragment>
{/if}
</Nested>

@ -0,0 +1,9 @@
[
{
"code": "invalid-mix-element-and-conditional-slot",
"message": "Do not mix <svelte:fragment> and other elements under the same {#if}{:else} group. Default slot content should be wrapped with <svelte:fragment slot=\"default\">",
"start": { "line": 9, "column": 6, "character": 124 },
"end": { "line": 9, "column": 36, "character": 154 },
"pos": 124
}
]

@ -0,0 +1,12 @@
<script>
import Nested from './Nested.svelte';
export let condition;
</script>
<Nested>
<div>
{#if condition}
<svelte:fragment slot='xxx' />
{/if}
</div>
</Nested>

@ -0,0 +1,9 @@
[
{
"code": "invalid-mix-element-and-conditional-slot",
"message": "Do not mix <svelte:fragment> and other elements under the same {#if}{:else} group. Default slot content should be wrapped with <svelte:fragment slot=\"default\">",
"start": { "line": 11, "column": 2, "character": 170 },
"end": { "line": 11, "column": 19, "character": 187 },
"pos": 170
}
]

@ -0,0 +1,13 @@
<script>
import Nested from "./Nested.svelte";
export let condition;
</script>
<Nested>
{#if condition}
<svelte:fragment>
<div>test</div>
</svelte:fragment>
<span>test</span>
{/if}
</Nested>
Loading…
Cancel
Save