Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Dynamic elements implementation <svelte:element> #6898

Merged
merged 73 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
f001107
Implement svelte:element for dynamically setting HTML DOM type
Sep 25, 2020
4fcc681
Add add_css_class method to DynamicElement
Oct 7, 2020
54e1c23
Reuse DOM elements if possible. Add dev warnings when passing nullish…
Mar 31, 2021
392bdc6
Add documentation
Mar 31, 2021
7d308cb
Merge branch 'master' into dynamic-elements-implementation
Sep 26, 2021
bd41b85
tag -> this
Sep 26, 2021
45d3ab0
fix: css classes for dynamic elements
Oct 21, 2021
a6a8ffd
Merge remote-tracking branch 'upstream/master' into dynamic-elements-…
baseballyama Oct 23, 2021
80e93bc
replace double quote to single quote
baseballyama Oct 23, 2021
7b5247d
fix lint
baseballyama Oct 23, 2021
185332e
clean up nodes
baseballyama Oct 26, 2021
714433b
little clean up render_dom
baseballyama Oct 27, 2021
89d2733
refactor tag.ts
baseballyama Oct 28, 2021
14ca990
refactor nodes
baseballyama Oct 30, 2021
ed2eed5
refactor render_dom
baseballyama Oct 30, 2021
5d1bc8b
refactor render_dom
baseballyama Oct 31, 2021
84d9f83
refactor render_dom
baseballyama Oct 31, 2021
d65da8d
add tests
baseballyama Oct 31, 2021
7ced0ce
refactor render_ssr
baseballyama Oct 31, 2021
24a338a
refactor render_ssr
baseballyama Oct 31, 2021
ca63111
fix named slot bug
baseballyama Oct 31, 2021
85eab20
rafactor parse step
baseballyama Oct 31, 2021
e3c0091
refactor render_ssr
baseballyama Oct 31, 2021
195452e
refactor render_ssr
baseballyama Oct 31, 2021
8c9e93b
refactor parse step
baseballyama Nov 3, 2021
a86ec67
throw error if <svelte:element> uses animation
baseballyama Nov 3, 2021
89dd933
move DynamicElementWrapper folder
baseballyama Nov 3, 2021
f8cc52a
merge DynamicElement to Element
baseballyama Nov 3, 2021
e2a36a4
Reuse DOM elements if possible.
baseballyama Nov 3, 2021
75621dc
revert useless changes
baseballyama Nov 3, 2021
42e4656
refactor
baseballyama Nov 6, 2021
d223df6
stop to use dynamic_element_block
baseballyama Nov 6, 2021
cdd7180
refactor render_dom
baseballyama Nov 6, 2021
9068c90
refactor render_dom
baseballyama Nov 6, 2021
9cd3fc6
refactor render_dom
baseballyama Nov 6, 2021
c77b73c
improve transition behavior
baseballyama Nov 6, 2021
f51d0f5
refactor render_dom
baseballyama Nov 6, 2021
5160b2b
support animation
baseballyama Nov 6, 2021
da22d63
revert useless changes
baseballyama Nov 6, 2021
11d12ab
use getter
baseballyama Nov 6, 2021
25a12a7
refactor
baseballyama Nov 6, 2021
9d76a92
make it more correct of claim_element
baseballyama Nov 6, 2021
9863c5e
refactor
baseballyama Nov 6, 2021
b34b0ca
stop to use anonymous function
baseballyama Nov 7, 2021
49bef3a
improve performance of render_ssr
baseballyama Nov 7, 2021
6788b47
use child_dynamic_element
baseballyama Nov 13, 2021
28b2665
Revert "support animation"
baseballyama Nov 13, 2021
3a96f18
fix compile error
baseballyama Nov 13, 2021
2e22241
refactor
baseballyama Nov 13, 2021
7c0337a
stop reuse children of <svelte:element>
baseballyama Nov 13, 2021
7b6dbcd
revert add_transitions
baseballyama Nov 13, 2021
8560d7c
revert unnecessary changes
baseballyama Nov 13, 2021
2171c67
fix in transition when tag of <svelte:element> changed
baseballyama Nov 28, 2021
d146c29
call of action when <svelte:element> tag changed
baseballyama Nov 28, 2021
7a832d6
stop to transform to normal element even if svelte:element's value of…
baseballyama Nov 28, 2021
fa1b22a
Merge branch 'master' into dynamic-elements-implementation
baseballyama Jan 8, 2022
b437985
refactor redundant if statement
baseballyama Jan 8, 2022
5850155
prevent to create element if value of `this` attribute is null / unde…
baseballyama Jan 9, 2022
dc25e2e
Merge branch 'master' into dynamic-elements-implementation
baseballyama Feb 12, 2022
02cc14f
workaround ts error
baseballyama Feb 12, 2022
6c71959
Merge branch 'master' into dynamic-elements-implementation
Apr 1, 2022
a8d7905
refactor update block, support animations, support falsy values, adju…
Apr 1, 2022
024a748
does not always need to create a new anchor
tanhauhau Apr 6, 2022
ef8a3f9
validate tag variable in dev mode
tanhauhau Apr 6, 2022
4965010
support template literals
baseballyama Apr 6, 2022
31573fd
add test for validating tag definition
baseballyama Apr 6, 2022
c5c0dee
remove useless validation
baseballyama Apr 6, 2022
2858cf5
add test for shorthand this
baseballyama Apr 6, 2022
1e86df5
add more parser validation test
tanhauhau Apr 7, 2022
f4a8e79
improve testcases
tanhauhau Apr 7, 2022
0ef1dfc
animate while switching dynamic element
tanhauhau Apr 7, 2022
13c6c12
fix test
tanhauhau Apr 7, 2022
8057a14
add tutorial section
Apr 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions site/content/docs/02-template-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,28 @@ If `this` is falsy, no component is rendered.
<svelte:component this={currentSelection.component} foo={bar}/>
```

### `<svelte:element>`

```sv
<svelte:element this={expression}/>
```

---

The `<svelte:element>` element lets you render an element of a dynamically specified type. This is useful for example when rich text content from a CMS. If the tag is changed, the children will be preserved unless there's a transition attached to the element. Any properties and event listeners present will be applied to the element.

The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) does not work with a dynamic tag type.

If `this` has a nullish value, a warning will be logged in development mode.

```sv
<script>
let tag = 'div';
export let handler;
</script>

