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/hip-flowers-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: do not dispatch introstart event with animation of animate directive
68 changes: 47 additions & 21 deletions packages/svelte/src/internal/client/dom/elements/transitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
Expand Down Expand Up @@ -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)) {
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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, []);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script>
import { flip } from "svelte/animate";

let numbers = $state([0,1]);
</script>

<button onclick={() => numbers.reverse()}>reverse</button>

{#each numbers as num (num)}
<div
onintrostart={() => console.log("intro start")}
onoutrostart={() => console.log("outro start")}
onintroend={() => console.log("intro end")}
onoutroend={() => console.log("outro end")}
animate:flip={{ duration: 100 }}
>
{num}
</div>
{/each}