Skip to content

Commit 5385faf

Browse files
committed
feat: allow dom elements as svelte:element this attribute
1 parent e2bbc56 commit 5385faf

File tree

7 files changed

+65
-11
lines changed

7 files changed

+65
-11
lines changed

.changeset/loud-mayflies-melt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
feat: allow dom elements as `svelte:element` `this` attribute

packages/svelte/src/internal/client/dom/blocks/svelte-element.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { is_raw_text_element } from '../../../../utils.js';
2626

2727
/**
2828
* @param {Comment | Element} node
29-
* @param {() => string} get_tag
29+
* @param {() => string | HTMLElement | SVGElement} get_tag
3030
* @param {boolean} is_svg
3131
* @param {undefined | ((element: Element, anchor: Node | null) => void)} render_fn,
3232
* @param {undefined | (() => string)} get_namespace
@@ -42,10 +42,10 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
4242

4343
var filename = DEV && location && component_context?.function[FILENAME];
4444

45-
/** @type {string | null} */
45+
/** @type {string | HTMLElement | SVGElement | null} */
4646
var tag;
4747

48-
/** @type {string | null} */
48+
/** @type {string | HTMLElement | SVGElement | null} */
4949
var current_tag;
5050

5151
/** @type {null | Element} */
@@ -100,9 +100,11 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
100100
effect = branch(() => {
101101
element = hydrating
102102
? /** @type {Element} */ (element)
103-
: ns
104-
? document.createElementNS(ns, next_tag)
105-
: document.createElement(next_tag);
103+
: typeof next_tag === 'string'
104+
? ns
105+
? document.createElementNS(ns, next_tag)
106+
: document.createElement(next_tag)
107+
: next_tag;
106108

107109
if (DEV && location) {
108110
// @ts-expect-error
@@ -118,7 +120,10 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
118120
assign_nodes(element, element);
119121

120122
if (render_fn) {
121-
if (hydrating && is_raw_text_element(next_tag)) {
123+
if (
124+
hydrating &&
125+
is_raw_text_element(typeof next_tag === 'string' ? next_tag : next_tag.nodeName)
126+
) {
122127
// prevent hydration glitches
123128
element.append(document.createComment(''));
124129
}

packages/svelte/src/internal/shared/validate.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,23 @@ import * as e from './errors.js';
77
export { invalid_default_snippet } from './errors.js';
88

99
/**
10-
* @param {() => string} tag_fn
10+
* @param {() => string | HTMLElement | SVGElement} tag_fn
1111
* @returns {void}
1212
*/
1313
export function validate_void_dynamic_element(tag_fn) {
1414
const tag = tag_fn();
15-
if (tag && is_void(tag)) {
16-
w.dynamic_void_element_content(tag);
15+
const tag_name = typeof tag === 'string' ? tag : tag?.tagName;
16+
if (tag_name && is_void(tag_name)) {
17+
w.dynamic_void_element_content(tag_name);
1718
}
1819
}
1920

2021
/** @param {() => unknown} tag_fn */
2122
export function validate_dynamic_element_tag(tag_fn) {
2223
const tag = tag_fn();
2324
const is_string = typeof tag === 'string';
24-
if (tag && !is_string) {
25+
const is_element = tag instanceof HTMLElement || tag instanceof SVGElement;
26+
if (tag && !(is_string || is_element)) {
2527
e.svelte_element_invalid_this_value();
2628
}
2729
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
ssrHtml: ``,
5+
test({ assert, target }) {
6+
assert.htmlEqual(target.innerHTML, `<div><b>children</b><p>children</p></div>`);
7+
}
8+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let div = $state(null);
3+
4+
$effect(()=>{
5+
const to_add = document.createElement("div");
6+
to_add.innerHTML=`<b>children</b>`;
7+
div = to_add;
8+
})
9+
</script>
10+
11+
<svelte:element this={div}>
12+
<p>children</p>
13+
</svelte:element>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
ssrHtml: `<div><p>children</p></div>`,
5+
test({ assert, target }) {
6+
assert.htmlEqual(target.innerHTML, `<div><b>children</b><p>children</p></div>`);
7+
}
8+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let div = $state(null);
3+
4+
$effect(()=>{
5+
const to_add = document.createElement("div");
6+
to_add.innerHTML=`<b>children</b>`;
7+
div = to_add;
8+
})
9+
</script>
10+
11+
<svelte:element this={div ?? "div"}>
12+
<p>children</p>
13+
</svelte:element>

0 commit comments

Comments
 (0)