Repository: sveltejs/svelte (JavaScript)
Date: 2021-03-02 14:25:34+00:00
Task type: Bug Fix

Issue:
Transitioning when slot element changes does not update slot element
**Describe the bug**

If a transition happens on an element that contains a slot, the slot does not properly update if it is rendered again immediately after the transition ends.

**To Reproduce**

https://svelte.dev/repl/46ba49b1c588454f8f4899b4b47b8df0?version=3.12.0

In the repl, 1. is an example of this bug, 2. is an example showing how if the component is transitioned out completely then the slot is updated properly.

**Expected behavior**

In the repl, 1. should fade out "Foo" and fade in "Bar" rather than fade out "Foo" and fade in "Foo".

**Information about your Svelte project:**

Chrome v76
Windows 10
Svelte v3.12.0
See REPL

**Severity**

Not too important as it is a corner case but additional context explains why fixing it may be useful.

**Additional context**

I discovered this bug because I was trying to create page transitions in sapper but could not find a elegant way of doing so which resulted in me trying this workaround and finding this bug.

The main problem is that there is no way of adding a transition when a \<slot\> changes unless you add transitions to the slot contents which is a problem in sapper because there isn't an easy way of wrapping every page component to include intro and outro transitions.

Fixing this bug would allow the parent component to add any kind of transitioning to \<slot\> elements.


Issue discussion:
I am having the same problem with slots. I thought awaiting the tick could work but that didnt either. This isnt a huge problem for now, but I can see it being a great feature if this gets fixed in the future
Ok I found a weird solution to this. If you have the show flag toggle between 2 slots rather than hide and show one it works.

RPEL: https://svelte.dev/repl/634b9ef186f8441e8c49cf6b5725772f?version=3.12.0

I don't know about the performance around this change, but it woks in Sapper as well. This results in fairly clean page transitions

```html
<script>
  import { scale } from "svelte/transition";
  import Nav from "../components/Nav.svelte";

  export let segment;
  const timeout = 300;
  let show = true;

  const change = () => {
    show = !show;
  };

  $: change(segment);
</script>

<style>
  main {
    position: relative;
    max-width: 56em;
    background-color: white;
    padding: 2em;
    margin: 0 auto;
    box-sizing: border-box;
  }
</style>

<Nav {segment} />

{#if show}
  <main
    in:scale={{ duration: timeout, delay: timeout }}
    out:scale={{ duration: timeout }}>
    <slot />
  </main>
{:else}
  <main
    in:scale={{ duration: timeout, delay: timeout }}
    out:scale={{ duration: timeout }}>
    <slot />
  </main>
{/if}
```

Solution patch:
diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts
--- a/src/compiler/compile/render_dom/wrappers/Slot.ts
+++ b/src/compiler/compile/render_dom/wrappers/Slot.ts
@@ -164,12 +164,17 @@ export default class SlotWrapper extends Wrapper {
 			? Array.from(this.fallback.dependencies).filter((name) => this.is_dependency_dynamic(name))
 			: [];
 
+		let condition = renderer.dirty(dynamic_dependencies);
+		if (block.has_outros) {
+			condition = x`!#current || ${condition}`;
+		}
+
 		const slot_update = get_slot_spread_changes_fn ? b`
-			if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
+			if (${slot}.p && ${condition}) {
 				@update_slot_spread(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_spread_changes_fn}, ${get_slot_context_fn});
 			}
 		` : b`
-			if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
+			if (${slot}.p && ${condition}) {
 				@update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn});
 			}
 		`;


Test patch:
diff --git a/test/runtime/samples/transition-js-slot-2/Nested.svelte b/test/runtime/samples/transition-js-slot-2/Nested.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-slot-2/Nested.svelte
@@ -0,0 +1,18 @@
+<script>
+	export let visible;
+
+	function fade(node) {
+		return {
+			duration: 100,
+			tick: t => {
+				node.foo = t;
+			}
+		};
+	}
+</script>
+
+{#if visible}
+	<div transition:fade>
+		<slot></slot>
+	</div>
+{/if}
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-slot-2/_config.js b/test/runtime/samples/transition-js-slot-2/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-slot-2/_config.js
@@ -0,0 +1,23 @@
+export default {
+	html: `
+		<div>Foo</div>
+	`,
+
+	async test({ assert, component, target, window, raf }) {
+		await component.hide();
+		const div = target.querySelector('div');
+
+		raf.tick(50);
+		assert.equal(div.foo, 0.5);
+
+		await component.show();
+
+		assert.htmlEqual(target.innerHTML, '<div>Bar</div>');
+
+		raf.tick(75);
+		assert.equal(div.foo, 0.75);
+
+		raf.tick(100);
+		assert.equal(div.foo, 1);
+	}
+};
diff --git a/test/runtime/samples/transition-js-slot-2/main.svelte b/test/runtime/samples/transition-js-slot-2/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-slot-2/main.svelte
@@ -0,0 +1,18 @@
+<script>
+	import Nested from './Nested.svelte';
+
+	let name = 'Foo';
+	let visible = true;
+
+	export function show() {
+		visible = true;
+	}
+	export function hide() {
+		visible = false;
+		name = 'Bar';
+	}
+</script>
+
+<Nested {visible}>
+	{name}
+</Nested>