<svelte:element this={tag} on:click={handler}>Foo</svelte:element>
```

### `<svelte:window>`

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script>
const options = ['h1', 'h3', 'p'];
let selected = options[0];
</script>

<select bind:value={selected}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>

{#if selected === 'h1'}
<h1>I'm a h1 tag</h1>
{:else if selected === 'h3'}
<h3>I'm a h3 tag</h3>
{:else if selected === 'p'}
<p>I'm a p tag</p>
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
const options = ['h1', 'h3', 'p'];
let selected = options[0];
</script>

<select bind:value={selected}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>

<svelte:element this={selected}>I'm a {selected} tag</svelte:element>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: <svelte:element>
---

Sometimes we don't know in advance what kind of DOM element to render. `<svelte:element>` comes in handy here. Instead of a sequence of `if` blocks...

```html
{#if selected === 'h1'}
<h1>I'm a h1 tag</h1>
{:else if selected === 'h3'}
<h3>I'm a h3 tag</h3>
{:else if selected === 'p'}
<p>I'm a p tag</p>
{/if}
```

...we can have a single dynamic component:

```html
<svelte:element this={selected}>I'm a {selected} tag</svelte:element>
```

The `this` value can be any string, or a falsy value — if it's falsy, no element is rendered.
4 changes: 4 additions & 0 deletions src/compiler/compile/compiler_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ export default {
code: 'invalid-animation',
message: 'An element that uses the animate directive must be the sole child of a keyed each block'
},
invalid_animation_dynamic_element: {
code: 'invalid-animation',
message: '<svelte:element> cannot have a animate directive'
},
invalid_directive_value: {
code: 'invalid-directive-value',
message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)'
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/compile/nodes/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import Let from './Let';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
import Component from '../Component';
import Expression from './shared/Expression';
import { string_literal } from '../utils/stringify';
import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors';

Expand Down Expand Up @@ -190,11 +193,26 @@ export default class Element extends Node {
children: INode[];
namespace: string;
needs_manual_style_scoping: boolean;
tag_expr: Expression;

get is_dynamic_element() {
return this.name === 'svelte:element';
}

constructor(component: Component, parent: Node, scope: TemplateScope, info: any) {
super(component, parent, scope, info);
this.name = info.name;

if (info.name === 'svelte:element') {
if (typeof info.tag !== 'string') {
this.tag_expr = new Expression(component, this, scope, info.tag);
} else {
this.tag_expr = new Expression(component, this, scope, string_literal(info.tag) as Literal);
}
} else {
this.tag_expr = new Expression(component, this, scope, string_literal(this.name) as Literal);
}

this.namespace = get_namespace(parent as Element, this, component.namespace);

if (this.namespace !== namespaces.foreign) {
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/compile/render_dom/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default class Block {
hydrate: Array<Node | Node[]>;
mount: Array<Node | Node[]>;
measure: Array<Node | Node[]>;
restore_measurements: Array<Node | Node[]>;
fix: Array<Node | Node[]>;
animate: Array<Node | Node[]>;
intro: Array<Node | Node[]>;
Expand Down Expand Up @@ -96,6 +97,7 @@ export default class Block {
hydrate: [],
mount: [],
measure: [],
restore_measurements: [],
fix: [],
animate: [],
intro: [],
Expand Down Expand Up @@ -326,6 +328,12 @@ export default class Block {
${this.chunks.measure}
}`;

if (this.chunks.restore_measurements.length) {
properties.restore_measurements = x`function #restore_measurements(#measurement) {
${this.chunks.restore_measurements}
}`;
}

properties.fix = x`function #fix() {
${this.chunks.fix}
}`;
Expand Down Expand Up @@ -379,6 +387,7 @@ export default class Block {
m: ${properties.mount},
p: ${properties.update},
r: ${properties.measure},
s: ${properties.restore_measurements},
f: ${properties.fix},
a: ${properties.animate},
i: ${properties.intro},
Expand Down
Loading