diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 9fb41b6108cc..46771f562394 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -219,7 +219,8 @@ export default class Component { this.add_var(node, { name, injected: true, - referenced: true + referenced: true, + reassigned: name === '$$slots' }); } else if (name[0] === '$') { this.add_var(node, { @@ -720,7 +721,8 @@ export default class Component { } else if (is_reserved_keyword(name)) { this.add_var(node, { name, - injected: true + injected: true, + reassigned: name === '$$slots' }); } else if (name[0] === '$') { if (name === '$' || name[1] === '$') { diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index bad3911673fc..e285f3f16868 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -289,5 +289,16 @@ export default { invalid_style_directive_modifier: (valid: string) => ({ code: 'invalid-style-directive-modifier', 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 and other elements under the same {#if}{:else} group. Default slot content should be wrapped with ' + }, + 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}>` }) }; diff --git a/src/compiler/compile/nodes/ConstTag.ts b/src/compiler/compile/nodes/ConstTag.ts index edcb949c155b..8f68c093a53f 100644 --- a/src/compiler/compile/nodes/ConstTag.ts +++ b/src/compiler/compile/nodes/ConstTag.ts @@ -12,7 +12,7 @@ import get_object from '../utils/get_object'; import compiler_errors from '../compiler_errors'; import { Node as ESTreeNode } from 'estree'; -const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate', 'IfBlock', 'ElseBlock']); +const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate', 'IfBlock', 'ElseBlock', 'SlotTemplateIfBlock', 'SlotTemplateElseBlock']); export default class ConstTag extends Node { type: 'ConstTag'; diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index e9ac86a55cf5..2ae2243c54ab 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -10,7 +10,8 @@ import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; import { TemplateNode } from '../../interfaces'; import compiler_errors from '../compiler_errors'; -import { regex_only_whitespaces } from '../../utils/patterns'; +import { validate_get_slot_names } from './SlotTemplateIfBlock'; +import { extract_children_to_slot_templates } from './extract_children_to_slot_templates'; export default class InlineComponent extends Node { type: 'InlineComponent'; @@ -52,7 +53,7 @@ export default class InlineComponent extends Node { this.css_custom_properties.push(new Attribute(component, this, scope, node)); break; } - // fallthrough + // fallthrough case 'Spread': this.attributes.push(new Attribute(component, this, scope, node)); break; @@ -106,69 +107,20 @@ export default class InlineComponent extends Node { }); }); - const children = []; - for (let i = info.children.length - 1; i >= 0; i--) { - const child = info.children[i]; - if (child.type === 'SlotTemplate') { - children.push(child); - info.children.splice(i, 1); - } else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { - const slot_template = { - start: child.start, - end: child.end, - type: 'SlotTemplate', - name: 'svelte:fragment', - attributes: [], - children: [child] - }; - - // transfer attributes - for (let i = child.attributes.length - 1; i >= 0; i--) { - const attribute = child.attributes[i]; - if (attribute.type === 'Let') { - slot_template.attributes.push(attribute); - child.attributes.splice(i, 1); - } else if (attribute.type === 'Attribute' && attribute.name === 'slot') { - slot_template.attributes.push(attribute); - } - } - // transfer const - for (let i = child.children.length - 1; i >= 0; i--) { - const child_child = child.children[i]; - if (child_child.type === 'ConstTag') { - slot_template.children.push(child_child); - child.children.splice(i, 1); - } - } - - children.push(slot_template); - info.children.splice(i, 1); - } else if (child.type === 'Comment' && children.length > 0) { - children[children.length - 1].children.unshift(child); - } - } - - if (info.children.some(node => not_whitespace_text(node))) { - children.push({ - start: info.start, - end: info.end, - type: 'SlotTemplate', - name: 'svelte:fragment', - attributes: [], - children: info.children - }); - } + const children = extract_children_to_slot_templates(component, info, true); this.children = map_children(component, this, this.scope, children); + + this.validate_duplicate_slot_name(); } get slot_template_name() { return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string; } -} -function not_whitespace_text(node) { - return !(node.type === 'Text' && regex_only_whitespaces.test(node.data)); + validate_duplicate_slot_name() { + validate_get_slot_names(this.children, this.component, this.name); + } } function get_namespace(parent: Node, explicit_namespace: string) { diff --git a/src/compiler/compile/nodes/Slot.ts b/src/compiler/compile/nodes/Slot.ts index c21a48f5d665..f1f3ffa2b941 100644 --- a/src/compiler/compile/nodes/Slot.ts +++ b/src/compiler/compile/nodes/Slot.ts @@ -7,7 +7,8 @@ import { TemplateNode } from '../../interfaces'; import compiler_errors from '../compiler_errors'; export default class Slot extends Element { - type: 'Element'; + // @ts-ignore unable to override the type from Element, but this give us a right type + type: 'Slot'; name: string; children: INode[]; slot_name: string; diff --git a/src/compiler/compile/nodes/SlotTemplate.ts b/src/compiler/compile/nodes/SlotTemplate.ts index 90299c8e3954..7b28afb47d1a 100644 --- a/src/compiler/compile/nodes/SlotTemplate.ts +++ b/src/compiler/compile/nodes/SlotTemplate.ts @@ -66,7 +66,12 @@ export default class SlotTemplate extends Node { } 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); } } diff --git a/src/compiler/compile/nodes/SlotTemplateElseBlock.ts b/src/compiler/compile/nodes/SlotTemplateElseBlock.ts new file mode 100644 index 000000000000..10578da7e7c1 --- /dev/null +++ b/src/compiler/compile/nodes/SlotTemplateElseBlock.ts @@ -0,0 +1,47 @@ +import Component from '../Component'; +import Expression from './shared/Expression'; +import TemplateScope from './shared/TemplateScope'; +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)); + } +} diff --git a/src/compiler/compile/nodes/SlotTemplateIfBlock.ts b/src/compiler/compile/nodes/SlotTemplateIfBlock.ts new file mode 100644 index 000000000000..9a203155d7ad --- /dev/null +++ b/src/compiler/compile/nodes/SlotTemplateIfBlock.ts @@ -0,0 +1,73 @@ +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 SlotTemplate from './SlotTemplate'; +import { INode } from './interfaces'; +import { extract_children_to_slot_templates } from './extract_children_to_slot_templates'; + +export default class SlotTemplateIfBlock extends AbstractBlock { + type: 'SlotTemplateIfBlock'; + expression: Expression; + else: SlotTemplateElseBlock; + scope: TemplateScope; + const_tags: ConstTag[]; + slot_names = new Set(); + + constructor( + component: Component, + parent: Node, + scope: TemplateScope, + info: TemplateNode + ) { + super(component, parent, scope, info); + this.scope = scope.child(); + + const children = extract_children_to_slot_templates(component, info, false); + + 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 { + 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: INode[], component: Component, component_name: string) { + const slot_names = new Map(); + 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; +} diff --git a/src/compiler/compile/nodes/extract_children_to_slot_templates.ts b/src/compiler/compile/nodes/extract_children_to_slot_templates.ts new file mode 100644 index 000000000000..246468f361a9 --- /dev/null +++ b/src/compiler/compile/nodes/extract_children_to_slot_templates.ts @@ -0,0 +1,107 @@ +import { x } from 'code-red'; +import { TemplateNode } from '../../interfaces'; +import { regex_only_whitespaces } from '../../utils/patterns'; +import compiler_errors from '../compiler_errors'; +import Component from '../Component'; + +export function extract_children_to_slot_templates(component: Component, node: TemplateNode, extract_default_slot: boolean) { + const result = []; + for (let i = node.children.length - 1; i >= 0; i--) { + const child = node.children[i]; + if (child.type === 'SlotTemplate') { + result.push(child); + node.children.splice(i, 1); + } else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { + let slot_template = { + start: child.start, + end: child.end, + type: 'SlotTemplate', + name: 'svelte:fragment', + attributes: [], + children: [child] + }; + + // transfer attributes + for (let i = child.attributes.length - 1; i >= 0; i--) { + const attribute = child.attributes[i]; + if (attribute.type === 'Let') { + slot_template.attributes.push(attribute); + child.attributes.splice(i, 1); + } else if (attribute.type === 'Attribute' && attribute.name === 'slot') { + slot_template.attributes.push(attribute); + } + } + // transfer const + for (let i = child.children.length - 1; i >= 0; i--) { + const child_child = child.children[i]; + if (child_child.type === 'ConstTag') { + slot_template.children.push(child_child); + child.children.splice(i, 1); + } + } + + // if the does not have any fallback + // then we make + // into {#if $$slots.x}{/if} + // this makes the slots forwarding to passthrough + if (child.type === 'Slot' && child.children.length === 0) { + const slot_template_name = child.attributes.find(attribute => attribute.name === 'name')?.value[0].data ?? 'default'; + slot_template = { + start: slot_template.start, + end: slot_template.end, + type: 'SlotTemplateIfBlock', + expression: x`$$slots.${slot_template_name}`, + children: [slot_template] + } as any; + } + + result.push(slot_template); + node.children.splice(i, 1); + } else if (child.type === 'Comment' && result.length > 0) { + result[result.length - 1].children.unshift(child); + node.children.splice(i, 1); + } else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) { + // ignore + } else if (child.type === 'ConstTag') { + if (!extract_default_slot) { + result.push(child); + } + } else if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) { + result.push({ + ...child, + type: 'SlotTemplateIfBlock' + }); + node.children.splice(i, 1); + } else if (!extract_default_slot) { + component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot); + } + } + + if (extract_default_slot) { + if (node.children.some(node => not_whitespace_text(node))) { + result.push({ + start: node.start, + end: node.end, + type: 'SlotTemplate', + name: 'svelte:fragment', + attributes: [], + children: node.children + }); + } + } + return result.reverse(); +} + + +function not_whitespace_text(node) { + return !(node.type === 'Text' && regex_only_whitespaces.test(node.data)); +} + +function if_block_contains_slot_template(node: TemplateNode) { + for (const child of node.children) { + if (child.type === 'SlotTemplate') return true; + if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) return true; + if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) return true; + } + return false; +} diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts index b4740267fdfe..73e44e92e67b 100644 --- a/src/compiler/compile/nodes/interfaces.ts +++ b/src/compiler/compile/nodes/interfaces.ts @@ -34,6 +34,8 @@ import ThenBlock from './ThenBlock'; import Title from './Title'; import Transition from './Transition'; 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 // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions @@ -65,6 +67,8 @@ export type INode = Action | RawMustacheTag | Slot | SlotTemplate +| SlotTemplateIfBlock +| SlottemplateElseBlock | StyleDirective | Tag | Text @@ -80,4 +84,6 @@ export type INodeAllowConstTag = | CatchBlock | ThenBlock | InlineComponent -| SlotTemplate; +| SlotTemplate +| SlotTemplateIfBlock +| SlottemplateElseBlock; diff --git a/src/compiler/compile/nodes/shared/map_children.ts b/src/compiler/compile/nodes/shared/map_children.ts index 7338148b635f..673ef9a99240 100644 --- a/src/compiler/compile/nodes/shared/map_children.ts +++ b/src/compiler/compile/nodes/shared/map_children.ts @@ -20,6 +20,7 @@ import Title from '../Title'; import Window from '../Window'; import { TemplateNode } from '../../../interfaces'; import { push_array } from '../../../utils/push_array'; +import SlotTemplateIfBlock from '../SlotTemplateIfBlock'; export type Children = ReturnType; @@ -42,6 +43,7 @@ function get_constructor(type) { case 'DebugTag': return DebugTag; case 'Slot': return Slot; case 'SlotTemplate': return SlotTemplate; + case 'SlotTemplateIfBlock': return SlotTemplateIfBlock; case 'Text': return Text; case 'Title': return Title; case 'Window': return Window; diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 879bc93a9552..5f4e859a91f5 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -86,7 +86,7 @@ export default function dom( let compute_slots: Node[] | undefined; if (uses_slots) { 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}`)};` )} ${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; diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index ab140a75c500..0522721359ac 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -9,8 +9,6 @@ import { sanitize } from '../../../../utils/names'; import add_to_set from '../../../utils/add_to_set'; import { b, x, p } from 'code-red'; 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 { Node, Identifier, ObjectExpression } from 'estree'; import EventHandler from '../Element/EventHandler'; @@ -22,17 +20,18 @@ import { is_head } from '../shared/is_head'; import compiler_warnings from '../../../compiler_warnings'; import { namespaces } from '../../../../utils/namespaces'; import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore'; - -type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }; +import SlotTemplateIfBlockWrapper from '../SlotTemplateIfBlock'; +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; export default class InlineComponentWrapper extends Wrapper { var: Identifier; - slots: Map = new Map(); node: InlineComponent; fragment: FragmentWrapper; - children: Array = []; + children: Array = []; + has_conditional_slots: boolean = false; constructor( renderer: Renderer, @@ -89,22 +88,19 @@ 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)); + } 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)); + } + } } block.add_outro(); } - 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); - } - warn_if_reactive() { const { name } = this.node; const variable = this.renderer.component.var_lookup.get(name); @@ -137,25 +133,30 @@ export default class InlineComponentWrapper extends Wrapper { const statements: Array = []; const updates: Array = []; + 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); - 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; - const name_changes = block.get_unique_name(`${name.name}_changes`); 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 is_svg_namespace = this.node.namespace === namespaces.svg; 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); } - 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: { - ${Array.from(this.slots).map(([name, slot]) => { - return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`; - })} + ${this.children.filter((slot: SlotTemplateWrapper) => slot.slot_definition).map((slot: SlotTemplateWrapper) => p`${slot.slot_template_name}: ${slot.slot_definition}`)} }`, - p`$$scope: { - ctx: #ctx - }` + p`$$scope: { ctx: #ctx }` ] : []; @@ -201,19 +203,12 @@ export default class InlineComponentWrapper extends Wrapper { component_opts.properties.push(p`$$inline: true`); } - const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []); - this.slots.forEach(slot => { - 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 fragment_dependencies = new Set(this.children.length ? ['$$scope'] : []); + collect_slot_fragment_dependencies(renderer, this.children, fragment_dependencies); 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} = {};`); } @@ -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 => { component.has_reactive_assignments = true; diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 0a589e339489..14721c7edbc1 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -125,37 +125,47 @@ export default class SlotWrapper extends Wrapper { } const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`); - const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`); - const slot_or_fallback = has_fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot; + const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); + const anchor = needs_anchor + ? block.get_unique_name(`${this.var.name}_anchor`) + : (this.next && this.next.var) || x`null`; block.chunks.init.push(b` - 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 + ? 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});` + : 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( - b`if (${slot_or_fallback}) ${slot_or_fallback}.c();` + b`${slot}.c();` ); if (renderer.options.hydratable) { 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` - if (${slot_or_fallback}) { - ${slot_or_fallback}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); - } + ${slot}.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( - b`@transition_in(${slot_or_fallback}, #local);` + b`@transition_in(${slot}, #local);` ); 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)); @@ -178,17 +188,17 @@ export default class SlotWrapper extends Wrapper { let slot_update: Node[]; 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` - if (${slot}.p && ${condition}) { - @update_slot_base(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${dirty}, ${get_slot_context_fn}); + if (${slot}.s && ${slot}.s.p && ${condition}) { + @update_slot_base(${slot}.s, ${slot}.x, #ctx, ${renderer.reference('$$scope')}, ${dirty}, ${get_slot_context_fn}); } `; } else { slot_update = b` - if (${slot}.p && ${condition}) { - @update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn}); + if (${slot}.s && ${slot}.s.p && ${condition}) { + @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` - if (${slot_or_fallback} && ${slot_or_fallback}.p && ${fallback_condition}) { - ${slot_or_fallback}.p(#ctx, ${fallback_dirty}); + if (${slot}.f.p && ${fallback_condition}) { + ${slot}.f.p(#ctx, ${fallback_dirty}); } `; - if (fallback_update) { - block.chunks.update.push(b` - if (${slot}) { + const slot_or_fallback_update = fallback_update + ? b` + if (${slot}.s) { ${slot_update} } else { ${fallback_update} } - `); - } else { - block.chunks.update.push(b` - if (${slot}) { + ` + : b` + if (${slot}.s) { ${slot_update} } - `); - } + `; + + block.chunks.update.push(b` + if (!(${renderer.dirty(['#slots'])} && ${slot}.p(#ctx))) { + ${slot_or_fallback_update} + } + `); block.chunks.destroy.push( - b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);` + b`if (${slot}) ${slot}.d(detaching);` ); } diff --git a/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts b/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts index a50f74fc04c8..2e8ca7c2b87e 100644 --- a/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts +++ b/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts @@ -6,17 +6,22 @@ import create_debugging_comment from './shared/create_debugging_comment'; import { get_slot_definition } from './shared/get_slot_definition'; import { b, x } from 'code-red'; import { sanitize } from '../../../utils/names'; -import { Identifier } from 'estree'; +import { Identifier, Node } from 'estree'; import InlineComponentWrapper from './InlineComponent'; import { extract_names } from 'periscopic'; import SlotTemplate from '../../nodes/SlotTemplate'; 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 { node: SlotTemplate; fragment: FragmentWrapper; block: Block; - parent: InlineComponentWrapper; + parent: InlineComponentWrapper | SlotTemplateIfBlockWrapper; + slot_template_name: string; + slot_definition: Node; + scope: TemplateScope; constructor( renderer: Renderer, @@ -48,14 +53,16 @@ export default class SlotTemplateWrapper extends Wrapper { this.renderer.blocks.push(this.block); 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); }); - this.parent.set_slot( - slot_template_name, - get_slot_definition(this.block, scope, lets) - ); + const slot_definition = 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( renderer, @@ -69,18 +76,47 @@ export default class SlotTemplateWrapper extends Wrapper { 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); if (this.node.const_tags.length > 0) { this.render_get_context(); } + + if (this.slot_template_name === 'default' && !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() { + const if_const_tags = []; + let parent = this.node.parent; + while (parent.type === 'SlotTemplateIfBlock' || parent.type === 'SlotTemplateElseBlock') { + if_const_tags.push(parent.const_tags); + if (parent.type === 'SlotTemplateElseBlock') parent = parent.parent; + parent = parent.parent; + } + const const_tags = []; + for (let i = if_const_tags.length - 1; i >= 0; i--) { + const_tags.push(...if_const_tags[i]); + } + const_tags.push(...this.node.const_tags); + const get_context = this.block.renderer.component.get_unique_name('get_context'); this.block.renderer.blocks.push(b` function ${get_context}(#ctx) { - ${add_const_tags(this.block, this.node.const_tags, '#ctx')} + ${add_const_tags(this.block, const_tags, '#ctx')} } `); this.block.chunks.declarations.push(b`${get_context}(#ctx)`); @@ -88,4 +124,10 @@ export default class SlotTemplateWrapper extends Wrapper { 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; + } } diff --git a/src/compiler/compile/render_dom/wrappers/SlotTemplateIfBlock.ts b/src/compiler/compile/render_dom/wrappers/SlotTemplateIfBlock.ts new file mode 100644 index 000000000000..8acb61df04bd --- /dev/null +++ b/src/compiler/compile/render_dom/wrappers/SlotTemplateIfBlock.ts @@ -0,0 +1,101 @@ +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 { x, b } from 'code-red'; +import InlineComponentWrapper from './InlineComponent'; +import TemplateScope from '../../nodes/shared/TemplateScope'; +import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'; + +export default class SlotTemplateIfBlockWrapper extends Wrapper { + node: SlotTemplateIfBlock; + needs_update = false; + + var: Identifier = { type: 'Identifier', name: 'if_block' }; + children: Array = []; + else: Array = []; + 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; + + add_const_tags_context(renderer, this.node.const_tags); + + 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) { + add_const_tags_context(renderer, this.node.else.const_tags); + 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) { + let if_get_context; + let else_get_context; + + if (this.node.const_tags.length) { + if_get_context = block.renderer.component.get_unique_name('get_context'); + block.renderer.blocks.push(b` + function ${if_get_context}(#ctx) { + ${add_const_tags(block, this.node.const_tags, '#ctx')} + } + `); + } + if (this.node.else && this.node.else.const_tags.length) { + else_get_context = block.renderer.component.get_unique_name('get_context'); + block.renderer.blocks.push(b` + function ${else_get_context}(#ctx) { + ${add_const_tags(block, this.node.const_tags, '#ctx')} + } + `); + } + + if (this.else.length > 0) { + return b` + if (${this.node.expression.manipulate(block, '#ctx')}) { + ${if_get_context ? x`${if_get_context}(#ctx)` : null} + ${this.children.map(slot => slot.render_slot_template_definition(block))} + } else { + ${else_get_context ? x`${else_get_context}(#ctx)` : null} + ${this.else.map(slot => slot.render_slot_template_definition(block))} + } + `; + } + + return b` + if (${this.node.expression.manipulate(block, '#ctx')}) { + ${if_get_context ? x`${if_get_context}(#ctx)` : null} + ${this.children.map(slot => slot.render_slot_template_definition(block))} + } + `; + } +} diff --git a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts index 2114096988ab..531e6397edfc 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts @@ -2,9 +2,11 @@ import Let from '../../../nodes/Let'; import { x, p } from 'code-red'; import Block from '../../Block'; 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 }; const context_input = { diff --git a/src/compiler/compile/render_dom/wrappers/shared/slots.ts b/src/compiler/compile/render_dom/wrappers/shared/slots.ts new file mode 100644 index 000000000000..64ee0e6af082 --- /dev/null +++ b/src/compiler/compile/render_dom/wrappers/shared/slots.ts @@ -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, + fragment_dependencies: Set +) { + function collect( + children: Array + ) { + 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) { + const result = new Set(); + + function collect(children: Array) { + 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; +} diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index ac78d032ead8..ca317246ebea 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -10,12 +10,13 @@ import InlineComponent from './handlers/InlineComponent'; import KeyBlock from './handlers/KeyBlock'; import Slot from './handlers/Slot'; import SlotTemplate from './handlers/SlotTemplate'; +import SlotTemplateIfBlock from './handlers/SlotTemplateIfBlock'; import Tag from './handlers/Tag'; import Text from './handlers/Text'; import Title from './handlers/Title'; import { AppendTarget, CompileOptions } from '../../interfaces'; import { INode } from '../nodes/interfaces'; -import { Expression, TemplateLiteral, Identifier } from 'estree'; +import { Expression, TemplateLiteral, Identifier, Node } from 'estree'; import { collapse_template_literal } from '../utils/collapse_template_literal'; import { escape_template } from '../utils/stringify'; @@ -40,6 +41,7 @@ const handlers: Record = { RawMustacheTag: HtmlTag, Slot, SlotTemplate, + SlotTemplateIfBlock, Text, Title, Window: noop @@ -48,6 +50,7 @@ const handlers: Record = { export interface RenderOptions extends CompileOptions{ locate: (c: number) => { line: number; column: number }; head_id?: string; + slot_scopes?: Array; } export default class Renderer { diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index 73d31940e203..e2e7793ba097 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -66,27 +66,20 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend : node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any) ); - const slot_fns = []; + const slot_scopes = []; const children = node.children; - if (children.length) { - const slot_scopes = new Map(); - renderer.render(children, Object.assign({}, options, { slot_scopes })); - - slot_scopes.forEach(({ input, output, statements }, name) => { - slot_fns.push( - p`${name}: (${input}) => { ${statements}; return ${output}; }` - ); - }); } - const slots = x`{ - ${slot_fns} - }`; + const slots = x`(() => { + const #slots_definition = {}; + ${slot_scopes} + return #slots_definition; + })()`; if (node.css_custom_properties.length > 0) { if (node.namespace === namespaces.svg) { diff --git a/src/compiler/compile/render_ssr/handlers/Slot.ts b/src/compiler/compile/render_ssr/handlers/Slot.ts index f89b619c466d..18d96c6a6ee8 100644 --- a/src/compiler/compile/render_ssr/handlers/Slot.ts +++ b/src/compiler/compile/render_ssr/handlers/Slot.ts @@ -1,12 +1,10 @@ import Renderer, { RenderOptions } from '../Renderer'; import Slot from '../../nodes/Slot'; -import { x } from 'code-red'; +import { x, b } from 'code-red'; import get_slot_data from '../../utils/get_slot_data'; import { get_slot_scope } from './shared/get_slot_scope'; -export default function(node: Slot, renderer: Renderer, options: RenderOptions & { - slot_scopes: Map; -}) { +export default function(node: Slot, renderer: Renderer, options: RenderOptions) { const slot_data = get_slot_data(node.values); const slot = node.get_static_attribute_value('slot'); const nearest_inline_component = node.find_nearest(/InlineComponent/); @@ -32,9 +30,9 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions & nearest_inline_component.lets.forEach(l => { if (!seen.has(l.name.name)) lets.push(l); }); - options.slot_scopes.set(slot, { - input: get_slot_scope(node.lets), - output: renderer.pop() - }); + + options.slot_scopes.push(b`#slots_definition['${node.slot_template_name}'] = + (${get_slot_scope(node.lets)}) => ${renderer.pop()}; + `); } } diff --git a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts index 09f329330157..f7ad3ebdd927 100644 --- a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts +++ b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts @@ -4,11 +4,11 @@ import remove_whitespace_children from './utils/remove_whitespace_children'; import { get_slot_scope } from './shared/get_slot_scope'; import InlineComponent from '../../nodes/InlineComponent'; import { get_const_tags } from './shared/get_const_tags'; +import { x } from 'code-red'; +import { INode } from '../../nodes/interfaces'; -export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions & { - slot_scopes: Map; -}) { - const parent_inline_component = node.parent as InlineComponent; +export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions) { + const parent_inline_component = get_parent_inline_component(node.parent); const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next); renderer.push(); @@ -22,18 +22,9 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO const slot_fragment_content = renderer.pop(); if (!is_empty_template_literal(slot_fragment_content)) { - if (options.slot_scopes.has(node.slot_template_name)) { - if (node.slot_template_name === 'default') { - throw new Error('Found elements without slot attribute when using slot="default"'); - } - throw new Error(`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`); - } - - options.slot_scopes.set(node.slot_template_name, { - input: get_slot_scope(node.lets), - output: slot_fragment_content, - statements: get_const_tags(node.const_tags) - }); + options.slot_scopes.push(x`#slots_definition['${node.slot_template_name}'] = + (${get_slot_scope(node.lets)}) => { ${get_const_tags(node.const_tags)}; return ${slot_fragment_content}; } + `); } } @@ -44,3 +35,11 @@ function is_empty_template_literal(template_literal) { template_literal.quasis[0].value.raw === '' ); } + +function get_parent_inline_component(node: INode) { + let parent = node; + while (parent.type !== 'InlineComponent') { + parent = parent.parent; + } + return parent as InlineComponent; +} diff --git a/src/compiler/compile/render_ssr/handlers/SlotTemplateIfBlock.ts b/src/compiler/compile/render_ssr/handlers/SlotTemplateIfBlock.ts new file mode 100644 index 000000000000..7e9ddcd2cac1 --- /dev/null +++ b/src/compiler/compile/render_ssr/handlers/SlotTemplateIfBlock.ts @@ -0,0 +1,32 @@ +import Renderer, { RenderOptions } from '../Renderer'; +import { b } from 'code-red'; +import SlotTemplateIfBlock from '../../nodes/SlotTemplateIfBlock'; +import { get_const_tags } from './shared/get_const_tags'; + +export default function (node: SlotTemplateIfBlock, renderer: Renderer, options: RenderOptions) { + const if_slot_scopes = []; + renderer.render(node.children, Object.assign({}, options, { + slot_scopes: if_slot_scopes + })); + + if (node.else) { + const else_slot_scopes = []; + renderer.render(node.else.children, Object.assign({}, options, { + slot_scopes: else_slot_scopes + })); + options.slot_scopes.push(b` + if (${node.expression.node}) { + ${get_const_tags(node.const_tags)} + ${if_slot_scopes} + } else { + ${get_const_tags(node.else.const_tags)} + ${else_slot_scopes} + } + `); + } else { + options.slot_scopes.push(b`if (${node.expression.node}) { + ${get_const_tags(node.const_tags)} + ${if_slot_scopes} + }`); + } +} diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 1507bad5419b..de80608213c8 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,6 +1,7 @@ import type { Readable } from '../store'; +import { check_outros, group_outros, transition_in, transition_out } from './transitions'; -export function noop() {} +export function noop() { } export const identity = x => x; @@ -84,11 +85,111 @@ export function component_subscribe(component, store, callback) { component.$$.on_destroy.push(subscribe(store, callback)); } -export function create_slot(definition, ctx, $$scope, fn) { - if (definition) { - const slot_ctx = get_slot_context(definition, ctx, $$scope, fn); - return definition[0](slot_ctx); +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) { + const slot_ctx = get_slot_context(definition, ctx, $$scope, get_slot_context_fn); + 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), + l: (nodes) => slot_block && slot_block.l(nodes) + }; + + 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), + l: (nodes) => slot_or_fallback.l(nodes) + }; + + init(); + return slot; } function get_slot_context(definition, ctx, $$scope, fn) { @@ -168,7 +269,7 @@ export function compute_slots(slots) { export function once(fn) { let ran = false; - return function(this: any, ...args) { + return function (this: any, ...args) { if (ran) return; ran = true; fn.call(this, ...args); diff --git a/test/runtime/samples/$$slot-dynamic/A.svelte b/test/runtime/samples/$$slot-dynamic/A.svelte new file mode 100644 index 000000000000..8b73875de244 --- /dev/null +++ b/test/runtime/samples/$$slot-dynamic/A.svelte @@ -0,0 +1,28 @@ + + + + + +$$slots: {toString($$slots)} {stringified} + +{#if $$slots.b} +
+ +
+{:else} + Slot b is not available +{/if} \ No newline at end of file diff --git a/test/runtime/samples/$$slot-dynamic/_config.js b/test/runtime/samples/$$slot-dynamic/_config.js new file mode 100644 index 000000000000..2a3cc2bc9c54 --- /dev/null +++ b/test/runtime/samples/$$slot-dynamic/_config.js @@ -0,0 +1,23 @@ +export default { + html: ` + byedefault + hello a + $$slots: {"a":true,"default":true} {"a":true,"default":true} + Slot b is not available + `, + + async test({ assert, component, target }) { + assert.equal(component.getData(), '{"a":true,"default":true}'); + + component.show_b = true; + assert.htmlEqual(target.innerHTML, ` + byedefault + hello a + $$slots: {"a":true,"b":true,"default":true} {"a":true,"b":true,"default":true} +
hello b
+ `); + assert.equal(component.getData(), '{"a":true,"b":true,"default":true}'); + + component.show_default = false; + } +}; diff --git a/test/runtime/samples/$$slot-dynamic/main.svelte b/test/runtime/samples/$$slot-dynamic/main.svelte new file mode 100644 index 000000000000..bcaeafd16749 --- /dev/null +++ b/test/runtime/samples/$$slot-dynamic/main.svelte @@ -0,0 +1,30 @@ + + + + {#if show_a} + + hello a + + {/if} + {#if show_default} + + bye + default + + {/if} + {#if show_b} + + hello b + + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-1/Nested.svelte b/test/runtime/samples/component-conditional-slot-1/Nested.svelte new file mode 100644 index 000000000000..e53bc9e0c29f --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-1/Nested.svelte @@ -0,0 +1 @@ +

Fallback

\ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-1/_config.js b/test/runtime/samples/component-conditional-slot-1/_config.js new file mode 100644 index 000000000000..6162641102db --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-1/_config.js @@ -0,0 +1,24 @@ +export default { + html: '

Fallback

', + test({ assert, component, target }) { + component.value = 1; + assert.htmlEqual(target.innerHTML, ` +

One

+ `); + + component.value = 2; + assert.htmlEqual(target.innerHTML, ` +

Two

+ `); + + component.value = 3; + assert.htmlEqual(target.innerHTML, ` +

Fallback

+ `); + + component.value = 4; + assert.htmlEqual(target.innerHTML, ` +

Fallback

+ `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-1/main.svelte b/test/runtime/samples/component-conditional-slot-1/main.svelte new file mode 100644 index 000000000000..10f636572e64 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-1/main.svelte @@ -0,0 +1,12 @@ + + + + {#if value === 1} + One + {:else if value === 2} + Two + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-10-default/Foo.svelte b/test/runtime/samples/component-conditional-slot-10-default/Foo.svelte new file mode 100644 index 000000000000..7ffa56838602 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-10-default/Foo.svelte @@ -0,0 +1,3 @@ +
+ default fallback +
diff --git a/test/runtime/samples/component-conditional-slot-10-default/_config.js b/test/runtime/samples/component-conditional-slot-10-default/_config.js new file mode 100644 index 000000000000..6a107285eac7 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-10-default/_config.js @@ -0,0 +1,13 @@ +export default { + html: ` +
+
default fallback
+ `, + test({ assert, component, target }) { + component.condition = true; + assert.htmlEqual(target.innerHTML, ` +
hello #1
+
hello #2
+ `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-10-default/main.svelte b/test/runtime/samples/component-conditional-slot-10-default/main.svelte new file mode 100644 index 000000000000..a940df602c87 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-10-default/main.svelte @@ -0,0 +1,16 @@ + + + + {#if condition} + hello #1 + {/if} + + + + {#if condition} + hello #2 + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-11-slot-attribute/Foo.svelte b/test/runtime/samples/component-conditional-slot-11-slot-attribute/Foo.svelte new file mode 100644 index 000000000000..b570a157ca7b --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-11-slot-attribute/Foo.svelte @@ -0,0 +1,3 @@ +default a +
+default b diff --git a/test/runtime/samples/component-conditional-slot-11-slot-attribute/_config.js b/test/runtime/samples/component-conditional-slot-11-slot-attribute/_config.js new file mode 100644 index 000000000000..1cca76d7831a --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-11-slot-attribute/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` + default a +
+
hello b
+ `, + test({ assert, component, target }) { + component.condition = true; + assert.htmlEqual(target.innerHTML, ` +
hello a
+
+
hello b
+ `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-11-slot-attribute/main.svelte b/test/runtime/samples/component-conditional-slot-11-slot-attribute/main.svelte new file mode 100644 index 000000000000..b2d8e8ccf33d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-11-slot-attribute/main.svelte @@ -0,0 +1,11 @@ + + + + {#if condition} +
hello a
+ {/if} +
hello b
+
diff --git a/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Bar.svelte b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Bar.svelte new file mode 100644 index 000000000000..2577fb1d28e0 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Bar.svelte @@ -0,0 +1,3 @@ +x: fallback x +y: fallback y +z: fallback z \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Foo.svelte b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Foo.svelte new file mode 100644 index 000000000000..fefd89a050e0 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Foo.svelte @@ -0,0 +1,15 @@ + + + + Fallback a + {#if condition1} + Fallback b + {/if} + {#if condition2} + Fallback c + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-12-slot-fowarding/_config.js b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/_config.js new file mode 100644 index 000000000000..60928a9a903d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/_config.js @@ -0,0 +1,37 @@ +export default { + html: ` + x: Fallback a + y: fallback y + z:
hello c
+ `, + test({ assert, component, target }) { + component.condition1 = true; + assert.htmlEqual(target.innerHTML, ` + x: Fallback a + y:
hello b
+ z:
hello c
+ `); + + component.condition3 = true; + assert.htmlEqual(target.innerHTML, ` + x:
hello a
+ y:
hello b
+ z:
hello c
+ `); + + component.condition4 = false; + component.condition1 = false; + assert.htmlEqual(target.innerHTML, ` + x:
hello a
+ y: fallback y + z:
hello c
+ `); + + component.condition2 = false; + assert.htmlEqual(target.innerHTML, ` + x:
hello a
+ y: fallback y + z: fallback z + `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-12-slot-fowarding/main.svelte b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/main.svelte new file mode 100644 index 000000000000..2daa8363746d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/main.svelte @@ -0,0 +1,17 @@ + + + + {#if condition3} +
hello a
+ {/if} + {#if condition4} +
hello b
+ {/if} +
hello c
+
diff --git a/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Bar.svelte b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Bar.svelte new file mode 100644 index 000000000000..2577fb1d28e0 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Bar.svelte @@ -0,0 +1,3 @@ +x: fallback x +y: fallback y +z: fallback z \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Foo.svelte b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Foo.svelte new file mode 100644 index 000000000000..38bffe757c85 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Foo.svelte @@ -0,0 +1,9 @@ + + + + + + + diff --git a/test/runtime/samples/component-conditional-slot-13-slot-fowarding/_config.js b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/_config.js new file mode 100644 index 000000000000..fe0ae82314d3 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` + x: fallback x + y:
hello b
+ z: fallback z + `, + test({ assert, component, target }) { + component.condition = true; + assert.htmlEqual(target.innerHTML, ` + x:
hello a
+ y:
hello b
+ z: fallback z + `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-13-slot-fowarding/main.svelte b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/main.svelte new file mode 100644 index 000000000000..b2d8e8ccf33d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/main.svelte @@ -0,0 +1,11 @@ + + + + {#if condition} +
hello a
+ {/if} +
hello b
+
diff --git a/test/runtime/samples/component-conditional-slot-2/Nested.svelte b/test/runtime/samples/component-conditional-slot-2/Nested.svelte new file mode 100644 index 000000000000..8a317301bb9d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-2/Nested.svelte @@ -0,0 +1,15 @@ +
Slot A
+ + + +
Slot B
+ +
Fallback B
+ +
Slot C
+ +
Fallback C
+ +
Slot D
+ + \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-2/_config.js b/test/runtime/samples/component-conditional-slot-2/_config.js new file mode 100644 index 000000000000..096bd3077fe7 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-2/_config.js @@ -0,0 +1,78 @@ +export default { + html: ` +
Slot A
+ 4A +
Slot B
+ 4B +
Slot C
+
Fallback C
+
Slot D
+ `, + test({ assert, component, target }) { + component.value = 1; + assert.htmlEqual(target.innerHTML, ` +
Slot A
+ A +
Slot B
+
Fallback B
+
Slot C
+
Fallback C
+
Slot D
+ `); + + component.value = 2; + assert.htmlEqual(target.innerHTML, ` +
Slot A
+ 2A +
Slot B
+ 2B +
Slot C
+ 2C +
Slot D
+ `); + + component.value = 3; + assert.htmlEqual(target.innerHTML, ` +
Slot A
+ 3A +
Slot B
+ 3B +
Slot C
+
Fallback C
+
Slot D
+ `); + + component.condition = false; + assert.htmlEqual(target.innerHTML, ` +
Slot A
+ 3A +
Slot B
+
Fallback B
+
Slot C
+
Fallback C
+
Slot D
+ 3D + `); + + component.value = 4; + assert.htmlEqual(target.innerHTML, ` +
Slot A
+
Slot B
+
Fallback B
+
Slot C
+
Fallback C
+
Slot D
+ `); + + component.value = 5; + assert.htmlEqual(target.innerHTML, ` +
Slot A
+ 5A +
Slot B
+
Fallback B
+
Slot C
+
Fallback C
+
Slot D
+ `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-2/main.svelte b/test/runtime/samples/component-conditional-slot-2/main.svelte new file mode 100644 index 000000000000..a03c15139048 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-2/main.svelte @@ -0,0 +1,31 @@ + + + + {#if value === 1} + A + {:else if value === 2} + 2A + 2B + 2C + {:else if value === 3} + 3A + {#if condition} + 3B + {:else} + 3D + {/if} + {:else} + {#if condition} + 4A + 4B + {:else} + {#if value === 5} + 5A + {/if} + {/if} + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-3/Nested.svelte b/test/runtime/samples/component-conditional-slot-3/Nested.svelte new file mode 100644 index 000000000000..0101a6214007 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-3/Nested.svelte @@ -0,0 +1,16 @@ + + +
+ value: {value} +
+ + fallback folder + +
+ + fallback file + +
+ diff --git a/test/runtime/samples/component-conditional-slot-3/_config.js b/test/runtime/samples/component-conditional-slot-3/_config.js new file mode 100644 index 000000000000..8773caf8e288 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-3/_config.js @@ -0,0 +1,77 @@ +export default { + html: ` +
+ value: _ +
+
+ value: a +
+
+ value: b +
+ fallback folder +
+
#2 level
+
+
+ fallback file +
+
+ fallback file +
+ `, + test({ assert, component, target }) { + const lvl1 = target.querySelector('#a'); + const lvl2 = target.querySelector('#b'); + component.paths = ['x', 'y', 'z']; + assert.htmlEqual(target.innerHTML, ` +
+ value: _ +
+
+ value: x +
+
+ value: y +
+
+ value: z +
+ fallback folder +
+
#3 level
+
+
+ fallback file +
+
+ fallback file +
+
+ fallback file +
+ `); + + assert.equal(lvl1, target.querySelector('#x')); + assert.equal(lvl2, target.querySelector('#y')); + + component.paths = ['p']; + assert.htmlEqual(target.innerHTML, ` +
+ value: _ +
+
+ value: p +
+ fallback folder +
+
#1 level
+
+
+ fallback file +
+ `); + + assert.equal(lvl1, target.querySelector('#p')); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-3/main.svelte b/test/runtime/samples/component-conditional-slot-3/main.svelte new file mode 100644 index 000000000000..f92ce7c287fc --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-3/main.svelte @@ -0,0 +1,46 @@ + + + + {#if paths[0]} + + + {#if paths[1]} + + + {#if paths[2]} + + + {#if paths[3]} + + + + {:else} + +
#3 level
+
+ {/if} +
+
+ {:else} + +
#2 level
+
+ {/if} +
+
+ {:else} + +
#1 level
+
+ {/if} +
+
+ {:else} + +
#0 level
+
+ {/if} +
diff --git a/test/runtime/samples/component-conditional-slot-4/Nested.svelte b/test/runtime/samples/component-conditional-slot-4/Nested.svelte new file mode 100644 index 000000000000..4ad905aa7eff --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-4/Nested.svelte @@ -0,0 +1,7 @@ + + +{value} \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-4/_config.js b/test/runtime/samples/component-conditional-slot-4/_config.js new file mode 100644 index 000000000000..f32f20ddb4f7 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-4/_config.js @@ -0,0 +1,27 @@ +export default { + 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'); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-4/main.svelte b/test/runtime/samples/component-conditional-slot-4/main.svelte new file mode 100644 index 000000000000..a065c872ddae --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-4/main.svelte @@ -0,0 +1,23 @@ + + + + {#if condition === 1} + + {a} + + {:else if condition === 2} + + {a} + {b} = {a + b} + + {:else if condition === 3} + + b: {b} + + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-5/Bar.svelte b/test/runtime/samples/component-conditional-slot-5/Bar.svelte new file mode 100644 index 000000000000..b4202403fa4c --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-5/Bar.svelte @@ -0,0 +1,5 @@ +top fallback +
+middle fallback +
+bottom fallback \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-5/Foo.svelte b/test/runtime/samples/component-conditional-slot-5/Foo.svelte new file mode 100644 index 000000000000..41d109ae5619 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-5/Foo.svelte @@ -0,0 +1,13 @@ + + + + {#if $$slots.top} + + {/if} + Middle + {#if $$slots.bottom} + + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-5/_config.js b/test/runtime/samples/component-conditional-slot-5/_config.js new file mode 100644 index 000000000000..effe0c04a0e2 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-5/_config.js @@ -0,0 +1,25 @@ +export default { + html: ` +
+ Top content +
+ Middle +
+ bottom fallback +
+
+ Top content +
+ Middle +
+ Bottom content +
+
+ top fallback +
+ Middle Content +
+ bottom fallback +
+ ` +}; diff --git a/test/runtime/samples/component-conditional-slot-5/main.svelte b/test/runtime/samples/component-conditional-slot-5/main.svelte new file mode 100644 index 000000000000..9f3d3871d345 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-5/main.svelte @@ -0,0 +1,22 @@ + + +
+ + Top content + +
+ +
+ + Top content + Bottom content + +
+ +
+ + Middle Content + +
diff --git a/test/runtime/samples/component-conditional-slot-6/Bar.svelte b/test/runtime/samples/component-conditional-slot-6/Bar.svelte new file mode 100644 index 000000000000..b4202403fa4c --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-6/Bar.svelte @@ -0,0 +1,5 @@ +top fallback +
+middle fallback +
+bottom fallback \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-6/Foo.svelte b/test/runtime/samples/component-conditional-slot-6/Foo.svelte new file mode 100644 index 000000000000..41d109ae5619 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-6/Foo.svelte @@ -0,0 +1,13 @@ + + + + {#if $$slots.top} + + {/if} + Middle + {#if $$slots.bottom} + + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-6/_config.js b/test/runtime/samples/component-conditional-slot-6/_config.js new file mode 100644 index 000000000000..55c883f2675c --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-6/_config.js @@ -0,0 +1,30 @@ +export default { + html: ` + top fallback +
+ Middle +
+ bottom fallback + `, + test({ assert, component, target }) { + component.top = true; + assert.htmlEqual(target.innerHTML, ` + Top content +
+ Middle +
+ bottom fallback + `); + + component.top = false; + component.middle = true; + component.bottom = true; + assert.htmlEqual(target.innerHTML, ` + top fallback +
+ Middle content +
+ Bottom content + `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-6/main.svelte b/test/runtime/samples/component-conditional-slot-6/main.svelte new file mode 100644 index 000000000000..77bbe16ce206 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-6/main.svelte @@ -0,0 +1,18 @@ + + + + {#if top} + Top content + {/if} + {#if middle} + Middle content + {/if} + {#if bottom} + Bottom content + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-7/Foo.svelte b/test/runtime/samples/component-conditional-slot-7/Foo.svelte new file mode 100644 index 000000000000..4dc85932e25b --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-7/Foo.svelte @@ -0,0 +1,7 @@ + + + +
+ \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-7/_config.js b/test/runtime/samples/component-conditional-slot-7/_config.js new file mode 100644 index 000000000000..6d35733552e5 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-7/_config.js @@ -0,0 +1,55 @@ +export default { + html: ` + 3 ~ 15 +
+
sum: 7 product: 2 all: 14
+ `, + test({ assert, component, target }) { + component.top = { a: 5, b: 6 }; + assert.htmlEqual(target.innerHTML, ` + 11 ~ 55 +
+
sum: 7 product: 30 all: 42
+ `); + + component.main_constant = 3; + assert.htmlEqual(target.innerHTML, ` + 11 ~ 165 +
+
sum: 7 product: 90 all: 102
+ `); + + component.top = false; + assert.htmlEqual(target.innerHTML, ` +
+
sum: 7 all: 12
+ `); + + component.bottom = false; + assert.htmlEqual(target.innerHTML, ` +
+
35
+ `); + + component.top = { a: 2, b: 3 }; + assert.htmlEqual(target.innerHTML, ` + 5 ~ 75 +
+
35
+ `); + + component.main_constant = 1; + assert.htmlEqual(target.innerHTML, ` + 5 ~ 25 +
+
15
+ `); + + component.foo_constant = 3; + assert.htmlEqual(target.innerHTML, ` + 5 ~ 15 +
+
9
+ `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-7/main.svelte b/test/runtime/samples/component-conditional-slot-7/main.svelte new file mode 100644 index 000000000000..6a36f7ec317b --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-7/main.svelte @@ -0,0 +1,38 @@ + + + + {#if top} + {@const sum = top.a + top.b} + + {@const csum = sum * constant * main_constant} + {sum} ~ {csum} + + {/if} + {#if bottom} + {@const sum = bottom.a + bottom.b} + {#if top} + {@const product = top.a * top.b * main_constant} + + {@const all = constant + product + sum} +
sum: {sum} product: {product} all: {all}
+
+ {:else} + + {@const all = constant + sum} +
sum: {sum} all: {all}
+
+ {/if} + {:else} + {@const product = foo_constant + foo_constant} + + {@const all = constant + product * main_constant} +
{all}
+
+ {/if} +
diff --git a/test/runtime/samples/component-conditional-slot-8/Foo.svelte b/test/runtime/samples/component-conditional-slot-8/Foo.svelte new file mode 100644 index 000000000000..043fbb4a54e8 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-8/Foo.svelte @@ -0,0 +1,11 @@ + + +
+ fallback {value * 2} + {#if $$slots.alert} +
+ + {/if} +
diff --git a/test/runtime/samples/component-conditional-slot-8/_config.js b/test/runtime/samples/component-conditional-slot-8/_config.js new file mode 100644 index 000000000000..19596f4b6558 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-8/_config.js @@ -0,0 +1,76 @@ +export default { + html: ` +
+ 2 ~ 3 +
+ 3 ~ 5 +
+ 5 ~ 8 +
+ #2 > 5 +
+
+
+ `, + test({ assert, component, target }) { + component.array = [3, 5, 8]; + assert.htmlEqual(target.innerHTML, ` +
+ 2 ~ 5 +
+ 5 ~ 10 +
+ 10 ~ 18 +
+ #2 > 5 +
+
+ #1 > 5 +
+
+ `); + + component.value = 8; + assert.htmlEqual(target.innerHTML, ` +
+ 8 ~ 11 +
+ 11 ~ 16 +
+ 16 ~ 24 +
+ #2 > 5 +
+
+ #1 > 5 +
+
+ #0 > 5 +
+ `); + + component.array = [1, 3]; + assert.htmlEqual(target.innerHTML, ` +
+ 8 ~ 9 +
+ 9 ~ 12 +
+ fallback 24 +
+
+ #1 > 5 +
+
+ #0 > 5 +
+ `); + + component.array = []; + assert.htmlEqual(target.innerHTML, ` +
+ fallback 16 +
+ `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-8/main.svelte b/test/runtime/samples/component-conditional-slot-8/main.svelte new file mode 100644 index 000000000000..55422b07f8e5 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-8/main.svelte @@ -0,0 +1,39 @@ + + + + {#if array[0]} + {@const sum = array[0] + value} + {#if sum > 5} + #0 > 5 + {/if} + + {value} ~ {sum} + + {#if array[1]} + {@const sum1 = array[1] + sum} + {#if sum1 > 5} + #1 > 5 + {/if} + + {value} ~ {sum1} + + {#if array[2]} + {@const sum2 = array[2] + sum1} + {#if sum2 > 5} + #2 > 5 + {/if} + + {value} ~ {sum2} + + {/if} + + + {/if} + + + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-9/Foo.svelte b/test/runtime/samples/component-conditional-slot-9/Foo.svelte new file mode 100644 index 000000000000..043fbb4a54e8 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-9/Foo.svelte @@ -0,0 +1,11 @@ + + +
+ fallback {value * 2} + {#if $$slots.alert} +
+ + {/if} +
diff --git a/test/runtime/samples/component-conditional-slot-9/_config.js b/test/runtime/samples/component-conditional-slot-9/_config.js new file mode 100644 index 000000000000..19596f4b6558 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-9/_config.js @@ -0,0 +1,76 @@ +export default { + html: ` +
+ 2 ~ 3 +
+ 3 ~ 5 +
+ 5 ~ 8 +
+ #2 > 5 +
+
+
+ `, + test({ assert, component, target }) { + component.array = [3, 5, 8]; + assert.htmlEqual(target.innerHTML, ` +
+ 2 ~ 5 +
+ 5 ~ 10 +
+ 10 ~ 18 +
+ #2 > 5 +
+
+ #1 > 5 +
+
+ `); + + component.value = 8; + assert.htmlEqual(target.innerHTML, ` +
+ 8 ~ 11 +
+ 11 ~ 16 +
+ 16 ~ 24 +
+ #2 > 5 +
+
+ #1 > 5 +
+
+ #0 > 5 +
+ `); + + component.array = [1, 3]; + assert.htmlEqual(target.innerHTML, ` +
+ 8 ~ 9 +
+ 9 ~ 12 +
+ fallback 24 +
+
+ #1 > 5 +
+
+ #0 > 5 +
+ `); + + component.array = []; + assert.htmlEqual(target.innerHTML, ` +
+ fallback 16 +
+ `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-9/main.svelte b/test/runtime/samples/component-conditional-slot-9/main.svelte new file mode 100644 index 000000000000..815a2af6a8b3 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-9/main.svelte @@ -0,0 +1,39 @@ + + + + {#if array[0]} + {@const sum = array[0] + value} + {#if sum > 5} + #0 > 5 + {/if} + + {value} ~ {sum} + + {#if array[1]} + {@const sum = array[0] + array[1] + value} + {#if sum > 5} + #1 > 5 + {/if} + + {value} ~ {sum} + + {#if array[2]} + {@const sum = array[1] + array[2] + value} + {#if sum > 5} + #2 > 5 + {/if} + + {value} ~ {sum} + + {/if} + + + {/if} + + + {/if} + diff --git a/test/runtime/samples/component-slot-duplicate-error-2/Nested.svelte b/test/runtime/samples/component-slot-duplicate-error-2/Nested.svelte deleted file mode 100644 index 32eee1534acb..000000000000 --- a/test/runtime/samples/component-slot-duplicate-error-2/Nested.svelte +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/runtime/samples/component-slot-duplicate-error-2/_config.js b/test/runtime/samples/component-slot-duplicate-error-2/_config.js deleted file mode 100644 index 1c9fa51dc3d3..000000000000 --- a/test/runtime/samples/component-slot-duplicate-error-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - error: 'Duplicate slot name "foo" in ' -}; diff --git a/test/runtime/samples/component-slot-duplicate-error-3/Nested.svelte b/test/runtime/samples/component-slot-duplicate-error-3/Nested.svelte deleted file mode 100644 index 32eee1534acb..000000000000 --- a/test/runtime/samples/component-slot-duplicate-error-3/Nested.svelte +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/runtime/samples/component-slot-duplicate-error-3/_config.js b/test/runtime/samples/component-slot-duplicate-error-3/_config.js deleted file mode 100644 index 1c9fa51dc3d3..000000000000 --- a/test/runtime/samples/component-slot-duplicate-error-3/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - error: 'Duplicate slot name "foo" in ' -}; diff --git a/test/runtime/samples/component-slot-duplicate-error-4/Nested.svelte b/test/runtime/samples/component-slot-duplicate-error-4/Nested.svelte deleted file mode 100644 index 0385342cef1b..000000000000 --- a/test/runtime/samples/component-slot-duplicate-error-4/Nested.svelte +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/runtime/samples/component-slot-duplicate-error-4/_config.js b/test/runtime/samples/component-slot-duplicate-error-4/_config.js deleted file mode 100644 index cfe8e7054e0e..000000000000 --- a/test/runtime/samples/component-slot-duplicate-error-4/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - error: 'Found elements without slot attribute when using slot="default"' -}; diff --git a/test/runtime/samples/component-slot-duplicate-error/Nested.svelte b/test/runtime/samples/component-slot-duplicate-error/Nested.svelte deleted file mode 100644 index 32eee1534acb..000000000000 --- a/test/runtime/samples/component-slot-duplicate-error/Nested.svelte +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/runtime/samples/component-slot-duplicate-error/_config.js b/test/runtime/samples/component-slot-duplicate-error/_config.js deleted file mode 100644 index 1c9fa51dc3d3..000000000000 --- a/test/runtime/samples/component-slot-duplicate-error/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - error: 'Duplicate slot name "foo" in ' -}; diff --git a/test/validator/samples/component-slot-duplicate-1/errors.json b/test/validator/samples/component-slot-duplicate-1/errors.json new file mode 100644 index 000000000000..acb43dca0ac2 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-1/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Duplicate slot name \"foo\" in ", + "start": { "line": 7, "column": 1 }, + "end": { "line": 7, "column": 26 } + } +] diff --git a/test/runtime/samples/component-slot-duplicate-error/main.svelte b/test/validator/samples/component-slot-duplicate-1/input.svelte similarity index 100% rename from test/runtime/samples/component-slot-duplicate-error/main.svelte rename to test/validator/samples/component-slot-duplicate-1/input.svelte diff --git a/test/validator/samples/component-slot-duplicate-10/errors.json b/test/validator/samples/component-slot-duplicate-10/errors.json new file mode 100644 index 000000000000..c8a2e3e8ba05 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-10/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Duplicate slot name \"a\" in ", + "start": { "line": 11, "column": 2 }, + "end": { "line": 13, "column": 20 } + } +] diff --git a/test/validator/samples/component-slot-duplicate-10/input.svelte b/test/validator/samples/component-slot-duplicate-10/input.svelte new file mode 100644 index 000000000000..3ab263666154 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-10/input.svelte @@ -0,0 +1,15 @@ + + + + {#if condition} + +
test
+
+ +
test
+
+ {/if} +
\ No newline at end of file diff --git a/test/validator/samples/component-slot-duplicate-2/errors.json b/test/validator/samples/component-slot-duplicate-2/errors.json new file mode 100644 index 000000000000..1315d8c008ed --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-2/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Duplicate slot name \"foo\" in ", + "start": { "line": 7, "column": 1 }, + "end": { "line": 7, "column": 54 } + } +] diff --git a/test/runtime/samples/component-slot-duplicate-error-2/main.svelte b/test/validator/samples/component-slot-duplicate-2/input.svelte similarity index 100% rename from test/runtime/samples/component-slot-duplicate-error-2/main.svelte rename to test/validator/samples/component-slot-duplicate-2/input.svelte diff --git a/test/validator/samples/component-slot-duplicate-3/errors.json b/test/validator/samples/component-slot-duplicate-3/errors.json new file mode 100644 index 000000000000..acb43dca0ac2 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-3/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Duplicate slot name \"foo\" in ", + "start": { "line": 7, "column": 1 }, + "end": { "line": 7, "column": 26 } + } +] diff --git a/test/runtime/samples/component-slot-duplicate-error-3/main.svelte b/test/validator/samples/component-slot-duplicate-3/input.svelte similarity index 100% rename from test/runtime/samples/component-slot-duplicate-error-3/main.svelte rename to test/validator/samples/component-slot-duplicate-3/input.svelte diff --git a/test/validator/samples/component-slot-duplicate-4/errors.json b/test/validator/samples/component-slot-duplicate-4/errors.json new file mode 100644 index 000000000000..dcd561a10c22 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-4/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Found elements without slot attribute when using slot=\"default\"", + "start": { "line": 6, "column": 1 }, + "end": { "line": 6, "column": 56 } + } +] diff --git a/test/runtime/samples/component-slot-duplicate-error-4/main.svelte b/test/validator/samples/component-slot-duplicate-4/input.svelte similarity index 100% rename from test/runtime/samples/component-slot-duplicate-error-4/main.svelte rename to test/validator/samples/component-slot-duplicate-4/input.svelte diff --git a/test/validator/samples/component-slot-duplicate-5/errors.json b/test/validator/samples/component-slot-duplicate-5/errors.json new file mode 100644 index 000000000000..af2a14083a1b --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-5/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Found elements without slot attribute when using slot=\"default\"", + "start": { "line": 11, "column": 2 }, + "end": { "line": 13, "column": 20 } + } +] diff --git a/test/validator/samples/component-slot-duplicate-5/input.svelte b/test/validator/samples/component-slot-duplicate-5/input.svelte new file mode 100644 index 000000000000..6547516fccc7 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-5/input.svelte @@ -0,0 +1,15 @@ + + + + {#if condition} + test + {/if} + {#if condition} + +
test
+
+ {/if} +
\ No newline at end of file diff --git a/test/validator/samples/component-slot-duplicate-6/errors.json b/test/validator/samples/component-slot-duplicate-6/errors.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-6/errors.json @@ -0,0 +1 @@ +[] diff --git a/test/validator/samples/component-slot-duplicate-6/input.svelte b/test/validator/samples/component-slot-duplicate-6/input.svelte new file mode 100644 index 000000000000..50926409e6ef --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-6/input.svelte @@ -0,0 +1,66 @@ + + + + {#if condition} + +
test
+
+ {:else} + +
test
+
+ {/if} + + {#if condition} + +
test
+
+ {:else if condition} + +
test
+
+ {/if} + + {#if condition} + +
test
+
+ {:else} + {#if condition} + +
test
+
+ {:else} + +
test
+
+ {/if} + {/if} + + {#if condition} + +
test
+
+ {:else} + {#if condition} + {#if condition} + {#if condition} + +
test
+
+ {/if} + {/if} + {:else} + {#if condition} + {#if condition} + +
test
+
+ {/if} + {/if} + {/if} + {/if} +
\ No newline at end of file diff --git a/test/validator/samples/component-slot-duplicate-7/errors.json b/test/validator/samples/component-slot-duplicate-7/errors.json new file mode 100644 index 000000000000..fd44fcb9e2d2 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-7/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Duplicate slot name \"a\" in ", + "start": { "line": 20, "column": 5 }, + "end": { "line": 22, "column": 23 } + } +] diff --git a/test/validator/samples/component-slot-duplicate-7/input.svelte b/test/validator/samples/component-slot-duplicate-7/input.svelte new file mode 100644 index 000000000000..cc620d1029af --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-7/input.svelte @@ -0,0 +1,31 @@ + + + + {#if condition} + +
test
+
+ {/if} + + {#if condition} + +
test
+
+ {#if condition} + {#if condition} + {#if condition} + +
test
+
+ {/if} + {/if} + {/if} + {:else if condition} + +
test
+
+ {/if} +
diff --git a/test/validator/samples/component-slot-duplicate-8/errors.json b/test/validator/samples/component-slot-duplicate-8/errors.json new file mode 100644 index 000000000000..d7cce3a085ab --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-8/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Duplicate slot name \"a\" in ", + "start": { "line": 24, "column": 5 }, + "end": { "line": 26, "column": 23 } + } +] diff --git a/test/validator/samples/component-slot-duplicate-8/input.svelte b/test/validator/samples/component-slot-duplicate-8/input.svelte new file mode 100644 index 000000000000..45f934cc7c20 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-8/input.svelte @@ -0,0 +1,31 @@ + + + + {#if condition} + +
test
+
+ {/if} + + {#if condition} + +
test
+
+ {:else if condition} + +
test
+
+ {#if condition} + {#if condition} + {#if condition} + +
test
+
+ {/if} + {/if} + {/if} + {/if} +
diff --git a/test/validator/samples/component-slot-duplicate-9/errors.json b/test/validator/samples/component-slot-duplicate-9/errors.json new file mode 100644 index 000000000000..ada38aeddd0d --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-9/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "duplicate-slot-name-in-component", + "message": "Duplicate slot name \"d\" in ", + "start": { "line": 13, "column": 1 }, + "end": { "line": 15, "column": 19 } + } +] diff --git a/test/validator/samples/component-slot-duplicate-9/input.svelte b/test/validator/samples/component-slot-duplicate-9/input.svelte new file mode 100644 index 000000000000..cd821a69a6d8 --- /dev/null +++ b/test/validator/samples/component-slot-duplicate-9/input.svelte @@ -0,0 +1,16 @@ + + + + {#if condition} + +
test
+
+ {/if} + + +
test
+
+
diff --git a/test/validator/samples/component-slotted-if-block/errors.json b/test/validator/samples/component-slotted-if-block/errors.json deleted file mode 100644 index b6e46fff009e..000000000000 --- a/test/validator/samples/component-slotted-if-block/errors.json +++ /dev/null @@ -1,12 +0,0 @@ -[{ - "code": "invalid-slotted-content", - "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", - "start": { - "line": 7, - "column": 7 - }, - "end": { - "line": 7, - "column": 17 - } -}] diff --git a/test/validator/samples/component-slotted-if-block/input.svelte b/test/validator/samples/component-slotted-if-block/input.svelte deleted file mode 100644 index d9159dfd60fd..000000000000 --- a/test/validator/samples/component-slotted-if-block/input.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - - {#if thing} -
{thing}
- {/if} -
\ No newline at end of file diff --git a/test/validator/samples/conditional-slot-mix-element-2/errors.json b/test/validator/samples/conditional-slot-mix-element-2/errors.json new file mode 100644 index 000000000000..792a7c5d783f --- /dev/null +++ b/test/validator/samples/conditional-slot-mix-element-2/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "invalid-mix-element-and-conditional-slot", + "message": "Do not mix and other elements under the same {#if}{:else} group. Default slot content should be wrapped with ", + "start": { "line": 10, "column": 2 }, + "end": { "line": 12, "column": 20 } + } +] diff --git a/test/validator/samples/conditional-slot-mix-element-2/input.svelte b/test/validator/samples/conditional-slot-mix-element-2/input.svelte new file mode 100644 index 000000000000..efb0c08b5b78 --- /dev/null +++ b/test/validator/samples/conditional-slot-mix-element-2/input.svelte @@ -0,0 +1,14 @@ + + + + {#if condition} + test + {:else} + +
test
+
+ {/if} +
\ No newline at end of file diff --git a/test/validator/samples/conditional-slot-mix-element-3/errors.json b/test/validator/samples/conditional-slot-mix-element-3/errors.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/test/validator/samples/conditional-slot-mix-element-3/errors.json @@ -0,0 +1 @@ +[] diff --git a/test/validator/samples/conditional-slot-mix-element-3/input.svelte b/test/validator/samples/conditional-slot-mix-element-3/input.svelte new file mode 100644 index 000000000000..dc5c0a9d67eb --- /dev/null +++ b/test/validator/samples/conditional-slot-mix-element-3/input.svelte @@ -0,0 +1,18 @@ + + + + {#if condition} + test + {/if} + {#if condition} + +
test
+
+ +
test
+
+ {/if} +
\ No newline at end of file diff --git a/test/validator/samples/conditional-slot-mix-element-4/errors.json b/test/validator/samples/conditional-slot-mix-element-4/errors.json new file mode 100644 index 000000000000..a260e0910ce8 --- /dev/null +++ b/test/validator/samples/conditional-slot-mix-element-4/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "invalid-mix-element-and-conditional-slot", + "message": "Do not mix and other elements under the same {#if}{:else} group. Default slot content should be wrapped with ", + "start": { "line": 9, "column": 6 }, + "end": { "line": 9, "column": 36 } + } +] diff --git a/test/validator/samples/conditional-slot-mix-element-4/input.svelte b/test/validator/samples/conditional-slot-mix-element-4/input.svelte new file mode 100644 index 000000000000..d1179da0fef0 --- /dev/null +++ b/test/validator/samples/conditional-slot-mix-element-4/input.svelte @@ -0,0 +1,12 @@ + + + +
+ {#if condition} + + {/if} +
+
\ No newline at end of file diff --git a/test/validator/samples/conditional-slot-mix-element/errors.json b/test/validator/samples/conditional-slot-mix-element/errors.json new file mode 100644 index 000000000000..a6df528d1669 --- /dev/null +++ b/test/validator/samples/conditional-slot-mix-element/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "invalid-mix-element-and-conditional-slot", + "message": "Do not mix and other elements under the same {#if}{:else} group. Default slot content should be wrapped with ", + "start": { "line": 11, "column": 2 }, + "end": { "line": 11, "column": 19 } + } +] diff --git a/test/validator/samples/conditional-slot-mix-element/input.svelte b/test/validator/samples/conditional-slot-mix-element/input.svelte new file mode 100644 index 000000000000..2e1ddf5bb49e --- /dev/null +++ b/test/validator/samples/conditional-slot-mix-element/input.svelte @@ -0,0 +1,13 @@ + + + + {#if condition} + +
test
+
+ test + {/if} +