From ea954759c0e1885dbea26fe01254366037dd1fd1 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 18 Apr 2024 11:47:20 +0200 Subject: [PATCH] support dynamic svelte:element namespace through xmlns attribute --- .changeset/hip-pumpkins-boil.md | 5 +++ .../3-transform/client/visitors/template.js | 32 +++++++++++-------- .../client/dom/blocks/svelte-element.js | 12 +++---- .../_config.js | 10 ++++++ .../main.svelte | 14 ++++++++ 5 files changed, 52 insertions(+), 21 deletions(-) create mode 100644 .changeset/hip-pumpkins-boil.md create mode 100644 packages/svelte/tests/runtime-runes/samples/dynamic-element-dynamic-namespace/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/dynamic-element-dynamic-namespace/main.svelte diff --git a/.changeset/hip-pumpkins-boil.md b/.changeset/hip-pumpkins-boil.md new file mode 100644 index 0000000000..45966b5681 --- /dev/null +++ b/.changeset/hip-pumpkins-boil.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: fall back to component namespace when not statically determinable, add way to tell `` the namespace at runtime 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 44b295e44a..3e356c0d6a 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 @@ -2008,6 +2008,9 @@ export const template_visitors = { /** @type {Array} */ const attributes = []; + /** @type {import('#compiler').Attribute['value'] | undefined} */ + let dynamic_namespace = undefined; + /** @type {import('#compiler').ClassDirective[]} */ const class_directives = []; @@ -2036,7 +2039,11 @@ export const template_visitors = { for (const attribute of node.attributes) { if (attribute.type === 'Attribute') { - attributes.push(attribute); + if (attribute.name === 'xmlns' && !is_text_attribute(attribute)) { + dynamic_namespace = attribute.value; + } else { + attributes.push(attribute); + } } else if (attribute.type === 'SpreadAttribute') { attributes.push(attribute); } else if (attribute.type === 'ClassDirective') { @@ -2090,19 +2097,16 @@ export const template_visitors = { } }) ); - context.state.init.push( - b.stmt( - b.call( - '$.element', - context.state.node, - get_tag, - node.metadata.svg ? b.true : b.false, - inner.length === 0 - ? /** @type {any} */ (undefined) - : b.arrow([element_id, b.id('$$anchor')], b.block(inner)) - ) - ) - ); + + const args = [context.state.node, get_tag, node.metadata.svg ? b.true : b.false]; + if (inner.length > 0) { + args.push(b.arrow([element_id, b.id('$$anchor')], b.block(inner))); + } + if (dynamic_namespace) { + if (inner.length === 0) args.push(b.id('undefined')); + args.push(b.thunk(serialize_attribute_value(dynamic_namespace, context)[1])); + } + context.state.init.push(b.stmt(b.call('$.element', ...args))); }, EachBlock(node, context) { const each_node_meta = node.metadata; diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index 71218eb1d9..7298f27e9a 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -40,10 +40,11 @@ function swap_block_dom(effect, from, to) { * @param {Comment} anchor * @param {() => string} get_tag * @param {boolean} is_svg - * @param {undefined | ((element: Element, anchor: Node) => void)} render_fn + * @param {undefined | ((element: Element, anchor: Node) => void)} render_fn, + * @param {undefined | (() => string)} get_namespace * @returns {void} */ -export function element(anchor, get_tag, is_svg, render_fn) { +export function element(anchor, get_tag, is_svg, render_fn, get_namespace) { const parent_effect = /** @type {import('#client').Effect} */ (current_effect); render_effect(() => { @@ -68,17 +69,14 @@ export function element(anchor, get_tag, is_svg, render_fn) { block(() => { const next_tag = get_tag() || null; + const ns = get_namespace?.() || (is_svg || next_tag === 'svg' ? namespace_svg : null); + // Assumption: Noone changes the namespace but not the tag (what would that even mean?) if (next_tag === tag) return; // See explanation of `each_item_block` above var previous_each_item = current_each_item; set_current_each_item(each_item_block); - // The namespace may not be statically known but we can't really infer it either, - // because on the first render on the client (without hydration) the parent will be undefined, - // and the element itself could be a tag that changes the namespace. - const ns = is_svg || next_tag === 'svg' ? namespace_svg : null; - if (effect) { if (next_tag === null) { // start outro diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-element-dynamic-namespace/_config.js b/packages/svelte/tests/runtime-runes/samples/dynamic-element-dynamic-namespace/_config.js new file mode 100644 index 0000000000..df2a7d1778 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/dynamic-element-dynamic-namespace/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + assert.equal(target.querySelector('path')?.namespaceURI, 'http://www.w3.org/2000/svg'); + + await target.querySelector('button')?.click(); + assert.equal(target.querySelector('div')?.namespaceURI, 'http://www.w3.org/1999/xhtml'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-element-dynamic-namespace/main.svelte b/packages/svelte/tests/runtime-runes/samples/dynamic-element-dynamic-namespace/main.svelte new file mode 100644 index 0000000000..9a72d29de9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/dynamic-element-dynamic-namespace/main.svelte @@ -0,0 +1,14 @@ + + + + + + + +