Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support dynamic transition functions #9844

Merged
merged 4 commits into from
Dec 7, 2023
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/giant-roses-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: support dynamic transition functions
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)
Expand All @@ -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
)
Expand Down
3 changes: 3 additions & 0 deletions packages/svelte/src/internal/client/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
24 changes: 4 additions & 20 deletions packages/svelte/src/internal/client/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down
24 changes: 12 additions & 12 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
41 changes: 38 additions & 3 deletions packages/svelte/src/internal/client/transitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -21,6 +21,7 @@ import {
managed_effect,
managed_pre_effect,
mark_subtree_inert,
schedule_task,
untrack
} from './runtime.js';
import { raf } from './timing.js';
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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(() =>
Expand Down Expand Up @@ -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);
});
}
}
9 changes: 9 additions & 0 deletions packages/svelte/src/internal/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
@@ -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']);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];
Original file line number Diff line number Diff line change
@@ -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}