diff --git a/src/animate.ts b/src/animate.ts index cf64cd060a..f5500020cc 100644 --- a/src/animate.ts +++ b/src/animate.ts @@ -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; diff --git a/src/easing.ts b/src/easing.ts index 7a762394c2..c23019b320 100644 --- a/src/easing.ts +++ b/src/easing.ts @@ -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); } diff --git a/src/internal/animations.ts b/src/internal/animations.ts index 8d52b3ca7a..c7192fce29 100644 --- a/src/internal/animations.ts +++ b/src/internal/animations.ts @@ -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') { diff --git a/src/internal/dom.ts b/src/internal/dom.ts index 293040ce20..edf394e4da 100644 --- a/src/internal/dom.ts +++ b/src/internal/dom.ts @@ -53,8 +53,8 @@ export function object_without_properties(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(name:K):SVGElement { + return document.createElementNS('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]; diff --git a/src/internal/loop.ts b/src/internal/loop.ts index 73f118c234..c41d72ed74 100644 --- a/src/internal/loop.ts +++ b/src/internal/loop.ts @@ -1,6 +1,6 @@ import { now, raf } from './utils'; -export interface Task { abort(): void; promise: Promise } +export interface Task { abort(): void; promise: Promise } const tasks = new Set(); let running = false; @@ -32,7 +32,7 @@ export function loop(fn: (number)=>void): Task { } return { - promise: new Promise(fulfil => { + promise: new Promise(fulfil => { tasks.add(task = [fn, fulfil]); }), abort() { diff --git a/src/internal/ssr.ts b/src/internal/ssr.ts index aaa391b4dc..c80b4d9f45 100644 --- a/src/internal/ssr.ts +++ b/src/internal/ssr.ts @@ -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(store: Readable): T | undefined { let value; store.subscribe(_ => value = _)(); return value; diff --git a/src/internal/style_manager.ts b/src/internal/style_manager.ts index 9ef1b12d1c..2721200627 100644 --- a/src/internal/style_manager.ts +++ b/src/internal/style_manager.ts @@ -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 diff --git a/src/internal/transitions.ts b/src/internal/transitions.ts index 04942e3d16..f3e8414e5d 100644 --- a/src/internal/transitions.ts +++ b/src/internal/transitions.ts @@ -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|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); }); diff --git a/src/internal/utils.ts b/src/internal/utils.ts index c562cd02b6..7997a25b1d 100644 --- a/src/internal/utils.ts +++ b/src/internal/utils.ts @@ -2,13 +2,14 @@ export function noop() {} export const identity = x => x; -export function assign(tar, src) { +export function assign(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(value: any): value is PromiseLike { + 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'; } diff --git a/src/motion/spring.ts b/src/motion/spring.ts index 3977c08f79..f99e8db41f 100644 --- a/src/motion/spring.ts +++ b/src/motion/spring.ts @@ -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 { + inv_mass: number; + dt: number; + opts: Spring; + settled: boolean +} + +function tick_spring(ctx: TickContext, 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 = (target_value: T, value: T) => T; -interface Spring extends Readable{ - set: (new_value: T, opts?: SpringUpdateOpts) => (Promise | Promise); +interface Spring extends Readable{ + set: (new_value: T, opts?: SpringUpdateOpts) => Promise; + update: (fn: Updater, opts?: SpringUpdateOpts) => Promise; precision: number; - update: (fn, opts: SpringUpdateOpts) => Promise; damping: number; stiffness: number } -export function spring(value: T, opts: SpringOpts = {}) { +export function spring(value: T, opts: SpringOpts = {}): Spring { const store = writable(value); const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; @@ -61,7 +79,7 @@ export function spring(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 { target_value = new_value; const token = current_token = {}; @@ -91,7 +109,7 @@ export function spring(value: T, opts: SpringOpts = {}) { inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1); - const ctx = { + const ctx: TickContext = { inv_mass, opts: spring, settled: true, // tick_spring may signal false @@ -116,14 +134,14 @@ export function spring(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; return spring; } diff --git a/src/motion/tweened.ts b/src/motion/tweened.ts index 3b2d0ef5a2..ea3f2cdd86 100644 --- a/src/motion/tweened.ts +++ b/src/motion/tweened.ts @@ -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 { + delay?: number; + duration?: number | ((from: T, to: T) => number) + easing?: (t: number) => number; + interpolate?: (a: T, b: T) => (t: number) => T +} + +type Updater = (target_value: T, value: T) => T; + +interface Tweened extends Readable { + set(value: T, opts: Options): Promise; + + update(updater: Updater, opts: Options): Promise; +} + +export function tweened(value: T, defaults: Options = {}):Tweened { 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) { 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) => set(fn(target_value, value), opts), subscribe: store.subscribe }; } diff --git a/src/motion/utils.ts b/src/motion/utils.ts index f8bfcce14c..2eab8baa2f 100644 --- a/src/motion/utils.ts +++ b/src/motion/utils.ts @@ -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]'; } diff --git a/src/transition.ts b/src/transition.ts index 20619ee131..b3f4f21288 100644 --- a/src/transition.ts +++ b/src/transition.ts @@ -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; + +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() });