diff --git a/src/compiler/compile/nodes/DefaultSlotTemplate.ts b/src/compiler/compile/nodes/DefaultSlotTemplate.ts new file mode 100644 index 000000000000..7f55ee3a607f --- /dev/null +++ b/src/compiler/compile/nodes/DefaultSlotTemplate.ts @@ -0,0 +1,28 @@ +import Component from '../Component'; +import TemplateScope from './shared/TemplateScope'; +import Node from './shared/Node'; +import Let from './Let'; +import { INode } from './interfaces'; + +export default class DefaultSlotTemplate extends Node { + type: 'SlotTemplate'; + scope: TemplateScope; + children: INode[]; + lets: Let[] = []; + slot_template_name = 'default'; + + constructor( + component: Component, + parent: INode, + scope: TemplateScope, + info: any, + lets: Let[], + children: INode[] + ) { + super(component, parent, scope, info); + this.type = 'SlotTemplate'; + this.children = children; + this.scope = scope; + this.lets = lets; + } +} diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index f734c2b625c9..92ae775b349e 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -429,7 +429,7 @@ export default class Element extends Node { component.slot_outlets.add(name); } - if (!(parent.type === 'InlineComponent' || within_custom_element(parent))) { + if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) { component.error(attribute, { code: 'invalid-slotted-content', message: 'Element with a slot=\'...\' attribute must be a child of a component or a descendant of a custom element' @@ -863,6 +863,10 @@ export default class Element extends Node { ); } } + + get slot_template_name() { + return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string; + } } function should_have_attribute( diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index ea63c39ec4c0..6ae19e57513b 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -45,12 +45,6 @@ export default class InlineComponent extends Node { }); case 'Attribute': - if (node.name === 'slot') { - component.error(node, { - code: 'invalid-prop', - message: "'slot' is reserved for future use in named slots" - }); - } // fallthrough case 'Spread': this.attributes.push(new Attribute(component, this, scope, node)); @@ -111,6 +105,57 @@ export default class InlineComponent extends Node { }); }); - this.children = map_children(component, this, this.scope, info.children); + 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); + } + } + + children.push(slot_template); + info.children.splice(i, 1); + } + } + + 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 + }); + } + + this.children = map_children(component, this, this.scope, children); + } + + 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' && /^\s+$/.test(node.data)); +} diff --git a/src/compiler/compile/nodes/SlotTemplate.ts b/src/compiler/compile/nodes/SlotTemplate.ts new file mode 100644 index 000000000000..1006c0289e79 --- /dev/null +++ b/src/compiler/compile/nodes/SlotTemplate.ts @@ -0,0 +1,82 @@ +import map_children from './shared/map_children'; +import Component from '../Component'; +import TemplateScope from './shared/TemplateScope'; +import Node from './shared/Node'; +import Let from './Let'; +import Attribute from './Attribute'; +import { INode } from './interfaces'; + +export default class SlotTemplate extends Node { + type: 'SlotTemplate'; + scope: TemplateScope; + children: INode[]; + lets: Let[] = []; + slot_attribute: Attribute; + slot_template_name: string = 'default'; + + constructor( + component: Component, + parent: INode, + scope: TemplateScope, + info: any + ) { + super(component, parent, scope, info); + + this.validate_slot_template_placement(); + + const has_let = info.attributes.some((node) => node.type === 'Let'); + if (has_let) { + scope = scope.child(); + } + + info.attributes.forEach((node) => { + switch (node.type) { + case 'Let': { + const l = new Let(component, this, scope, node); + this.lets.push(l); + const dependencies = new Set([l.name.name]); + + l.names.forEach((name) => { + scope.add(name, dependencies, this); + }); + break; + } + case 'Attribute': { + if (node.name === 'slot') { + this.slot_attribute = new Attribute(component, this, scope, node); + if (!this.slot_attribute.is_static) { + component.error(node, { + code: 'invalid-slot-attribute', + message: 'slot attribute cannot have a dynamic value' + }); + } + const value = this.slot_attribute.get_static_value(); + if (typeof value === 'boolean') { + component.error(node, { + code: 'invalid-slot-attribute', + message: 'slot attribute value is missing' + }); + } + this.slot_template_name = value as string; + break; + } + throw new Error(`Invalid attribute '${node.name}' in `); + } + default: + throw new Error(`Not implemented: ${node.type}`); + } + }); + + this.scope = scope; + this.children = map_children(component, this, this.scope, info.children); + } + + validate_slot_template_placement() { + if (this.parent.type !== 'InlineComponent') { + this.component.error(this, { + code: 'invalid-slotted-content', + message: ' must be a child of a component' + }); + } + } +} diff --git a/src/compiler/compile/nodes/Text.ts b/src/compiler/compile/nodes/Text.ts index a4f26e7e9d68..55a9368bb97a 100644 --- a/src/compiler/compile/nodes/Text.ts +++ b/src/compiler/compile/nodes/Text.ts @@ -29,7 +29,7 @@ export default class Text extends Node { should_skip() { if (/\S/.test(this.data)) return false; - const parent_element = this.find_nearest(/(?:Element|InlineComponent|Head)/); + const parent_element = this.find_nearest(/(?:Element|InlineComponent|SlotTemplate|Head)/); if (!parent_element) return false; if (parent_element.type === 'Head') return true; diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts index d71d191cf445..a98c21511fb7 100644 --- a/src/compiler/compile/nodes/interfaces.ts +++ b/src/compiler/compile/nodes/interfaces.ts @@ -25,6 +25,8 @@ import Options from './Options'; import PendingBlock from './PendingBlock'; import RawMustacheTag from './RawMustacheTag'; import Slot from './Slot'; +import SlotTemplate from './SlotTemplate'; +import DefaultSlotTemplate from './DefaultSlotTemplate'; import Text from './Text'; import ThenBlock from './ThenBlock'; import Title from './Title'; @@ -58,6 +60,8 @@ export type INode = Action | PendingBlock | RawMustacheTag | Slot +| SlotTemplate +| DefaultSlotTemplate | Tag | Text | ThenBlock diff --git a/src/compiler/compile/nodes/shared/TemplateScope.ts b/src/compiler/compile/nodes/shared/TemplateScope.ts index 4e087eedf5d5..df694ed45ec7 100644 --- a/src/compiler/compile/nodes/shared/TemplateScope.ts +++ b/src/compiler/compile/nodes/shared/TemplateScope.ts @@ -3,8 +3,9 @@ import ThenBlock from '../ThenBlock'; import CatchBlock from '../CatchBlock'; import InlineComponent from '../InlineComponent'; import Element from '../Element'; +import SlotTemplate from '../SlotTemplate'; -type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element; +type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element | SlotTemplate; export default class TemplateScope { names: Set; @@ -40,7 +41,7 @@ export default class TemplateScope { is_let(name: string) { const owner = this.get_owner(name); - return owner && (owner.type === 'Element' || owner.type === 'InlineComponent'); + return owner && (owner.type === 'Element' || owner.type === 'InlineComponent' || owner.type === 'SlotTemplate'); } is_await(name: string) { diff --git a/src/compiler/compile/nodes/shared/map_children.ts b/src/compiler/compile/nodes/shared/map_children.ts index 5d5da223fb02..b1d0816aacbe 100644 --- a/src/compiler/compile/nodes/shared/map_children.ts +++ b/src/compiler/compile/nodes/shared/map_children.ts @@ -12,6 +12,7 @@ import Options from '../Options'; import RawMustacheTag from '../RawMustacheTag'; import DebugTag from '../DebugTag'; import Slot from '../Slot'; +import SlotTemplate from '../SlotTemplate'; import Text from '../Text'; import Title from '../Title'; import Window from '../Window'; @@ -35,6 +36,7 @@ function get_constructor(type) { case 'RawMustacheTag': return RawMustacheTag; case 'DebugTag': return DebugTag; case 'Slot': return Slot; + case 'SlotTemplate': return SlotTemplate; case 'Text': return Text; case 'Title': return Title; case 'Window': return Window; diff --git a/src/compiler/compile/render_dom/wrappers/Element/create_slot_block.ts b/src/compiler/compile/render_dom/wrappers/Element/create_slot_block.ts deleted file mode 100644 index 7078d4eee76e..000000000000 --- a/src/compiler/compile/render_dom/wrappers/Element/create_slot_block.ts +++ /dev/null @@ -1,61 +0,0 @@ -import ElementWrapper from './index'; -import SlotWrapper from '../Slot'; -import Block from '../../Block'; -import { sanitize } from '../../../../utils/names'; -import InlineComponentWrapper from '../InlineComponent'; -import create_debugging_comment from '../shared/create_debugging_comment'; -import { get_slot_definition } from '../shared/get_slot_definition'; - -export default function create_slot_block(attribute, element: ElementWrapper | SlotWrapper, block: Block) { - const owner = find_slot_owner(element.parent); - - if (owner && owner.node.type === 'InlineComponent') { - const name = attribute.get_static_value() as string; - - if (!((owner as unknown) as InlineComponentWrapper).slots.has(name)) { - const child_block = block.child({ - comment: create_debugging_comment(element.node, element.renderer.component), - name: element.renderer.component.get_unique_name( - `create_${sanitize(name)}_slot` - ), - type: 'slot' - }); - - const { scope, lets } = element.node; - const seen = new Set(lets.map(l => l.name.name)); - - ((owner as unknown) as InlineComponentWrapper).node.lets.forEach(l => { - if (!seen.has(l.name.name)) lets.push(l); - }); - - ((owner as unknown) as InlineComponentWrapper).slots.set( - name, - get_slot_definition(child_block, scope, lets) - ); - element.renderer.blocks.push(child_block); - } - - element.slot_block = ((owner as unknown) as InlineComponentWrapper).slots.get( - name - ).block; - - return element.slot_block; - } - - return block; -} - -function find_slot_owner(owner) { - while (owner) { - if (owner.node.type === 'InlineComponent') { - break; - } - - if (owner.node.type === 'Element' && /-/.test(owner.node.name)) { - break; - } - - owner = owner.parent; - } - return owner; -} diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index c6b88357a896..183a3a077b00 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -25,7 +25,6 @@ import { extract_names } from 'periscopic'; import Action from '../../../nodes/Action'; import MustacheTagWrapper from '../MustacheTag'; import RawMustacheTagWrapper from '../RawMustacheTag'; -import create_slot_block from './create_slot_block'; interface BindingGroup { events: string[]; @@ -141,7 +140,6 @@ export default class ElementWrapper extends Wrapper { event_handlers: EventHandler[]; class_dependencies: string[]; - slot_block: Block; select_binding_dependencies?: Set; var: any; @@ -174,9 +172,6 @@ export default class ElementWrapper extends Wrapper { } this.attributes = this.node.attributes.map(attribute => { - if (attribute.name === 'slot') { - block = create_slot_block(attribute, this, block); - } if (attribute.name === 'style') { return new StyleAttributeWrapper(this, block, attribute); } @@ -231,15 +226,6 @@ export default class ElementWrapper extends Wrapper { } this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling); - - if (this.slot_block) { - block.parent.add_dependencies(block.dependencies); - - // appalling hack - const index = block.parent.wrappers.indexOf(this); - block.parent.wrappers.splice(index, 1); - block.wrappers.push(this); - } } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { @@ -247,10 +233,6 @@ export default class ElementWrapper extends Wrapper { if (this.node.name === 'noscript') return; - if (this.slot_block) { - block = this.slot_block; - } - const node = this.var; const nodes = parent_nodes && block.get_unique_name(`${this.var.name}_nodes`); // if we're in unclaimable territory, i.e. , parent_nodes is null const children = x`@children(${this.node.name === 'template' ? x`${node}.content` : node})`; diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index 6b7ff56e6440..98805b9639b4 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -11,6 +11,7 @@ import InlineComponent from './InlineComponent/index'; import MustacheTag from './MustacheTag'; import RawMustacheTag from './RawMustacheTag'; import Slot from './Slot'; +import SlotTemplate from './SlotTemplate'; import Text from './Text'; import Title from './Title'; import Window from './Window'; @@ -36,6 +37,7 @@ const wrappers = { Options: null, RawMustacheTag, Slot, + SlotTemplate, Text, Title, Window diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index be3d6d146d97..f962169ed741 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -4,12 +4,11 @@ import Renderer from '../../Renderer'; import Block from '../../Block'; import InlineComponent from '../../../nodes/InlineComponent'; import FragmentWrapper from '../Fragment'; +import SlotTemplateWrapper from '../SlotTemplate'; 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 create_debugging_comment from '../shared/create_debugging_comment'; -import { get_slot_definition } from '../shared/get_slot_definition'; import TemplateScope from '../../../nodes/shared/TemplateScope'; import is_dynamic from '../shared/is_dynamic'; import bind_this from '../shared/bind_this'; @@ -18,12 +17,16 @@ import EventHandler from '../Element/EventHandler'; import { extract_names } from 'periscopic'; import mark_each_block_bindings from '../shared/mark_each_block_bindings'; import { string_to_member_expression } from '../../../utils/string_to_member_expression'; +import SlotTemplate from '../../../nodes/SlotTemplate'; + +type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }; export default class InlineComponentWrapper extends Wrapper { var: Identifier; - slots: Map = new Map(); + slots: Map = new Map(); node: InlineComponent; fragment: FragmentWrapper; + children: Array = []; constructor( renderer: Renderer, @@ -76,32 +79,22 @@ export default class InlineComponentWrapper extends Wrapper { }); }); - const default_slot = block.child({ - comment: create_debugging_comment(node, renderer.component), - name: renderer.component.get_unique_name('create_default_slot'), - type: 'slot' - }); - - this.renderer.blocks.push(default_slot); - - this.slots.set('default', get_slot_definition(default_slot, this.node.scope, this.node.lets)); - this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, strip_whitespace, next_sibling); - - const dependencies: Set = new Set(); - - // TODO is this filtering necessary? (I *think* so) - default_slot.dependencies.forEach(name => { - if (!this.node.scope.is_let(name)) { - dependencies.add(name); - } - }); - - block.add_dependencies(dependencies); + this.children = this.node.children.map(child => new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, 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); @@ -135,14 +128,10 @@ export default class InlineComponentWrapper extends Wrapper { const statements: Array = []; const updates: Array = []; - if (this.fragment) { + this.children.forEach((child) => { this.renderer.add_to_context('$$scope', true); - const default_slot = this.slots.get('default'); - - this.fragment.nodes.forEach((child) => { - child.render(default_slot.block, null, x`#nodes` as unknown as Identifier); - }); - } + child.render(block, null, x`#nodes` as Identifier); + }); let props; const name_changes = block.get_unique_name(`${name.name}_changes`); @@ -194,7 +183,7 @@ export default class InlineComponentWrapper extends Wrapper { component_opts.properties.push(p`$$inline: true`); } - const fragment_dependencies = new Set(this.fragment ? ['$$scope'] : []); + 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); diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 0746de3237a1..69829dd13d46 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -12,7 +12,6 @@ import Expression from '../../nodes/shared/Expression'; import is_dynamic from './shared/is_dynamic'; import { Identifier, ObjectExpression } from 'estree'; import create_debugging_comment from './shared/create_debugging_comment'; -import create_slot_block from './Element/create_slot_block'; export default class SlotWrapper extends Wrapper { node: Slot; @@ -44,10 +43,6 @@ export default class SlotWrapper extends Wrapper { renderer.blocks.push(this.fallback); } - if (this.node.values.has('slot')) { - block = create_slot_block(this.node.values.get('slot'), this, block); - } - this.fragment = new FragmentWrapper( renderer, this.fallback, diff --git a/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts b/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts new file mode 100644 index 000000000000..daf41e10c329 --- /dev/null +++ b/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts @@ -0,0 +1,80 @@ +import Wrapper from './shared/Wrapper'; +import Renderer from '../Renderer'; +import Block from '../Block'; +import FragmentWrapper from './Fragment'; +import create_debugging_comment from './shared/create_debugging_comment'; +import { get_slot_definition } from './shared/get_slot_definition'; +import { x } from 'code-red'; +import { sanitize } from '../../../utils/names'; +import { Identifier } from 'estree'; +import InlineComponentWrapper from './InlineComponent'; +import { extract_names } from 'periscopic'; +import { INode } from '../../nodes/interfaces'; +import Let from '../../nodes/Let'; +import TemplateScope from '../../nodes/shared/TemplateScope'; + +type NodeWithLets = INode & { + scope: TemplateScope; + lets: Let[]; + slot_template_name: string; +}; + +export default class SlotTemplateWrapper extends Wrapper { + node: NodeWithLets; + fragment: FragmentWrapper; + block: Block; + parent: InlineComponentWrapper; + + constructor( + renderer: Renderer, + block: Block, + parent: Wrapper, + node: NodeWithLets, + strip_whitespace: boolean, + next_sibling: Wrapper + ) { + super(renderer, block, parent, node); + + const { scope, lets, slot_template_name } = this.node; + + lets.forEach(l => { + extract_names(l.value || l.name).forEach(name => { + renderer.add_to_context(name, true); + }); + }); + + this.block = block.child({ + comment: create_debugging_comment(this.node, this.renderer.component), + name: this.renderer.component.get_unique_name( + `create_${sanitize(slot_template_name)}_slot` + ), + type: 'slot' + }); + this.renderer.blocks.push(this.block); + + const seen = new Set(lets.map(l => l.name.name)); + this.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) + ); + + this.fragment = new FragmentWrapper( + renderer, + this.block, + node.type === 'SlotTemplate' ? node.children : [node], + this, + strip_whitespace, + next_sibling + ); + + this.block.parent.add_dependencies(this.block.dependencies); + } + + render() { + this.fragment.render(this.block, null, x`#nodes` as Identifier); + } +} diff --git a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts index 6e295f99c125..8e9f7fab37c4 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts @@ -15,7 +15,7 @@ export default function create_debugging_comment( let d; - if (node.type === 'InlineComponent' || node.type === 'Element') { + if (node.type === 'InlineComponent' || node.type === 'Element' || node.type === 'SlotTemplate') { if (node.children.length) { d = node.children[0].start; while (source[d - 1] !== '>') d -= 1; diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index c633ff8b0ab5..64e9ee1f4e81 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -9,6 +9,7 @@ import IfBlock from './handlers/IfBlock'; import InlineComponent from './handlers/InlineComponent'; import KeyBlock from './handlers/KeyBlock'; import Slot from './handlers/Slot'; +import SlotTemplate from './handlers/SlotTemplate'; import Tag from './handlers/Tag'; import Text from './handlers/Text'; import Title from './handlers/Title'; @@ -36,6 +37,7 @@ const handlers: Record = { Options: noop, RawMustacheTag: HtmlTag, Slot, + SlotTemplate, Text, Title, Window: noop diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 163e2bbea9aa..d10c16519876 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -1,6 +1,5 @@ import { is_void } from '../../../utils/names'; import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value'; -import { get_slot_scope } from './shared/get_slot_scope'; import { boolean_attributes } from './shared/boolean_attributes'; import Renderer, { RenderOptions } from '../Renderer'; import Element from '../../nodes/Element'; @@ -8,9 +7,7 @@ import { x } from 'code-red'; import Expression from '../../nodes/shared/Expression'; import remove_whitespace_children from './utils/remove_whitespace_children'; -export default function(node: Element, renderer: Renderer, options: RenderOptions & { - slot_scopes: Map; -}) { +export default function(node: Element, renderer: Renderer, options: RenderOptions) { const children = remove_whitespace_children(node.children, node.next); @@ -23,13 +20,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - const slot = node.get_static_attribute_value('slot'); - const nearest_inline_component = node.find_nearest(/InlineComponent/); - - if (slot && nearest_inline_component) { - renderer.push(); - } - renderer.add_string(`<${node.name}`); const class_expression_list = node.classes.map(class_directive => { @@ -148,24 +138,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption if (!is_void(node.name)) { renderer.add_string(``); } - } else if (slot && nearest_inline_component) { - renderer.render(children, options); - - if (!is_void(node.name)) { - renderer.add_string(``); - } - - const lets = node.lets; - const seen = new Set(lets.map(l => l.name.name)); - - 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() - }); } else { renderer.render(children, options); diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index e5afbf59cccb..c4116e659105 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -1,8 +1,6 @@ import { string_literal } from '../../utils/stringify'; import Renderer, { RenderOptions } from '../Renderer'; -import { get_slot_scope } from './shared/get_slot_scope'; import InlineComponent from '../../nodes/InlineComponent'; -import remove_whitespace_children from './utils/remove_whitespace_children'; import { p, x } from 'code-red'; function get_prop_value(attribute) { @@ -68,28 +66,19 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend const slot_fns = []; - const children = remove_whitespace_children(node.children, node.next); + const children = node.children; if (children.length) { const slot_scopes = new Map(); - renderer.push(); - renderer.render(children, Object.assign({}, options, { slot_scopes })); - slot_scopes.set('default', { - input: get_slot_scope(node.lets), - output: renderer.pop() - }); - slot_scopes.forEach(({ input, output }, name) => { - if (!is_empty_template_literal(output)) { - slot_fns.push( - p`${name}: (${input}) => ${output}` - ); - } + slot_fns.push( + p`${name}: (${input}) => ${output}` + ); }); } @@ -99,11 +88,3 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`); } - -function is_empty_template_literal(template_literal) { - return ( - template_literal.expressions.length === 0 && - template_literal.quasis.length === 1 && - template_literal.quasis[0].value.raw === '' - ); -} diff --git a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts new file mode 100644 index 000000000000..568942c3f562 --- /dev/null +++ b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts @@ -0,0 +1,45 @@ +import Renderer, { RenderOptions } from '../Renderer'; +import SlotTemplate from '../../nodes/SlotTemplate'; +import remove_whitespace_children from './utils/remove_whitespace_children'; +import { get_slot_scope } from './shared/get_slot_scope'; +import InlineComponent from '../../nodes/InlineComponent'; +import Element from '../../nodes/Element'; + +export default function(node: SlotTemplate | Element | InlineComponent, renderer: Renderer, options: RenderOptions & { + slot_scopes: Map; +}) { + const parent_inline_component = node.parent as InlineComponent; + const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next); + + renderer.push(); + renderer.render(children, options); + + const lets = node.lets; + const seen = new Set(lets.map(l => l.name.name)); + parent_inline_component.lets.forEach(l => { + if (!seen.has(l.name.name)) lets.push(l); + }); + + 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 + }); + } +} + +function is_empty_template_literal(template_literal) { + return ( + template_literal.expressions.length === 0 && + template_literal.quasis.length === 1 && + template_literal.quasis[0].value.raw === '' + ); +} diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index efeb9433bc8e..2c02bc21636b 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -18,7 +18,7 @@ const meta_tags = new Map([ ['svelte:body', 'Body'] ]); -const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component'); +const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment'); const specials = new Map([ [ @@ -39,6 +39,7 @@ const specials = new Map([ const SELF = /^svelte:self(?=[\s/>])/; const COMPONENT = /^svelte:component(?=[\s/>])/; +const SLOT = /^svelte:fragment(?=[\s/>])/; function parent_is_head(stack) { let i = stack.length; @@ -107,8 +108,9 @@ export default function tag(parser: Parser) { const type = meta_tags.has(name) ? meta_tags.get(name) : (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent' - : name === 'title' && parent_is_head(parser.stack) ? 'Title' - : name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; + : name === 'svelte:fragment' ? 'SlotTemplate' + : name === 'title' && parent_is_head(parser.stack) ? 'Title' + : name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; const element: TemplateNode = { start, @@ -265,6 +267,8 @@ function read_tag_name(parser: Parser) { if (parser.read(COMPONENT)) return 'svelte:component'; + if (parser.read(SLOT)) return 'svelte:fragment'; + const name = parser.read_until(/(\s|\/|>)/); if (meta_tags.has(name)) return name; diff --git a/test/parser/samples/error-svelte-selfdestructive/error.json b/test/parser/samples/error-svelte-selfdestructive/error.json index 2443bb982207..52172c9b332c 100644 --- a/test/parser/samples/error-svelte-selfdestructive/error.json +++ b/test/parser/samples/error-svelte-selfdestructive/error.json @@ -1,6 +1,6 @@ { "code": "invalid-tag-name", - "message": "Valid tag names are svelte:head, svelte:options, svelte:window, svelte:body, svelte:self or svelte:component", + "message": "Valid tag names are svelte:head, svelte:options, svelte:window, svelte:body, svelte:self, svelte:component or svelte:fragment", "pos": 10, "start": { "character": 10, diff --git a/test/runtime/samples/component-slot-attribute-order/Component.svelte b/test/runtime/samples/component-slot-attribute-order/Component.svelte new file mode 100644 index 000000000000..55c0858fb9ef --- /dev/null +++ b/test/runtime/samples/component-slot-attribute-order/Component.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-attribute-order/_config.js b/test/runtime/samples/component-slot-attribute-order/_config.js new file mode 100644 index 000000000000..54dc21c446d1 --- /dev/null +++ b/test/runtime/samples/component-slot-attribute-order/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` + + + + `, + async test({ assert, component, target, window }) { + const [btn, btn1, btn2] = target.querySelectorAll('button'); + + await btn.dispatchEvent(new window.MouseEvent('click')); + + assert.equal(btn1.disabled, true); + assert.equal(btn2.disabled, true); + } +}; diff --git a/test/runtime/samples/component-slot-attribute-order/main.svelte b/test/runtime/samples/component-slot-attribute-order/main.svelte new file mode 100644 index 000000000000..85f99d75793d --- /dev/null +++ b/test/runtime/samples/component-slot-attribute-order/main.svelte @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named-b/Hello.svelte b/test/runtime/samples/component-slot-component-named-b/Hello.svelte new file mode 100644 index 000000000000..ba726f62c986 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-b/Hello.svelte @@ -0,0 +1,5 @@ + + +Hello {name} \ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named-b/Nested.svelte b/test/runtime/samples/component-slot-component-named-b/Nested.svelte new file mode 100644 index 000000000000..a94392ce5d82 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-b/Nested.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named-b/_config.js b/test/runtime/samples/component-slot-component-named-b/_config.js new file mode 100644 index 000000000000..2b4af715c253 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-b/_config.js @@ -0,0 +1,7 @@ +export default { + preserveIdentifiers: true, + + html: ` + Hello world + ` +}; diff --git a/test/runtime/samples/component-slot-component-named-b/main.svelte b/test/runtime/samples/component-slot-component-named-b/main.svelte new file mode 100644 index 000000000000..494a50a86ce7 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-b/main.svelte @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named-c/Hello.svelte b/test/runtime/samples/component-slot-component-named-c/Hello.svelte new file mode 100644 index 000000000000..28a2bb436a09 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-c/Hello.svelte @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named-c/Nested.svelte b/test/runtime/samples/component-slot-component-named-c/Nested.svelte new file mode 100644 index 000000000000..a94392ce5d82 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-c/Nested.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named-c/World.svelte b/test/runtime/samples/component-slot-component-named-c/World.svelte new file mode 100644 index 000000000000..680e6dff5b12 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-c/World.svelte @@ -0,0 +1 @@ +world \ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named-c/_config.js b/test/runtime/samples/component-slot-component-named-c/_config.js new file mode 100644 index 000000000000..9dfed6ff531e --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-c/_config.js @@ -0,0 +1,6 @@ +export default { + html: ` + Hello + world + ` +}; diff --git a/test/runtime/samples/component-slot-component-named-c/main.svelte b/test/runtime/samples/component-slot-component-named-c/main.svelte new file mode 100644 index 000000000000..4bf657d9cb80 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named-c/main.svelte @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named/Bar.svelte b/test/runtime/samples/component-slot-component-named/Bar.svelte new file mode 100644 index 000000000000..8124337d72c2 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named/Bar.svelte @@ -0,0 +1 @@ +

