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 <rich.harris@vercel.com>
pull/12353/head
Simon H 6 months ago committed by GitHub
parent e8c3729fc9
commit ba93e5fce3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: play transitions on `mount` by default

@ -1,9 +1,7 @@
/** @import { Source, Effect } from '#client' */
import { empty } from '../dom/operations.js';
import { block, branch, destroy_effect } from '../reactivity/effects.js';
import { set_should_intro } from '../render.js';
import { get } from '../runtime.js';
import { check_target } from './legacy.js';
/**
* @template {(anchor: Comment, props: any) => any} Component
@ -20,6 +18,8 @@ export function hmr(source) {
/** @type {Effect} */
let effect;
let ran = false;
block(() => {
const component = get(source);
@ -30,7 +30,9 @@ export function hmr(source) {
}
effect = branch(() => {
set_should_intro(false);
// when the component is invalidated, replace it without transitions
if (ran) set_should_intro(false);
// preserve getters/setters
Object.defineProperties(
instance,
@ -39,10 +41,13 @@ export function hmr(source) {
new.target ? new component(anchor, props) : component(anchor, props)
)
);
set_should_intro(true);
if (ran) set_should_intro(true);
});
});
ran = true;
return instance;
};
}

@ -24,8 +24,8 @@ export const root_event_handles = new Set();
/**
* This is normally true block effects should run their intro transitions
* but is false during hydration and mounting (unless `options.intro` is `true`)
* and when creating the children of a `<svelte:element>` that just changed tag
* but is false during hydration (unless `options.intro` is `true`) and
* when creating the children of a `<svelte:element>` that just changed tag
*/
export let should_intro = true;
@ -66,7 +66,8 @@ export function slot(anchor, slot_fn, slot_props, fallback_fn) {
}
/**
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component.
* Transitions will play during the initial render unless the `intro` option is set to `false`.
*
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
@ -126,6 +127,7 @@ export function hydrate(component, options) {
validate_component(component);
}
options.intro = options.intro ?? false;
const target = options.target;
const previous_hydrate_nodes = hydrate_nodes;
@ -190,7 +192,7 @@ export function hydrate(component, options) {
* }} options
* @returns {Exports}
*/
function _mount(Component, { target, anchor, props = {}, events, context, intro = false }) {
function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) {
init_operations();
const registered_events = new Set();

@ -77,7 +77,7 @@ class Svelte4Component {
target: options.target,
props,
context: options.context,
intro: options.intro,
intro: options.intro ?? false,
recover: options.recover
});

@ -0,0 +1,5 @@
<script>
import { fade } from 'svelte/transition';
</script>
<div in:fade|global={{ duration: 100 }}>DIV</div>

@ -0,0 +1,21 @@
import { flushSync } from 'svelte';
import { ok, test } from '../../test';
export default test({
async test({ assert, target, raf }) {
const [btn1, btn2] = target.querySelectorAll('button');
const div = target.querySelector('div');
ok(div);
btn1.click();
flushSync();
assert.htmlEqual(div.innerHTML, `<div style="opacity: 0;">DIV</div>`);
raf.tick(100);
assert.htmlEqual(div.innerHTML, `<div style="">DIV</div>`);
btn2.click();
flushSync();
assert.htmlEqual(div.innerHTML, `<div>DIV</div>`);
}
});

@ -0,0 +1,21 @@
<script>
import { mount, unmount } from 'svelte';
import Component from './Component.svelte';
let el;
let instance;
function intro(animate) {
if (instance) unmount(instance);
instance = mount(Component, {
target: el,
intro: animate
});
}
</script>
<div bind:this={el}></div>
<button onclick={() => intro()}>mount with intro transition</button>
<button onclick={() => intro(false)}>mount without intro transition</button>

@ -366,7 +366,8 @@ declare module 'svelte' {
/** Anything except a function */
type NotFunction<T> = T extends Function ? never : T;
/**
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component.
* Transitions will play during the initial render unless the `intro` option is set to `false`.
*
* */
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {

@ -281,3 +281,7 @@ In Svelte 4, `<svelte:element this="div">` is valid code. This makes little sens
```
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.
### `mount` plays transitions by default
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.

Loading…
Cancel
Save