Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/empty-bulldogs-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: improve outro behavior with transitions
19 changes: 13 additions & 6 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -1012,10 +1012,11 @@ export function mutate_store(store, expression, new_value) {

/**
* @param {import('./types.js').ComputationSignal} signal
* @param {import('./types.js').ComputationSignal} root
* @param {boolean} inert
* @returns {void}
*/
export function mark_subtree_inert(signal, inert) {
export function mark_subtree_inert(signal, root, inert, visited_blocks = new Set()) {
const flags = signal.f;
const is_already_inert = (flags & INERT) !== 0;
if (is_already_inert !== inert) {
Expand All @@ -1025,22 +1026,28 @@ export function mark_subtree_inert(signal, inert) {
}
// Nested if block effects
const block = signal.b;
if (block !== null) {
if (block !== null && !visited_blocks.has(block)) {
visited_blocks.add(block);
const type = block.t;
if (type === IF_BLOCK) {
const condition_effect = block.e;
const root_block = root.b?.p;
if (condition_effect !== null && root_block?.t === IF_BLOCK) {
mark_subtree_inert(condition_effect, root, inert);
}
const consequent_effect = block.ce;
if (consequent_effect !== null) {
mark_subtree_inert(consequent_effect, inert);
mark_subtree_inert(consequent_effect, root, inert, visited_blocks);
}
const alternate_effect = block.ae;
if (alternate_effect !== null) {
mark_subtree_inert(alternate_effect, inert);
mark_subtree_inert(alternate_effect, root, inert, visited_blocks);
}
} else if (type === EACH_BLOCK) {
const items = block.v;
for (let { e: each_item_effect } of items) {
if (each_item_effect !== null) {
mark_subtree_inert(each_item_effect, inert);
mark_subtree_inert(each_item_effect, root, inert, visited_blocks);
}
}
}
Expand All @@ -1050,7 +1057,7 @@ export function mark_subtree_inert(signal, inert) {
if (references !== null) {
let i;
for (i = 0; i < references.length; i++) {
mark_subtree_inert(references[i], inert);
mark_subtree_inert(references[i], root, inert, visited_blocks);
}
}
}
Expand Down
69 changes: 40 additions & 29 deletions packages/svelte/src/internal/client/transitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,22 +371,6 @@ function create_transition(dom, init, direction, effect) {
o() {
// @ts-ignore
const has_keyed_transition = dom.__animate;
const needs_reverse = direction === 'both' && curr_direction !== 'out';
curr_direction = 'out';
if (animation === null || cancelled) {
cancelled = false;
create_animation();
}
if (animation === null) {
transition.x();
} else {
dispatch_event(dom, 'outrostart');
if (needs_reverse) {
/** @type {Animation | TickAnimation} */ (animation).reverse();
} else {
/** @type {Animation | TickAnimation} */ (animation).play();
}
}
// If we're outroing an element that has an animation, then we need to fix
// its position to ensure it behaves nicely without causing layout shift.
if (has_keyed_transition) {
Expand All @@ -402,20 +386,46 @@ function create_transition(dom, init, direction, effect) {
dom.style.height = height;
const b = dom.getBoundingClientRect();
if (a.left !== b.left || a.top !== b.top) {
// Previously, in the Svelte 4, we'd just apply the transform the the DOM element. However,
// because we're now using Web Animations, we can't do that as it won't work properly if the
// animation is also making use of the same transformations. So instead, we apply an instantaneous
// animation and pause it on the first frame, just applying the same behavior.
const style = getComputedStyle(dom);
const transform = style.transform === 'none' ? '' : style.transform;
const frame = {
transform: `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`
};
const animation = dom.animate([frame, frame], { duration: 1 });
animation.pause();
const translate = `translate(${a.left - b.left}px, ${a.top - b.top}px)`;
const existing_transform = style.transform;
if (existing_transform === 'none') {
dom.style.transform = translate;
} else {
// Previously, in the Svelte 4, we'd just apply the transform the the DOM element. However,
// because we're now using Web Animations, we can't do that as it won't work properly if the
// animation is also making use of the same transformations. So instead, we apply an
// instantaneous animation and pause it on the first frame, just applying the same behavior.
// We also need to take into consideration matrix transforms and how they might combine with
// an existing behavior that is already in progress (such as scale).
// > Follow the white rabbit.
const transform = existing_transform.startsWith('matrix(1,')
? translate
: `matrix(1,0,0,1,0,0)`;
const frame = {
transform
};
const animation = dom.animate([frame, frame], { duration: 1 });
animation.pause();
}
}
}
}
const needs_reverse = direction === 'both' && curr_direction !== 'out';
curr_direction = 'out';
if (animation === null || cancelled) {
cancelled = false;
create_animation();
}
if (animation === null) {
transition.x();
} else {
dispatch_event(dom, 'outrostart');
if (needs_reverse) {
/** @type {Animation | TickAnimation} */ (animation).reverse();
} else {
/** @type {Animation | TickAnimation} */ (animation).play();
}
}
},
// cancel
c() {
Expand Down Expand Up @@ -584,14 +594,15 @@ export function trigger_transitions(transitions, target_direction, from) {
const outros = [];
for (const transition of transitions) {
const direction = transition.r;
const effect = transition.e;
if (target_direction === 'in') {
if (direction === 'in' || direction === 'both') {
transition.in();
} else {
transition.c();
}
transition.d.inert = false;
mark_subtree_inert(transition.e, false);
mark_subtree_inert(effect, effect, false);
} else if (target_direction === 'key') {
if (direction === 'key') {
transition.p = transition.i(/** @type {DOMRect} */ (from));
Expand All @@ -603,7 +614,7 @@ export function trigger_transitions(transitions, target_direction, from) {
outros.push(transition.o);
}
transition.d.inert = true;
mark_subtree_inert(transition.e, true);
mark_subtree_inert(effect, effect, true);
}
}
if (outros.length > 0) {
Expand Down