Skip to content

Commit ba93e5f

Browse files
breaking: play transitions on mount by default (#12351)
* breaking: play transitions on `mount` by default closes #11280 * only prevent transitions when the component is invalidated --------- Co-authored-by: Rich Harris <[email protected]>
1 parent e8c3729 commit ba93e5f

File tree

9 files changed

+74
-10
lines changed

9 files changed

+74
-10
lines changed

.changeset/hip-garlics-tap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
breaking: play transitions on `mount` by default

packages/svelte/src/internal/client/dev/hmr.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
/** @import { Source, Effect } from '#client' */
2-
import { empty } from '../dom/operations.js';
32
import { block, branch, destroy_effect } from '../reactivity/effects.js';
43
import { set_should_intro } from '../render.js';
54
import { get } from '../runtime.js';
6-
import { check_target } from './legacy.js';
75

86
/**
97
* @template {(anchor: Comment, props: any) => any} Component
@@ -20,6 +18,8 @@ export function hmr(source) {
2018
/** @type {Effect} */
2119
let effect;
2220

21+
let ran = false;
22+
2323
block(() => {
2424
const component = get(source);
2525

@@ -30,7 +30,9 @@ export function hmr(source) {
3030
}
3131

3232
effect = branch(() => {
33-
set_should_intro(false);
33+
// when the component is invalidated, replace it without transitions
34+
if (ran) set_should_intro(false);
35+
3436
// preserve getters/setters
3537
Object.defineProperties(
3638
instance,
@@ -39,10 +41,13 @@ export function hmr(source) {
3941
new.target ? new component(anchor, props) : component(anchor, props)
4042
)
4143
);
42-
set_should_intro(true);
44+
45+
if (ran) set_should_intro(true);
4346
});
4447
});
4548

49+
ran = true;
50+
4651
return instance;
4752
};
4853
}

packages/svelte/src/internal/client/render.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export const root_event_handles = new Set();
2424

2525
/**
2626
* This is normally true — block effects should run their intro transitions —
27-
* but is false during hydration and mounting (unless `options.intro` is `true`)
28-
* and when creating the children of a `<svelte:element>` that just changed tag
27+
* but is false during hydration (unless `options.intro` is `true`) and
28+
* when creating the children of a `<svelte:element>` that just changed tag
2929
*/
3030
export let should_intro = true;
3131

@@ -66,7 +66,8 @@ export function slot(anchor, slot_fn, slot_props, fallback_fn) {
6666
}
6767

6868
/**
69-
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
69+
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component.
70+
* Transitions will play during the initial render unless the `intro` option is set to `false`.
7071
*
7172
* @template {Record<string, any>} Props
7273
* @template {Record<string, any>} Exports
@@ -126,6 +127,7 @@ export function hydrate(component, options) {
126127
validate_component(component);
127128
}
128129

130+
options.intro = options.intro ?? false;
129131
const target = options.target;
130132
const previous_hydrate_nodes = hydrate_nodes;
131133

@@ -190,7 +192,7 @@ export function hydrate(component, options) {
190192
* }} options
191193
* @returns {Exports}
192194
*/
193-
function _mount(Component, { target, anchor, props = {}, events, context, intro = false }) {
195+
function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) {
194196
init_operations();
195197

196198
const registered_events = new Set();

packages/svelte/src/legacy/legacy-client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class Svelte4Component {
7777
target: options.target,
7878
props,
7979
context: options.context,
80-
intro: options.intro,
80+
intro: options.intro ?? false,
8181
recover: options.recover
8282
});
8383

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
import { fade } from 'svelte/transition';
3+
</script>
4+
5+
<div in:fade|global={{ duration: 100 }}>DIV</div>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { flushSync } from 'svelte';
2+
import { ok, test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target, raf }) {
6+
const [btn1, btn2] = target.querySelectorAll('button');
7+
const div = target.querySelector('div');
8+
ok(div);
9+
10+
btn1.click();
11+
flushSync();
12+
assert.htmlEqual(div.innerHTML, `<div style="opacity: 0;">DIV</div>`);
13+
14+
raf.tick(100);
15+
assert.htmlEqual(div.innerHTML, `<div style="">DIV</div>`);
16+
17+
btn2.click();
18+
flushSync();
19+
assert.htmlEqual(div.innerHTML, `<div>DIV</div>`);
20+
}
21+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script>
2+
import { mount, unmount } from 'svelte';
3+
import Component from './Component.svelte';
4+
5+
let el;
6+
let instance;
7+
8+
function intro(animate) {
9+
if (instance) unmount(instance);
10+
11+
instance = mount(Component, {
12+
target: el,
13+
intro: animate
14+
});
15+
}
16+
</script>
17+
18+
<div bind:this={el}></div>
19+
20+
<button onclick={() => intro()}>mount with intro transition</button>
21+
<button onclick={() => intro(false)}>mount without intro transition</button>

packages/svelte/types/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ declare module 'svelte' {
366366
/** Anything except a function */
367367
type NotFunction<T> = T extends Function ? never : T;
368368
/**
369-
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
369+
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component.
370+
* Transitions will play during the initial render unless the `intro` option is set to `false`.
370371
*
371372
* */
372373
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {

sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,7 @@ In Svelte 4, `<svelte:element this="div">` is valid code. This makes little sens
281281
```
282282

283283
Note that whereas Svelte 4 would treat `<svelte:element this="input">` (for example) identically to `<input>` for the purposes of determining which `bind:` directives could be applied, Svelte 5 does not.
284+
285+
### `mount` plays transitions by default
286+
287+
The `mount` function used to render a component tree plays transitions by default unless the `intro` option is set to `false`. This is different from legacy class components which, when manually instantiated, didn't play transitions by default.

0 commit comments

Comments
 (0)