Skip to content

Commit a26012f

Browse files
authored
fix: prevent transition action overfiring (#10163)
1 parent f11f451 commit a26012f

File tree

6 files changed

+59
-32
lines changed

6 files changed

+59
-32
lines changed

.changeset/spotty-spiders-compare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: prevent transition action overfiring

packages/svelte/src/internal/client/render.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,7 +1375,10 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
13751375
/** @type {null | import('./types.js').TemplateNode | Array<import('./types.js').TemplateNode>} */
13761376
let alternate_dom = null;
13771377
let has_mounted = false;
1378-
let has_mounted_branch = false;
1378+
/**
1379+
* @type {import("./types.js").EffectSignal | null}
1380+
*/
1381+
let current_branch_effect = null;
13791382

13801383
const if_effect = render_effect(
13811384
() => {
@@ -1432,20 +1435,24 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
14321435
);
14331436
// Managed effect
14341437
const consequent_effect = render_effect(
1435-
() => {
1436-
if (consequent_dom !== null) {
1438+
(
1439+
/** @type {any} */ _,
1440+
/** @type {import("./types.js").EffectSignal | null} */ consequent_effect
1441+
) => {
1442+
const result = block.v;
1443+
if (!result && consequent_dom !== null) {
14371444
remove(consequent_dom);
14381445
consequent_dom = null;
14391446
}
1440-
if (block.v) {
1447+
if (result && current_branch_effect !== consequent_effect) {
14411448
consequent_fn(anchor_node);
1442-
if (!has_mounted_branch) {
1449+
if (current_branch_effect === null) {
14431450
// Restore previous fragment so that Svelte continues to operate in hydration mode
14441451
set_current_hydration_fragment(previous_hydration_fragment);
1445-
has_mounted_branch = true;
14461452
}
1453+
current_branch_effect = consequent_effect;
1454+
consequent_dom = block.d;
14471455
}
1448-
consequent_dom = block.d;
14491456
block.d = null;
14501457
},
14511458
block,
@@ -1454,22 +1461,26 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
14541461
block.ce = consequent_effect;
14551462
// Managed effect
14561463
const alternate_effect = render_effect(
1457-
() => {
1458-
if (alternate_dom !== null) {
1464+
(
1465+
/** @type {any} */ _,
1466+
/** @type {import("./types.js").EffectSignal | null} */ alternate_effect
1467+
) => {
1468+
const result = block.v;
1469+
if (result && alternate_dom !== null) {
14591470
remove(alternate_dom);
14601471
alternate_dom = null;
14611472
}
1462-
if (!block.v) {
1473+
if (!result && current_branch_effect !== alternate_effect) {
14631474
if (alternate_fn !== null) {
14641475
alternate_fn(anchor_node);
14651476
}
1466-
if (!has_mounted_branch) {
1477+
if (current_branch_effect === null) {
14671478
// Restore previous fragment so that Svelte continues to operate in hydration mode
14681479
set_current_hydration_fragment(previous_hydration_fragment);
1469-
has_mounted_branch = true;
14701480
}
1481+
current_branch_effect = alternate_effect;
1482+
alternate_dom = block.d;
14711483
}
1472-
alternate_dom = block.d;
14731484
block.d = null;
14741485
},
14751486
block,

packages/svelte/src/internal/client/runtime.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,13 @@ function execute_signal_fn(signal) {
342342
try {
343343
let res;
344344
if (is_render_effect) {
345-
res = /** @type {(block: import('./types.js').Block) => V} */ (init)(
346-
/** @type {import('./types.js').Block} */ (signal.b)
347-
);
345+
res =
346+
/** @type {(block: import('./types.js').Block, signal: import('./types.js').Signal) => V} */ (
347+
init
348+
)(
349+
/** @type {import('./types.js').Block} */ (signal.b),
350+
/** @type {import('./types.js').Signal} */ (signal)
351+
);
348352
} else {
349353
res = /** @type {() => V} */ (init)();
350354
}

packages/svelte/src/internal/client/transitions.js

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const DELAY_NEXT_TICK = Number.MIN_SAFE_INTEGER;
3131

3232
/** @type {undefined | number} */
3333
let active_tick_ref = undefined;
34-
let skip_mount_intro = false;
3534

3635
/**
3736
* @template T
@@ -496,7 +495,7 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo
496495
can_show_intro_on_mount = true;
497496
} else if (transition_block.t === IF_BLOCK) {
498497
transition_block.r = if_block_transition;
499-
if (can_show_intro_on_mount && !skip_mount_intro) {
498+
if (can_show_intro_on_mount) {
500499
/** @type {import('./types.js').Block | null} */
501500
let if_block = transition_block;
502501
while (if_block.t === IF_BLOCK) {
@@ -511,14 +510,13 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo
511510
}
512511
}
513512
if (!can_apply_lazy_transitions && can_show_intro_on_mount) {
514-
can_show_intro_on_mount = !skip_mount_intro && transition_block.e !== null;
513+
can_show_intro_on_mount = transition_block.e !== null;
515514
}
516515
if (can_show_intro_on_mount || !global) {
517516
can_apply_lazy_transitions = true;
518517
}
519518
} else if (transition_block.t === ROOT_BLOCK && !can_apply_lazy_transitions) {
520-
can_show_intro_on_mount =
521-
!skip_mount_intro && (transition_block.e !== null || transition_block.i);
519+
can_show_intro_on_mount = transition_block.e !== null || transition_block.i;
522520
}
523521
transition_block = transition_block.p;
524522
}
@@ -527,14 +525,11 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo
527525
let transition;
528526

529527
effect(() => {
528+
let already_mounted = false;
530529
if (transition !== undefined) {
530+
already_mounted = true;
531531
// Destroy any existing transitions first
532-
try {
533-
skip_mount_intro = true;
534-
transition.x();
535-
} finally {
536-
skip_mount_intro = false;
537-
}
532+
transition.x();
538533
}
539534
const transition_fn = get_transition_fn();
540535
/** @param {DOMRect} [from] */
@@ -557,15 +552,15 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo
557552
const is_intro = direction === 'in';
558553
const show_intro = can_show_intro_on_mount && (is_intro || direction === 'both');
559554

560-
if (show_intro) {
555+
if (show_intro && !already_mounted) {
561556
transition.p = transition.i();
562557
}
563558

564559
const effect = managed_pre_effect(() => {
565560
destroy_signal(effect);
566561
dom.inert = false;
567562

568-
if (show_intro) {
563+
if (show_intro && !already_mounted) {
569564
transition.in();
570565
}
571566

@@ -662,7 +657,8 @@ function if_block_transition(transition) {
662657
const c = /** @type {Set<import('./types.js').Transition>} */ (consequent_transitions);
663658
c.delete(transition);
664659
if (c.size === 0) {
665-
execute_effect(/** @type {import('./types.js').EffectSignal} */ (block.ce));
660+
const consequent_effect = block.ce;
661+
execute_effect(/** @type {import('./types.js').EffectSignal} */ (consequent_effect));
666662
}
667663
});
668664
} else {
@@ -675,7 +671,8 @@ function if_block_transition(transition) {
675671
const a = /** @type {Set<import('./types.js').Transition>} */ (alternate_transitions);
676672
a.delete(transition);
677673
if (a.size === 0) {
678-
execute_effect(/** @type {import('./types.js').EffectSignal} */ (block.ae));
674+
const alternate_effect = block.ae;
675+
execute_effect(/** @type {import('./types.js').EffectSignal} */ (alternate_effect));
679676
}
680677
});
681678
}

packages/svelte/src/internal/client/types.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,11 @@ export type ComputationSignal<V = unknown> = {
9999
/** The types that the signal represent, as a bitwise value */
100100
f: SignalFlags;
101101
/** init: The function that we invoke for effects and computeds */
102-
i: null | (() => V) | (() => void | (() => void)) | ((b: Block) => void | (() => void));
102+
i:
103+
| null
104+
| (() => V)
105+
| (() => void | (() => void))
106+
| ((b: Block, s: Signal) => void | (() => void));
103107
/** references: Anything that a signal owns */
104108
r: null | ComputationSignal[];
105109
/** value: The latest value for this signal, doubles as the teardown for effects */

packages/svelte/tests/runtime-runes/samples/dynamic-transition/_config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ export default test({
2020
b2.click();
2121
});
2222

23+
assert.deepEqual(log, ['transition 2']);
24+
25+
flushSync(() => {
26+
b1.click();
27+
});
28+
2329
assert.deepEqual(log, ['transition 2', 'transition 1']);
2430
}
2531
});

0 commit comments

Comments
 (0)