diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index daf89b6141..82bfa62d67 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -1109,10 +1109,30 @@ const common_visitors = { if (attribute.type === 'Attribute') { if (attribute.name === 'xmlns' && is_text_attribute(attribute)) { node.metadata.svg = attribute.value[0].data === namespace_svg; - break; + return; } } } + + for (let i = context.path.length - 1; i >= 0; i--) { + const ancestor = context.path[i]; + if ( + ancestor.type === 'Component' || + ancestor.type === 'SvelteComponent' || + ancestor.type === 'SvelteFragment' || + ancestor.type === 'SnippetBlock' + ) { + // Inside a slot or a snippet -> this resets the namespace, so we can't determine it + return; + } + if (ancestor.type === 'SvelteElement' || ancestor.type === 'RegularElement') { + node.metadata.svg = + ancestor.type === 'RegularElement' && ancestor.name === 'foreignObject' + ? false + : ancestor.metadata.svg; + return; + } + } } }; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 1da9dd7f6b..9fed2b7edb 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -9,7 +9,7 @@ import { import { binding_properties } from '../../../bindings.js'; import { clean_nodes, - determine_element_namespace, + determine_namespace_for_children, escape_html, infer_namespace } from '../../utils.js'; @@ -1683,6 +1683,7 @@ export const template_visitors = { context.state.template.push(``); }, HtmlTag(node, context) { + console.log('html tag'); context.state.template.push(''); // push into init, so that bindings run afterwards, which might trigger another run and override hydration @@ -1851,7 +1852,7 @@ export const template_visitors = { const metadata = context.state.metadata; const child_metadata = { ...context.state.metadata, - namespace: determine_element_namespace(node, context.state.metadata.namespace, context.path) + namespace: determine_namespace_for_children(node, context.state.metadata.namespace) }; context.state.template.push(`<${node.name}`); @@ -2076,11 +2077,7 @@ export const template_visitors = { /** @type {import('estree').ExpressionStatement[]} */ const lets = []; - const namespace = determine_element_namespace( - node, - context.state.metadata.namespace, - context.path - ); + const namespace = determine_namespace_for_children(node, context.state.metadata.namespace); // Create a temporary context which picks up the init/update statements. // They'll then be added to the function parameter of $.element diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 3aba32e22f..1d1b2f8e53 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -15,7 +15,7 @@ import { } from '../../constants.js'; import { clean_nodes, - determine_element_namespace, + determine_namespace_for_children, escape_html, infer_namespace, transform_inspect_rune @@ -1142,7 +1142,7 @@ const template_visitors = { RegularElement(node, context) { const metadata = { ...context.state.metadata, - namespace: determine_element_namespace(node, context.state.metadata.namespace, context.path) + namespace: determine_namespace_for_children(node, context.state.metadata.namespace) }; context.state.template.push(t_string(`<${node.name}`)); @@ -1234,7 +1234,7 @@ const template_visitors = { const metadata = { ...context.state.metadata, - namespace: determine_element_namespace(node, context.state.metadata.namespace, context.path) + namespace: determine_namespace_for_children(node, context.state.metadata.namespace) }; /** @type {import('./types').ComponentContext} */ const inner_context = { diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index 5ad21f5594..ceb8838c90 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -188,7 +188,7 @@ export function clean_nodes( } /** - * Infers the new namespace for the children of a node. + * Infers the namespace for the children of a node that should be used when creating the `$.template(...)`. * @param {import('#compiler').Namespace} namespace * @param {import('#compiler').SvelteNode} parent * @param {import('#compiler').SvelteNode[]} nodes @@ -201,19 +201,28 @@ export function infer_namespace(namespace, parent, nodes, path) { path.at(-1) : parent; - if ( - namespace !== 'foreign' && + if (namespace !== 'foreign') { + if (parent_node?.type === 'RegularElement' && parent_node.name === 'foreignObject') { + return 'html'; + } + + if (parent_node?.type === 'RegularElement' || parent_node?.type === 'SvelteElement') { + return parent_node.metadata.svg ? 'svg' : 'html'; + } + // Re-evaluate the namespace inside slot nodes that reset the namespace - (parent_node === undefined || + if ( + parent_node === undefined || parent_node.type === 'Root' || parent_node.type === 'Component' || parent_node.type === 'SvelteComponent' || parent_node.type === 'SvelteFragment' || - parent_node.type === 'SnippetBlock') - ) { - const new_namespace = check_nodes_for_namespace(nodes, 'keep'); - if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') { - namespace = new_namespace; + parent_node.type === 'SnippetBlock' + ) { + const new_namespace = check_nodes_for_namespace(nodes, 'keep'); + if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') { + return new_namespace; + } } } @@ -279,48 +288,21 @@ function check_nodes_for_namespace(nodes, namespace) { } /** + * Determines the namespace the children of this node are in. * @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node * @param {import('#compiler').Namespace} namespace - * @param {import('#compiler').SvelteNode[]} path * @returns {import('#compiler').Namespace} */ -export function determine_element_namespace(node, namespace, path) { - if (namespace !== 'foreign') { - let parent; - for (let i = path.length - 1; i >= 0; i--) { - parent = path[i]; - if (parent.type === 'Fragment') { - parent = path[i - 1]; - i--; - } - if ( - parent?.type !== 'EachBlock' && - parent?.type !== 'IfBlock' && - parent?.type !== 'KeyBlock' && - parent?.type !== 'AwaitBlock' - ) { - break; - } - } +export function determine_namespace_for_children(node, namespace) { + if (namespace === 'foreign') { + return namespace; + } - if (parent?.type === 'RegularElement' && parent.name === 'foreignObject') { - return 'html'; - } else if ( - namespace !== 'svg' || - parent?.type === 'Component' || - parent?.type === 'SvelteComponent' || - parent?.type === 'SvelteFragment' || - parent?.type === 'SnippetBlock' - ) { - if (node.metadata.svg) { - return 'svg'; - } else { - return 'html'; - } - } + if (node.name === 'foreignObject') { + return 'html'; } - return namespace; + return node.metadata.svg ? 'svg' : 'html'; } /** diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 91c156fe19..6b0d7e50c4 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -309,7 +309,10 @@ export interface SvelteElement extends BaseElement { name: 'svelte:element'; tag: Expression; metadata: { - /** `true` if this is an svg element */ + /** + * `true` if this is definitely an svg element. + * `false` could still mean that it is one, but we can't know statically. + */ svg: boolean; }; } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 03e79ab051..25e09e44f9 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -1636,7 +1636,7 @@ export function element(anchor_node, tag_fn, namespace, render_fn) { namespace ?? (tag === 'svg' ? namespace_svg - : anchor_node.parentElement?.tagName === 'FOREIGNOBJECT' + : anchor_node.parentElement?.tagName === 'foreignObject' ? null : anchor_node.parentElement?.namespaceURI ?? null); const next_element = tag