From 64fa48e699c58d2cc03c2735e9d5c56f1cbfe674 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 12 May 2018 15:46:01 -0400 Subject: [PATCH] outroing each blocks --- src/compile/dom/Block.ts | 2 +- src/compile/nodes/EachBlock.ts | 63 ++++++++++++++----- src/compile/nodes/Element.ts | 6 +- src/shared/keyed-each.js | 3 - src/shared/utils.js | 6 ++ .../_config.js | 28 +++++++++ .../transition-js-nested-each-keyed/main.html | 20 ++++++ .../transition-js-nested-each/_config.js | 28 +++++++++ .../transition-js-nested-each/main.html | 20 ++++++ 9 files changed, 154 insertions(+), 22 deletions(-) create mode 100644 test/runtime/samples/transition-js-nested-each-keyed/_config.js create mode 100644 test/runtime/samples/transition-js-nested-each-keyed/main.html create mode 100644 test/runtime/samples/transition-js-nested-each/_config.js create mode 100644 test/runtime/samples/transition-js-nested-each/main.html diff --git a/src/compile/dom/Block.ts b/src/compile/dom/Block.ts index 71d85bba77..69a6736fcd 100644 --- a/src/compile/dom/Block.ts +++ b/src/compile/dom/Block.ts @@ -272,7 +272,7 @@ export default class Block { ${outroing} = true; ${hasIntros && `${introing} = false;`} - ${this.outros > 1 && `var #outros = ${this.outros};`} + ${this.outros > 1 && `#outrocallback = @callAfter(#outrocallback, ${this.outros});`} ${this.builders.outro} }, diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index 78a9e4d48c..e55ecd8119 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -118,6 +118,10 @@ export default class EachBlock extends Node { ); this.else.block.hasUpdateMethod = this.else.block.dependencies.size > 0; } + + if (this.block.hasOutroMethod || (this.else && this.else.block.hasOutroMethod)) { + block.addOutro(); + } } build( @@ -313,9 +317,24 @@ export default class EachBlock extends Node { block.builders.update.addBlock(deindent` var ${this.each_block_value} = ${snippet}; + ${this.block.hasOutroMethod && `@transitionManager.groupOutros();`} ${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.each_block_value}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context}); `); + block.builders.outro.addBlock(deindent` + const keys = Object.keys(${lookup}).filter(key => ${lookup}[key]); + #outrocallback = @callAfter(#outrocallback, keys.length); + + function outro(key) { + ${lookup}[key].o(() => { + ${lookup}[key] = null; + #outrocallback(); + }); + } + + for (let #i = 0; #i < keys.length; #i += 1) outro(keys[#i]); + `) + block.builders.destroy.addBlock(deindent` for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].d(${parentNode ? '' : 'detach'}); `); @@ -372,6 +391,21 @@ export default class EachBlock extends Node { allDependencies.add(dependency); }); + const outro = this.block.hasOutroMethod && block.getUniqueName('outro') + if (outro) { + block.builders.init.addBlock(deindent` + function ${outro}(i, detach, fn) { + if (${iterations}[i]) { + ${iterations}[i].o(() => { + ${iterations}[i].d(detach); + if (detach) ${iterations}[i] = null; + if (fn) fn(); + }); + } + } + `); + } + // TODO do this for keyed blocks as well const condition = Array.from(allDependencies) .map(dependency => `changed.${dependency}`) @@ -406,27 +440,21 @@ export default class EachBlock extends Node { const start = this.block.hasUpdateMethod ? '0' : `${iterations}.length`; - const outro = block.getUniqueName('outro'); - const destroy = this.block.hasOutroMethod - ? deindent` - function ${outro}(i) { - if (${iterations}[i]) { - ${iterations}[i].o(function() { - ${iterations}[i].d(1); - ${iterations}[i] = null; - }); - } - } + let destroy; + if (this.block.hasOutroMethod) { + destroy = deindent` @transitionManager.groupOutros(); - for (; #i < ${iterations}.length; #i += 1) ${outro}(#i); - ` - : deindent` + for (; #i < ${iterations}.length; #i += 1) ${outro}(#i, 1); + `; + } else { + destroy = deindent` for (; #i < ${iterations}.length; #i += 1) { ${iterations}[#i].d(1); } ${iterations}.length = ${this.each_block_value}.${length}; `; + } block.builders.update.addBlock(deindent` if (${condition}) { @@ -443,6 +471,13 @@ export default class EachBlock extends Node { `); } + if (outro) { + block.builders.outro.addBlock(deindent` + #outrocallback = @callAfter(#outrocallback, #i); + for (let #i = 0; #i < ${iterations}.length; #i += 1) ${outro}(#i, 0, #outrocallback);` + ); + } + block.builders.destroy.addBlock(`@destroyEach(${iterations}, detach);`); } diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 28d01ae938..cdfe1926df 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -712,7 +712,7 @@ export default class Element extends Node { block.builders.outro.addBlock(deindent` ${name}.run(0, () => { - ${block.outros > 1 ? `if (--#outros === 0) #outrocallback();` : `#outrocallback();`} + #outrocallback(); ${name} = null; }); `); @@ -759,9 +759,7 @@ export default class Element extends Node { // group) prior to their removal from the DOM block.builders.outro.addBlock(deindent` ${outroName} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, false); - ${outroName}.run(0, () => { - ${block.outros > 1 ? `if (--#outros === 0) #outrocallback();` : `#outrocallback();`} - }); + ${outroName}.run(0, #outrocallback); `); } } diff --git a/src/shared/keyed-each.js b/src/shared/keyed-each.js index 60b0fe8bcd..dac16930ee 100644 --- a/src/shared/keyed-each.js +++ b/src/shared/keyed-each.js @@ -1,5 +1,3 @@ -import { transitionManager } from './transitions.js'; - export function destroyBlock(block, lookup) { block.d(1); lookup[block.key] = null; @@ -45,7 +43,6 @@ export function updateKeyedEach(old_blocks, component, changed, get_key, dynamic var did_move = {}; var destroy = has_outro ? outroAndDestroyBlock : destroyBlock; - if (has_outro) transitionManager.groupOutros(); function insert(block) { block[intro_method](node, next); diff --git a/src/shared/utils.js b/src/shared/utils.js index f9fbf80e66..d1f2c7f5e5 100644 --- a/src/shared/utils.js +++ b/src/shared/utils.js @@ -12,4 +12,10 @@ export function assignTrue(tar, src) { export function isPromise(value) { return value && typeof value.then === 'function'; +} + +export function callAfter(fn, i) { + return () => { + if (!--i) fn(); + }; } \ No newline at end of file diff --git a/test/runtime/samples/transition-js-nested-each-keyed/_config.js b/test/runtime/samples/transition-js-nested-each-keyed/_config.js new file mode 100644 index 0000000000..7011ada5aa --- /dev/null +++ b/test/runtime/samples/transition-js-nested-each-keyed/_config.js @@ -0,0 +1,28 @@ +export default { + skipIntroByDefault: true, + nestedTransitions: true, + + data: { + x: false, + things: ['a'] + }, + + test(assert, component, target, window, raf) { + component.set({ x: true }); + + const div = target.querySelector('div'); + assert.equal(div.foo, 0); + + raf.tick(100); + assert.equal(div.foo, 1); + + component.set({ x: false }); + assert.htmlEqual(target.innerHTML, '
'); + + raf.tick(150); + assert.equal(div.foo, 0.5); + + raf.tick(200); + assert.htmlEqual(target.innerHTML, ''); + }, +}; diff --git a/test/runtime/samples/transition-js-nested-each-keyed/main.html b/test/runtime/samples/transition-js-nested-each-keyed/main.html new file mode 100644 index 0000000000..335380695d --- /dev/null +++ b/test/runtime/samples/transition-js-nested-each-keyed/main.html @@ -0,0 +1,20 @@ +{#if x} + {#each things as thing (thing)} +
+ {/each} +{/if} + + \ No newline at end of file diff --git a/test/runtime/samples/transition-js-nested-each/_config.js b/test/runtime/samples/transition-js-nested-each/_config.js new file mode 100644 index 0000000000..7011ada5aa --- /dev/null +++ b/test/runtime/samples/transition-js-nested-each/_config.js @@ -0,0 +1,28 @@ +export default { + skipIntroByDefault: true, + nestedTransitions: true, + + data: { + x: false, + things: ['a'] + }, + + test(assert, component, target, window, raf) { + component.set({ x: true }); + + const div = target.querySelector('div'); + assert.equal(div.foo, 0); + + raf.tick(100); + assert.equal(div.foo, 1); + + component.set({ x: false }); + assert.htmlEqual(target.innerHTML, '
'); + + raf.tick(150); + assert.equal(div.foo, 0.5); + + raf.tick(200); + assert.htmlEqual(target.innerHTML, ''); + }, +}; diff --git a/test/runtime/samples/transition-js-nested-each/main.html b/test/runtime/samples/transition-js-nested-each/main.html new file mode 100644 index 0000000000..c47940c1cf --- /dev/null +++ b/test/runtime/samples/transition-js-nested-each/main.html @@ -0,0 +1,20 @@ +{#if x} + {#each things as thing} +
+ {/each} +{/if} + + \ No newline at end of file