diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index ea0160e2b4..bd688e0bdd 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -106,10 +106,11 @@ export default class Element extends Node { children: INode[]; namespace: string; needs_manual_style_scoping: boolean; + dynamic_tag: boolean; constructor(component, parent, scope, info: any) { super(component, parent, scope, info); - this.name = info.name; + this.name = this.get_element_name(info); this.namespace = get_namespace(parent, this, component.namespace); @@ -223,6 +224,26 @@ export default class Element extends Node { component.stylesheet.apply(this); } + get_element_name(info: any) { + let elementName = info.name; + + if (elementName === 'svelte:element') { + const tag_attribute = info.attributes.find(node => node.name === 'tag'); + + if (tag_attribute) { + const tagValue = tag_attribute.value[0]; + + if (tagValue.data) { + elementName = tagValue.data; + } else { + this.dynamic_tag = tagValue.expression.name; + } + } + } + + return elementName; + } + validate() { if (a11y_distracting_elements.has(this.name)) { // no-distracting-elements diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index e3eec5d600..fa60feac62 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -393,7 +393,7 @@ export default class ElementWrapper extends Wrapper { } get_render_statement() { - const { name, namespace } = this.node; + const { name, namespace, dynamic_tag } = this.node; if (namespace === 'http://www.w3.org/2000/svg') { return x`@svg_element("${name}")`; @@ -408,6 +408,10 @@ export default class ElementWrapper extends Wrapper { return x`@element_is("${name}", ${is.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`)});`; } + if (dynamic_tag) { + return x`@element(#ctx.${dynamic_tag} || ${dynamic_tag})`; + } + return x`@element("${name}")`; } diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index c7e761afc2..df55528712 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:element'); const specials = new Map([ [ @@ -39,6 +39,7 @@ const specials = new Map([ const SELF = /^svelte:self(?=[\s/>])/; const COMPONENT = /^svelte:component(?=[\s/>])/; +const ELEMENT = /^svelte:element(?=[\s/>])/; function parent_is_head(stack) { let i = stack.length; @@ -182,6 +183,24 @@ export default function tag(parser: Parser) { element.expression = definition.value[0].expression; } + if (name === 'svelte:element') { + const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'tag'); + if (!~index) { + parser.error({ + code: `missing-component-definition`, + message: ` must have a 'tag' attribute` + }, start); + } + + const definition = element.attributes[index]; + if (definition.value === true || definition.value.length !== 1 || (definition.value[0].type !== 'Text' && definition.value[0].type !== 'MustacheTag')) { + parser.error({ + code: `invalid-tag-definition`, + message: `invalid tag definition` + }, definition.start); + } + } + // special cases – top-level