diff --git a/CHANGELOG.md b/CHANGELOG.md index c1adaf0b76..94d7326eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Svelte changelog +## 3.5.3 + +* Don't double-destroy keyed each blocks with outros ([#3055](https://github.com/sveltejs/svelte/issues/3055)) + +## 3.5.2 + +* Prevent duplicated outros causing errors ([#3001](https://github.com/sveltejs/svelte/issues/3001)) +* Fix automatic name generation ([#2843](https://github.com/sveltejs/svelte/issues/2843)) +* Fix .d.ts stubs ([#3009](https://github.com/sveltejs/svelte/pull/3009)) +* Don't strip non-breaking spaces ([#3014](https://github.com/sveltejs/svelte/issues/3014)) +* Fix `requestAnimationFrame` context ([#2933](https://github.com/sveltejs/svelte/issues/2933)) +* Allow space before attribute value ([#3026](https://github.com/sveltejs/svelte/issues/3026)) +* Remove null/undefined attributes ([#1434](https://github.com/sveltejs/svelte/issues/1434)) +* Fix whitespace in static markup ([#3030](https://github.com/sveltejs/svelte/pull/3030)) + ## 3.5.1 * Accommodate webpack idiosyncracies diff --git a/package-lock.json b/package-lock.json index aea08689b1..8a28bf9997 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.5.1", + "version": "3.5.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -253,7 +253,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, @@ -676,7 +676,7 @@ }, "commander": { "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, @@ -1876,7 +1876,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -2357,7 +2357,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -2384,7 +2384,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -2790,7 +2790,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -3527,7 +3527,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { diff --git a/package.json b/package.json index 37bd6f75e7..9047af529e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.5.1", + "version": "3.5.3", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte b/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte index fc6725e894..04b4de5cf2 100644 --- a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte +++ b/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte @@ -7,7 +7,7 @@ - {#each [0, 1, 2, 3, 4, 5, 6, 7, 8] as layer} + {#each layers as layer} - {#each [0, 1, 2, 3, 4, 5, 6, 7, 8] as layer} + {#each layers as layer} =10.0.0" diff --git a/src/compiler/compile/render-dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render-dom/wrappers/AwaitBlock.ts index 73ca3fe4f5..321f7fd9ff 100644 --- a/src/compiler/compile/render-dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render-dom/wrappers/AwaitBlock.ts @@ -177,7 +177,7 @@ export default class AwaitBlockWrapper extends Wrapper { `); if (has_transitions) { - block.builders.intro.add_line(`${info}.block.i();`); + block.builders.intro.add_line(`@transition_in(${info}.block);`); } const conditions = []; @@ -216,7 +216,7 @@ export default class AwaitBlockWrapper extends Wrapper { block.builders.outro.add_block(deindent` for (let #i = 0; #i < 3; #i += 1) { const block = ${info}.blocks[#i]; - if (block) block.o(); + @transition_out(block); } `); } diff --git a/src/compiler/compile/render-dom/wrappers/EachBlock.ts b/src/compiler/compile/render-dom/wrappers/EachBlock.ts index a117a717c5..9e4f0689fa 100644 --- a/src/compiler/compile/render-dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render-dom/wrappers/EachBlock.ts @@ -204,7 +204,7 @@ export default class EachBlockWrapper extends Wrapper { if (this.block.has_intro_method || this.block.has_outro_method) { block.builders.intro.add_block(deindent` - for (var #i = 0; #i < ${this.vars.data_length}; #i += 1) ${this.vars.iterations}[#i].i(); + for (var #i = 0; #i < ${this.vars.data_length}; #i += 1) @transition_in(${this.vars.iterations}[#i]); `); } @@ -362,7 +362,7 @@ export default class EachBlockWrapper extends Wrapper { if (this.block.has_outros) { block.builders.outro.add_block(deindent` - for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].o(); + for (#i = 0; #i < ${view_length}; #i += 1) @transition_out(${iterations}[#i]); `); } @@ -425,24 +425,6 @@ export default class EachBlockWrapper extends Wrapper { all_dependencies.add(dependency); }); - const outro_block = this.block.has_outros && block.get_unique_name('outro_block'); - if (outro_block) { - block.builders.init.add_block(deindent` - function ${outro_block}(i, detaching, local) { - if (${iterations}[i]) { - if (detaching) { - @on_outro(() => { - ${iterations}[i].d(detaching); - ${iterations}[i] = null; - }); - } - - ${iterations}[i].o(local); - } - } - `); - } - const condition = Array.from(all_dependencies) .map(dependency => `changed.${dependency}`) .join(' || '); @@ -454,18 +436,18 @@ export default class EachBlockWrapper extends Wrapper { ? deindent` if (${iterations}[#i]) { ${iterations}[#i].p(changed, child_ctx); - ${has_transitions && `${iterations}[#i].i(1);`} + ${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`} } else { ${iterations}[#i] = ${create_each_block}(child_ctx); ${iterations}[#i].c(); - ${has_transitions && `${iterations}[#i].i(1);`} + ${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`} ${iterations}[#i].m(${update_mount_node}, ${anchor}); } ` : deindent` ${iterations}[#i] = ${create_each_block}(child_ctx); ${iterations}[#i].c(); - ${has_transitions && `${iterations}[#i].i(1);`} + ${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`} ${iterations}[#i].m(${update_mount_node}, ${anchor}); `; @@ -474,9 +456,16 @@ export default class EachBlockWrapper extends Wrapper { let remove_old_blocks; if (this.block.has_outros) { + const out = block.get_unique_name('out'); + + block.builders.init.add_block(deindent` + const ${out} = i => @transition_out(${iterations}[i], 1, () => { + ${iterations}[i] = null; + }); + `); remove_old_blocks = deindent` @group_outros(); - for (; #i < ${view_length}; #i += 1) ${outro_block}(#i, 1, 1); + for (; #i < ${view_length}; #i += 1) ${out}(#i); @check_outros(); `; } else { @@ -507,10 +496,10 @@ export default class EachBlockWrapper extends Wrapper { `); } - if (outro_block) { + if (this.block.has_outros) { block.builders.outro.add_block(deindent` ${iterations} = ${iterations}.filter(@_Boolean); - for (let #i = 0; #i < ${view_length}; #i += 1) ${outro_block}(#i, 0, 0);` + for (let #i = 0; #i < ${view_length}; #i += 1) @transition_out(${iterations}[#i]);` ); } diff --git a/src/compiler/compile/render-dom/wrappers/Element/index.ts b/src/compiler/compile/render-dom/wrappers/Element/index.ts index 1b19281461..390196160d 100644 --- a/src/compiler/compile/render-dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render-dom/wrappers/Element/index.ts @@ -335,6 +335,8 @@ export default class ElementWrapper extends Wrapper { function to_html(wrapper: ElementWrapper | TextWrapper) { if (wrapper.node.type === 'Text') { + if (wrapper.node.use_space) return ' '; + const parent = wrapper.node.parent as Element; const raw = parent && ( @@ -342,9 +344,9 @@ export default class ElementWrapper extends Wrapper { parent.name === 'style' ); - return raw + return (raw ? wrapper.node.data - : escape_html(wrapper.node.data) + : escape_html(wrapper.node.data)) .replace(/\\/g, '\\\\') .replace(/`/g, '\\`') .replace(/\$/g, '\\$'); diff --git a/src/compiler/compile/render-dom/wrappers/Fragment.ts b/src/compiler/compile/render-dom/wrappers/Fragment.ts index f2e2ab7a7a..bd20c9107f 100644 --- a/src/compiler/compile/render-dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render-dom/wrappers/Fragment.ts @@ -42,6 +42,13 @@ function link(next: Wrapper, prev: Wrapper) { if (next) next.prev = prev; } +function trimmable_at(child: INode, next_sibling: Wrapper): boolean { + // Whitespace is trimmable if one of the following is true: + // The child and its sibling share a common nearest each block (not at an each block boundary) + // The next sibling's previous node is an each block + return (next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/)) || next_sibling.node.prev.type === 'EachBlock'; +} + export default class FragmentWrapper { nodes: Wrapper[]; @@ -85,7 +92,7 @@ export default class FragmentWrapper { if (this.nodes.length === 0) { const should_trim = ( // @ts-ignore todo: probably error, should it be next_sibling.node.data? - next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.data)) : !child.has_ancestor('EachBlock') + next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock') ); if (should_trim) { diff --git a/src/compiler/compile/render-dom/wrappers/IfBlock.ts b/src/compiler/compile/render-dom/wrappers/IfBlock.ts index bce4743488..a783364d03 100644 --- a/src/compiler/compile/render-dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render-dom/wrappers/IfBlock.ts @@ -160,7 +160,7 @@ export default class IfBlockWrapper extends Wrapper { if (has_outros) { this.render_compound_with_outros(block, parent_node, parent_nodes, dynamic, vars, detaching); - block.builders.outro.add_line(`if (${name}) ${name}.o();`); + block.builders.outro.add_line(`@transition_out(${name});`); } else { this.render_compound(block, parent_node, parent_nodes, dynamic, vars, detaching); } @@ -168,7 +168,7 @@ export default class IfBlockWrapper extends Wrapper { this.render_simple(block, parent_node, parent_nodes, dynamic, vars, detaching); if (has_outros) { - block.builders.outro.add_line(`if (${name}) ${name}.o();`); + block.builders.outro.add_line(`@transition_out(${name});`); } } @@ -181,7 +181,7 @@ export default class IfBlockWrapper extends Wrapper { } if (has_intros || has_outros) { - block.builders.intro.add_line(`if (${name}) ${name}.i();`); + block.builders.intro.add_line(`@transition_in(${name});`); } if (needs_anchor) { @@ -238,7 +238,7 @@ export default class IfBlockWrapper extends Wrapper { ${name} = ${current_block_type_and}${current_block_type}(ctx); if (${name}) { ${name}.c(); - ${has_transitions && `${name}.i(1);`} + ${has_transitions && `@transition_in(${name}, 1);`} ${name}.m(${update_mount_node}, ${anchor}); } `; @@ -326,11 +326,9 @@ export default class IfBlockWrapper extends Wrapper { const destroy_old_block = deindent` @group_outros(); - @on_outro(() => { - ${if_blocks}[${previous_block_index}].d(1); + @transition_out(${if_blocks}[${previous_block_index}], 1, () => { ${if_blocks}[${previous_block_index}] = null; }); - ${name}.o(1); @check_outros(); `; @@ -340,7 +338,7 @@ export default class IfBlockWrapper extends Wrapper { ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx); ${name}.c(); } - ${has_transitions && `${name}.i(1);`} + ${has_transitions && `@transition_in(${name}, 1);`} ${name}.m(${update_mount_node}, ${anchor}); `; @@ -414,11 +412,11 @@ export default class IfBlockWrapper extends Wrapper { ? deindent` if (${name}) { ${name}.p(changed, ctx); - ${has_transitions && `${name}.i(1);`} + ${has_transitions && `@transition_in(${name}, 1);`} } else { ${name} = ${branch.block.name}(ctx); ${name}.c(); - ${has_transitions && `${name}.i(1);`} + ${has_transitions && `@transition_in(${name}, 1);`} ${name}.m(${update_mount_node}, ${anchor}); } ` @@ -426,38 +424,37 @@ export default class IfBlockWrapper extends Wrapper { if (!${name}) { ${name} = ${branch.block.name}(ctx); ${name}.c(); - ${has_transitions && `${name}.i(1);`} + ${has_transitions && `@transition_in(${name}, 1);`} ${name}.m(${update_mount_node}, ${anchor}); ${has_transitions && `} else { - ${name}.i(1);`} + @transition_in(${name}, 1);`} } `; // no `p()` here — we don't want to update outroing nodes, // as that will typically result in glitching - const exit = branch.block.has_outro_method - ? deindent` - @group_outros(); - @on_outro(() => { + if (branch.block.has_outro_method) { + block.builders.update.add_block(deindent` + if (${branch.condition}) { + ${enter} + } else if (${name}) { + @group_outros(); + @transition_out(${name}, 1, () => { + ${name} = null; + }); + @check_outros(); + } + `); + } else { + block.builders.update.add_block(deindent` + if (${branch.condition}) { + ${enter} + } else if (${name}) { ${name}.d(1); ${name} = null; - }); - - ${name}.o(1); - @check_outros(); - ` - : deindent` - ${name}.d(1); - ${name} = null; - `; - - block.builders.update.add_block(deindent` - if (${branch.condition}) { - ${enter} - } else if (${name}) { - ${exit} - } - `); + } + `); + } block.builders.destroy.add_line(`${if_name}${name}.d(${detaching});`); } diff --git a/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts index 696fff51c0..8d5c751add 100644 --- a/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts @@ -423,10 +423,9 @@ export default class InlineComponentWrapper extends Wrapper { if (${name}) { @group_outros(); const old_component = ${name}; - @on_outro(() => { - old_component.$destroy(); + @transition_out(old_component.$$.fragment, 1, () => { + @destroy_component(old_component); }); - old_component.$$.fragment.o(1); @check_outros(); } @@ -437,7 +436,7 @@ export default class InlineComponentWrapper extends Wrapper { ${munged_handlers} ${name}.$$.fragment.c(); - ${name}.$$.fragment.i(1); + @transition_in(${name}.$$.fragment, 1); @mount_component(${name}, ${update_mount_node}, ${anchor}); } else { ${name} = null; @@ -446,7 +445,7 @@ export default class InlineComponentWrapper extends Wrapper { `); block.builders.intro.add_block(deindent` - if (${name}) ${name}.$$.fragment.i(#local); + @transition_in(${name}.$$.fragment, #local); `); if (updates.length) { @@ -458,10 +457,10 @@ export default class InlineComponentWrapper extends Wrapper { } block.builders.outro.add_line( - `if (${name}) ${name}.$$.fragment.o(#local);` + `if (${name}) @transition_out(${name}.$$.fragment, #local);` ); - block.builders.destroy.add_line(`if (${name}) ${name}.$destroy(${parent_node ? '' : 'detaching'});`); + block.builders.destroy.add_line(`if (${name}) @destroy_component(${name}, ${parent_node ? '' : 'detaching'});`); } else { const expression = this.node.name === 'svelte:self' ? '__svelte:self__' // TODO conflict-proof this @@ -490,7 +489,7 @@ export default class InlineComponentWrapper extends Wrapper { ); block.builders.intro.add_block(deindent` - ${name}.$$.fragment.i(#local); + @transition_in(${name}.$$.fragment, #local); `); if (updates.length) { @@ -501,11 +500,11 @@ export default class InlineComponentWrapper extends Wrapper { } block.builders.destroy.add_block(deindent` - ${name}.$destroy(${parent_node ? '' : 'detaching'}); + @destroy_component(${name}, ${parent_node ? '' : 'detaching'}); `); block.builders.outro.add_line( - `${name}.$$.fragment.o(#local);` + `@transition_out(${name}.$$.fragment, #local);` ); } } diff --git a/src/compiler/compile/render-dom/wrappers/Slot.ts b/src/compiler/compile/render-dom/wrappers/Slot.ts index 5347b87de4..3376a797b8 100644 --- a/src/compiler/compile/render-dom/wrappers/Slot.ts +++ b/src/compiler/compile/render-dom/wrappers/Slot.ts @@ -142,11 +142,11 @@ export default class SlotWrapper extends Wrapper { `); block.builders.intro.add_line( - `if (${slot} && ${slot}.i) ${slot}.i(#local);` + `@transition_in(${slot}, #local);` ); block.builders.outro.add_line( - `if (${slot} && ${slot}.o) ${slot}.o(#local);` + `@transition_out(${slot}, #local);` ); let update_conditions = [...this.dependencies].map(name => `changed.${name}`).join(' || '); diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 7624d5c829..753d1c8a98 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -2,6 +2,7 @@ import { add_render_callback, flush, schedule_update, dirty_components } from '. import { current_component, set_current_component } from './lifecycle'; import { blank_object, is_function, run, run_all, noop } from './utils'; import { children } from './dom'; +import { transition_in } from './transitions'; // eslint-disable-next-line @typescript-eslint/class-name-casing interface T$$ { @@ -49,10 +50,11 @@ export function mount_component(component, target, anchor) { after_render.forEach(add_render_callback); } -function destroy(component, detaching) { - if (component.$$) { +export function destroy_component(component, detaching) { + if (component.$$.fragment) { run_all(component.$$.on_destroy); - component.$$.fragment.d(detaching); + + if (detaching) component.$$.fragment.d(1); // TODO null out other refs, including component.$$ (but need to // preserve final state?) @@ -123,7 +125,7 @@ export function init(component, options, instance, create_fragment, not_equal, p $$.fragment!.c(); } - if (options.intro && component.$$.fragment.i) component.$$.fragment.i(); + if (options.intro) transition_in(component.$$.fragment); mount_component(component, options.target, options.anchor); flush(); } @@ -153,7 +155,7 @@ if (typeof HTMLElement !== 'undefined') { } $destroy() { - destroy(this, true); + destroy_component(this, 1); this.$destroy = noop; } @@ -178,7 +180,7 @@ export class SvelteComponent { $$: T$$; $destroy() { - destroy(this, true); + destroy_component(this, 1); this.$destroy = noop; } diff --git a/src/runtime/internal/await-block.ts b/src/runtime/internal/await-block.ts index 9527b000ca..a3313497ff 100644 --- a/src/runtime/internal/await-block.ts +++ b/src/runtime/internal/await-block.ts @@ -1,5 +1,5 @@ import { assign, is_promise } from './utils'; -import { check_outros, group_outros, on_outro } from './transitions'; +import { check_outros, group_outros, transition_in, transition_out } from './transitions'; import { flush } from '../internal/scheduler'; export function handle_promise(promise, info) { @@ -18,11 +18,9 @@ export function handle_promise(promise, info) { info.blocks.forEach((block, i) => { if (i !== index && block) { group_outros(); - on_outro(() => { - block.d(1); + transition_out(block, 1, () => { info.blocks[i] = null; }); - block.o(1); check_outros(); } }); @@ -31,7 +29,7 @@ export function handle_promise(promise, info) { } block.c(); - if (block.i) block.i(1); + transition_in(block, 1); block.m(info.mount(), info.anchor); flush(); diff --git a/src/runtime/internal/keyed-each.ts b/src/runtime/internal/keyed-each.ts index f13c858897..e18ae61770 100644 --- a/src/runtime/internal/keyed-each.ts +++ b/src/runtime/internal/keyed-each.ts @@ -1,4 +1,4 @@ -import { on_outro } from './transitions'; +import { transition_in, transition_out } from './transitions'; export function destroy_block(block, lookup) { block.d(1); @@ -6,11 +6,9 @@ export function destroy_block(block, lookup) { } export function outro_and_destroy_block(block, lookup) { - on_outro(() => { - destroy_block(block, lookup); + transition_out(block, 1, () => { + lookup.delete(block.key); }); - - block.o(1); } export function fix_and_destroy_block(block, lookup) { @@ -57,7 +55,7 @@ export function update_keyed_each(old_blocks, changed, get_key, dynamic, ctx, li const did_move = new Set(); function insert(block) { - if (block.i) block.i(1); + transition_in(block, 1); block.m(node, next); lookup.set(block.key, block); next = block.first; diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 80c76ffec5..bbb24ba9ee 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -23,6 +23,7 @@ function dispatch(node: Element, direction: boolean, kind: 'start' | 'end') { node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`)); } +const outroing = new Set(); let outros; export function group_outros() { @@ -38,9 +39,30 @@ export function check_outros() { } } -export function on_outro(callback) { - outros.callbacks.push(callback); +export function transition_in(block, local?: 0 | 1) { + if (block && block.i) { + outroing.delete(block); + block.i(local); + } } + +export function transition_out(block, local: 0 | 1, callback) { + if (block && block.o) { + if (outroing.has(block)) return; + outroing.add(block); + + outros.callbacks.push(() => { + outroing.delete(block); + if (callback) { + block.d(1); + callback(); + } + }); + + block.o(local); + } +} + type TransitionFn = (node: Element, params: any) => TransitionConfig; export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) { diff --git a/test/js/samples/component-static-array/expected.js b/test/js/samples/component-static-array/expected.js index 875c741076..6997f431dd 100644 --- a/test/js/samples/component-static-array/expected.js +++ b/test/js/samples/component-static-array/expected.js @@ -1,10 +1,13 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + destroy_component, init, mount_component, noop, - safe_not_equal + safe_not_equal, + transition_in, + transition_out } from "svelte/internal"; function create_fragment(ctx) { @@ -26,18 +29,18 @@ function create_fragment(ctx) { i(local) { if (current) return; - nested.$$.fragment.i(local); + transition_in(nested.$$.fragment, local); current = true; }, o(local) { - nested.$$.fragment.o(local); + transition_out(nested.$$.fragment, local); current = false; }, d(detaching) { - nested.$destroy(detaching); + destroy_component(nested, detaching); } }; } diff --git a/test/js/samples/component-static-immutable/expected.js b/test/js/samples/component-static-immutable/expected.js index 6612a7f1b6..4b33d537ca 100644 --- a/test/js/samples/component-static-immutable/expected.js +++ b/test/js/samples/component-static-immutable/expected.js @@ -1,10 +1,13 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + destroy_component, init, mount_component, noop, - not_equal + not_equal, + transition_in, + transition_out } from "svelte/internal"; function create_fragment(ctx) { @@ -26,18 +29,18 @@ function create_fragment(ctx) { i(local) { if (current) return; - nested.$$.fragment.i(local); + transition_in(nested.$$.fragment, local); current = true; }, o(local) { - nested.$$.fragment.o(local); + transition_out(nested.$$.fragment, local); current = false; }, d(detaching) { - nested.$destroy(detaching); + destroy_component(nested, detaching); } }; } diff --git a/test/js/samples/component-static-immutable2/expected.js b/test/js/samples/component-static-immutable2/expected.js index 6612a7f1b6..4b33d537ca 100644 --- a/test/js/samples/component-static-immutable2/expected.js +++ b/test/js/samples/component-static-immutable2/expected.js @@ -1,10 +1,13 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + destroy_component, init, mount_component, noop, - not_equal + not_equal, + transition_in, + transition_out } from "svelte/internal"; function create_fragment(ctx) { @@ -26,18 +29,18 @@ function create_fragment(ctx) { i(local) { if (current) return; - nested.$$.fragment.i(local); + transition_in(nested.$$.fragment, local); current = true; }, o(local) { - nested.$$.fragment.o(local); + transition_out(nested.$$.fragment, local); current = false; }, d(detaching) { - nested.$destroy(detaching); + destroy_component(nested, detaching); } }; } diff --git a/test/js/samples/component-static/expected.js b/test/js/samples/component-static/expected.js index 12ebeb28b7..5a031a32a4 100644 --- a/test/js/samples/component-static/expected.js +++ b/test/js/samples/component-static/expected.js @@ -1,10 +1,13 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + destroy_component, init, mount_component, noop, - safe_not_equal + safe_not_equal, + transition_in, + transition_out } from "svelte/internal"; function create_fragment(ctx) { @@ -26,18 +29,18 @@ function create_fragment(ctx) { i(local) { if (current) return; - nested.$$.fragment.i(local); + transition_in(nested.$$.fragment, local); current = true; }, o(local) { - nested.$$.fragment.o(local); + transition_out(nested.$$.fragment, local); current = false; }, d(detaching) { - nested.$destroy(detaching); + destroy_component(nested, detaching); } }; } diff --git a/test/js/samples/dynamic-import/expected.js b/test/js/samples/dynamic-import/expected.js index 2d51bcf60d..9a6a67bcd9 100644 --- a/test/js/samples/dynamic-import/expected.js +++ b/test/js/samples/dynamic-import/expected.js @@ -1,10 +1,13 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + destroy_component, init, mount_component, noop, - safe_not_equal + safe_not_equal, + transition_in, + transition_out } from "svelte/internal"; import LazyLoad from "./LazyLoad.svelte"; @@ -27,18 +30,18 @@ function create_fragment(ctx) { i(local) { if (current) return; - lazyload.$$.fragment.i(local); + transition_in(lazyload.$$.fragment, local); current = true; }, o(local) { - lazyload.$$.fragment.o(local); + transition_out(lazyload.$$.fragment, local); current = false; }, d(detaching) { - lazyload.$destroy(detaching); + destroy_component(lazyload, detaching); } }; } diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index 20ac461c76..42a21aa496 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -1,13 +1,16 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + destroy_component, detach, init, insert, mount_component, noop, safe_not_equal, - space + space, + transition_in, + transition_out } from "svelte/internal"; import Imported from "Imported.svelte"; @@ -36,27 +39,27 @@ function create_fragment(ctx) { i(local) { if (current) return; - imported.$$.fragment.i(local); + transition_in(imported.$$.fragment, local); - nonimported.$$.fragment.i(local); + transition_in(nonimported.$$.fragment, local); current = true; }, o(local) { - imported.$$.fragment.o(local); - nonimported.$$.fragment.o(local); + transition_out(imported.$$.fragment, local); + transition_out(nonimported.$$.fragment, local); current = false; }, d(detaching) { - imported.$destroy(detaching); + destroy_component(imported, detaching); if (detaching) { detach(t); } - nonimported.$destroy(detaching); + destroy_component(nonimported, detaching); } }; } diff --git a/test/js/samples/transition-local/expected.js b/test/js/samples/transition-local/expected.js index 2df99a4c87..53bce5f5d0 100644 --- a/test/js/samples/transition-local/expected.js +++ b/test/js/samples/transition-local/expected.js @@ -9,7 +9,8 @@ import { init, insert, noop, - safe_not_equal + safe_not_equal, + transition_in } from "svelte/internal"; // (8:0) {#if x} @@ -34,10 +35,10 @@ function create_if_block(ctx) { if (!if_block) { if_block = create_if_block_1(ctx); if_block.c(); - if_block.i(1); + transition_in(if_block, 1); if_block.m(if_block_anchor.parentNode, if_block_anchor); } else { - if_block.i(1); + transition_in(if_block, 1); } } else if (if_block) { if_block.d(1); diff --git a/test/js/samples/transition-repeated-outro/expected.js b/test/js/samples/transition-repeated-outro/expected.js new file mode 100644 index 0000000000..959bd47201 --- /dev/null +++ b/test/js/samples/transition-repeated-outro/expected.js @@ -0,0 +1,129 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponent, + check_outros, + create_out_transition, + detach, + element, + empty, + group_outros, + init, + insert, + safe_not_equal, + transition_in, + transition_out +} from "svelte/internal"; +import { fade } from "svelte/transition"; + +// (7:0) {#if num < 5} +function create_if_block(ctx) { + var div, div_outro, current; + + return { + c() { + div = element("div"); + div.innerHTML = `

