From 0e9b83c20d533f50e1188f6b361c9cc897efb7c3 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 10 Jan 2024 02:46:00 +0000 Subject: [PATCH] fix: improve outro behavior with transitions (#10139) * fix: improve outro behavior with transitions * debug * revise --- .changeset/empty-bulldogs-exercise.md | 5 ++ .../svelte/src/internal/client/runtime.js | 19 +++-- .../svelte/src/internal/client/transitions.js | 69 +++++++++++-------- 3 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 .changeset/empty-bulldogs-exercise.md diff --git a/.changeset/empty-bulldogs-exercise.md b/.changeset/empty-bulldogs-exercise.md new file mode 100644 index 0000000000..087a8f7775 --- /dev/null +++ b/.changeset/empty-bulldogs-exercise.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve outro behavior with transitions diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index a475796e5c..231dd0aebb 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -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) { @@ -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); } } } @@ -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); } } } diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 6334c6310f..0fb950094b 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -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) { @@ -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() { @@ -584,6 +594,7 @@ 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(); @@ -591,7 +602,7 @@ export function trigger_transitions(transitions, target_direction, from) { 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)); @@ -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) {