svelte/motion

closes #1875
pull/1941/head
Rich Harris 6 years ago committed by GitHub
parent feadc6cfd0
commit b87f930002
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -8,6 +8,7 @@ node_modules
/internal.*
/store.js
/easing.js
/motion.*
/transition.js
/scratch/
/coverage/

@ -3,6 +3,8 @@ Adapted from https://github.com/mattdesl
Distributed under MIT License https://github.com/mattdesl/eases/blob/master/LICENSE.md
*/
export { identity as linear } from './internal';
export function backInOut(t) {
var s = 1.70158 * 1.525;
if ((t *= 2) < 1) return 0.5 * (t * t * ((s + 1) * t - s));
@ -112,10 +114,6 @@ export function expoOut(t) {
return t === 1.0 ? t : 1.0 - Math.pow(2.0, -10.0 * t);
}
export function linear(t) {
return t;
}
export function quadInOut(t) {
t /= 0.5;
if (t < 1) return 0.5 * t * t;

@ -52,27 +52,31 @@ export default [
experimentalCodeSplitting: true
},
/* internal.[m]js */
{
input: 'src/internal/index.js',
/* internal.[m]js, motion.mjs */
...['internal', 'motion'].map(name => ({
input: `src/${name}/index.js`,
output: [
{
file: 'internal.mjs',
format: 'esm'
file: `${name}.mjs`,
format: 'esm',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
},
{
file: 'internal.js',
format: 'cjs'
file: `${name}.js`,
format: 'cjs',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
}
]
},
],
external: id => id.startsWith('svelte/')
})),
// runtime API
// everything else
...['index', 'store', 'easing', 'transition'].map(name => ({
input: `${name}.mjs`,
output: {
file: `${name}.js`,
format: 'cjs'
format: 'cjs',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
},
external: id => id !== `${name}.mjs`
}))

@ -341,7 +341,7 @@ export default class EachBlockWrapper extends Wrapper {
block.builders.update.addBlock(deindent`
const ${this.vars.each_block_value} = ${snippet};
${this.block.hasOutros && `@groupOutros();`}
${this.block.hasOutros && `@group_outros();`}
${this.node.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`}
${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.vars.get_each_context});
${this.node.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`}
@ -466,7 +466,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.block.hasOutros) {
destroy = deindent`
@groupOutros();
@group_outros();
for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1);
`;
} else {

@ -595,13 +595,13 @@ export default class ElementWrapper extends Wrapper {
if (${name}) ${name}.invalidate();
@add_render_callback(() => {
if (!${name}) ${name} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, true);
if (!${name}) ${name} = @create_transition(${this.var}, ${fn}, ${snippet}, true);
${name}.run(1);
});
`);
block.builders.outro.addBlock(deindent`
if (!${name}) ${name} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, false);
if (!${name}) ${name} = @create_transition(${this.var}, ${fn}, ${snippet}, false);
${name}.run(0, () => {
#outrocallback();
${name} = null;
@ -630,7 +630,7 @@ export default class ElementWrapper extends Wrapper {
block.builders.intro.addConditional(`@intros.enabled`, deindent`
@add_render_callback(() => {
${introName} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, true);
${introName} = @create_transition(${this.var}, ${fn}, ${snippet}, true);
${introName}.run(1);
});
`);
@ -651,7 +651,7 @@ export default class ElementWrapper extends Wrapper {
// TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM
block.builders.outro.addBlock(deindent`
${outroName} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, false);
${outroName} = @create_transition(${this.var}, ${fn}, ${snippet}, false);
${outroName}.run(0, #outrocallback);
`);
@ -666,18 +666,18 @@ export default class ElementWrapper extends Wrapper {
const { component } = this.renderer;
const rect = block.getUniqueName('rect');
const animation = block.getUniqueName('animation');
const stop_animation = block.getUniqueName('stop_animation');
block.addVariable(rect);
block.addVariable(animation);
block.addVariable(stop_animation, '@noop');
block.builders.measure.addBlock(deindent`
${rect} = ${this.var}.getBoundingClientRect();
`);
block.builders.fix.addBlock(deindent`
@fixPosition(${this.var});
if (${animation}) ${animation}.stop();
@fix_position(${this.var});
${stop_animation}();
`);
const params = this.node.animation.expression ? this.node.animation.expression.render() : '{}';
@ -685,8 +685,8 @@ export default class ElementWrapper extends Wrapper {
const name = component.qualify(this.node.animation.name);
block.builders.animate.addBlock(deindent`
if (${animation}) ${animation}.stop();
${animation} = @wrapAnimation(${this.var}, ${rect}, ${name}, ${params});
${stop_animation}();
${stop_animation} = @animate(${this.var}, ${rect}, ${name}, ${params});
`);
}

@ -322,7 +322,7 @@ export default class IfBlockWrapper extends Wrapper {
const updateMountNode = this.getUpdateMountNode(anchor);
const destroyOldBlock = deindent`
@groupOutros();
@group_outros();
${name}.o(function() {
${if_blocks}[${previous_block_index}].d(1);
${if_blocks}[${previous_block_index}] = null;
@ -445,7 +445,7 @@ export default class IfBlockWrapper extends Wrapper {
// as that will typically result in glitching
const exit = branch.block.hasOutroMethod
? deindent`
@groupOutros();
@group_outros();
${name}.o(function() {
${name}.d(1);
${name} = null;

@ -368,7 +368,7 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.update.addBlock(deindent`
if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${name}) {
@groupOutros();
@group_outros();
const old_component = ${name};
old_component.$$.fragment.o(() => {
old_component.$destroy();

@ -1,87 +1,85 @@
import { transitionManager, linear, generateRule, hash } from './transitions.js';
import { identity as linear, noop } from './utils.js';
import { loop } from './loop.js';
import { create_rule, delete_rule } from './style_manager.js';
export function wrapAnimation(node, from, fn, params) {
export function animate(node, from, fn, params) {
if (!from) return;
const to = node.getBoundingClientRect();
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return;
const info = fn(node, { from, to }, params);
const {
delay = 0,
duration = 300,
easing = linear,
start: start_time = window.performance.now() + delay,
end = start_time + duration,
tick = noop,
css
} = fn(node, { from, to }, params);
const duration = 'duration' in info ? info.duration : 300;
const delay = 'delay' in info ? info.delay : 0;
const ease = info.easing || linear;
const start = window.performance.now() + delay;
const end = start + duration;
const program = {
a: 0,
t: 0,
b: 1,
delta: 1,
duration,
start,
end
};
let running = true;
let started = false;
let name;
const cssText = node.style.cssText;
const animation = {
pending: delay ? program : null,
program: delay ? null : program,
running: true,
start() {
if (info.css) {
if (delay) node.style.cssText = cssText;
const rule = generateRule(program, ease, info.css);
program.name = `__svelte_${hash(rule)}`;
transitionManager.addRule(rule, program.name);
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
.join(', ');
}
animation.program = program;
animation.pending = null;
},
update: now => {
const p = now - program.start;
const t = program.a + program.delta * ease(p / program.duration);
if (info.tick) info.tick(t, 1 - t);
},
done() {
if (info.tick) info.tick(1, 0);
animation.stop();
},
stop() {
if (info.css) transitionManager.deleteRule(node, program.name);
animation.running = false;
function start() {
if (css) {
if (delay) node.style.cssText = cssText;
name = create_rule({ a: 0, b: 1, delta: 1, duration }, easing, css);
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(anim => anim && !/__svelte/.test(anim))
.concat(`${name} ${duration}ms linear 1 forwards`)
.join(', ');
}
};
transitionManager.add(animation);
started = true;
}
function stop() {
if (css) delete_rule(node, name);
running = false;
}
if (info.tick) info.tick(0, 1);
const { abort, promise } = loop(now => {
if (!started && now >= start_time) {
start();
}
if (started && now >= end) {
tick(1, 0);
stop();
}
if (!running) {
return false;
}
if (started) {
const p = now - start_time;
const t = 0 + 1 * easing(p / duration);
tick(t, 1 - t);
}
return true;
});
if (delay) {
if (info.css) node.style.cssText += info.css(0, 1);
if (css) node.style.cssText += css(0, 1);
} else {
animation.start();
start();
}
return animation;
tick(0, 1);
return stop;
}
export function fixPosition(node) {
export function fix_position(node) {
const style = getComputedStyle(node);
if (style.position !== 'absolute' && style.position !== 'fixed') {

@ -1,5 +1,5 @@
import { assign, run_all, isPromise } from './utils.js';
import { groupOutros } from './transitions.js';
import { group_outros } from './transitions.js';
import { flush } from '../internal/scheduler.js';
export function handlePromise(promise, info) {
@ -17,7 +17,7 @@ export function handlePromise(promise, info) {
if (info.blocks) {
info.blocks.forEach((block, i) => {
if (i !== index && block) {
groupOutros();
group_outros();
block.o(() => {
block.d(1);
info.blocks[i] = null;

@ -3,6 +3,7 @@ export * from './await-block.js';
export * from './dom.js';
export * from './keyed-each.js';
export * from './lifecycle.js';
export * from './loop.js';
export * from './scheduler.js';
export * from './spread.js';
export * from './ssr.js';

@ -105,19 +105,4 @@ export function measure(blocks) {
let i = blocks.length;
while (i--) rects[blocks[i].key] = blocks[i].node.getBoundingClientRect();
return rects;
}
export function animate(blocks, rects, fn, params) {
let i = blocks.length;
while (i--) {
const block = blocks[i];
const from = rects[block.key];
if (!from) continue;
const to = block.node.getBoundingClientRect();
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) continue;
}
}

@ -0,0 +1,38 @@
const tasks = new Set();
let running = false;
function run_tasks() {
tasks.forEach(task => {
if (!task[0](window.performance.now())) {
tasks.delete(task);
task[1]();
}
});
running = tasks.size > 0;
if (running) requestAnimationFrame(run_tasks);
}
export function clear_loops() {
// for testing...
tasks.forEach(task => tasks.delete(task));
running = false;
}
export function loop(fn) {
let task;
if (!running) {
running = true;
requestAnimationFrame(run_tasks);
}
return {
promise: new Promise(fulfil => {
tasks.add(task = [fn, fulfil]);
}),
abort() {
tasks.delete(task);
}
};
}

@ -0,0 +1,56 @@
import { createElement } from './dom.js';
let stylesheet;
let active = 0;
let current_rules = {};
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str) {
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return hash >>> 0;
}
export function create_rule({ a, b, delta, duration }, ease, fn) {
const step = 16.666 / duration;
let keyframes = '{\n';
for (let p = 0; p <= 1; p += step) {
const t = a + delta * ease(p);
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
}
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
const name = `__svelte_${hash(rule)}`;
if (!current_rules[name]) {
if (!stylesheet) {
const style = createElement('style');
document.head.appendChild(style);
stylesheet = style.sheet;
}
current_rules[name] = true;
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
}
active += 1;
return name;
}
export function delete_rule(node, name) {
node.style.animation = node.style.animation
.split(', ')
.filter(anim => anim && anim.indexOf(name) === -1)
.join(', ');
if (--active <= 0) clear_rules();
}
export function clear_rules() {
let i = stylesheet.cssRules.length;
while (i--) stylesheet.deleteRule(i);
current_rules = {};
}

@ -1,256 +1,175 @@
import { createElement } from './dom.js';
import { noop, run } from './utils.js';
export function linear(t) {
return t;
}
export function generateRule({ a, b, delta, duration }, ease, fn) {
const step = 16.666 / duration;
let keyframes = '{\n';
for (let p = 0; p <= 1; p += step) {
const t = a + delta * ease(p);
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
import { identity as linear, noop, run } from './utils.js';
import { loop } from './loop.js';
import { create_rule, delete_rule } from './style_manager.js';
let promise;
function wait() {
if (!promise) {
promise = Promise.resolve();
promise.then(() => {
promise = null;
});
}
return keyframes + `100% {${fn(b, 1 - b)}}\n}`;
return promise;
}
// https://github.com/darkskyapp/string-hash/blob/master/index.js
export function hash(str) {
let hash = 5381;
let i = str.length;
let outros;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return hash >>> 0;
export function group_outros() {
outros = {
remaining: 0,
callbacks: []
};
}
export function wrapTransition(component, node, fn, params, intro) {
let obj = fn.call(component, node, params);
let duration;
let ease;
export function create_transition(node, fn, params, intro) {
let config = fn(node, params);
let cssText;
let initialised = false;
let ready = !intro;
let t = intro ? 0 : 1;
return {
t: intro ? 0 : 1,
running: false,
program: null,
pending: null,
run(b, callback) {
if (typeof obj === 'function') {
transitionManager.wait().then(() => {
obj = obj();
this._run(b, callback);
});
} else {
this._run(b, callback);
}
},
let running = false;
let running_program = null;
let pending_program = null;
_run(b, callback) {
duration = obj.duration || 300;
ease = obj.easing || linear;
function start(program, delay, duration, easing) {
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}start`));
const program = {
start: window.performance.now() + (obj.delay || 0),
b,
callback: callback || noop
};
program.a = t;
program.d = program.b - program.a;
program.duration = duration * Math.abs(program.b - program.a);
program.end = program.start + program.duration;
if (intro && !initialised) {
if (obj.css && obj.delay) {
cssText = node.style.cssText;
node.style.cssText += obj.css(0, 1);
}
if (config.css) {
if (delay) node.style.cssText = cssText;
if (obj.tick) obj.tick(0, 1);
initialised = true;
}
program.name = create_rule(program, easing, config.css);
if (!b) {
program.group = outros.current;
outros.current.remaining += 1;
}
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(anim => anim && (program.d < 0 || !/__svelte/.test(anim)))
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
.join(', ');
}
if (obj.delay) {
this.pending = program;
} else {
this.start(program);
}
running_program = program;
pending_program = null;
}
if (!this.running) {
this.running = true;
transitionManager.add(this);
}
},
function done() {
const program = running_program;
running_program = null;
start(program) {
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}start`));
t = program.b;
program.a = this.t;
program.delta = program.b - program.a;
program.duration = duration * Math.abs(program.b - program.a);
program.end = program.start + program.duration;
if (config.tick) config.tick(t, 1 - t);
if (obj.css) {
if (obj.delay) node.style.cssText = cssText;
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}end`));
const rule = generateRule(program, ease, obj.css);
transitionManager.addRule(rule, program.name = '__svelte_' + hash(rule));
if (!program.b && !program.invalidated) {
program.group.callbacks.push(() => {
program.callback();
if (config.css) delete_rule(node, program.name);
});
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
.join(', ');
if (--program.group.remaining === 0) {
program.group.callbacks.forEach(run);
}
} else {
if (config.css) delete_rule(node, program.name);
}
this.program = program;
this.pending = null;
},
running = !!pending_program;
}
update(now) {
const program = this.program;
if (!program) return;
function go(b, callback) {
const {
delay = 0,
duration = 300,
easing = linear
} = config;
const program = {
start: window.performance.now() + delay,
b,
callback
};
if (!ready) {
if (config.css && delay) {
cssText = node.style.cssText;
node.style.cssText += config.css(0, 1);
}
const p = now - program.start;
this.t = program.a + program.delta * ease(p / program.duration);
if (obj.tick) obj.tick(this.t, 1 - this.t);
},
if (config.tick) config.tick(0, 1);
ready = true;
}
done() {
const program = this.program;
this.t = program.b;
if (!b) {
program.group = outros;
outros.remaining += 1;
}
if (obj.tick) obj.tick(this.t, 1 - this.t);
if (delay) {
pending_program = program;
} else {
start(program, delay, duration, easing);
}
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}end`));
if (!running) {
running = true;
if (!program.b && !program.invalidated) {
program.group.callbacks.push(() => {
program.callback();
if (obj.css) transitionManager.deleteRule(node, program.name);
});
const { abort, promise } = loop(now => {
if (running_program && now >= running_program.end) {
done();
}
if (--program.group.remaining === 0) {
program.group.callbacks.forEach(run);
if (pending_program && now >= pending_program.start) {
start(pending_program, delay, duration, easing);
}
if (running) {
if (running_program) {
const p = now - running_program.start;
t = running_program.a + running_program.d * easing(p / running_program.duration);
if (config.tick) config.tick(t, 1 - t);
}
return true;
}
});
}
}
return {
run(b, callback = noop) {
if (typeof config === 'function') {
wait().then(() => {
config = config();
go(b, callback);
});
} else {
if (obj.css) transitionManager.deleteRule(node, program.name);
go(b, callback);
}
this.running = !!this.pending;
},
abort(reset) {
if (this.program) {
if (reset && obj.tick) obj.tick(1, 0);
if (obj.css) transitionManager.deleteRule(node, this.program.name);
this.program = this.pending = null;
this.running = false;
if (reset && config.tick) config.tick(1, 0);
if (running_program) {
if (config.css) delete_rule(node, running_program.name);
running_program = pending_program = null;
running = false;
}
},
invalidate() {
if (this.program) {
this.program.invalidated = true;
if (running_program) {
running_program.invalidated = true;
}
}
};
}
export let outros = {};
export function groupOutros() {
outros.current = {
remaining: 0,
callbacks: []
};
}
export var transitionManager = {
running: false,
transitions: [],
bound: null,
stylesheet: null,
activeRules: {},
promise: null,
add(transition) {
this.transitions.push(transition);
if (!this.running) {
this.running = true;
requestAnimationFrame(this.bound || (this.bound = this.next.bind(this)));
}
},
addRule(rule, name) {
if (!this.stylesheet) {
const style = createElement('style');
document.head.appendChild(style);
transitionManager.stylesheet = style.sheet;
}
if (!this.activeRules[name]) {
this.activeRules[name] = true;
this.stylesheet.insertRule(`@keyframes ${name} ${rule}`, this.stylesheet.cssRules.length);
}
},
next() {
this.running = false;
const now = window.performance.now();
let i = this.transitions.length;
while (i--) {
const transition = this.transitions[i];
if (transition.program && now >= transition.program.end) {
transition.done();
}
if (transition.pending && now >= transition.pending.start) {
transition.start(transition.pending);
}
if (transition.running) {
transition.update(now);
this.running = true;
} else if (!transition.pending) {
this.transitions.splice(i, 1);
}
}
if (this.running) {
requestAnimationFrame(this.bound);
} else if (this.stylesheet) {
let i = this.stylesheet.cssRules.length;
while (i--) this.stylesheet.deleteRule(i);
this.activeRules = {};
}
},
deleteRule(node, name) {
node.style.animation = node.style.animation
.split(', ')
.filter(anim => anim && anim.indexOf(name) === -1)
.join(', ');
},
wait() {
if (!transitionManager.promise) {
transitionManager.promise = Promise.resolve();
transitionManager.promise.then(() => {
transitionManager.promise = null;
});
}
return transitionManager.promise;
}
};
}

@ -0,0 +1,2 @@
export * from './spring.js';
export * from './tweened.js';

@ -0,0 +1,155 @@
import { writable } from 'svelte/store';
import { loop } from 'svelte/internal';
import { is_date } from './utils.js';
function get_initial_velocity(value) {
if (typeof value === 'number' || is_date(value)) return 0;
if (Array.isArray(value)) return value.map(get_initial_velocity);
if (value && typeof value === 'object') {
const velocities = {};
for (const k in value) velocities[k] = get_initial_velocity(value[k]);
return velocities;
}
throw new Error(`Cannot spring ${typeof value} values`);
}
function get_threshold(value, target_value, precision) {
if (typeof value === 'number' || is_date(value)) return precision * Math.abs((target_value - value));
if (Array.isArray(value)) return value.map((v, i) => get_threshold(v, target_value[i], precision));
if (value && typeof value === 'object') {
const threshold = {};
for (const k in value) threshold[k] = get_threshold(value[k], target_value[k], precision);
return threshold;
}
throw new Error(`Cannot spring ${typeof value} values`);
}
function tick_spring(velocity, current_value, target_value, stiffness, damping, multiplier, threshold) {
let settled = true;
let value;
if (typeof current_value === 'number' || is_date(current_value)) {
const delta = target_value - current_value;
const spring = stiffness * delta;
const damper = damping * velocity;
const acceleration = spring - damper;
velocity += acceleration;
const d = velocity * multiplier;
if (is_date(current_value)) {
value = new Date(current_value.getTime() + d);
} else {
value = current_value + d;
}
if (Math.abs(d) > threshold) settled = false;
}
else if (Array.isArray(current_value)) {
value = current_value.map((v, i) => {
const result = tick_spring(
velocity[i],
v,
target_value[i],
stiffness,
damping,
multiplier,
threshold[i]
);
velocity[i] = result.velocity;
if (!result.settled) settled = false;
return result.value;
});
}
else if (typeof current_value === 'object') {
value = {};
for (const k in current_value) {
const result = tick_spring(
velocity[k],
current_value[k],
target_value[k],
stiffness,
damping,
multiplier,
threshold[k]
);
velocity[k] = result.velocity;
if (!result.settled) settled = false;
value[k] = result.value;
}
}
else {
throw new Error(`Cannot spring ${typeof value} values`);
}
return { velocity, value, settled };
}
export function spring(value, opts = {}) {
const store = writable(value);
const { stiffness = 0.15, damping = 0.8 } = opts;
const velocity = get_initial_velocity(value);
let task;
let target_value = value;
let last_time;
let settled;
let threshold;
function set(new_value) {
target_value = new_value;
threshold = get_threshold(value, target_value, 0.000001); // TODO make precision configurable?
if (!task) {
last_time = window.performance.now();
settled = false;
task = loop(now=> {
({ value, settled } = tick_spring(
velocity,
value,
target_value,
spring.stiffness,
spring.damping,
(now - last_time) * 60 / 1000,
threshold
));
last_time = now;
if (settled) {
value = target_value;
task = null;
}
store.set(value);
return !settled;
});
}
return task.promise;
}
const spring = {
set,
update: fn => set(fn(target_value, value)),
subscribe: store.subscribe,
stiffness,
damping
};
return spring;
}

@ -0,0 +1,112 @@
import { writable } from 'svelte/store';
import { assign, loop } from 'svelte/internal';
import { linear } from 'svelte/easing';
import { is_date } from './utils.js';
function get_interpolator(a, b) {
if (a === b || a !== a) return () => a;
const type = typeof a;
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
throw new Error('Cannot interpolate values of different type');
}
if (Array.isArray(a)) {
const arr = b.map((bi, i) => {
return get_interpolator(a[i], bi);
});
return t => arr.map(fn => fn(t));
}
if (type === 'object') {
if (!a || !b) throw new Error('Object cannot be null');
if (is_date(a) && is_date(b)) {
a = a.getTime();
b = b.getTime();
const delta = b - a;
return t => new Date(a + t * delta);
}
const keys = Object.keys(b);
const interpolators = {};
keys.forEach(key => {
interpolators[key] = get_interpolator(a[key], b[key]);
});
return t => {
const result = {};
keys.forEach(key => {
result[key] = interpolators[key](t);
});
return result;
};
}
if (type === 'number') {
const delta = b - a;
return t => a + t * delta;
}
throw new Error(`Cannot interpolate ${type} values`);
}
export function tweened(value, defaults = {}) {
const store = writable(value);
let task;
let target_value = value;
function set(new_value, opts) {
target_value = new_value;
let previous_task = task;
let started = false;
let {
delay = 0,
duration = 400,
easing = linear,
interpolate = get_interpolator
} = assign(assign({}, defaults), opts);
const start = window.performance.now() + delay;
let fn;
task = loop(now => {
if (now < start) return true;
if (!started) {
fn = interpolate(value, new_value);
if (typeof duration === 'function') duration = duration(value, new_value);
started = true;
}
if (previous_task) {
previous_task.abort();
previous_task = null;
}
const elapsed = now - start;
if (elapsed > duration) {
store.set(value = new_value);
return false;
}
store.set(value = fn(easing(elapsed / duration)));
return true;
});
return task.promise;
}
return {
set,
update: (fn, opts) => set(fn(target_value, value), opts),
subscribe: store.subscribe
};
}

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

@ -1,5 +1,5 @@
/* generated by Svelte vX.Y.Z */
import { SvelteComponent as SvelteComponent_1, append, blankObject, createComment, createElement, createText, detachNode, fixAndOutroAndDestroyBlock, fixPosition, flush, init, insert, run, safe_not_equal, setData, updateKeyedEach, wrapAnimation } from "svelte/internal";
import { SvelteComponent as SvelteComponent_1, animate, append, blankObject, createComment, createElement, createText, detachNode, fixAndOutroAndDestroyBlock, fix_position, flush, init, insert, noop, run, safe_not_equal, setData, updateKeyedEach } from "svelte/internal";
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
@ -9,7 +9,7 @@ function get_each_context(ctx, list, i) {
// (19:0) {#each things as thing (thing.id)}
function create_each_block(component, key_1, ctx) {
var div, text_value = ctx.thing.name, text, rect, animation;
var div, text_value = ctx.thing.name, text, rect, stop_animation = noop;
return {
key: key_1,
@ -38,13 +38,13 @@ function create_each_block(component, key_1, ctx) {
},
f() {
fixPosition(div);
if (animation) animation.stop();
fix_position(div);
stop_animation();
},
a() {
if (animation) animation.stop();
animation = wrapAnimation(div, rect, foo, {});
stop_animation();
stop_animation = animate(div, rect, foo, {});
},
d(detach) {

@ -3,7 +3,7 @@ import * as path from "path";
import * as fs from "fs";
import { rollup } from 'rollup';
import * as virtual from 'rollup-plugin-virtual';
import { transitionManager } from "../../internal.js";
import { clear_loops } from "../../internal.js";
import {
showOutput,
@ -93,9 +93,8 @@ describe("runtime", () => {
return Promise.resolve()
.then(() => {
// set of hacks to support transition tests
transitionManager.running = false;
transitionManager.transitions = [];
// hack to support transition tests
clear_loops();
const raf = {
time: 0,

Loading…
Cancel
Save