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
pull/9848/head
Dominic Gannaway 10 months ago committed by GitHub
parent ab21253073
commit fd78acfec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: support dynamic transition functions

@ -1767,7 +1767,9 @@ export const template_visitors = {
b.call(
'$.animate',
state.node,
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name))),
b.thunk(
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name)))
),
expression
)
)
@ -1791,7 +1793,9 @@ export const template_visitors = {
b.call(
type,
state.node,
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name))),
b.thunk(
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name)))
),
expression,
node.modifiers.includes('global') ? b.true : b.false
)

@ -175,10 +175,13 @@ export function create_each_block(flags, anchor) {
*/
export function create_each_item_block(item, index, key) {
return {
// animate transition
a: null,
// dom
d: null,
// effect
e: null,
// index
i: index,
// key
k: key,

@ -686,7 +686,7 @@ function destroy_active_transition_blocks(active_transitions) {
* @param {import('./types.js').Block} block
* @returns {Text | Element | Comment}
*/
function get_first_element(block) {
export function get_first_element(block) {
const current = block.d;
if (is_array(current)) {
@ -717,25 +717,9 @@ function update_each_item_block(block, item, index, type) {
const transitions = block.s;
const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0;
// Handle each item animations
if (transitions !== null && (type & EACH_KEYED) !== 0) {
let prev_index = block.i;
if (index_is_reactive) {
prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v;
}
const items = block.p.v;
if (prev_index !== index && /** @type {number} */ (index) < items.length) {
const from_dom = /** @type {Element} */ (get_first_element(block));
const from = from_dom.getBoundingClientRect();
// Cancel any existing key transitions
for (const transition of transitions) {
if (transition.r === 'key') {
transition.c();
}
}
schedule_task(() => {
trigger_transitions(transitions, 'key', from);
});
}
const each_animation = block.a;
if (transitions !== null && (type & EACH_KEYED) !== 0 && each_animation !== null) {
each_animation(block, transitions, index, index_is_reactive);
}
if (index_is_reactive) {
set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index);

@ -2086,49 +2086,49 @@ export function html(dom, get_value, svg) {
/**
* @template P
* @param {HTMLElement} dom
* @param {import('./types.js').TransitionFn<P | undefined>} transition_fn
* @param {() => import('./types.js').TransitionFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props
* @param {any} global
* @returns {void}
*/
export function transition(dom, transition_fn, props, global = false) {
bind_transition(dom, transition_fn, props, 'both', global);
export function transition(dom, get_transition_fn, props, global = false) {
bind_transition(dom, get_transition_fn, props, 'both', global);
}
/**
* @template P
* @param {HTMLElement} dom
* @param {import('./types.js').TransitionFn<P | undefined>} transition_fn
* @param {() => import('./types.js').TransitionFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props
* @returns {void}
*/
export function animate(dom, transition_fn, props) {
bind_transition(dom, transition_fn, props, 'key', false);
export function animate(dom, get_transition_fn, props) {
bind_transition(dom, get_transition_fn, props, 'key', false);
}
/**
* @template P
* @param {HTMLElement} dom
* @param {import('./types.js').TransitionFn<P | undefined>} transition_fn
* @param {() => import('./types.js').TransitionFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props
* @param {any} global
* @returns {void}
*/
function in_fn(dom, transition_fn, props, global = false) {
bind_transition(dom, transition_fn, props, 'in', global);
function in_fn(dom, get_transition_fn, props, global = false) {
bind_transition(dom, get_transition_fn, props, 'in', global);
}
export { in_fn as in };
/**
* @template P
* @param {HTMLElement} dom
* @param {import('./types.js').TransitionFn<P | undefined>} transition_fn
* @param {() => import('./types.js').TransitionFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props
* @param {any} global
* @returns {void}
*/
export function out(dom, transition_fn, props, global = false) {
bind_transition(dom, transition_fn, props, 'out', global);
export function out(dom, get_transition_fn, props, global = false) {
bind_transition(dom, get_transition_fn, props, 'out', global);
}
/**

@ -9,7 +9,7 @@ import {
KEY_BLOCK,
ROOT_BLOCK
} from './block.js';
import { destroy_each_item_block } from './each.js';
import { destroy_each_item_block, get_first_element } from './each.js';
import { append_child } from './operations.js';
import { empty } from './render.js';
import {
@ -21,6 +21,7 @@ import {
managed_effect,
managed_pre_effect,
mark_subtree_inert,
schedule_task,
untrack
} from './runtime.js';
import { raf } from './timing.js';
@ -411,13 +412,13 @@ function is_transition_block(block) {
/**
* @template P
* @param {HTMLElement} dom
* @param {import('./types.js').TransitionFn<P | undefined> | import('./types.js').AnimateFn<P | undefined>} transition_fn
* @param {() => import('./types.js').TransitionFn<P | undefined> | import('./types.js').AnimateFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props_fn
* @param {'in' | 'out' | 'both' | 'key'} direction
* @param {boolean} global
* @returns {void}
*/
export function bind_transition(dom, transition_fn, props_fn, direction, global) {
export function bind_transition(dom, get_transition_fn, props_fn, direction, global) {
const transition_effect = /** @type {import('./types.js').EffectSignal} */ (current_effect);
const block = current_block;
const props = props_fn === null ? {} : props_fn();
@ -432,6 +433,7 @@ export function bind_transition(dom, transition_fn, props_fn, direction, global)
if (transition_block.t === EACH_ITEM_BLOCK) {
// Lazily apply the each block transition
transition_block.r = each_item_transition;
transition_block.a = each_item_animate;
transition_block = transition_block.p;
} else if (transition_block.t === AWAIT_BLOCK && transition_block.n /* pending */) {
can_show_intro_on_mount = false;
@ -458,6 +460,11 @@ export function bind_transition(dom, transition_fn, props_fn, direction, global)
let transition;
effect(() => {
if (transition !== undefined) {
// Destroy any existing transitions first
transition.x();
}
const transition_fn = get_transition_fn();
/** @param {DOMRect} [from] */
const init = (from) =>
untrack(() =>
@ -641,3 +648,31 @@ function each_item_transition(transition) {
});
transitions.add(transition);
}
/**
*
* @param {import('./types.js').EachItemBlock} block
* @param {Set<import('./types.js').Transition>} transitions
* @param {number} index
* @param {boolean} index_is_reactive
*/
function each_item_animate(block, transitions, index, index_is_reactive) {
let prev_index = block.i;
if (index_is_reactive) {
prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v;
}
const items = block.p.v;
if (prev_index !== index && /** @type {number} */ (index) < items.length) {
const from_dom = /** @type {Element} */ (get_first_element(block));
const from = from_dom.getBoundingClientRect();
// Cancel any existing key transitions
for (const transition of transitions) {
if (transition.r === 'key') {
transition.c();
}
}
schedule_task(() => {
trigger_transitions(transitions, 'key', from);
});
}
}

@ -285,6 +285,15 @@ export type EachBlock = {
};
export type EachItemBlock = {
/** transition */
a:
| null
| ((
block: EachItemBlock,
transitions: Set<Transition>,
index: number,
index_is_reactive: boolean
) => void);
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */

@ -0,0 +1,25 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
import { log } from './log.js';
export default test({
before_test() {
log.length = 0;
},
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
flushSync(() => {
b1.click();
});
assert.deepEqual(log, ['transition 2']);
flushSync(() => {
b2.click();
});
assert.deepEqual(log, ['transition 2', 'transition 1', 'transition 1']);
}
});

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -0,0 +1,33 @@
<script>
import { log } from './log.js';
function transition1() {
log.push('transition 1')
return {
tick() {
}
}
}
function transition2() {
log.push('transition 2')
return {
tick() {
}
}
}
let toggle = $state(false);
let toggleTransition = $state(false);
const derived = $derived(toggleTransition ? transition1 : transition2)
</script>
<button on:click={() => toggle = !toggle}>{toggle}</button>
<button on:click={() => toggleTransition = !toggleTransition}>{toggleTransition}</button>
{#if toggle}<div transition:derived></div>{/if}
Loading…
Cancel
Save