bar

\ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named/Foo.svelte b/test/runtime/samples/component-slot-component-named/Foo.svelte new file mode 100644 index 000000000000..998ea4094d49 --- /dev/null +++ b/test/runtime/samples/component-slot-component-named/Foo.svelte @@ -0,0 +1 @@ +

foo

\ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named/Nested.svelte b/test/runtime/samples/component-slot-component-named/Nested.svelte new file mode 100644 index 000000000000..665555820a3d --- /dev/null +++ b/test/runtime/samples/component-slot-component-named/Nested.svelte @@ -0,0 +1,5 @@ +
+ + + +
\ No newline at end of file diff --git a/test/runtime/samples/component-slot-component-named/_config.js b/test/runtime/samples/component-slot-component-named/_config.js new file mode 100644 index 000000000000..f7b74a7b4f4c --- /dev/null +++ b/test/runtime/samples/component-slot-component-named/_config.js @@ -0,0 +1,9 @@ +export default { + html: ` +
+ Hello +

bar

+

foo

+
+ ` +}; diff --git a/test/runtime/samples/component-slot-component-named/main.svelte b/test/runtime/samples/component-slot-component-named/main.svelte new file mode 100644 index 000000000000..c16f005d5d0f --- /dev/null +++ b/test/runtime/samples/component-slot-component-named/main.svelte @@ -0,0 +1,12 @@ + + + + Hello + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-duplicate-error-2/Nested.svelte b/test/runtime/samples/component-slot-duplicate-error-2/Nested.svelte new file mode 100644 index 000000000000..32eee1534acb --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-2/Nested.svelte @@ -0,0 +1 @@ + \ 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 new file mode 100644 index 000000000000..1c9fa51dc3d3 --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-2/_config.js @@ -0,0 +1,3 @@ +export default { + error: 'Duplicate slot name "foo" in ' +}; diff --git a/test/runtime/samples/component-slot-duplicate-error-2/main.svelte b/test/runtime/samples/component-slot-duplicate-error-2/main.svelte new file mode 100644 index 000000000000..fefa1dc861cf --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-2/main.svelte @@ -0,0 +1,8 @@ + + + + {value} + {value} + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-duplicate-error-3/Nested.svelte b/test/runtime/samples/component-slot-duplicate-error-3/Nested.svelte new file mode 100644 index 000000000000..32eee1534acb --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-3/Nested.svelte @@ -0,0 +1 @@ + \ 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 new file mode 100644 index 000000000000..1c9fa51dc3d3 --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-3/_config.js @@ -0,0 +1,3 @@ +export default { + error: 'Duplicate slot name "foo" in ' +}; diff --git a/test/runtime/samples/component-slot-duplicate-error-3/main.svelte b/test/runtime/samples/component-slot-duplicate-error-3/main.svelte new file mode 100644 index 000000000000..596c1313d600 --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-3/main.svelte @@ -0,0 +1,8 @@ + + + + {value} +

