diff --git a/.changeset/loud-mayflies-melt.md b/.changeset/loud-mayflies-melt.md new file mode 100644 index 0000000000..af64eb8b29 --- /dev/null +++ b/.changeset/loud-mayflies-melt.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: allow dom elements as `svelte:element` `this` attribute 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 18641300e5..b4fe477972 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -26,7 +26,7 @@ import { is_raw_text_element } from '../../../../utils.js'; /** * @param {Comment | Element} node - * @param {() => string} get_tag + * @param {() => string | HTMLElement | SVGElement} get_tag * @param {boolean} is_svg * @param {undefined | ((element: Element, anchor: Node | null) => void)} render_fn, * @param {undefined | (() => string)} get_namespace @@ -42,10 +42,10 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio var filename = DEV && location && component_context?.function[FILENAME]; - /** @type {string | null} */ + /** @type {string | HTMLElement | SVGElement | null} */ var tag; - /** @type {string | null} */ + /** @type {string | HTMLElement | SVGElement | null} */ var current_tag; /** @type {null | Element} */ @@ -100,9 +100,11 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio effect = branch(() => { element = hydrating ? /** @type {Element} */ (element) - : ns - ? document.createElementNS(ns, next_tag) - : document.createElement(next_tag); + : typeof next_tag === 'string' + ? ns + ? document.createElementNS(ns, next_tag) + : document.createElement(next_tag) + : next_tag; if (DEV && location) { // @ts-expect-error @@ -118,7 +120,10 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio assign_nodes(element, element); if (render_fn) { - if (hydrating && is_raw_text_element(next_tag)) { + if ( + hydrating && + is_raw_text_element(typeof next_tag === 'string' ? next_tag : next_tag.nodeName) + ) { // prevent hydration glitches element.append(document.createComment('')); } diff --git a/packages/svelte/src/internal/shared/validate.js b/packages/svelte/src/internal/shared/validate.js index 852c0e83bf..07bff85134 100644 --- a/packages/svelte/src/internal/shared/validate.js +++ b/packages/svelte/src/internal/shared/validate.js @@ -7,13 +7,14 @@ import * as e from './errors.js'; export { invalid_default_snippet } from './errors.js'; /** - * @param {() => string} tag_fn + * @param {() => string | HTMLElement | SVGElement} tag_fn * @returns {void} */ export function validate_void_dynamic_element(tag_fn) { const tag = tag_fn(); - if (tag && is_void(tag)) { - w.dynamic_void_element_content(tag); + const tag_name = typeof tag === 'string' ? tag : tag?.tagName; + if (tag_name && is_void(tag_name)) { + w.dynamic_void_element_content(tag_name); } } @@ -21,7 +22,8 @@ export function validate_void_dynamic_element(tag_fn) { export function validate_dynamic_element_tag(tag_fn) { const tag = tag_fn(); const is_string = typeof tag === 'string'; - if (tag && !is_string) { + const is_element = tag instanceof HTMLElement || tag instanceof SVGElement; + if (tag && !(is_string || is_element)) { e.svelte_element_invalid_this_value(); } } diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-element-null/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-element-element-null/_config.js new file mode 100644 index 0000000000..29c900d592 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-element-null/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + ssrHtml: ``, + test({ assert, target }) { + assert.htmlEqual(target.innerHTML, `
children
children
+children
children
children
+