wheeee

`; + }, + + m(target, anchor) { + insert(target, div, anchor); + current = true; + }, + + i(local) { + if (current) return; + if (div_outro) div_outro.end(1); + + current = true; + }, + + o(local) { + div_outro = create_out_transition(div, fade, {}); + + current = false; + }, + + d(detaching) { + if (detaching) { + detach(div); + if (div_outro) div_outro.end(); + } + } + }; +} + +function create_fragment(ctx) { + var if_block_anchor, current; + + var if_block = (ctx.num < 5) && create_if_block(ctx); + + return { + c() { + if (if_block) if_block.c(); + if_block_anchor = empty(); + }, + + m(target, anchor) { + if (if_block) if_block.m(target, anchor); + insert(target, if_block_anchor, anchor); + current = true; + }, + + p(changed, ctx) { + if (ctx.num < 5) { + if (!if_block) { + if_block = create_if_block(ctx); + if_block.c(); + transition_in(if_block, 1); + if_block.m(if_block_anchor.parentNode, if_block_anchor); + } else { + transition_in(if_block, 1); + } + } else if (if_block) { + group_outros(); + transition_out(if_block, 1, () => { + if_block = null; + }); + check_outros(); + } + }, + + i(local) { + if (current) return; + transition_in(if_block); + current = true; + }, + + o(local) { + transition_out(if_block); + current = false; + }, + + d(detaching) { + if (if_block) if_block.d(detaching); + + if (detaching) { + detach(if_block_anchor); + } + } + }; +} + +function instance($$self, $$props, $$invalidate) { + let { num = 1 } = $$props; + + $$self.$set = $$props => { + if ('num' in $$props) $$invalidate('num', num = $$props.num); + }; + + return { num }; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, instance, create_fragment, safe_not_equal, ["num"]); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/transition-repeated-outro/input.svelte b/test/js/samples/transition-repeated-outro/input.svelte new file mode 100644 index 0000000000..d5114846b8 --- /dev/null +++ b/test/js/samples/transition-repeated-outro/input.svelte @@ -0,0 +1,11 @@ + + +{#if num < 5} +
+

wheeee

+
+{/if} \ No newline at end of file diff --git a/test/runtime/samples/each-block-keyed-shift/Nested.svelte b/test/runtime/samples/each-block-keyed-shift/Nested.svelte new file mode 100644 index 0000000000..82df711742 --- /dev/null +++ b/test/runtime/samples/each-block-keyed-shift/Nested.svelte @@ -0,0 +1,5 @@ + + +

{title}

\ No newline at end of file diff --git a/test/runtime/samples/each-block-keyed-shift/_config.js b/test/runtime/samples/each-block-keyed-shift/_config.js new file mode 100644 index 0000000000..44ca8447f0 --- /dev/null +++ b/test/runtime/samples/each-block-keyed-shift/_config.js @@ -0,0 +1,27 @@ +export default { + props: { + titles: [{ name: 'a', }, { name: 'b' }, { name: 'c' }] + }, + + html: ` +