{value}

+
\ No newline at end of file diff --git a/test/runtime/samples/component-slot-duplicate-error-4/Nested.svelte b/test/runtime/samples/component-slot-duplicate-error-4/Nested.svelte new file mode 100644 index 000000000000..0385342cef1b --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-4/Nested.svelte @@ -0,0 +1 @@ + \ 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 new file mode 100644 index 000000000000..cfe8e7054e0e --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-4/_config.js @@ -0,0 +1,3 @@ +export default { + error: 'Found elements without slot attribute when using slot="default"' +}; diff --git a/test/runtime/samples/component-slot-duplicate-error-4/main.svelte b/test/runtime/samples/component-slot-duplicate-error-4/main.svelte new file mode 100644 index 000000000000..ee1dcfad7bfb --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error-4/main.svelte @@ -0,0 +1,8 @@ + + + + value +

value

+
\ No newline at end of file diff --git a/test/runtime/samples/component-slot-duplicate-error/Nested.svelte b/test/runtime/samples/component-slot-duplicate-error/Nested.svelte new file mode 100644 index 000000000000..32eee1534acb --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error/Nested.svelte @@ -0,0 +1 @@ + \ 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 new file mode 100644 index 000000000000..1c9fa51dc3d3 --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error/_config.js @@ -0,0 +1,3 @@ +export default { + error: 'Duplicate slot name "foo" in ' +}; diff --git a/test/runtime/samples/component-slot-duplicate-error/main.svelte b/test/runtime/samples/component-slot-duplicate-error/main.svelte new file mode 100644 index 000000000000..9024bcd46f65 --- /dev/null +++ b/test/runtime/samples/component-slot-duplicate-error/main.svelte @@ -0,0 +1,8 @@ + + + +

