diff --git a/src/generators/nodes/EachBlock.ts b/src/generators/nodes/EachBlock.ts index fa57058cbad1..bf9ea29d1442 100644 --- a/src/generators/nodes/EachBlock.ts +++ b/src/generators/nodes/EachBlock.ts @@ -1,7 +1,6 @@ import deindent from '../../utils/deindent'; import Node from './shared/Node'; import ElseBlock from './ElseBlock'; -import { DomGenerator } from '../dom/index'; import Block from '../dom/Block'; import createDebuggingComment from '../../utils/createDebuggingComment'; @@ -254,6 +253,11 @@ export default class EachBlock extends Node { const head = block.getUniqueName(`${each}_head`); const last = block.getUniqueName(`${each}_last`); const expected = block.getUniqueName(`${each}_expected`); + const keep = block.getUniqueName(`${each}_keep`); + const all = block.getUniqueName(`${each}_all`); + const inserts = block.getUniqueName(`${each}_inserts`); + const moves = block.getUniqueName(`${each}_moves`); + const next_iteration = block.getUniqueName(`${each}_next_iteration`); block.addVariable(lookup, `@blankObject()`); block.addVariable(head); @@ -318,7 +322,7 @@ export default class EachBlock extends Node { const dynamic = this.block.hasUpdateMethod; - let destroy; + const fn_destroy = block.getUniqueName('destroy'); if (this.block.hasOutroMethod) { const fn = block.getUniqueName(`${each}_outro`); block.builders.init.addBlock(deindent` @@ -329,63 +333,56 @@ export default class EachBlock extends Node { ${lookup}[iteration.key] = null; }); } - `); - - destroy = deindent` - while (${expected}) { - ${fn}(${expected}); - ${expected} = ${expected}.next; - } - - for (#i = 0; #i < discard_pile.length; #i += 1) { - if (discard_pile[#i].discard) { - ${fn}(discard_pile[#i]); + function ${fn_destroy}(${all}, ${keep}) { + for (var key_all in ${all}) { + if (!${keep}[key_all]) { + ${fn}(${all}[key_all]); + } } } - `; + `); } else { const fn = block.getUniqueName(`${each}_destroy`); block.builders.init.addBlock(deindent` function ${fn}(iteration) { - iteration.u(); + var first = iteration.first + if (first && first.parentNode) { + iteration.u(); + } iteration.d(); ${lookup}[iteration.key] = null; } - `); - - destroy = deindent` - while (${expected}) { - ${fn}(${expected}); - ${expected} = ${expected}.next; - } - - for (#i = 0; #i < discard_pile.length; #i += 1) { - var ${iteration} = discard_pile[#i]; - if (${iteration}.discard) { - ${fn}(${iteration}); + function ${fn_destroy}(${all}, ${keep}) { + for (var key_all in ${all}) { + if (!${keep}[key_all]) { + ${fn}(${all}[key_all]); + } } } - `; + `); } block.builders.update.addBlock(deindent` var ${each_block_value} = ${snippet}; - var ${expected} = ${head}; var ${last} = null; - var rendered = {}; - var all = {}; + var ${keep} = {}; + var ${all} = {}; + var ${inserts} = {}; + var ${moves} = {}; + var ${next_iteration} = null; var each_all = ${head}; - while(each_all) { - all[each_all.key] = each_all; - each_all = each_all.next; - } - + while(each_all) { + ${all}[each_all.key] = each_all; + each_all = each_all.next; + } for (#i = 0; #i < ${each_block_value}.${length}; #i += 1) { var ${key} = ${each_block_value}[#i].${this.key}; var ${iteration} = ${lookup}[${key}]; + var next_data = ${each_block_value}[#i+1]; + var next = next_data && ${lookup}[next_data.${this.key}]; var ${this.each_context} = @assign({}, state, { ${this.contextProps.join(',\n')} @@ -393,66 +390,76 @@ export default class EachBlock extends Node { ${dynamic && `if (${iteration}) ${iteration}.p(changed, ${this.each_context});`} - if (${expected}) { if (${key} === ${expected}.key) { - ${expected} = ${expected}.next; + var first = ${iteration} && ${iteration}.first; + var parentNode = first && first.parentNode + if (!parentNode) ${inserts}[${key}] = ${iteration}; + else if ((${iteration} && ${iteration}.next) != next) ${moves}[${key}] = ${iteration}; + ${expected} = ${iteration}.next; } else { if (${iteration}) { - - var next_data = ${each_block_value}[#i+1]; - var next = next_data && ${lookup}[next_data.id]; - var first = ${iteration}.first; - var first_next = next && next.first; - ///insertNode(first, tbody, first_next); - ${updateMountNode}.insertBefore(first, first_next); - ${expected} = next; - ${iteration}.next = ${expected}; - var prev_data = ${each_block_value}[#i-1]; - var prev = prev_data && ${lookup}[prev_data.id]; - if (prev) { - prev.next = ${iteration}; - } - + ${moves}[${key}] = ${iteration}; + ${expected} = ${iteration}.next; } else { // key is being inserted ${iteration} = ${lookup}[${key}] = ${create_each_block}(#component, ${key}, ${this.each_context}); ${iteration}.c(); - ${iteration}.${mountOrIntro}(${updateMountNode}, ${expected}.first); - - ${expected}.last = ${iteration}; - ${iteration}.next = ${expected}; + ${inserts}[${key}] = ${iteration}; + ${expected} = null; } } } else { - // we're appending from this point forward if (${iteration}) { - ${iteration}.next = null; - ${iteration}.m(${updateMountNode}, ${anchor}); + ${moves}[${key}] = ${iteration}; + ${expected} = ${iteration}.next; } else { ${iteration} = ${lookup}[${key}] = ${create_each_block}(#component, ${key}, ${this.each_context}); ${iteration}.c(); - ${iteration}.${mountOrIntro}(${updateMountNode}, ${anchor}); + ${inserts}[${key}] = ${iteration}; + ${expected} = null; } } - if (${iteration}) { - rendered[${iteration}.key] = ${iteration}; - } + ${lookup}[${key}] = ${iteration}; - if (${last}) ${last}.next = ${iteration}; - ${iteration}.last = ${last}; - ${this.block.hasIntroMethod && `${iteration}.i(${updateMountNode}, ${anchor});`} + if (${iteration}) { + ${keep}[${iteration}.key] = ${iteration}; + } ${last} = ${iteration}; } + ${fn_destroy}(${all}, ${keep}) - if (${last}) ${last}.next = null; - - for (var key_all in all) { - if (!rendered[key_all]) all[key_all].d(); - } - - + // Work backwards due to DOM api having insertBefore + for (#i = ${each_block_value}.${length} - 1; #i >= 0; #i -= 1) { + var data = ${each_block_value}[#i]; + var ${key} = data.${this.key}; + ${iteration} = ${lookup}[${key}]; + var anchor; + ${this.block.hasOutroMethod + ? deindent` + var key_next_iteration = ${next_iteration} && ${next_iteration}.key; + var iteration_anchor = ${iteration}.next; + var key_anchor = iteration_anchor && iteration_anchor.key; + anchor = ${next_iteration} && ${next_iteration}.first; + while(iteration_anchor && key_anchor != key_next_iteration && !${keep}[key_anchor]) { + anchor = iteration_anchor && iteration_anchor.first; + iteration_anchor = iteration_anchor && iteration_anchor.next; + key_anchor = iteration_anchor && iteration_anchor.key; + }` + : deindent` + anchor = ${next_iteration} && ${next_iteration}.first; + ` } + if (${inserts}[${key}]) { + ${inserts}[${key}].${mountOrIntro}(${updateMountNode}, anchor); + } else if (${moves}[${key}]) { + ${this.block.hasIntroMethod && `${iteration}.i(${updateMountNode}, ${anchor});`} + ${moves}[${key}].m(${updateMountNode}, anchor); + } + ${iteration}.next = ${next_iteration}; + if (${next_iteration}) ${next_iteration}.last = ${iteration}; + ${next_iteration} = ${iteration}; + } ${head} = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${this.key}]; `); diff --git a/src/shared/dom.js b/src/shared/dom.js index 9003faee3e14..b665e9cd4e99 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -177,4 +177,4 @@ export function selectMultipleValue(select) { return [].map.call(select.querySelectorAll(':checked'), function(option) { return option.__value; }); -} \ No newline at end of file +} diff --git a/src/shared/transitions.js b/src/shared/transitions.js index 6d573f68fdde..bcdd42f0b54d 100644 --- a/src/shared/transitions.js +++ b/src/shared/transitions.js @@ -1,4 +1,3 @@ -import { assign, noop } from './utils.js'; import { createElement } from './dom.js'; export function linear(t) { diff --git a/test/helpers.js b/test/helpers.js index caf78894ce86..4a65b995b7ac 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,3 +1,4 @@ +import jsdom from 'jsdom'; import { JSDOM } from 'jsdom'; import assert from 'assert'; import glob from 'glob'; @@ -45,7 +46,8 @@ export function tryToReadFile(file) { } } -const { window } = new JSDOM('
'); +export const virtualConsole = new jsdom.VirtualConsole(); +const { window } = new JSDOM('
', {virtualConsole}); global.document = window.document; export function env() { diff --git a/test/runtime/index.js b/test/runtime/index.js index aaafaff21b2c..9854dd87d06f 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -11,7 +11,8 @@ import { loadSvelte, env, setupHtmlEqual, - spaces + spaces, + virtualConsole } from "../helpers.js"; let svelte; @@ -183,7 +184,7 @@ describe("runtime", () => { if (config.html) { assert.htmlEqual(target.innerHTML, config.html); } - + virtualConsole.on('debug', () => console.debug) if (config.test) { return Promise.resolve(config.test(assert, component, target, window, raf)).then(() => { component.destroy(); diff --git a/test/runtime/samples/each-block-keyed-random-permute/_config.js b/test/runtime/samples/each-block-keyed-random-permute/_config.js index c14c4cd0f7bb..904208c6a95e 100644 --- a/test/runtime/samples/each-block-keyed-random-permute/_config.js +++ b/test/runtime/samples/each-block-keyed-random-permute/_config.js @@ -41,6 +41,8 @@ export default { test( 'g' ); test( '' ); test( 'abc' ); + test( 'duqbmineapjhtlofrskcg' ); + test( 'hdnkjougmrvftewsqpailcb' ); // then, we party for ( let i = 0; i < 100; i += 1 ) test( permute() ); diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js index 0b5c3a1d1a8f..8c527c8d1721 100644 --- a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js +++ b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js @@ -62,4 +62,4 @@ export default { assert.equal( divs[1].foo, 1 ); assert.equal( divs[2].foo, 1 ); } -}; \ No newline at end of file +}; diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html index c755bb12e685..58f01fc27f65 100644 --- a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html +++ b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html @@ -15,4 +15,4 @@ } } }; - \ No newline at end of file + diff --git a/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js index 567a671d2c44..a3cb98dc8eeb 100644 --- a/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js +++ b/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js @@ -27,4 +27,4 @@ export default { assert.equal( divs[1].foo, 0.5 ); assert.equal( divs[2].foo, undefined ); } -}; \ No newline at end of file +}; diff --git a/test/runtime/samples/transition-js-each-block-keyed-outro/main.html b/test/runtime/samples/transition-js-each-block-keyed-outro/main.html index 2e735125299e..1ec5d640b14f 100644 --- a/test/runtime/samples/transition-js-each-block-keyed-outro/main.html +++ b/test/runtime/samples/transition-js-each-block-keyed-outro/main.html @@ -15,4 +15,4 @@ } } }; - \ No newline at end of file + diff --git a/test/setup.js b/test/setup.js index 8307fdd67fbb..add29849157c 100644 --- a/test/setup.js +++ b/test/setup.js @@ -36,4 +36,4 @@ require.extensions['.js'] = function(module, filename) { console.log(code); // eslint-disable-line no-console throw err; } -}; \ No newline at end of file +};