a

+

b

+

c

+ `, + + test({ assert, component, target }) { + component.titles = [{ name: 'b' }, { name: 'c' }]; + + assert.htmlEqual(target.innerHTML, ` +

b

+

c

+ `); + + component.titles = [{ name: 'c' }]; + + assert.htmlEqual(target.innerHTML, ` +

c

+ `); + + } +}; diff --git a/test/runtime/samples/each-block-keyed-shift/main.svelte b/test/runtime/samples/each-block-keyed-shift/main.svelte new file mode 100644 index 0000000000..b8954e17fd --- /dev/null +++ b/test/runtime/samples/each-block-keyed-shift/main.svelte @@ -0,0 +1,9 @@ + + +{#each titles as title (title.name)} + +{/each} \ No newline at end of file diff --git a/test/runtime/samples/fragment-trailing-whitespace/_config.js b/test/runtime/samples/fragment-trailing-whitespace/_config.js new file mode 100644 index 0000000000..2251d6dae8 --- /dev/null +++ b/test/runtime/samples/fragment-trailing-whitespace/_config.js @@ -0,0 +1,16 @@ +const message = "the quick brown fox jumps over the lazy dog"; +const expected = [...message].map(c => `${c + " "}`).join(""); + +export default { + props: { + message + }, + + async test({ assert, target }) { + const firstSpanList = target.children[0]; + assert.equal(firstSpanList.innerHTML, expected); + + const secondSpanList = target.children[1]; + assert.equal(secondSpanList.innerHTML, expected); + } +}; diff --git a/test/runtime/samples/fragment-trailing-whitespace/main.svelte b/test/runtime/samples/fragment-trailing-whitespace/main.svelte new file mode 100644 index 0000000000..f36f2694a6 --- /dev/null +++ b/test/runtime/samples/fragment-trailing-whitespace/main.svelte @@ -0,0 +1,11 @@ + + +
+ {#each message as char} + {char} + {/each} +
+ +
{#each message as char}{char} {/each}
\ No newline at end of file diff --git a/test/runtime/samples/script-style-non-top-level/_config.js b/test/runtime/samples/script-style-non-top-level/_config.js index 1aade72239..86eddf4611 100644 --- a/test/runtime/samples/script-style-non-top-level/_config.js +++ b/test/runtime/samples/script-style-non-top-level/_config.js @@ -2,7 +2,7 @@ export default { html: `
- +
` }; \ No newline at end of file diff --git a/test/runtime/samples/script-style-non-top-level/main.svelte b/test/runtime/samples/script-style-non-top-level/main.svelte index 94cf72e4bd..73b0dfcf38 100644 --- a/test/runtime/samples/script-style-non-top-level/main.svelte +++ b/test/runtime/samples/script-style-non-top-level/main.svelte @@ -1,4 +1,4 @@
- +
\ No newline at end of file