diff --git a/.changeset/common-candles-sneeze.md b/.changeset/common-candles-sneeze.md new file mode 100644 index 0000000000..a0ef7b610b --- /dev/null +++ b/.changeset/common-candles-sneeze.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't mark deriveds while an effect is updating diff --git a/.changeset/hip-flowers-give.md b/.changeset/hip-flowers-give.md new file mode 100644 index 0000000000..77f4dd8892 --- /dev/null +++ b/.changeset/hip-flowers-give.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: do not dispatch introstart event with animation of animate directive diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index 0c5431f030..7c4d385807 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -115,10 +115,17 @@ export function animation(element, get_fn, get_params) { ) { const options = get_fn()(this.element, { from, to }, get_params?.()); - animation = animate(this.element, options, undefined, 1, () => { - animation?.abort(); - animation = undefined; - }); + animation = animate( + this.element, + options, + undefined, + 1, + () => {}, + () => { + animation?.abort(); + animation = undefined; + } + ); } }, fix() { @@ -239,15 +246,24 @@ export function transition(flags, element, get_fn, get_params) { intro?.abort(); } - intro = animate(element, get_options(), outro, 1, () => { - dispatch_event(element, 'introend'); - - // Ensure we cancel the animation to prevent leaking - intro?.abort(); - intro = current_options = undefined; - - element.style.overflow = overflow; - }); + intro = animate( + element, + get_options(), + outro, + 1, + () => { + dispatch_event(element, 'introstart'); + }, + () => { + dispatch_event(element, 'introend'); + + // Ensure we cancel the animation to prevent leaking + intro?.abort(); + intro = current_options = undefined; + + element.style.overflow = overflow; + } + ); }, out(fn) { if (!is_outro) { @@ -258,10 +274,19 @@ export function transition(flags, element, get_fn, get_params) { element.inert = true; - outro = animate(element, get_options(), intro, 0, () => { - dispatch_event(element, 'outroend'); - fn?.(); - }); + outro = animate( + element, + get_options(), + intro, + 0, + () => { + dispatch_event(element, 'outrostart'); + }, + () => { + dispatch_event(element, 'outroend'); + fn?.(); + } + ); }, stop: () => { intro?.abort(); @@ -306,10 +331,11 @@ export function transition(flags, element, get_fn, get_params) { * @param {AnimationConfig | ((opts: { direction: 'in' | 'out' }) => AnimationConfig)} options * @param {Animation | undefined} counterpart The corresponding intro/outro to this outro/intro * @param {number} t2 The target `t` value — `1` for intro, `0` for outro + * @param {(() => void)} on_begin Called just before beginning the animation * @param {(() => void)} on_finish Called after successfully completing the animation * @returns {Animation} */ -function animate(element, options, counterpart, t2, on_finish) { +function animate(element, options, counterpart, t2, on_begin, on_finish) { var is_intro = t2 === 1; if (is_function(options)) { @@ -323,7 +349,7 @@ function animate(element, options, counterpart, t2, on_finish) { queue_micro_task(() => { if (aborted) return; var o = options({ direction: is_intro ? 'in' : 'out' }); - a = animate(element, o, counterpart, t2, on_finish); + a = animate(element, o, counterpart, t2, on_begin, on_finish); }); // ...but we want to do so without using `async`/`await` everywhere, so @@ -342,7 +368,7 @@ function animate(element, options, counterpart, t2, on_finish) { counterpart?.deactivate(); if (!options?.duration && !options?.delay) { - dispatch_event(element, is_intro ? 'introstart' : 'outrostart'); + on_begin(); on_finish(); return { @@ -382,7 +408,7 @@ function animate(element, options, counterpart, t2, on_finish) { // remove dummy animation from the stack to prevent conflict with main animation animation.cancel(); - dispatch_event(element, is_intro ? 'introstart' : 'outrostart'); + on_begin(); // for bidirectional transitions, we start from the current position, // rather than doing a full intro/outro diff --git a/packages/svelte/tests/runtime-runes/samples/animate-no-transition-events/_config.js b/packages/svelte/tests/runtime-runes/samples/animate-no-transition-events/_config.js new file mode 100644 index 0000000000..3144b53687 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/animate-no-transition-events/_config.js @@ -0,0 +1,31 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, raf, target, logs }) { + let divs = target.querySelectorAll('div'); + divs.forEach((div) => { + // @ts-expect-error + div.getBoundingClientRect = function () { + // @ts-expect-error + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + }; + }); + + const [btn] = target.querySelectorAll('button'); + flushSync(() => btn.click()); + + raf.tick(1); + assert.deepEqual(logs, []); + raf.tick(100); + assert.deepEqual(logs, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/animate-no-transition-events/main.svelte b/packages/svelte/tests/runtime-runes/samples/animate-no-transition-events/main.svelte new file mode 100644 index 0000000000..a6ba765cf4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/animate-no-transition-events/main.svelte @@ -0,0 +1,19 @@ + + + + +{#each numbers as num (num)} +