Skip to content

Commit fd78acf

Browse files
authored
fix: support dynamic transition functions (#9844)
* fix: support dynamic transition functions * add test * lint * load dynamic code lazily load dynamic code lazily load dynamic code lazily
1 parent ab21253 commit fd78acf

File tree

10 files changed

+137
-37
lines changed

10 files changed

+137
-37
lines changed

.changeset/giant-roses-press.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: support dynamic transition functions

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,7 +1767,9 @@ export const template_visitors = {
17671767
b.call(
17681768
'$.animate',
17691769
state.node,
1770-
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name))),
1770+
b.thunk(
1771+
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name)))
1772+
),
17711773
expression
17721774
)
17731775
)
@@ -1791,7 +1793,9 @@ export const template_visitors = {
17911793
b.call(
17921794
type,
17931795
state.node,
1794-
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name))),
1796+
b.thunk(
1797+
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name)))
1798+
),
17951799
expression,
17961800
node.modifiers.includes('global') ? b.true : b.false
17971801
)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,13 @@ export function create_each_block(flags, anchor) {
175175
*/
176176
export function create_each_item_block(item, index, key) {
177177
return {
178+
// animate transition
179+
a: null,
178180
// dom
179181
d: null,
180182
// effect
181183
e: null,
184+
// index
182185
i: index,
183186
// key
184187
k: key,

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

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ function destroy_active_transition_blocks(active_transitions) {
686686
* @param {import('./types.js').Block} block
687687
* @returns {Text | Element | Comment}
688688
*/
689-
function get_first_element(block) {
689+
export function get_first_element(block) {
690690
const current = block.d;
691691

692692
if (is_array(current)) {
@@ -717,25 +717,9 @@ function update_each_item_block(block, item, index, type) {
717717
const transitions = block.s;
718718
const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0;
719719
// Handle each item animations
720-
if (transitions !== null && (type & EACH_KEYED) !== 0) {
721-
let prev_index = block.i;
722-
if (index_is_reactive) {
723-
prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v;
724-
}
725-
const items = block.p.v;
726-
if (prev_index !== index && /** @type {number} */ (index) < items.length) {
727-
const from_dom = /** @type {Element} */ (get_first_element(block));
728-
const from = from_dom.getBoundingClientRect();
729-
// Cancel any existing key transitions
730-
for (const transition of transitions) {
731-
if (transition.r === 'key') {
732-
transition.c();
733-
}
734-
}
735-
schedule_task(() => {
736-
trigger_transitions(transitions, 'key', from);
737-
});
738-
}
720+
const each_animation = block.a;
721+
if (transitions !== null && (type & EACH_KEYED) !== 0 && each_animation !== null) {
722+
each_animation(block, transitions, index, index_is_reactive);
739723
}
740724
if (index_is_reactive) {
741725
set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index);

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2086,49 +2086,49 @@ export function html(dom, get_value, svg) {
20862086
/**
20872087
* @template P
20882088
* @param {HTMLElement} dom
2089-
* @param {import('./types.js').TransitionFn<P | undefined>} transition_fn
2089+
* @param {() => import('./types.js').TransitionFn<P | undefined>} get_transition_fn
20902090
* @param {(() => P) | null} props
20912091
* @param {any} global
20922092
* @returns {void}
20932093
*/
2094-
export function transition(dom, transition_fn, props, global = false) {
2095-
bind_transition(dom, transition_fn, props, 'both', global);
2094+
export function transition(dom, get_transition_fn, props, global = false) {
2095+
bind_transition(dom, get_transition_fn, props, 'both', global);
20962096
}
20972097

20982098
/**
20992099
* @template P
21002100
* @param {HTMLElement} dom
2101-
* @param {import('./types.js').TransitionFn<P | undefined>} transition_fn
2101+
* @param {() => import('./types.js').TransitionFn<P | undefined>} get_transition_fn
21022102
* @param {(() => P) | null} props
21032103
* @returns {void}
21042104
*/
2105-
export function animate(dom, transition_fn, props) {
2106-
bind_transition(dom, transition_fn, props, 'key', false);
2105+
export function animate(dom, get_transition_fn, props) {
2106+
bind_transition(dom, get_transition_fn, props, 'key', false);
21072107
}
21082108

21092109
/**
21102110
* @template P
21112111
* @param {HTMLElement} dom
2112-
* @param {import('./types.js').TransitionFn<P | undefined>} transition_fn
2112+
* @param {() => import('./types.js').TransitionFn<P | undefined>} get_transition_fn
21132113
* @param {(() => P) | null} props
21142114
* @param {any} global
21152115
* @returns {void}
21162116
*/
2117-
function in_fn(dom, transition_fn, props, global = false) {
2118-
bind_transition(dom, transition_fn, props, 'in', global);
2117+
function in_fn(dom, get_transition_fn, props, global = false) {
2118+
bind_transition(dom, get_transition_fn, props, 'in', global);
21192119
}
21202120
export { in_fn as in };
21212121

21222122
/**
21232123
* @template P
21242124
* @param {HTMLElement} dom
2125-
* @param {import('./types.js').TransitionFn<P | undefined>} transition_fn
2125+
* @param {() => import('./types.js').TransitionFn<P | undefined>} get_transition_fn
21262126
* @param {(() => P) | null} props
21272127
* @param {any} global
21282128
* @returns {void}
21292129
*/
2130-
export function out(dom, transition_fn, props, global = false) {
2131-
bind_transition(dom, transition_fn, props, 'out', global);
2130+
export function out(dom, get_transition_fn, props, global = false) {
2131+
bind_transition(dom, get_transition_fn, props, 'out', global);
21322132
}
21332133

21342134
/**

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
KEY_BLOCK,
1010
ROOT_BLOCK
1111
} from './block.js';
12-
import { destroy_each_item_block } from './each.js';
12+
import { destroy_each_item_block, get_first_element } from './each.js';
1313
import { append_child } from './operations.js';
1414
import { empty } from './render.js';
1515
import {
@@ -21,6 +21,7 @@ import {
2121
managed_effect,
2222
managed_pre_effect,
2323
mark_subtree_inert,
24+
schedule_task,
2425
untrack
2526
} from './runtime.js';
2627
import { raf } from './timing.js';
@@ -411,13 +412,13 @@ function is_transition_block(block) {
411412
/**
412413
* @template P
413414
* @param {HTMLElement} dom
414-
* @param {import('./types.js').TransitionFn<P | undefined> | import('./types.js').AnimateFn<P | undefined>} transition_fn
415+
* @param {() => import('./types.js').TransitionFn<P | undefined> | import('./types.js').AnimateFn<P | undefined>} get_transition_fn
415416
* @param {(() => P) | null} props_fn
416417
* @param {'in' | 'out' | 'both' | 'key'} direction
417418
* @param {boolean} global
418419
* @returns {void}
419420
*/
420-
export function bind_transition(dom, transition_fn, props_fn, direction, global) {
421+
export function bind_transition(dom, get_transition_fn, props_fn, direction, global) {
421422
const transition_effect = /** @type {import('./types.js').EffectSignal} */ (current_effect);
422423
const block = current_block;
423424
const props = props_fn === null ? {} : props_fn();
@@ -432,6 +433,7 @@ export function bind_transition(dom, transition_fn, props_fn, direction, global)
432433
if (transition_block.t === EACH_ITEM_BLOCK) {
433434
// Lazily apply the each block transition
434435
transition_block.r = each_item_transition;
436+
transition_block.a = each_item_animate;
435437
transition_block = transition_block.p;
436438
} else if (transition_block.t === AWAIT_BLOCK && transition_block.n /* pending */) {
437439
can_show_intro_on_mount = false;
@@ -458,6 +460,11 @@ export function bind_transition(dom, transition_fn, props_fn, direction, global)
458460
let transition;
459461

460462
effect(() => {
463+
if (transition !== undefined) {
464+
// Destroy any existing transitions first
465+
transition.x();
466+
}
467+
const transition_fn = get_transition_fn();
461468
/** @param {DOMRect} [from] */
462469
const init = (from) =>
463470
untrack(() =>
@@ -641,3 +648,31 @@ function each_item_transition(transition) {
641648
});
642649
transitions.add(transition);
643650
}
651+
652+
/**
653+
*
654+
* @param {import('./types.js').EachItemBlock} block
655+
* @param {Set<import('./types.js').Transition>} transitions
656+
* @param {number} index
657+
* @param {boolean} index_is_reactive
658+
*/
659+
function each_item_animate(block, transitions, index, index_is_reactive) {
660+
let prev_index = block.i;
661+
if (index_is_reactive) {
662+
prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v;
663+
}
664+
const items = block.p.v;
665+
if (prev_index !== index && /** @type {number} */ (index) < items.length) {
666+
const from_dom = /** @type {Element} */ (get_first_element(block));
667+
const from = from_dom.getBoundingClientRect();
668+
// Cancel any existing key transitions
669+
for (const transition of transitions) {
670+
if (transition.r === 'key') {
671+
transition.c();
672+
}
673+
}
674+
schedule_task(() => {
675+
trigger_transitions(transitions, 'key', from);
676+
});
677+
}
678+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,15 @@ export type EachBlock = {
285285
};
286286

287287
export type EachItemBlock = {
288+
/** transition */
289+
a:
290+
| null
291+
| ((
292+
block: EachItemBlock,
293+
transitions: Set<Transition>,
294+
index: number,
295+
index_is_reactive: boolean
296+
) => void);
288297
/** dom */
289298
d: null | TemplateNode | Array<TemplateNode>;
290299
/** effect */
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
import { log } from './log.js';
4+
5+
export default test({
6+
before_test() {
7+
log.length = 0;
8+
},
9+
10+
async test({ assert, target }) {
11+
const [b1, b2] = target.querySelectorAll('button');
12+
13+
flushSync(() => {
14+
b1.click();
15+
});
16+
17+
assert.deepEqual(log, ['transition 2']);
18+
19+
flushSync(() => {
20+
b2.click();
21+
});
22+
23+
assert.deepEqual(log, ['transition 2', 'transition 1', 'transition 1']);
24+
}
25+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/** @type {any[]} */
2+
export const log = [];
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<script>
2+
import { log } from './log.js';
3+
4+
function transition1() {
5+
log.push('transition 1')
6+
return {
7+
tick() {
8+
9+
}
10+
}
11+
}
12+
13+
function transition2() {
14+
log.push('transition 2')
15+
return {
16+
tick() {
17+
18+
}
19+
}
20+
}
21+
22+
let toggle = $state(false);
23+
let toggleTransition = $state(false);
24+
25+
const derived = $derived(toggleTransition ? transition1 : transition2)
26+
</script>
27+
28+
29+
<button on:click={() => toggle = !toggle}>{toggle}</button>
30+
<button on:click={() => toggleTransition = !toggleTransition}>{toggleTransition}</button>
31+
32+
{#if toggle}<div transition:derived></div>{/if}
33+

0 commit comments

Comments
 (0)