From a8d7905ea4e9a9e27ea47bbb3533ceec02bed30c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 1 Apr 2022 17:56:56 +0200 Subject: [PATCH] refactor update block, support animations, support falsy values, adjust tests --- src/compiler/compile/nodes/Element.ts | 12 --- .../render_dom/wrappers/Element/index.ts | 84 +++++++++++++------ src/runtime/internal/dev.ts | 6 -- .../_config.js | 9 -- .../main.svelte | 5 -- .../_config.js | 9 -- .../main.svelte | 5 -- .../_config.js | 9 -- .../main.svelte | 5 -- .../dynamic-element-action-update/_config.js | 8 +- .../dynamic-element-action-update/main.svelte | 7 +- .../_config.js | 3 - .../dynamic-element-animation/_config.js | 62 ++++++++++++++ .../main.svelte | 10 +-- .../_config.js | 11 ++- .../main.svelte | 2 +- 16 files changed, 143 insertions(+), 104 deletions(-) delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte delete mode 100644 test/runtime/samples/dynamic-element-animation-invalid/_config.js create mode 100644 test/runtime/samples/dynamic-element-animation/_config.js rename test/runtime/samples/{dynamic-element-animation-invalid => dynamic-element-animation}/main.svelte (70%) rename test/runtime/samples/{dynamic-element-transition-css-change-tag => dynamic-element-transition}/_config.js (63%) rename test/runtime/samples/{dynamic-element-transition-css-change-tag => dynamic-element-transition}/main.svelte (75%) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index d27df6ef40..00a9b6b22f 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -17,7 +17,6 @@ import list from '../../utils/list'; import Let from './Let'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; -import { TemplateNode } from '../../interfaces'; import Component from '../Component'; import Expression from './shared/Expression'; import { string_literal } from '../utils/stringify'; @@ -323,9 +322,6 @@ export default class Element extends Node { this.scope = scope; this.children = map_children(component, this, this.scope, info.children); - if (this.is_dynamic_element) { - this.validate_dynamic_element(info); - } this.validate(); this.optimise(); @@ -333,14 +329,6 @@ export default class Element extends Node { component.apply_stylesheet(this); } - validate_dynamic_element(info: TemplateNode) { - info.attributes.forEach(node => { - if (node.type === 'Animation') { - this.component.error(node, compiler_errors.invalid_animation_dynamic_element); - } - }); - } - validate() { if (this.component.var_lookup.has(this.name) && this.component.var_lookup.get(this.name).imported) { this.component.warn(this, compiler_warnings.component_name_lowercase(this.name)); diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 8a9257de66..e0d942b864 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -272,41 +272,63 @@ export default class ElementWrapper extends Wrapper { ); const previous_tag = block.get_unique_name('previous_tag'); - const snippet = this.node.tag_expr.manipulate(block); - block.add_variable(previous_tag, snippet); + const tag = this.node.tag_expr.manipulate(block); + block.add_variable(previous_tag, tag); block.chunks.init.push(b` - let ${this.var} = ${snippet} && ${this.child_dynamic_element_block.name}(#ctx); + let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); `); - block.chunks.create.push(b`if (${this.var}) ${this.var}.c();`); + block.chunks.create.push(b` + if (${this.var}) ${this.var}.c(); + `); if (this.renderer.options.hydratable) { - block.chunks.claim.push(b`if (${this.var}) ${this.var}.l(${parent_nodes});`); + block.chunks.claim.push(b` + if (${this.var}) ${this.var}.l(${parent_nodes}); + `); } - block.chunks.mount.push( - b`if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});` - ); + block.chunks.mount.push(b` + if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); + `); - const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const anchor = block.get_unique_name(`${this.child_dynamic_element_block.name.name}_anchor`); + const has_transitions = !!(this.node.intro || this.node.outro); const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; - const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - block.chunks.update.unshift(b` - if (${snippet}) { - if (${this.var}) ${this.var}.p(#ctx, #dirty); - if (${if_statement}) { - if (${this.var}) ${this.var}.d(1); + block.chunks.update.push(b` + if (${tag}) { + if (!${previous_tag}) { + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else if (${not_equal}(${previous_tag}, ${tag})) { + ${this.var}.d(1); ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var}.c(); - @transition_in(${this.var}); - this.m(${this.get_update_mount_node(anchor)}, ${anchor}); + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else { + ${this.var}.p(#ctx, #dirty); + } + } else if (${previous_tag}) { + ${ + has_transitions + ? b` + @group_outros(); + @transition_out(${this.var}, 1, 1, () => { + ${this.var} = null; + }); + @check_outros(); + ` + : b` + ${this.var}.d(1); + ${this.var} = null; + ` } - } else if (${this.var}) { - ${this.var}.d(1); - ${this.var} = null; } + ${previous_tag} = ${tag}; `); if (this.child_dynamic_element_block.has_intros) { @@ -319,13 +341,23 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`if (${this.var}) ${this.var}.d(detaching)`); - if (this.renderer.options.dev) { - block.chunks.create.push(b`@validate_dynamic_element(${snippet});`); - if (this.renderer.options.hydratable) { - block.chunks.claim.push(b`@validate_dynamic_element(${snippet});`); - } - block.chunks.update.push(b`@validate_dynamic_element(${snippet});`); + if (this.node.animation) { + block.chunks.measure.push(b`${this.var}.r()`); + block.chunks.fix.push(b`${this.var}.f()`); + block.chunks.animate.push(b`${this.var}.a()`); } + + // Needs to come last due to statement ordering + block.add_element( + anchor as Identifier, + x`@empty()`, + parent_nodes && x`@empty()`, + parent_node + ); + } + + is_dom_node() { + return super.is_dom_node() && !this.child_dynamic_element; } render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index ee40fb0456..76d68086c8 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -107,12 +107,6 @@ export function validate_slots(name, slot, keys) { } } -export function validate_dynamic_element(tag) { - if (!tag) { - console.warn(' expects a non-nullish value in attribute "this"'); - } -} - type Props = Record; export interface SvelteComponentDev { $set(props?: Props): void; diff --git a/test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js deleted file mode 100644 index a010e9365a..0000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - compileOptions: { - dev: true - }, - - warnings: [ - ' expects a non-nullish value in attribute "this"' - ] -}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte deleted file mode 100644 index 978f3d25be..0000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js deleted file mode 100644 index a010e9365a..0000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - compileOptions: { - dev: true - }, - - warnings: [ - ' expects a non-nullish value in attribute "this"' - ] -}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte deleted file mode 100644 index e094a54fac..0000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js deleted file mode 100644 index a010e9365a..0000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - compileOptions: { - dev: true - }, - - warnings: [ - ' expects a non-nullish value in attribute "this"' - ] -}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte deleted file mode 100644 index 6e5934d581..0000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/test/runtime/samples/dynamic-element-action-update/_config.js b/test/runtime/samples/dynamic-element-action-update/_config.js index c2c6c16451..3756313dab 100644 --- a/test/runtime/samples/dynamic-element-action-update/_config.js +++ b/test/runtime/samples/dynamic-element-action-update/_config.js @@ -9,10 +9,16 @@ export default { assert.equal(component.updateText, ''); assert.equal(component.destroyText, ''); + component.opt = 'opt2'; + + assert.equal(component.tag, 'h1'); + assert.equal(component.updateText, 'update: h1,opt2'); + assert.equal(component.destroyText, ''); + component.tag = 'h2'; assert.equal(component.tag, 'h2'); - assert.equal(component.updateText, 'update: h2'); + assert.equal(component.updateText, 'update: h1,opt2'); assert.equal(component.destroyText, 'destroy'); assert.htmlEqual(target.innerHTML, `

tag is h2.

diff --git a/test/runtime/samples/dynamic-element-action-update/main.svelte b/test/runtime/samples/dynamic-element-action-update/main.svelte index 1abec7a649..beccf0071b 100644 --- a/test/runtime/samples/dynamic-element-action-update/main.svelte +++ b/test/runtime/samples/dynamic-element-action-update/main.svelte @@ -2,12 +2,13 @@ export let updateText = ""; export let destroyText = ""; export let tag = "h1"; - function foo(node, tag) { + export let opt = "opt1"; + function foo(node, {tag, opt}) { return { - update: (tag) => updateText = `update: ${tag}`, + update: ({tag, opt}) => updateText = `update: ${tag},${opt}`, destroy: () => destroyText = 'destroy' }; } -tag is {tag}. +tag is {tag}. diff --git a/test/runtime/samples/dynamic-element-animation-invalid/_config.js b/test/runtime/samples/dynamic-element-animation-invalid/_config.js deleted file mode 100644 index a3b4e1f9d3..0000000000 --- a/test/runtime/samples/dynamic-element-animation-invalid/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - error: ' cannot have a animate directive' -}; diff --git a/test/runtime/samples/dynamic-element-animation/_config.js b/test/runtime/samples/dynamic-element-animation/_config.js new file mode 100644 index 0000000000..e3c57a868e --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation/_config.js @@ -0,0 +1,62 @@ +export default { + props: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ], + tag: 'div' + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + test({ assert, component, target, raf }) { + component.tag = 'p'; + assert.equal(target.querySelectorAll('p').length, 5); + + component.tag = 'div'; + let divs = target.querySelectorAll('div'); + divs.forEach(div => { + div.getBoundingClientRect = function() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + }; + }); + + component.things = [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ]; + + divs = target.querySelectorAll('div'); + assert.ok(~divs[0].style.animation.indexOf('__svelte')); + assert.equal(divs[1].style.animation, ''); + assert.equal(divs[2].style.animation, ''); + assert.equal(divs[3].style.animation, ''); + assert.ok(~divs[4].style.animation.indexOf('__svelte')); + + raf.tick(100); + assert.deepEqual([ + divs[0].style.animation, + divs[4].style.animation + ], ['', '']); + } +}; diff --git a/test/runtime/samples/dynamic-element-animation-invalid/main.svelte b/test/runtime/samples/dynamic-element-animation/main.svelte similarity index 70% rename from test/runtime/samples/dynamic-element-animation-invalid/main.svelte rename to test/runtime/samples/dynamic-element-animation/main.svelte index 870d1b3cff..596d12c77a 100644 --- a/test/runtime/samples/dynamic-element-animation-invalid/main.svelte +++ b/test/runtime/samples/dynamic-element-animation/main.svelte @@ -1,8 +1,6 @@ {#each things as thing (thing.id)} - -{/each} + {thing.name} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js b/test/runtime/samples/dynamic-element-transition/_config.js similarity index 63% rename from test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js rename to test/runtime/samples/dynamic-element-transition/_config.js index 256d83af4e..cb8474afc1 100644 --- a/test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js +++ b/test/runtime/samples/dynamic-element-transition/_config.js @@ -2,13 +2,16 @@ export default { test({ assert, component, target, raf }) { component.visible = true; const h1 = target.querySelector('h1'); - assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); - raf.tick(50); + raf.tick(150); component.tag = 'h2'; const h2 = target.querySelector('h2'); - assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); - assert.equal(h2.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); + assert.equal(h1.style.animation, ''); + assert.equal(h2.style.animation, ''); + + raf.tick(50); + component.visible = false; + assert.equal(h2.style.animation, '__svelte_3750847757_0 100ms linear 0ms 1 both'); } }; diff --git a/test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte b/test/runtime/samples/dynamic-element-transition/main.svelte similarity index 75% rename from test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte rename to test/runtime/samples/dynamic-element-transition/main.svelte index b0f24c8dec..b8c0eff0bd 100644 --- a/test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte +++ b/test/runtime/samples/dynamic-element-transition/main.svelte @@ -13,5 +13,5 @@ {#if visible} - + {/if}