diff --git a/site/content/docs/03-template-syntax.md b/site/content/docs/03-template-syntax.md index 1094e25184..24b6111fae 100644 --- a/site/content/docs/03-template-syntax.md +++ b/site/content/docs/03-template-syntax.md @@ -1374,6 +1374,22 @@ Desugars to this: --- +For SVG namespace, the example above desugars into using `` instead: + +```sv + + + +``` + +**Note**: Since this is an extra ``, beware that your CSS structure might accidentally target this. Be mindful of this added wrapper element when using this feature. + +--- + Svelte's CSS Variables support allows for easily themable components: ```sv diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index 13fb13c278..8871c5f306 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -22,6 +22,7 @@ export default class InlineComponent extends Node { css_custom_properties: Attribute[] = []; children: INode[]; scope: TemplateScope; + namespace: string; constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); @@ -33,6 +34,7 @@ export default class InlineComponent extends Node { } this.name = info.name; + this.namespace = get_namespace(parent, component.namespace); this.expression = this.name === 'svelte:component' ? new Expression(component, this, scope, info.expression) @@ -165,3 +167,13 @@ export default class InlineComponent extends Node { function not_whitespace_text(node) { return !(node.type === 'Text' && /^\s+$/.test(node.data)); } + +function get_namespace(parent: Node, explicit_namespace: string) { + const parent_element = parent.find_nearest(/^Element/); + + if (!parent_element) { + return explicit_namespace; + } + + return parent_element.namespace; +} diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 5b2883850a..37aaf2e7b2 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -20,6 +20,7 @@ import { string_to_member_expression } from '../../../utils/string_to_member_exp import SlotTemplate from '../../../nodes/SlotTemplate'; import { is_head } from '../shared/is_head'; import compiler_warnings from '../../../compiler_warnings'; +import { namespaces } from '../../../../utils/namespaces'; type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }; @@ -150,7 +151,9 @@ export default class InlineComponentWrapper extends Wrapper { } const has_css_custom_properties = this.node.css_custom_properties.length > 0; - const css_custom_properties_wrapper = has_css_custom_properties ? block.get_unique_name('div') : null; + const is_svg_namespace = this.node.namespace === namespaces.svg; + const css_custom_properties_wrapper_element = is_svg_namespace ? 'g' : 'div'; + const css_custom_properties_wrapper = has_css_custom_properties ? block.get_unique_name(css_custom_properties_wrapper_element) : null; if (has_css_custom_properties) { block.add_variable(css_custom_properties_wrapper); } @@ -411,7 +414,7 @@ export default class InlineComponentWrapper extends Wrapper { const snippet = this.node.expression.manipulate(block); if (has_css_custom_properties) { - this.set_css_custom_properties(block, css_custom_properties_wrapper); + this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); } block.chunks.init.push(b` @@ -440,7 +443,7 @@ export default class InlineComponentWrapper extends Wrapper { block.chunks.mount.push(b`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`); if (to_claim) { - if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper); + if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); block.chunks.claim.push(b`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`); } @@ -514,7 +517,7 @@ export default class InlineComponentWrapper extends Wrapper { `); if (has_css_custom_properties) { - this.set_css_custom_properties(block, css_custom_properties_wrapper); + this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); } block.chunks.create.push(b`@create_component(${name}.$$.fragment);`); @@ -522,7 +525,7 @@ export default class InlineComponentWrapper extends Wrapper { block.chunks.mount.push(b`@mount_component(${name}, ${mount_target}, ${mount_anchor});`); if (to_claim) { - if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper); + if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); block.chunks.claim.push(b`@claim_component(${name}.$$.fragment, ${claim_nodes});`); } @@ -568,11 +571,14 @@ export default class InlineComponentWrapper extends Wrapper { private create_css_custom_properties_wrapper_claim_chunk( block: Block, parent_nodes: Identifier, - css_custom_properties_wrapper: Identifier | null + css_custom_properties_wrapper: Identifier | null, + css_custom_properties_wrapper_element: string, + is_svg_namespace: boolean ) { const nodes = block.get_unique_name(`${css_custom_properties_wrapper.name}_nodes`); + const claim_element = is_svg_namespace ? x`@claim_svg_element` : x`@claim_element`; block.chunks.claim.push(b` - ${css_custom_properties_wrapper} = @claim_element(${parent_nodes}, "DIV", { style: true }) + ${css_custom_properties_wrapper} = ${claim_element}(${parent_nodes}, "${css_custom_properties_wrapper_element.toUpperCase()}", { style: true }) var ${nodes} = @children(${css_custom_properties_wrapper}); `); return nodes; @@ -580,10 +586,13 @@ export default class InlineComponentWrapper extends Wrapper { private set_css_custom_properties( block: Block, - css_custom_properties_wrapper: Identifier + css_custom_properties_wrapper: Identifier, + css_custom_properties_wrapper_element: string, + is_svg_namespace: boolean ) { - block.chunks.create.push(b`${css_custom_properties_wrapper} = @element("div");`); - block.chunks.hydrate.push(b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`); + const element = is_svg_namespace ? x`@svg_element` : x`@element`; + block.chunks.create.push(b`${css_custom_properties_wrapper} = ${element}("${css_custom_properties_wrapper_element}");`); + if (!is_svg_namespace) block.chunks.hydrate.push(b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`); this.node.css_custom_properties.forEach((attr) => { const dependencies = attr.get_dependencies(); const should_cache = attr.should_cache(); diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index 626365e3e2..8287b46268 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -3,6 +3,7 @@ import { get_attribute_value } from './shared/get_attribute_value'; import Renderer, { RenderOptions } from '../Renderer'; import InlineComponent from '../../nodes/InlineComponent'; import { p, x } from 'code-red'; +import { namespaces } from '../../../utils/namespaces'; function get_prop_value(attribute) { if (attribute.is_true) return x`true`; @@ -88,11 +89,16 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend }`; if (node.css_custom_properties.length > 0) { - renderer.add_string('
'); } @@ -100,6 +106,10 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`); if (node.css_custom_properties.length > 0) { - renderer.add_string('
'); + if (node.namespace === namespaces.svg) { + renderer.add_string('
'); + } else { + renderer.add_string(''); + } } } diff --git a/src/compiler/utils/namespaces.ts b/src/compiler/utils/namespaces.ts index 7da64afc8c..d51b303fe1 100644 --- a/src/compiler/utils/namespaces.ts +++ b/src/compiler/utils/namespaces.ts @@ -25,4 +25,4 @@ export const valid_namespaces = [ xmlns ]; -export const namespaces: Record = { foreign, html, mathml, svg, xlink, xml, xmlns }; +export const namespaces = { foreign, html, mathml, svg, xlink, xml, xmlns } as const; diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/Svg.svelte b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/Svg.svelte new file mode 100644 index 0000000000..26e93bff29 --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/Svg.svelte @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/_config.js b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/_config.js new file mode 100644 index 0000000000..ea8b5584db --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/_config.js @@ -0,0 +1,55 @@ +export default { + props: { + rectColor1: 'green', + circleColor1: 'red', + rectColor2: 'black', + circleColor2: 'blue' + }, + html: ` + + + + + + + + + + + + + + + `, + test({ component, assert, target }) { + component.rectColor1 = 'yellow'; + component.circleColor2 = 'cyan'; + + assert.htmlEqual(target.innerHTML, ` + + + + + + + + + + + + + + + `); + + const circleColor1 = target.querySelector('#svg-1 circle'); + const rectColor1 = target.querySelector('#svg-1 rect'); + const circleColor2 = target.querySelector('#svg-2 circle'); + const rectColor2 = target.querySelector('#svg-2 rect'); + + assert.htmlEqual(window.getComputedStyle(circleColor1).fill, 'rgb(255, 0, 0)'); + assert.htmlEqual(window.getComputedStyle(rectColor1).fill, 'rgb(255, 255, 0)'); + assert.htmlEqual(window.getComputedStyle(circleColor2).fill, 'rgb(0, 255, 255)'); + assert.htmlEqual(window.getComputedStyle(rectColor2).fill, 'rgb(0, 0, 0)'); + } +}; diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/main.svelte b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/main.svelte new file mode 100644 index 0000000000..b30be2dbac --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic-svg/main.svelte @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file