You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte/src/runtime/internal/transitions.ts

187 lines
5.6 KiB

import { TransitionConfig } from '../transition';
import { Fragment } from './Component';
import { custom_event } from './dom';
import { now, noop } from 'svelte/environment';
import { setFrameTimeout, setTweenTimeout } from './loop';
import { add_measure_callback } from './scheduler';
import { animate_css } from './style_manager';
type TransitionFn = (node: HTMLElement, params: any) => TransitionConfig;
export type StopReverse = (t?: number | -1) => StopReverse | void;
export const transition_in = (block: Fragment, local?) => {
if (!block || !block.i) return;
outroing.delete(block);
block.i(local);
};
export const transition_out = (block: Fragment, local?) => {
if (!block || !block.o || outroing.has(block)) return;
outroing.add(block);
block.o(local);
};
type TransitionGroup = {
/* parent group */ p: TransitionGroup;
/* callbacks */ c: (() => void)[];
/* running outros */ r: number;
/* stop callbacks */ s: ((t: number) => void)[];
/* outro timeout */ t: number;
};
let transition_group: TransitionGroup;
const outroing = new Set();
export const group_transition_out = (fn) => {
const c = [];
const current_group = (transition_group = { p: transition_group, c, r: 0, s: [], t: 0 });
fn((block, callback, detach = true) => {
if (!block || !block.o || outroing.has(block)) return;
outroing.add(block);
c.push(() => {
if (outroing.has(block)) {
outroing.delete(block);
if (detach) block.d(1);
// callback always ?
callback();
}
});
block.o(1);
});
if (!current_group.r) for (let i = 0; i < current_group.c.length; i++) current_group.c[i]();
transition_group = transition_group.p;
};
const swap = (fn, rx) =>
fn.length === 1 ? (rx & tx.intro ? fn : (t) => fn(1 - t)) : rx & tx.intro ? (t) => fn(t, 1 - t) : (t) => fn(1 - t, t);
const mirrored = (fn, rx, easing) => {
const run = swap(fn, rx);
return easing ? (rx & tx.intro ? (t) => run(easing(t)) : (t) => run(1 - easing(1 - t))) : run;
};
const reversed = (fn, rx, easing, start = 0, end = 1) => {
const run = swap(fn, rx);
const difference = end - start;
return easing ? (t) => run(start + difference * easing(t)) : (t) => run(start + difference * t);
};
export enum tx {
intro = 1,
outro = 2,
reverse = 3,
bidirectional = 4,
animation = 8,
}
export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(function transition(
this: HTMLElement,
fn: TransitionFn,
rx: tx,
params = {},
/* internal to this file */
elapsed_duration = 0,
delay_left = -1,
elapsed_ratio = 0
) {
let config;
let running = true;
let cancel_css;
let cancel_raf;
let start_time = 0;
let end_time = 0;
const current_group = transition_group;
if (rx & tx.outro) current_group.r++;
add_measure_callback(() => {
if (null === (config = fn(this, params))) return noop;
return (current_frame_time) => {
if (false === running) return;
let { delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: TransitionConfig =
'function' === typeof config ? (config = config()) : config;
const solver = 'reverse' === strategy ? reversed : mirrored;
const runner = (fn) => solver(fn, rx, easing, elapsed_ratio, 1);
if (rx & tx.bidirectional) {
if (-1 !== delay_left) delay = delay_left;
if (solver === reversed) duration -= elapsed_duration;
else if (solver === mirrored) delay -= elapsed_duration;
}
end_time = (start_time = current_frame_time + delay) + duration;
if (0 === (rx & tx.animation)) {
this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}trostart`));
}
if (css) cancel_css = animate_css(this, runner(css), duration, delay);
if (rx & tx.outro && css) {
if (current_group.s.push(stop) === current_group.r) {
setFrameTimeout((t) => {
for (let i = 0; i < current_group.s.length; i++) current_group.s[i](t);
}, Math.max(end_time, current_group.t));
} else {
current_group.t = Math.max(end_time, current_group.t);
}
if (tick) {
cancel_raf = setTweenTimeout(noop, end_time, runner(tick), duration);
}
} else {
cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time);
}
};
});
const stop: StopReverse = (t?: number | -1) => {
if (rx & tx.outro && 0 === (rx & tx.bidirectional) && void 0 === t && 'tick' in config) config.tick(1, 0);
if (false === running) return;
else running = false;
if (cancel_css) cancel_css();
if (cancel_raf) cancel_raf();
if (rx & tx.animation) return;
if (t >= end_time) this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}troend`));
if (rx & tx.outro && !--current_group.r) for (let i = 0; i < current_group.c.length; i++) current_group.c[i]();
if (0 === (rx & tx.bidirectional)) return;
if (-1 === t)
return (
(t = now()) < end_time &&
run_transition(
this,
() => config,
rx ^ tx.reverse,
params,
end_time - t,
start_time > t ? start_time - t : 0,
(1 - elapsed_ratio) * (1 - (config.easing || ((v) => v))(1 - (end_time - t) / (end_time - start_time)))
)
);
else running_bidi.delete(this);
};
return stop;
});
const running_bidi: Map<HTMLElement, StopReverse> = new Map();
export const run_bidirectional_transition = /*#__PURE__*/ Function.prototype.call.bind(function bidirectional(
this: HTMLElement,
fn: TransitionFn,
rx: tx.intro | tx.outro,
params: any
) {
let cancel;
running_bidi.set(
this,
(cancel =
(running_bidi.has(this) && running_bidi.get(this)(-1)) || run_transition(this, fn, rx | tx.bidirectional, params))
);
return cancel;
});
export const run_duration = (duration, value1, value2?): number =>
typeof duration === 'function' ? duration(value1, value2) : duration;