refactor update block, support animations, support falsy values, adjust tests

pull/6898/head
Simon 5 years ago
parent 6c719596ad
commit a8d7905ea4

@ -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));

@ -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) {

@ -107,12 +107,6 @@ export function validate_slots(name, slot, keys) {
}
}
export function validate_dynamic_element(tag) {
if (!tag) {
console.warn('<svelte:element> expects a non-nullish value in attribute "this"');
}
}
type Props = Record<string, any>;
export interface SvelteComponentDev {
$set(props?: Props): void;

@ -1,9 +0,0 @@
export default {
compileOptions: {
dev: true
},
warnings: [
'<svelte:element> expects a non-nullish value in attribute "this"'
]
};

@ -1,5 +0,0 @@
<script>
let tag = null;
</script>
<svelte:element this={tag}></svelte:element>

@ -1,9 +0,0 @@
export default {
compileOptions: {
dev: true
},
warnings: [
'<svelte:element> expects a non-nullish value in attribute "this"'
]
};

@ -1,5 +0,0 @@
<script>
let tag = '';
</script>
<svelte:element this={tag}></svelte:element>

@ -1,9 +0,0 @@
export default {
compileOptions: {
dev: true
},
warnings: [
'<svelte:element> expects a non-nullish value in attribute "this"'
]
};

@ -1,5 +0,0 @@
<script>
let tag;
</script>
<svelte:element this={tag}></svelte:element>

@ -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, `
<h2>tag is h2.</h2>

@ -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'
};
}
</script>
<svelte:element this={tag} use:foo={tag}>tag is {tag}.</svelte:element>
<svelte:element this={tag} use:foo={{tag, opt}}>tag is {tag}.</svelte:element>

@ -1,3 +0,0 @@
export default {
error: '<svelte:element> cannot have a animate directive'
};

@ -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: `
<div>a</div>
<div>b</div>
<div>c</div>
<div>d</div>
<div>e</div>
`,
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
], ['', '']);
}
};

@ -1,8 +1,6 @@
<script>
let tag = 'div';
let things = [
{ id: 1, name: 'a' },
];
export let things;
export let tag;
function flip(node, animation, params) {
const dx = animation.from.left - animation.to.left;
@ -16,5 +14,5 @@
</script>
{#each things as thing (thing.id)}
<svelte:element this={tag} animate:flip></svelte:element>
<svelte:element this={tag} animate:flip>{thing.name}</svelte:element>
{/each}

@ -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');
}
};

@ -13,5 +13,5 @@
</script>
{#if visible}
<svelte:element this={tag} in:foo></svelte:element>
<svelte:element this={tag} transition:foo></svelte:element>
{/if}
Loading…
Cancel
Save