Repository: sveltejs/svelte (JavaScript)
Date: 2020-12-31 03:23:17+00:00
Task type: Bug Fix

Issue:
Local transition in #each block is is disabled if parent node has out transition cancelled 
Is this about svelte@next? This project is currently in a pre-release stage and breaking changes may occur at any time. Please do not post any kind of bug reports or questions on GitHub about it.

No

**Describe the bug**
A clear and concise description of what the bug is.

Items inside of an #each block have a local out transition. The parent node has a transition defined on it. If said parent node is unmounted, but then re-mounted before its transition finishes, the transitions in the items in the each block no longer work.

REPL: https://svelte.dev/repl/388e9996f8464f88b9e65a569c34912d?version=3.31.0

**Logs**
Please include browser console and server logs around the time this bug occurred.

N/A

**To Reproduce**
To help us help you, if you've found a bug please consider the following:

* If you can demonstrate the bug using https://svelte.dev/repl, please do.

I gotchu boo:  https://svelte.dev/repl/388e9996f8464f88b9e65a569c34912d?version=3.31.0

* If that's not possible, we recommend creating a small repo that illustrates the problem.
* Reproductions should be small, self-contained, correct examples – http://sscce.org.

Occasionally, this won't be possible, and that's fine – we still appreciate you raising the issue. But please understand that Svelte is run by unpaid volunteers in their free time, and issues that follow these instructions will get fixed faster.

**Expected behavior**
A clear and concise description of what you expected to happen.

out transition should work

**Stacktraces**
If you have a stack trace to include, we recommend putting inside a `<details>` block for the sake of the thread's readability:

N/A

<details>
  <summary>Stack trace</summary>

  Stack trace goes here...
</details>

**Information about your Svelte project:**
To make your life easier, just run `npx envinfo --system --npmPackages svelte,rollup,webpack --binaries --browsers` and paste the output here.

N/A

- Your browser and the version: (e.x. Chrome 52.1, Firefox 48.0, IE 10)

Chrome 87

- Your operating system: (e.x. OS X 10, Ubuntu Linux 19.10, Windows XP, etc)

Mac

- Svelte version (Please check you can reproduce the issue with the latest release!)

Whatever's in the REPL

- Whether your project uses Webpack or Rollup

N/A

**Severity**
How severe an issue is this bug to you? Is this annoying, blocking some users, blocking an upgrade or blocking your usage of Svelte entirely?

High

Note: the more honest and specific you are here the more we will take you seriously.

**Additional context**
Add any other context about the problem here.



Issue discussion:

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
 * Fix checkbox `bind:group` in keyed `{#each}` where the array can be reordered ([#5779](https://github.com/sveltejs/svelte/issues/5779))
 * Fix checkbox `bind:group` in nested `{#each}` contexts ([#5811](https://github.com/sveltejs/svelte/issues/5811))
 * Add graphics roles as known ARIA roles ([#5822](https://github.com/sveltejs/svelte/pull/5822))
+* Fix local transitions if a parent has a cancelled outro transition ([#5822](https://github.com/sveltejs/svelte/issues/5822))
 
 ## 3.31.0
 
diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts
--- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts
+++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts
@@ -450,7 +450,7 @@ export default class EachBlockWrapper extends Wrapper {
 			this.block.maintain_context = true;
 
 			this.updates.push(b`
-				const ${this.vars.each_block_value} = ${snippet};
+				${this.vars.each_block_value} = ${snippet};
 				${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`}
 
 				${this.block.has_outros && b`@group_outros();`}


Test patch:
diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js
--- a/test/js/samples/each-block-keyed-animated/expected.js
+++ b/test/js/samples/each-block-keyed-animated/expected.js
@@ -94,7 +94,7 @@ function create_fragment(ctx) {
 		},
 		p(ctx, [dirty]) {
 			if (dirty & /*things*/ 1) {
-				const each_value = /*things*/ ctx[0];
+				each_value = /*things*/ ctx[0];
 				for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].r();
 				each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, fix_and_destroy_block, create_each_block, each_1_anchor, get_each_context);
 				for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].a();
diff --git a/test/js/samples/each-block-keyed/expected.js b/test/js/samples/each-block-keyed/expected.js
--- a/test/js/samples/each-block-keyed/expected.js
+++ b/test/js/samples/each-block-keyed/expected.js
@@ -79,7 +79,7 @@ function create_fragment(ctx) {
 		},
 		p(ctx, [dirty]) {
 			if (dirty & /*things*/ 1) {
-				const each_value = /*things*/ ctx[0];
+				each_value = /*things*/ ctx[0];
 				each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, destroy_block, create_each_block, each_1_anchor, get_each_context);
 			}
 		},
diff --git a/test/runtime/samples/transition-js-each-outro-cancelled/_config.js b/test/runtime/samples/transition-js-each-outro-cancelled/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-outro-cancelled/_config.js
@@ -0,0 +1,57 @@
+export default {
+	html: '<section></section>',
+	async test({ assert, component, target, raf }) {
+		await component.add();
+		await component.add();
+
+		let time = 0;
+
+		assert.htmlEqual(target.innerHTML, `
+			<section>
+				<div t="0">Thing 1</div>
+				<div t="0">Thing 2</div>
+			</section>
+		`);
+
+		raf.tick(time += 400);
+
+		assert.htmlEqual(target.innerHTML, `
+			<section>
+				<div t="1">Thing 1</div>
+				<div t="1">Thing 2</div>
+			</section>
+		`);
+
+		await component.toggle();
+		// transition halfway
+		raf.tick(time += 200);
+
+		assert.htmlEqual(target.innerHTML, `
+			<section t="0.5">
+				<div t="1">Thing 1</div>
+				<div t="1">Thing 2</div>
+			</section>
+		`);
+
+		await component.toggle();
+		// transition back
+		raf.tick(time += 200);
+
+		assert.htmlEqual(target.innerHTML, `
+			<section t="1">
+				<div t="1">Thing 1</div>
+				<div t="1">Thing 2</div>
+			</section>
+		`);
+
+		await component.remove(1);
+
+		raf.tick(time += 400);
+
+		assert.htmlEqual(target.innerHTML, `
+			<section t="1">
+				<div t="1">Thing 2</div>
+			</section>
+		`);
+	}
+};
diff --git a/test/runtime/samples/transition-js-each-outro-cancelled/main.svelte b/test/runtime/samples/transition-js-each-outro-cancelled/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-outro-cancelled/main.svelte
@@ -0,0 +1,29 @@
+<script>
+	function fade(node) {
+		return {
+			duration: 400,
+			tick(t) {
+				node.setAttribute('t', t);
+			}
+		};
+	}
+
+	let shown = true;
+	let _id = 1;
+	let items = [];
+
+	export const toggle = () => (shown = !shown);
+	export const add = () => {
+		items = items.concat({ _id, name: `Thing ${_id}` });
+		_id++;
+	};
+	export const remove = (id) => (items = items.filter(({ _id }) => _id !== id));
+</script>
+
+{#if shown}
+	<section transition:fade>
+		{#each items as thing (thing._id)}
+			<div in:fade|local out:fade|local>{thing.name}</div>
+		{/each}
+	</section>
+{/if}