{value}

+

{value}

+
\ No newline at end of file diff --git a/test/runtime/samples/component-slot-warning/_config.js b/test/runtime/samples/component-slot-warning/_config.js index 6ffe624782e0..c1785f27640e 100644 --- a/test/runtime/samples/component-slot-warning/_config.js +++ b/test/runtime/samples/component-slot-warning/_config.js @@ -3,7 +3,6 @@ export default { dev: true }, warnings: [ - ' received an unexpected slot "default".', ' received an unexpected slot "slot1".' ] }; diff --git a/test/runtime/samples/component-svelte-slot-2/Nested.svelte b/test/runtime/samples/component-svelte-slot-2/Nested.svelte new file mode 100644 index 000000000000..a94392ce5d82 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-2/Nested.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-2/_config.js b/test/runtime/samples/component-svelte-slot-2/_config.js new file mode 100644 index 000000000000..9dfed6ff531e --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-2/_config.js @@ -0,0 +1,6 @@ +export default { + html: ` + Hello + world + ` +}; diff --git a/test/runtime/samples/component-svelte-slot-2/main.svelte b/test/runtime/samples/component-svelte-slot-2/main.svelte new file mode 100644 index 000000000000..8212f54cf849 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-2/main.svelte @@ -0,0 +1,15 @@ + + + + + Hello + + + + + + world + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-aliased/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-aliased/Nested.svelte new file mode 100644 index 000000000000..6eaf6fe3adcb --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-aliased/Nested.svelte @@ -0,0 +1,9 @@ + + +
+ {#each things as thing} + + {/each} +
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-aliased/_config.js b/test/runtime/samples/component-svelte-slot-let-aliased/_config.js new file mode 100644 index 000000000000..d66f613bb4b0 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-aliased/_config.js @@ -0,0 +1,24 @@ +export default { + props: { + things: [1, 2, 3] + }, + + html: ` +
+ 1 + 2 + 3 +
`, + + test({ assert, component, target }) { + component.things = [1, 2, 3, 4]; + assert.htmlEqual(target.innerHTML, ` +
+ 1 + 2 + 3 + 4 +
+ `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-aliased/main.svelte b/test/runtime/samples/component-svelte-slot-let-aliased/main.svelte new file mode 100644 index 000000000000..dabf1d65ef0c --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-aliased/main.svelte @@ -0,0 +1,11 @@ + + + + + {x} + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-b/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-b/Nested.svelte new file mode 100644 index 000000000000..36c10ecbc215 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-b/Nested.svelte @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-b/_config.js b/test/runtime/samples/component-svelte-slot-let-b/_config.js new file mode 100644 index 000000000000..f13a3b2bb662 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-b/_config.js @@ -0,0 +1,18 @@ +export default { + html: ` + + 0 + `, + + async test({ assert, target, window }) { + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); + + await button.dispatchEvent(click); + + assert.htmlEqual(target.innerHTML, ` + + 1 + `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-b/main.svelte b/test/runtime/samples/component-svelte-slot-let-b/main.svelte new file mode 100644 index 000000000000..584240d14d27 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-b/main.svelte @@ -0,0 +1,9 @@ + + + + + {count} + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-c/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-c/Nested.svelte new file mode 100644 index 000000000000..122044059048 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-c/Nested.svelte @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-c/_config.js b/test/runtime/samples/component-svelte-slot-let-c/_config.js new file mode 100644 index 000000000000..fe8e68b1ac1e --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-c/_config.js @@ -0,0 +1,18 @@ +export default { + html: ` + + 0 (undefined) + `, + + async test({ assert, target, window }) { + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); + + await button.dispatchEvent(click); + + assert.htmlEqual(target.innerHTML, ` + + 1 (undefined) + `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-c/main.svelte b/test/runtime/samples/component-svelte-slot-let-c/main.svelte new file mode 100644 index 000000000000..9753c45075d5 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-c/main.svelte @@ -0,0 +1,9 @@ + + + + + {c} ({count}) + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-d/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-d/Nested.svelte new file mode 100644 index 000000000000..9f3a345acdd5 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-d/Nested.svelte @@ -0,0 +1,7 @@ + + +
+ +
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-d/_config.js b/test/runtime/samples/component-svelte-slot-let-d/_config.js new file mode 100644 index 000000000000..e293ae136c72 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-d/_config.js @@ -0,0 +1,20 @@ +export default { + html: ` +
+

a

+
+ `, + + async test({ assert, target, window }) { + const div = target.querySelector('div'); + const click = new window.MouseEvent('click'); + + await div.dispatchEvent(click); + + assert.htmlEqual(target.innerHTML, ` +
+

b

+
+ `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-d/main.svelte b/test/runtime/samples/component-svelte-slot-let-d/main.svelte new file mode 100644 index 000000000000..7ba94c7b8ad7 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-d/main.svelte @@ -0,0 +1,9 @@ + + + + +

{bar}

+
+
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-destructured-2/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-destructured-2/Nested.svelte new file mode 100644 index 000000000000..f46046c78e6f --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-destructured-2/Nested.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/component-svelte-slot-let-destructured-2/_config.js b/test/runtime/samples/component-svelte-slot-let-destructured-2/_config.js new file mode 100644 index 000000000000..38b04b7b5e06 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-destructured-2/_config.js @@ -0,0 +1,68 @@ +export default { + html: ` +
+ hello world 0 hello + +
+
+ hello world 0 hello + +
+
+ hello world 0 hello + +
+ `, + async test({ assert, component, target, window }) { + const [button1, button2, button3] = target.querySelectorAll('button'); + const event = new window.MouseEvent('click'); + + await button1.dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ` +
+ hello world 1 hello + +
+
+ hello world 0 hello + +
+
+ hello world 0 hello + +
+ `); + + await button2.dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ` +
+ hello world 1 hello + +
+
+ hello world 1 hello + +
+
+ hello world 0 hello + +
+ `); + + await button3.dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ` +
+ hello world 1 hello + +
+
+ hello world 1 hello + +
+
+ hello world 1 hello + +
+ `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-destructured-2/main.svelte b/test/runtime/samples/component-svelte-slot-let-destructured-2/main.svelte new file mode 100644 index 000000000000..d4d399188169 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-destructured-2/main.svelte @@ -0,0 +1,34 @@ + + +
+ + + {pair[0]} {pair[1]} {c} {foo} + + + + +
+ +
+ + + {a} {b} {d} {foo} + + + + +
+ +
+ + + {a} {b} {e} {foo} + + + + +
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-destructured/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-destructured/Nested.svelte new file mode 100644 index 000000000000..99afb32aa068 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-destructured/Nested.svelte @@ -0,0 +1,9 @@ + + +
+ {#each things as thing} + + {/each} +
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-destructured/_config.js b/test/runtime/samples/component-svelte-slot-let-destructured/_config.js new file mode 100644 index 000000000000..d7aaae2d7735 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-destructured/_config.js @@ -0,0 +1,34 @@ +export default { + props: { + things: [ + { num: 1 }, + { num: 2 }, + { num: 3 } + ] + }, + + html: ` +
+ 1 + 2 + 3 +
`, + + test({ assert, component, target }) { + component.things = [ + { num: 1 }, + { num: 2 }, + { num: 3 }, + { num: 4 } + ]; + + assert.htmlEqual(target.innerHTML, ` +
+ 1 + 2 + 3 + 4 +
+ `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-destructured/main.svelte b/test/runtime/samples/component-svelte-slot-let-destructured/main.svelte new file mode 100644 index 000000000000..923e96311c0e --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-destructured/main.svelte @@ -0,0 +1,11 @@ + + + + + {num} + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-e/A.svelte b/test/runtime/samples/component-svelte-slot-let-e/A.svelte new file mode 100644 index 000000000000..3ba0e62cd961 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-e/A.svelte @@ -0,0 +1,11 @@ + + + + + {reflected} + + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-e/B.svelte b/test/runtime/samples/component-svelte-slot-let-e/B.svelte new file mode 100644 index 000000000000..352f10a24947 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-e/B.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-e/_config.js b/test/runtime/samples/component-svelte-slot-let-e/_config.js new file mode 100644 index 000000000000..967955dfd856 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-e/_config.js @@ -0,0 +1,19 @@ +export default { + html: ` + 1 + 1 + 1 + 1 + `, + + async test({ assert, target, component }) { + component.x = 2; + + assert.htmlEqual(target.innerHTML, ` + 2 + 2 + 2 + 2 + `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-e/main.svelte b/test/runtime/samples/component-svelte-slot-let-e/main.svelte new file mode 100644 index 000000000000..80c9a868cd44 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-e/main.svelte @@ -0,0 +1,16 @@ + + + + + {reflected} + + + + + + {reflected} + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-f/A.svelte b/test/runtime/samples/component-svelte-slot-let-f/A.svelte new file mode 100644 index 000000000000..4f4ac95014bb --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-f/A.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-f/_config.js b/test/runtime/samples/component-svelte-slot-let-f/_config.js new file mode 100644 index 000000000000..73b90885b545 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-f/_config.js @@ -0,0 +1,22 @@ +export default { + html: ` + 1 + 0 + `, + async test({ assert, target, component, window }) { + component.x = 2; + + assert.htmlEqual(target.innerHTML, ` + 2 + 0 + `); + + const span = target.querySelector('span'); + await span.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` + 2 + 2 + `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-f/main.svelte b/test/runtime/samples/component-svelte-slot-let-f/main.svelte new file mode 100644 index 000000000000..a47f1ba6de3b --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-f/main.svelte @@ -0,0 +1,17 @@ + + + + + y = reflected} + class={reflected} + > + {reflected} + + + +{ y } \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-in-binding/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-in-binding/Nested.svelte new file mode 100644 index 000000000000..95b4842ee6ea --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-in-binding/Nested.svelte @@ -0,0 +1,9 @@ + + +
+ {#each items as item, index} + + {/each} +
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-in-binding/_config.js b/test/runtime/samples/component-svelte-slot-let-in-binding/_config.js new file mode 100644 index 000000000000..41e77a9574eb --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-in-binding/_config.js @@ -0,0 +1,26 @@ +export default { + html: ` +
+ + + +
+ `, + + ssrHtml: ` +
+ + + +
+ `, + + async test({ assert, component, target, window }) { + const inputs = target.querySelectorAll('input'); + + inputs[2].value = 'd'; + await inputs[2].dispatchEvent(new window.Event('input')); + + assert.deepEqual(component.letters, ['a', 'b', 'd']); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-in-binding/main.svelte b/test/runtime/samples/component-svelte-slot-let-in-binding/main.svelte new file mode 100644 index 000000000000..c6edd8200359 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-in-binding/main.svelte @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-in-slot/Inner.svelte b/test/runtime/samples/component-svelte-slot-let-in-slot/Inner.svelte new file mode 100644 index 000000000000..50baf3aa93cd --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-in-slot/Inner.svelte @@ -0,0 +1 @@ + diff --git a/test/runtime/samples/component-svelte-slot-let-in-slot/Outer.svelte b/test/runtime/samples/component-svelte-slot-let-in-slot/Outer.svelte new file mode 100644 index 000000000000..d6c86307a5a4 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-in-slot/Outer.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/component-svelte-slot-let-in-slot/_config.js b/test/runtime/samples/component-svelte-slot-let-in-slot/_config.js new file mode 100644 index 000000000000..eb16c27fda78 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-in-slot/_config.js @@ -0,0 +1,12 @@ +export default { + props: { + prop: 'a' + }, + + html: 'a', + + test({ assert, component, target }) { + component.prop = 'b'; + assert.htmlEqual( target.innerHTML, 'b' ); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-in-slot/main.svelte b/test/runtime/samples/component-svelte-slot-let-in-slot/main.svelte new file mode 100644 index 000000000000..2de0cb11ef44 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-in-slot/main.svelte @@ -0,0 +1,16 @@ + + + + + + + {value} + + + + diff --git a/test/runtime/samples/component-svelte-slot-let-named/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-named/Nested.svelte new file mode 100644 index 000000000000..a3b09ff5b213 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-named/Nested.svelte @@ -0,0 +1,9 @@ + + +
+ {#each things as thing} + + {/each} +
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-named/_config.js b/test/runtime/samples/component-svelte-slot-let-named/_config.js new file mode 100644 index 000000000000..f65448af93be --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-named/_config.js @@ -0,0 +1,24 @@ +export default { + props: { + things: [1, 2, 3] + }, + + html: ` +
+
1
+
2
+
3
+
`, + + test({ assert, component, target }) { + component.things = [1, 2, 3, 4]; + assert.htmlEqual(target.innerHTML, ` +
+
1
+
2
+
3
+
4
+
+ `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let-named/main.svelte b/test/runtime/samples/component-svelte-slot-let-named/main.svelte new file mode 100644 index 000000000000..f9e9e3a10fc6 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-named/main.svelte @@ -0,0 +1,11 @@ + + + +
+ {thing} +
+
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-static/Nested.svelte b/test/runtime/samples/component-svelte-slot-let-static/Nested.svelte new file mode 100644 index 000000000000..32eee1534acb --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-static/Nested.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let-static/_config.js b/test/runtime/samples/component-svelte-slot-let-static/_config.js new file mode 100644 index 000000000000..ede7a679c5dd --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-static/_config.js @@ -0,0 +1,3 @@ +export default { + html: '

Hi

' +}; diff --git a/test/runtime/samples/component-svelte-slot-let-static/main.svelte b/test/runtime/samples/component-svelte-slot-let-static/main.svelte new file mode 100644 index 000000000000..3eeaba976e1b --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let-static/main.svelte @@ -0,0 +1,7 @@ + + + +

{value}

+
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let/Nested.svelte b/test/runtime/samples/component-svelte-slot-let/Nested.svelte new file mode 100644 index 000000000000..3758adbd7ce4 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let/Nested.svelte @@ -0,0 +1,9 @@ + + +
+ {#each things as thing} + + {/each} +
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-let/_config.js b/test/runtime/samples/component-svelte-slot-let/_config.js new file mode 100644 index 000000000000..d66f613bb4b0 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let/_config.js @@ -0,0 +1,24 @@ +export default { + props: { + things: [1, 2, 3] + }, + + html: ` +
+ 1 + 2 + 3 +
`, + + test({ assert, component, target }) { + component.things = [1, 2, 3, 4]; + assert.htmlEqual(target.innerHTML, ` +
+ 1 + 2 + 3 + 4 +
+ `); + } +}; diff --git a/test/runtime/samples/component-svelte-slot-let/main.svelte b/test/runtime/samples/component-svelte-slot-let/main.svelte new file mode 100644 index 000000000000..d00bb8e41eed --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-let/main.svelte @@ -0,0 +1,9 @@ + + + + {thing} + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-nested/Child.svelte b/test/runtime/samples/component-svelte-slot-nested/Child.svelte new file mode 100644 index 000000000000..3f87a7dbf252 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-nested/Child.svelte @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-nested/Nested.svelte b/test/runtime/samples/component-svelte-slot-nested/Nested.svelte new file mode 100644 index 000000000000..a94392ce5d82 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-nested/Nested.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot-nested/_config.js b/test/runtime/samples/component-svelte-slot-nested/_config.js new file mode 100644 index 000000000000..eecd155e1f48 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-nested/_config.js @@ -0,0 +1,6 @@ +export default { + html: ` + Default +

B slot

+ ` +}; diff --git a/test/runtime/samples/component-svelte-slot-nested/main.svelte b/test/runtime/samples/component-svelte-slot-nested/main.svelte new file mode 100644 index 000000000000..e5b3e2f84afb --- /dev/null +++ b/test/runtime/samples/component-svelte-slot-nested/main.svelte @@ -0,0 +1,12 @@ + + + + + Default + + +

B slot

+
+
diff --git a/test/runtime/samples/component-svelte-slot/B.svelte b/test/runtime/samples/component-svelte-slot/B.svelte new file mode 100644 index 000000000000..3bbf00a60ca5 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot/B.svelte @@ -0,0 +1,6 @@ + + +
Hello
+
{name}
diff --git a/test/runtime/samples/component-svelte-slot/Nested.svelte b/test/runtime/samples/component-svelte-slot/Nested.svelte new file mode 100644 index 000000000000..09cfc5aa394b --- /dev/null +++ b/test/runtime/samples/component-svelte-slot/Nested.svelte @@ -0,0 +1,2 @@ +
a:
+
b:
\ No newline at end of file diff --git a/test/runtime/samples/component-svelte-slot/_config.js b/test/runtime/samples/component-svelte-slot/_config.js new file mode 100644 index 000000000000..360b4709b572 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot/_config.js @@ -0,0 +1,6 @@ +export default { + html: ` +
a: content a
+
b:
Hello
world
+ ` +}; diff --git a/test/runtime/samples/component-svelte-slot/main.svelte b/test/runtime/samples/component-svelte-slot/main.svelte new file mode 100644 index 000000000000..b27c3795ed73 --- /dev/null +++ b/test/runtime/samples/component-svelte-slot/main.svelte @@ -0,0 +1,13 @@ + + + + content { a } + + + + + diff --git a/test/validator/samples/prop-slot/errors.json b/test/validator/samples/prop-slot/errors.json deleted file mode 100644 index 4fbfa3935c56..000000000000 --- a/test/validator/samples/prop-slot/errors.json +++ /dev/null @@ -1,15 +0,0 @@ -[{ - "code": "invalid-prop", - "message": "'slot' is reserved for future use in named slots", - "start": { - "line": 5, - "column": 8, - "character": 67 - }, - "end": { - "line": 5, - "column": 18, - "character": 77 - }, - "pos": 67 -}] diff --git a/test/validator/samples/prop-slot/input.svelte b/test/validator/samples/prop-slot/input.svelte deleted file mode 100644 index 816c656aa063..000000000000 --- a/test/validator/samples/prop-slot/input.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/test/validator/samples/svelte-slot-placement-2/errors.json b/test/validator/samples/svelte-slot-placement-2/errors.json new file mode 100644 index 000000000000..5659b35e7d5e --- /dev/null +++ b/test/validator/samples/svelte-slot-placement-2/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-slotted-content", + "message": " must be a child of a component", + "start": { "line": 5, "column": 0, "character": 59 }, + "end": { "line": 7, "column": 18, "character": 112 }, + "pos": 59 + } +] diff --git a/test/validator/samples/svelte-slot-placement-2/input.svelte b/test/validator/samples/svelte-slot-placement-2/input.svelte new file mode 100644 index 000000000000..4fad457bd8da --- /dev/null +++ b/test/validator/samples/svelte-slot-placement-2/input.svelte @@ -0,0 +1,7 @@ + + + +
test
+
\ No newline at end of file diff --git a/test/validator/samples/svelte-slot-placement/errors.json b/test/validator/samples/svelte-slot-placement/errors.json new file mode 100644 index 000000000000..ce825243f04e --- /dev/null +++ b/test/validator/samples/svelte-slot-placement/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-slotted-content", + "message": " must be a child of a component", + "start": { "line": 7, "column": 2, "character": 77 }, + "end": { "line": 9, "column": 20, "character": 134 }, + "pos": 77 + } +] diff --git a/test/validator/samples/svelte-slot-placement/input.svelte b/test/validator/samples/svelte-slot-placement/input.svelte new file mode 100644 index 000000000000..a09639dd7175 --- /dev/null +++ b/test/validator/samples/svelte-slot-placement/input.svelte @@ -0,0 +1,11 @@ + + + +
+ +
test
+
+
+
\ No newline at end of file