improve typings for animate, easing, transition, motion and internal apis

pull/2891/head
Bogdan Savluk 6 years ago
parent 673a3c92e4
commit 96c0fd0c41

@ -1,7 +1,22 @@
import { cubicOut } from 'svelte/easing';
import { is_function } from 'svelte/internal';
export function flip(node, animation, params) {
// todo: same as Transition, should it be shared?
export interface AnimationConfig {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
interface FlipParams {
delay: number;
duration: number | ((len: number) => number);
easing: (t: number) => number,
}
export function flip(node: Element, animation: { from: DOMRect, to: DOMRect }, params: FlipParams): AnimationConfig {
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;

@ -5,23 +5,23 @@ Distributed under MIT License https://github.com/mattdesl/eases/blob/master/LICE
export { identity as linear } from 'svelte/internal';
export function backInOut(t) {
export function backInOut(t: number) {
const s = 1.70158 * 1.525;
if ((t *= 2) < 1) return 0.5 * (t * t * ((s + 1) * t - s));
return 0.5 * ((t -= 2) * t * ((s + 1) * t + s) + 2);
}
export function backIn(t) {
export function backIn(t: number) {
const s = 1.70158;
return t * t * ((s + 1) * t - s);
}
export function backOut(t) {
export function backOut(t: number) {
const s = 1.70158;
return --t * t * ((s + 1) * t + s) + 1;
}
export function bounceOut(t) {
export function bounceOut(t: number) {
const a = 4.0 / 11.0;
const b = 8.0 / 11.0;
const c = 9.0 / 10.0;
@ -41,43 +41,43 @@ export function bounceOut(t) {
: 10.8 * t * t - 20.52 * t + 10.72;
}
export function bounceInOut(t) {
export function bounceInOut(t: number) {
return t < 0.5
? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0))
: 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5;
}
export function bounceIn(t) {
export function bounceIn(t: number) {
return 1.0 - bounceOut(1.0 - t);
}
export function circInOut(t) {
export function circInOut(t: number) {
if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1);
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
}
export function circIn(t) {
export function circIn(t: number) {
return 1.0 - Math.sqrt(1.0 - t * t);
}
export function circOut(t) {
export function circOut(t: number) {
return Math.sqrt(1 - --t * t);
}
export function cubicInOut(t) {
export function cubicInOut(t: number) {
return t < 0.5 ? 4.0 * t * t * t : 0.5 * Math.pow(2.0 * t - 2.0, 3.0) + 1.0;
}
export function cubicIn(t) {
export function cubicIn(t: number) {
return t * t * t;
}
export function cubicOut(t) {
export function cubicOut(t: number) {
const f = t - 1.0;
return f * f * f + 1.0;
}
export function elasticInOut(t) {
export function elasticInOut(t: number) {
return t < 0.5
? 0.5 *
Math.sin(((+13.0 * Math.PI) / 2) * 2.0 * t) *
@ -88,17 +88,17 @@ export function elasticInOut(t) {
1.0;
}
export function elasticIn(t) {
export function elasticIn(t: number) {
return Math.sin((13.0 * t * Math.PI) / 2) * Math.pow(2.0, 10.0 * (t - 1.0));
}
export function elasticOut(t) {
export function elasticOut(t: number) {
return (
Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2) * Math.pow(2.0, -10.0 * t) + 1.0
);
}
export function expoInOut(t) {
export function expoInOut(t: number) {
return t === 0.0 || t === 1.0
? t
: t < 0.5
@ -106,66 +106,66 @@ export function expoInOut(t) {
: -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0;
}
export function expoIn(t) {
export function expoIn(t: number) {
return t === 0.0 ? t : Math.pow(2.0, 10.0 * (t - 1.0));
}
export function expoOut(t) {
export function expoOut(t: number) {
return t === 1.0 ? t : 1.0 - Math.pow(2.0, -10.0 * t);
}
export function quadInOut(t) {
export function quadInOut(t: number) {
t /= 0.5;
if (t < 1) return 0.5 * t * t;
t--;
return -0.5 * (t * (t - 2) - 1);
}
export function quadIn(t) {
export function quadIn(t: number) {
return t * t;
}
export function quadOut(t) {
export function quadOut(t: number) {
return -t * (t - 2.0);
}
export function quartInOut(t) {
export function quartInOut(t: number) {
return t < 0.5
? +8.0 * Math.pow(t, 4.0)
: -8.0 * Math.pow(t - 1.0, 4.0) + 1.0;
}
export function quartIn(t) {
export function quartIn(t: number) {
return Math.pow(t, 4.0);
}
export function quartOut(t) {
export function quartOut(t: number) {
return Math.pow(t - 1.0, 3.0) * (1.0 - t) + 1.0;
}
export function quintInOut(t) {
export function quintInOut(t: number) {
if ((t *= 2) < 1) return 0.5 * t * t * t * t * t;
return 0.5 * ((t -= 2) * t * t * t * t + 2);
}
export function quintIn(t) {
export function quintIn(t: number) {
return t * t * t * t * t;
}
export function quintOut(t) {
export function quintOut(t: number) {
return --t * t * t * t * t + 1;
}
export function sineInOut(t) {
export function sineInOut(t: number) {
return -0.5 * (Math.cos(Math.PI * t) - 1);
}
export function sineIn(t) {
export function sineIn(t: number) {
const v = Math.cos(t * Math.PI * 0.5);
if (Math.abs(v) < 1e-14) return 1;
else return 1 - v;
}
export function sineOut(t) {
export function sineOut(t: number) {
return Math.sin((t * Math.PI) / 2);
}

@ -1,18 +1,28 @@
import { identity as linear, noop, now } from './utils';
import { loop } from './loop';
import { create_rule, delete_rule } from './style_manager';
import { AnimationConfig } from '../animate';
export function create_animation(node, from, fn, params) {
//todo: documentation says it is DOMRect, but in IE it would be ClientReact
type PositionReact = DOMRect|ClientRect;
type AnimationFn = (node: Element, { from, to }: { from: PositionReact, to: PositionReact }, params: any) => AnimationConfig;
export function create_animation(node: Element & ElementCSSInlineStyle, from: PositionReact, fn: AnimationFn, params) {
if (!from) return noop;
const to = node.getBoundingClientRect();
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return noop;
const {
delay = 0,
duration = 300,
easing = linear,
// @ts-ignore todo: should this be separated from destructuring? Or start/end added to public api and documentation?
start: start_time = now() + delay,
// @ts-ignore todo:
end = start_time + duration,
tick = noop,
css
@ -67,7 +77,7 @@ export function create_animation(node, from, fn, params) {
return stop;
}
export function fix_position(node) {
export function fix_position(node: Element & ElementCSSInlineStyle) {
const style = getComputedStyle(node);
if (style.position !== 'absolute' && style.position !== 'fixed') {

@ -53,8 +53,8 @@ export function object_without_properties<T,K extends keyof T>(obj:T, exclude: K
return target;
}
export function svg_element(name:string):SVGElement {
return document.createElementNS('http://www.w3.org/2000/svg', name);
export function svg_element<K extends keyof SVGElementTagNameMap>(name:K):SVGElement {
return document.createElementNS<K>('http://www.w3.org/2000/svg', name);
}
export function text(data:string) {
@ -95,7 +95,7 @@ export function attr(node: Element, attribute: string, value?: string) {
else node.setAttribute(attribute, value);
}
export function set_attributes(node: HTMLElement, attributes: { [x: string]: string; }) {
export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string; }) {
for (const key in attributes) {
if (key === 'style') {
node.style.cssText = attributes[key];

@ -1,6 +1,6 @@
import { now, raf } from './utils';
export interface Task { abort(): void; promise: Promise<undefined> }
export interface Task { abort(): void; promise: Promise<void> }
const tasks = new Set();
let running = false;
@ -32,7 +32,7 @@ export function loop(fn: (number)=>void): Task {
}
return {
promise: new Promise<undefined>(fulfil => {
promise: new Promise<void>(fulfil => {
tasks.add(task = [fn, fulfil]);
}),
abort() {

@ -1,5 +1,6 @@
import { set_current_component, current_component } from './lifecycle';
import { run_all, blank_object } from './utils';
import { Readable } from 'svelte/store';
export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
@ -113,7 +114,7 @@ export function create_ssr_component(fn) {
};
}
export function get_store_value(store) {
export function get_store_value<T>(store: Readable<T>): T | undefined {
let value;
store.subscribe(_ => value = _)();
return value;

@ -6,7 +6,7 @@ let active = 0;
let current_rules = {};
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str) {
function hash(str: string) {
let hash = 5381;
let i = str.length;
@ -14,7 +14,7 @@ function hash(str) {
return hash >>> 0;
}
export function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) {
export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: number, duration: number, delay: number, ease: (t: number) => number, fn: (t: number, u: number) => string, uid: number = 0) {
const step = 16.666 / duration;
let keyframes = '{\n';
@ -44,7 +44,7 @@ export function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) {
return name;
}
export function delete_rule(node, name?) {
export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string) {
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(name

@ -1,10 +1,11 @@
import { identity as linear, noop, now, run_all } from './utils';
import { identity as linear, is_function, noop, now, run_all } from './utils';
import { loop } from './loop';
import { create_rule, delete_rule } from './style_manager';
import { custom_event } from './dom';
import { add_render_callback } from './scheduler';
import { TransitionConfig } from '../transition';
let promise;
let promise: Promise<void>|null;
function wait() {
if (!promise) {
@ -17,7 +18,7 @@ function wait() {
return promise;
}
function dispatch(node, direction, kind) {
function dispatch(node: Element, direction: boolean, kind: 'start' | 'end') {
node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`));
}
@ -39,8 +40,9 @@ export function check_outros() {
export function on_outro(callback) {
outros.callbacks.push(callback);
}
type TransitionFn = (node: Element, params: any) => TransitionConfig;
export function create_in_transition(node, fn, params) {
export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) {
let config = fn(node, params);
let running = false;
let animation_name;
@ -95,7 +97,7 @@ export function create_in_transition(node, fn, params) {
delete_rule(node);
if (typeof config === 'function') {
if (is_function(config)) {
config = config();
wait().then(go);
} else {
@ -116,7 +118,7 @@ export function create_in_transition(node, fn, params) {
};
}
export function create_out_transition(node, fn, params) {
export function create_out_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) {
let config = fn(node, params);
let running = true;
let animation_name;
@ -163,8 +165,9 @@ export function create_out_transition(node, fn, params) {
});
}
if (typeof config === 'function') {
if (is_function(config)) {
wait().then(() => {
// @ts-ignore
config = config();
go();
});
@ -186,7 +189,7 @@ export function create_out_transition(node, fn, params) {
};
}
export function create_bidirectional_transition(node, fn, params, intro) {
export function create_bidirectional_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any, intro: boolean) {
let config = fn(node, params);
let t = intro ? 0 : 1;
@ -295,8 +298,9 @@ export function create_bidirectional_transition(node, fn, params, intro) {
return {
run(b) {
if (typeof config === 'function') {
if (is_function(config)) {
wait().then(() => {
// @ts-ignore
config = config();
go(b);
});

@ -2,13 +2,14 @@ export function noop() {}
export const identity = x => x;
export function assign(tar, src) {
export function assign<T, S>(tar:T, src:S): T & S {
// @ts-ignore
for (const k in src) tar[k] = src[k];
return tar;
return tar as T & S;
}
export function is_promise(value) {
return value && typeof value.then === 'function';
export function is_promise<T = any>(value: any): value is PromiseLike<T> {
return value && typeof value === 'object' && typeof value.then === 'function';
}
export function add_location(element, file, line, column, char) {
@ -29,7 +30,7 @@ export function run_all(fns) {
fns.forEach(run);
}
export function is_function(thing) {
export function is_function(thing: any): thing is Function {
return typeof thing === 'function';
}

@ -2,9 +2,18 @@ import { Readable, writable } from 'svelte/store';
import { loop, now, Task } from 'svelte/internal';
import { is_date } from './utils';
function tick_spring(ctx, last_value, current_value, target_value) {
interface TickContext<T> {
inv_mass: number;
dt: number;
opts: Spring<T>;
settled: boolean
}
function tick_spring<T>(ctx: TickContext<T>, last_value: T, current_value: T, target_value: T):T {
if (typeof current_value === 'number' || is_date(current_value)) {
// @ts-ignore
const delta = target_value - current_value;
// @ts-ignore
const velocity = (current_value - last_value) / (ctx.dt||1/60); // guard div by 0
const spring = ctx.opts.stiffness * delta;
const damper = ctx.opts.damping * velocity;
@ -15,16 +24,20 @@ function tick_spring(ctx, last_value, current_value, target_value) {
return target_value; // settled
} else {
ctx.settled = false; // signal loop to keep ticking
// @ts-ignore
return is_date(current_value) ?
new Date(current_value.getTime() + d) : current_value + d;
}
} else if (Array.isArray(current_value)) {
// @ts-ignore
return current_value.map((_, i) =>
tick_spring(ctx, last_value[i], current_value[i], target_value[i]));
} else if (typeof current_value === 'object') {
const next_value = {};
for (const k in current_value)
// @ts-ignore
next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]);
// @ts-ignore
return next_value;
} else {
throw new Error(`Cannot spring ${typeof current_value} values`);
@ -37,17 +50,22 @@ interface SpringOpts {
precision?: number,
}
type SpringUpdateOpts = { hard?: any; soft?: string | number | boolean; };
interface SpringUpdateOpts {
hard?: any;
soft?: string | number | boolean;
}
type Updater<T> = (target_value: T, value: T) => T;
interface Spring<T=any> extends Readable<T>{
set: (new_value: T, opts?: SpringUpdateOpts) => (Promise<T> | Promise<T>);
interface Spring<T> extends Readable<T>{
set: (new_value: T, opts?: SpringUpdateOpts) => Promise<void>;
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>;
precision: number;
update: (fn, opts: SpringUpdateOpts) => Promise<T>;
damping: number;
stiffness: number
}
export function spring<T=any>(value: T, opts: SpringOpts = {}) {
export function spring<T=any>(value: T, opts: SpringOpts = {}): Spring<T> {
const store = writable(value);
const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts;
@ -61,7 +79,7 @@ export function spring<T=any>(value: T, opts: SpringOpts = {}) {
let inv_mass_recovery_rate = 0;
let cancel_task = false;
function set(new_value: any, opts: SpringUpdateOpts={}) {
function set(new_value: T, opts: SpringUpdateOpts={}): Promise<void> {
target_value = new_value;
const token = current_token = {};
@ -91,7 +109,7 @@ export function spring<T=any>(value: T, opts: SpringOpts = {}) {
inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1);
const ctx = {
const ctx: TickContext<T> = {
inv_mass,
opts: spring,
settled: true, // tick_spring may signal false
@ -116,14 +134,14 @@ export function spring<T=any>(value: T, opts: SpringOpts = {}) {
});
}
const spring: Spring = {
const spring = {
set,
update: (fn, opts:SpringUpdateOpts) => set(fn(target_value, value), opts),
subscribe: store.subscribe,
stiffness,
damping,
precision
};
} as Spring<T>;
return spring;
}

@ -1,6 +1,6 @@
import { writable } from 'svelte/store'; // eslint-disable-line import/no-unresolved
import { assign, loop, now } from 'svelte/internal'; // eslint-disable-line import/no-unresolved
import { linear } from 'svelte/easing'; // eslint-disable-line import/no-unresolved
import { Readable, writable } from 'svelte/store';
import { assign, loop, now, Task } from 'svelte/internal';
import { linear } from 'svelte/easing';
import { is_date } from './utils';
function get_interpolator(a, b) {
@ -54,13 +54,28 @@ function get_interpolator(a, b) {
throw new Error(`Cannot interpolate ${type} values`);
}
export function tweened(value, defaults = {}) {
interface Options<T> {
delay?: number;
duration?: number | ((from: T, to: T) => number)
easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T
}
type Updater<T> = (target_value: T, value: T) => T;
interface Tweened<T> extends Readable<T> {
set(value: T, opts: Options<T>): Promise<void>;
update(updater: Updater<T>, opts: Options<T>): Promise<void>;
}
export function tweened<T>(value: T, defaults: Options<T> = {}):Tweened<T> {
const store = writable(value);
let task;
let task: Task;
let target_value = value;
function set(new_value, opts) {
function set(new_value: T, opts: Options<T>) {
target_value = new_value;
let previous_task = task;
@ -97,6 +112,7 @@ export function tweened(value, defaults = {}) {
return false;
}
// @ts-ignore
store.set(value = fn(easing(elapsed / duration)));
return true;
});
@ -106,7 +122,7 @@ export function tweened(value, defaults = {}) {
return {
set,
update: (fn, opts) => set(fn(target_value, value), opts),
update: (fn, opts:Options<T>) => set(fn(target_value, value), opts),
subscribe: store.subscribe
};
}

@ -1,3 +1,3 @@
export function is_date(obj: any) {
export function is_date(obj: any): obj is Date {
return Object.prototype.toString.call(obj) === '[object Date]';
}

@ -1,10 +1,23 @@
import { cubicOut, cubicInOut } from 'svelte/easing';
import { assign, is_function } from 'svelte/internal';
export function fade(node, {
export interface TransitionConfig {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
interface FadeParams {
delay: number;
duration: number;
}
export function fade(node: Element, {
delay = 0,
duration = 400
}) {
}: FadeParams): TransitionConfig {
const o = +getComputedStyle(node).opacity;
return {
@ -14,14 +27,23 @@ export function fade(node, {
};
}
export function fly(node, {
interface FlyParams {
delay: number;
duration: number;
easing: (t: number)=>number,
x: number;
y: number;
opacity: number;
}
export function fly(node: Element, {
delay = 0,
duration = 400,
easing = cubicOut,
x = 0,
y = 0,
opacity = 0
}) {
}: FlyParams): TransitionConfig {
const style = getComputedStyle(node);
const target_opacity = +style.opacity;
const transform = style.transform === 'none' ? '' : style.transform;
@ -38,11 +60,17 @@ export function fly(node, {
};
}
export function slide(node, {
interface SlideParams {
delay: number;
duration: number;
easing: (t: number)=>number,
}
export function slide(node: Element, {
delay = 0,
duration = 400,
easing = cubicOut
}) {
}: SlideParams): TransitionConfig {
const style = getComputedStyle(node);
const opacity = +style.opacity;
const height = parseFloat(style.height);
@ -70,13 +98,21 @@ export function slide(node, {
};
}
export function scale(node, {
interface ScaleParams {
delay: number;
duration: number;
easing: (t: number)=>number,
start: number;
opacity: number;
}
export function scale(node: Element, {
delay = 0,
duration = 400,
easing = cubicOut,
start = 0,
opacity = 0
}) {
}: ScaleParams): TransitionConfig {
const style = getComputedStyle(node);
const target_opacity = +style.opacity;
const transform = style.transform === 'none' ? '' : style.transform;
@ -95,12 +131,19 @@ export function scale(node, {
};
}
export function draw(node, {
interface DrawParams {
delay: number;
speed: number;
duration: number | ((len: number) => number);
easing: (t: number) => number,
}
export function draw(node: SVGElement & { getTotalLength(): number }, {
delay = 0,
speed,
duration,
easing = cubicInOut
}) {
}: DrawParams): TransitionConfig {
const len = node.getTotalLength();
if (duration === undefined) {
@ -121,11 +164,21 @@ export function draw(node, {
};
}
export function crossfade({ fallback, ...defaults }) {
const to_receive = new Map();
const to_send = new Map();
interface CrossfadeParams {
delay: number;
duration: number | ((len: number) => number);
easing: (t: number) => number,
}
type ClientRectMap = Map<any, { rect: ClientRect }>;
export function crossfade({ fallback, ...defaults }: CrossfadeParams & {
fallback: (node: Element, params: CrossfadeParams, intro: boolean)=> TransitionConfig
}) {
const to_receive: ClientRectMap = new Map();
const to_send: ClientRectMap = new Map();
function crossfade(from, node, params) {
function crossfade(from: ClientRect, node: Element, params: CrossfadeParams):TransitionConfig {
const {
delay = 0,
duration = d => Math.sqrt(d) * 30,
@ -152,8 +205,8 @@ export function crossfade({ fallback, ...defaults }) {
};
}
function transition(items, counterparts, intro) {
return (node, params) => {
function transition(items: ClientRectMap, counterparts: ClientRectMap, intro: boolean) {
return (node: Element, params: CrossfadeParams & { key: any }) => {
items.set(params.key, {
rect: node.getBoundingClientRect()
});

Loading…
Cancel
Save