From e8a780676d2a9115bd713eaab783531913cb2cda Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 6 May 2018 15:50:41 -0400 Subject: [PATCH] =?UTF-8?q?Abort=20outro=20if=20block=20is=20recreated=20?= =?UTF-8?q?=E2=80=94=C2=A0fixes=20#1425?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compile/dom/Block.ts | 4 +- src/compile/nodes/AwaitBlock.ts | 4 +- src/compile/nodes/EachBlock.ts | 4 +- src/compile/nodes/Element.ts | 6 +++ src/compile/nodes/IfBlock.ts | 4 +- src/shared/transitions.js | 26 +++++++++---- .../_config.js | 39 +++++++++++++++++++ .../main.html | 19 +++++++++ .../transition-js-aborted-outro/_config.js | 24 ++++++++++++ .../transition-js-aborted-outro/main.html | 18 +++++++++ 10 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 test/runtime/samples/transition-js-aborted-outro-in-each/_config.js create mode 100644 test/runtime/samples/transition-js-aborted-outro-in-each/main.html create mode 100644 test/runtime/samples/transition-js-aborted-outro/_config.js create mode 100644 test/runtime/samples/transition-js-aborted-outro/main.html diff --git a/src/compile/dom/Block.ts b/src/compile/dom/Block.ts index 25b324a14092..4e88d27a1855 100644 --- a/src/compile/dom/Block.ts +++ b/src/compile/dom/Block.ts @@ -232,7 +232,7 @@ export default class Block { } } - if (this.hasIntroMethod) { + if (this.hasIntroMethod || this.hasOutroMethod) { if (hasIntros) { properties.addBlock(deindent` ${dev ? 'i: function intro' : 'i'}(#target, anchor) { @@ -252,9 +252,7 @@ export default class Block { }, `); } - } - if (this.hasOutroMethod) { if (hasOutros) { properties.addBlock(deindent` ${dev ? 'o: function outro' : 'o'}(#outrocallback) { diff --git a/src/compile/nodes/AwaitBlock.ts b/src/compile/nodes/AwaitBlock.ts index 26bad55cc982..0d74dc608bce 100644 --- a/src/compile/nodes/AwaitBlock.ts +++ b/src/compile/nodes/AwaitBlock.ts @@ -132,8 +132,10 @@ export default class AwaitBlock extends Node { const initialMountNode = parentNode || '#target'; const anchorNode = parentNode ? 'null' : 'anchor'; + const hasTransitions = this.pending.block.hasIntroMethod || this.pending.block.hasOutroMethod; + block.builders.mount.addBlock(deindent` - ${info}.block.${this.pending.block.hasIntroMethod ? 'i' : 'm'}(${initialMountNode}, ${info}.anchor = ${anchorNode}); + ${info}.block.${hasTransitions ? 'i' : 'm'}(${initialMountNode}, ${info}.anchor = ${anchorNode}); ${info}.mount = () => ${updateMountNode}; `); diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index 5747b753a0d5..78a9e4d48c72 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -146,7 +146,7 @@ export default class EachBlock extends Node { compiler.code.overwrite(c, c + 4, 'length'); const length = `[✂${c}-${c+4}✂]`; - const mountOrIntro = this.block.hasIntroMethod ? 'i' : 'm'; + const mountOrIntro = (this.block.hasIntroMethod || this.block.hasOutroMethod) ? 'i' : 'm'; const vars = { each, create_each_block, @@ -379,7 +379,7 @@ export default class EachBlock extends Node { if (condition !== '') { const forLoopBody = this.block.hasUpdateMethod - ? this.block.hasIntroMethod + ? (this.block.hasIntroMethod || this.block.hasOutroMethod) ? deindent` if (${iterations}[#i]) { ${iterations}[#i].p(changed, child_ctx); diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index a3fb34e7e6ee..ec166a8130c8 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -701,6 +701,8 @@ export default class Element extends Node { const fn = `%transitions-${intro.name}`; block.builders.intro.addBlock(deindent` + if (${name}) ${name}.invalidate(); + #component.root._aftercreate.push(() => { if (!${name}) ${name} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, true); ${name}.run(1); @@ -748,6 +750,10 @@ export default class Element extends Node { const fn = `%transitions-${outro.name}`; + block.builders.intro.addBlock(deindent` + if (${outroName}) ${outroName}.abort(); + `); + // TODO hide elements that have outro'd (unless they belong to a still-outroing // group) prior to their removal from the DOM block.builders.outro.addBlock(deindent` diff --git a/src/compile/nodes/IfBlock.ts b/src/compile/nodes/IfBlock.ts index a9b91bf98ecf..dd6e98a82a0e 100644 --- a/src/compile/nodes/IfBlock.ts +++ b/src/compile/nodes/IfBlock.ts @@ -367,7 +367,7 @@ export default class IfBlock extends Node { const updateMountNode = this.getUpdateMountNode(anchor); const enter = dynamic - ? branch.hasIntroMethod + ? (branch.hasIntroMethod || branch.hasOutroMethod) ? deindent` if (${name}) { ${name}.p(changed, ctx); @@ -387,7 +387,7 @@ export default class IfBlock extends Node { ${name}.m(${updateMountNode}, ${anchor}); } ` - : branch.hasIntroMethod + : (branch.hasIntroMethod || branch.hasOutroMethod) ? deindent` if (!${name}) { ${name} = ${branch.block}(#component, ctx); diff --git a/src/shared/transitions.js b/src/shared/transitions.js index dcc0bb83b181..6520652730f3 100644 --- a/src/shared/transitions.js +++ b/src/shared/transitions.js @@ -6,9 +6,10 @@ export function linear(t) { } export function generateRule({ a, b, delta, duration }, ease, fn) { + const step = 16.666 / duration; let keyframes = '{\n'; - for (let p = 0; p <= 1; p += 16.666 / duration) { + for (let p = 0; p <= 1; p += step) { const t = a + delta * ease(p); keyframes += p * 100 + `%{${fn(t)}}\n`; } @@ -112,7 +113,7 @@ export function wrapTransition(component, node, fn, params, intro) { component.fire(`${program.b ? 'intro' : 'outro'}.end`, { node }); - if (!program.b) { + if (!program.b && !program.invalidated) { program.group.callbacks.push(() => { program.callback(); if (obj.css) transitionManager.deleteRule(node, program.name); @@ -123,17 +124,26 @@ export function wrapTransition(component, node, fn, params, intro) { fn(); }); } + } else { + if (obj.css) transitionManager.deleteRule(node, program.name); } - this.program = null; this.running = !!this.pending; }, abort() { - if (obj.tick) obj.tick(1); - if (obj.css) transitionManager.deleteRule(node, this.program.name); - this.program = this.pending = null; - this.running = false; + if (this.program) { + if (obj.tick) obj.tick(1); + if (obj.css) transitionManager.deleteRule(node, this.program.name); + this.program = this.pending = null; + this.running = false; + } + }, + + invalidate() { + if (this.program) { + this.program.invalidated = true; + } } }; } @@ -204,7 +214,7 @@ export var transitionManager = { deleteRule(node, name) { node.style.animation = node.style.animation .split(', ') - .filter(anim => anim.indexOf(name) === -1) + .filter(anim => anim && anim.indexOf(name) === -1) .join(', '); }, diff --git a/test/runtime/samples/transition-js-aborted-outro-in-each/_config.js b/test/runtime/samples/transition-js-aborted-outro-in-each/_config.js new file mode 100644 index 000000000000..ae2317927046 --- /dev/null +++ b/test/runtime/samples/transition-js-aborted-outro-in-each/_config.js @@ -0,0 +1,39 @@ +export default { + data: { + things: [ + 'one', + 'two', + 'three' + ] + }, + + test(assert, component, target, window, raf) { + const { things } = component.get(); + + component.set({ things: [] }); + const spans = target.querySelectorAll('span'); + + raf.tick(25); + assert.equal(spans[0].foo, 0.75); + assert.equal(spans[1].foo, undefined); + assert.equal(spans[2].foo, undefined); + + raf.tick(125); + assert.equal(spans[0].foo, 0); + assert.equal(spans[1].foo, 0.25); + assert.equal(spans[2].foo, 0.75); + + component.set({ things }); + raf.tick(225); + + assert.htmlEqual(target.innerHTML, ` + one + two + three + `); + + assert.equal(spans[0].foo, 1); + assert.equal(spans[1].foo, 1); + assert.equal(spans[2].foo, 1); + }, +}; diff --git a/test/runtime/samples/transition-js-aborted-outro-in-each/main.html b/test/runtime/samples/transition-js-aborted-outro-in-each/main.html new file mode 100644 index 000000000000..fabdc527a38d --- /dev/null +++ b/test/runtime/samples/transition-js-aborted-outro-in-each/main.html @@ -0,0 +1,19 @@ +{#each things as thing, i} + {thing} +{/each} + + \ No newline at end of file diff --git a/test/runtime/samples/transition-js-aborted-outro/_config.js b/test/runtime/samples/transition-js-aborted-outro/_config.js new file mode 100644 index 000000000000..345fed1a7521 --- /dev/null +++ b/test/runtime/samples/transition-js-aborted-outro/_config.js @@ -0,0 +1,24 @@ +export default { + data: { + visible: true, + }, + + test(assert, component, target, window, raf) { + component.set({ visible: false }); + const span = target.querySelector('span'); + + raf.tick(50); + assert.equal(span.foo, 0.5); + + component.set({ visible: true }); + assert.equal(span.foo, 1); + + raf.tick(75); + assert.equal(span.foo, 1); + + raf.tick(100); + assert.htmlEqual(target.innerHTML, ` + hello + `); + }, +}; diff --git a/test/runtime/samples/transition-js-aborted-outro/main.html b/test/runtime/samples/transition-js-aborted-outro/main.html new file mode 100644 index 000000000000..47c93a23216f --- /dev/null +++ b/test/runtime/samples/transition-js-aborted-outro/main.html @@ -0,0 +1,18 @@ +{#if visible} + hello +{/if} + + \ No newline at end of file